diff --git a/src/clients/mcp-http-server.ts b/src/clients/mcp-http-server.ts index eeaf30c..1b6264c 100644 --- a/src/clients/mcp-http-server.ts +++ b/src/clients/mcp-http-server.ts @@ -28,6 +28,8 @@ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"; import { createServer, IncomingMessage, ServerResponse } from "node:http"; import { randomUUID, timingSafeEqual } from "node:crypto"; import { createMCPServer, MCPServerConfig } from "./mcp-server.js"; +import { MultiIndexRunner } from "./multi-index-runner.js"; +import { buildClientUserAgent } from "../core/utils.js"; /** * HTTP error with status code for proper client error responses. @@ -165,9 +167,26 @@ export async function createMCPHttpServer( // Store transports by session ID const transports: Map = new Map(); + // Create a shared MultiIndexRunner for all HTTP sessions to avoid redundant + // store.list() and store.loadSearch() calls on every session initialization. + // This is safe because MultiIndexRunner already has lazy client caching. + // Each session still gets its own MCP Server instance (required by MCP protocol), + // but they all share the same underlying runner and search clients. + const clientUserAgent = buildClientUserAgent("mcp"); + const sharedRunner = await MultiIndexRunner.create({ + store: config.store, + indexNames: config.indexNames, + searchOnly: config.searchOnly, + clientUserAgent, + }); + // Create the underlying MCP server factory (creates new instance per session) + // Each session gets its own Server instance but shares the MultiIndexRunner const createServerInstance = async (): Promise => { - return createMCPServer(config); + return createMCPServer({ + ...config, + runner: sharedRunner, + }); }; /** diff --git a/src/clients/mcp-server.ts b/src/clients/mcp-server.ts index cd5151e..bd24435 100644 --- a/src/clients/mcp-server.ts +++ b/src/clients/mcp-server.ts @@ -71,6 +71,14 @@ export interface MCPServerConfig { * @default "0.1.0" */ version?: string; + /** + * Optional pre-initialized MultiIndexRunner to share across sessions. + * When provided, this runner is used instead of creating a new one. + * This is useful for HTTP servers to avoid redundant store.list() and + * store.loadSearch() calls for every session. + * @internal Used by mcp-http-server for session sharing optimization + */ + runner?: MultiIndexRunner; } /** * Create an MCP server instance. @@ -94,16 +102,23 @@ export interface MCPServerConfig { export async function createMCPServer( config: MCPServerConfig ): Promise { - // Create shared runner for multi-index operations - // Build User-Agent for analytics tracking - const clientUserAgent = buildClientUserAgent("mcp"); - - const runner = await MultiIndexRunner.create({ - store: config.store, - indexNames: config.indexNames, - searchOnly: config.searchOnly, - clientUserAgent, - }); + // Use provided runner if available (for HTTP session sharing), + // otherwise create a new one (for stdio server) + let runner: MultiIndexRunner; + if (config.runner) { + runner = config.runner; + } else { + // Build User-Agent for analytics tracking + const clientUserAgent = buildClientUserAgent("mcp"); + + runner = await MultiIndexRunner.create({ + store: config.store, + indexNames: config.indexNames, + searchOnly: config.searchOnly, + clientUserAgent, + }); + } + const { indexNames, indexes } = runner; const searchOnly = !runner.hasFileOperations(); // Format index list for tool descriptions @@ -122,6 +137,9 @@ export async function createMCPServer( ); // Use the SDK's oninitialized callback to capture MCP client info // This preserves the SDK's protocol version negotiation + // Note: When using a shared runner (HTTP sessions), this updates the runner + // for all sessions (last writer wins). This is intentional - we want to track + // the most recent client info for analytics. server.oninitialized = () => { const clientInfo = server.getClientVersion(); if (clientInfo) {