Merge pull request #3685 from Tibsfox/fix/mcp-server-subpath-imports

fix(mcp): use createRequire to resolve SDK wildcard subpath imports
This commit is contained in:
Jeremy McSpadden 2026-04-07 07:04:38 -05:00 committed by GitHub
commit 0163b5b5bf
2 changed files with 54 additions and 2 deletions

View file

@ -19,6 +19,10 @@ export interface McpToolDef {
// MCP SDK subpath imports use wildcard exports (./*) that NodeNext resolves
// at runtime but TypeScript cannot statically type-check. We construct the
// specifiers dynamically so tsc treats them as `any`.
// Use createRequire to resolve wildcard subpaths — CJS resolver auto-appends
// .js, which the ESM wildcard export map does not (#3603).
import { createRequire } from 'node:module'
const _require = createRequire(import.meta.url)
const MCP_PKG = '@modelcontextprotocol/sdk'
/**
@ -42,8 +46,8 @@ export async function startMcpServer(options: {
const { tools, version = '0.0.0' } = options
const serverMod = await import(`${MCP_PKG}/server`)
const stdioMod = await import(`${MCP_PKG}/server/stdio`)
const typesMod = await import(`${MCP_PKG}/types`)
const stdioMod = await import(_require.resolve(`${MCP_PKG}/server/stdio`))
const typesMod = await import(_require.resolve(`${MCP_PKG}/types`))
const Server = serverMod.Server
const StdioServerTransport = stdioMod.StdioServerTransport

View file

@ -0,0 +1,48 @@
/**
* Regression test for #3603 MCP server subpath imports via createRequire
*
* The ESM wildcard export map in @modelcontextprotocol/sdk does not resolve
* subpath imports correctly. The fix uses createRequire from node:module to
* resolve wildcard subpaths via the CJS resolver which auto-appends .js.
*
* Structural verification test reads source to confirm createRequire import
* and _require.resolve usage exist.
*/
import { describe, test } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const source = readFileSync(join(__dirname, '..', 'mcp-server.ts'), 'utf-8');
describe('MCP server createRequire subpath resolution (#3603)', () => {
test('createRequire is imported from node:module', () => {
assert.match(source, /import\s*\{\s*createRequire\s*\}\s*from\s*['"]node:module['"]/,
'createRequire should be imported from node:module');
});
test('_require is created from import.meta.url', () => {
assert.match(source, /createRequire\(import\.meta\.url\)/,
'_require should be created using createRequire(import.meta.url)');
});
test('_require.resolve is used for subpath imports', () => {
assert.match(source, /_require\.resolve\(/,
'_require.resolve should be used for subpath resolution');
});
test('server/stdio subpath uses _require.resolve', () => {
assert.match(source, /_require\.resolve\(`\$\{MCP_PKG\}\/server\/stdio`\)/,
'server/stdio import should use _require.resolve');
});
test('types subpath uses _require.resolve', () => {
assert.match(source, /_require\.resolve\(`\$\{MCP_PKG\}\/types`\)/,
'types import should use _require.resolve');
});
});