From 701d045496b09649ba9cd9b0a11760bc7a9bace7 Mon Sep 17 00:00:00 2001 From: alban bertolini Date: Tue, 18 Aug 2026 11:56:11 +0200 Subject: [PATCH] fix(mcp-server): blame the missing dependency, not the configured path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A storage module whose own dependency is not installed reported FOREST_MCP_UPLOAD_STORAGE_MODULE "…" was not found, sending the operator to check a path that was correct. Node raises MODULE_NOT_FOUND for that case too, naming the dependency as the subject and carrying the module's own path in the require stack, so matching the resolved path anywhere in the message caught it. Matching the quoted name separates them: the entry point failing reads Cannot find module '', a missing dependency reads Cannot find module ''. Found writing the S3 example for a customer: `npm i @aws-sdk/client-s3` forgotten is the first thing anyone configuring this variable will hit. --- packages/mcp-server/src/utils/load-file-uploads.ts | 4 +++- .../mcp-server/test/utils/load-file-uploads.test.ts | 10 ++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/mcp-server/src/utils/load-file-uploads.ts b/packages/mcp-server/src/utils/load-file-uploads.ts index 97507f0db6..a902696438 100644 --- a/packages/mcp-server/src/utils/load-file-uploads.ts +++ b/packages/mcp-server/src/utils/load-file-uploads.ts @@ -36,9 +36,11 @@ export default async function loadFileUploads( } catch (error) { // "cannot find it" and "it ran and failed" send the operator to different places, and only the // first is about the path they configured. + // The quoted name, not the whole message: a module whose own dependency is missing reports + // MODULE_NOT_FOUND too, with the resolved path in the require stack rather than as the subject. const absent = (error as NodeJS.ErrnoException)?.code === 'MODULE_NOT_FOUND' && - String((error as Error)?.message).includes(resolved); + String((error as Error)?.message).includes(`'${resolved}'`); throw withCause( absent diff --git a/packages/mcp-server/test/utils/load-file-uploads.test.ts b/packages/mcp-server/test/utils/load-file-uploads.test.ts index 59930c0503..8eec8107c2 100644 --- a/packages/mcp-server/test/utils/load-file-uploads.test.ts +++ b/packages/mcp-server/test/utils/load-file-uploads.test.ts @@ -53,6 +53,16 @@ describe('loadFileUploads', () => { }); }); + it('blames the dependency, not the path, when the module requires something missing', async () => { + const file = writeModule(`require('@this/does-not-exist');`); + + const error = (await loadFileUploads(file).catch((e: Error) => e)) as Error; + + expect(error.message).toContain('failed while loading'); + expect(error.message).toContain("Cannot find module '@this/does-not-exist'"); + expect(error.message).not.toContain('was not found'); + }); + it('names the module and the resolved path when it cannot be found', async () => { await expect(loadFileUploads('./does-not-exist.js')).rejects.toThrow( /"\.\/does-not-exist\.js" was not found \(resolved to .+\)/,