Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/cli/stdio-command.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Logger as WinstonLoggerType } from 'winston';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { DebugMcpServer } from '../server.js';
import { StdioOptions } from './setup.js';
import type { StdioOptions } from './setup.js';
import type { ProcessLike } from '../interfaces/process-interfaces.js';

export interface ServerFactoryOptions {
Expand Down
25 changes: 14 additions & 11 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,6 @@ import {
setupCheckRustBinaryCommand,
} from './cli/setup.js';
import { handleStdioCommand } from './cli/stdio-command.js';
import { handleSSECommand } from './cli/sse-command.js';
import { handleHttpCommand } from './cli/http-command.js';
import { handleCheckRustBinaryCommand } from './cli/commands/check-rust-binary.js';
import { getVersion } from './cli/version.js';
import fs from 'fs';
Expand Down Expand Up @@ -135,13 +133,18 @@ export async function main(): Promise<void> {
handleStdioCommand(options, { logger, serverFactory: createDebugMcpServer })
);

setupSSECommand(program, (options) =>
handleSSECommand(options, { logger, serverFactory: createDebugMcpServer })
);
// The SSE/HTTP command modules pull in express and the SDK's HTTP transport
// stacks; import them only when their subcommand actually runs so stdio mode
// (the common case) never pays for them (issue #400).
setupSSECommand(program, async (options) => {
const { handleSSECommand } = await import('./cli/sse-command.js');
return handleSSECommand(options, { logger, serverFactory: createDebugMcpServer });
});

setupHttpCommand(program, (options) =>
handleHttpCommand(options, { logger, serverFactory: createDebugMcpServer })
);
setupHttpCommand(program, async (options) => {
const { handleHttpCommand } = await import('./cli/http-command.js');
return handleHttpCommand(options, { logger, serverFactory: createDebugMcpServer });
});

setupCheckRustBinaryCommand(program, (binaryPath, options) =>
handleCheckRustBinaryCommand(binaryPath, options)
Expand Down Expand Up @@ -192,7 +195,9 @@ if (isMainModule) {
}
}

// Export for testing
// Export for testing. handleSSECommand/handleHttpCommand are deliberately not
// re-exported: their modules are lazy-imported inside the command actions
// (issue #400) and consumers import them from their own modules directly.
export {
setupErrorHandlers,
createCLI,
Expand All @@ -201,7 +206,5 @@ export {
setupHttpCommand,
setupCheckRustBinaryCommand,
handleStdioCommand,
handleSSECommand,
handleHttpCommand,
handleCheckRustBinaryCommand
};
25 changes: 25 additions & 0 deletions tests/unit/index-lazy-imports.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Import-graph test for issue #400: stdio mode (and any mere import of the
* entry point) must not evaluate the HTTP transport stacks. Express and the
* SSE/HTTP command modules are only loaded when their subcommand actually runs.
*/
import { describe, it, expect, vi } from 'vitest';

const loaded = vi.hoisted(() => ({ express: false }));

// The factory only runs if something in the imported module graph actually
// imports express — that evaluation is exactly what this test forbids.
vi.mock('express', () => {
loaded.express = true;
return { default: vi.fn() };
});

describe('index.ts import graph (issue #400)', () => {
it('does not evaluate express when the entry point is imported', async () => {
vi.stubEnv('DEBUG_MCP_SKIP_AUTO_START', '1');

await import('../../src/index.js');

expect(loaded.express).toBe(false);
});
});
Loading