Skip to content
Open
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
21 changes: 20 additions & 1 deletion src/clients/mcp-http-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -165,9 +167,26 @@ export async function createMCPHttpServer(
// Store transports by session ID
const transports: Map<string, StreamableHTTPServerTransport> = 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<Server> => {
return createMCPServer(config);
return createMCPServer({
...config,
runner: sharedRunner,
});
};

/**
Expand Down
38 changes: 28 additions & 10 deletions src/clients/mcp-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -94,16 +102,23 @@ export interface MCPServerConfig {
export async function createMCPServer(
config: MCPServerConfig
): Promise<Server> {
// 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
Expand All @@ -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) {
Expand Down