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
19 changes: 18 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,24 @@ export async function createMCPHttpServer(
// Store transports by session ID
const transports: Map<string, StreamableHTTPServerTransport> = new Map();

// Create a shared MultiIndexRunner instance for all HTTP sessions
// This avoids redundant store.list() + store.loadSearch() calls for every session
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 MCP Server instance (required by MCP protocol),
// but they all share the same MultiIndexRunner to avoid redundant store operations
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 shared MultiIndexRunner instance.
* When provided, this runner is used instead of creating a new one.
* This is useful for sharing a single runner across multiple MCP server instances
* (e.g., in HTTP server with multiple sessions) to avoid redundant store operations.
* @default undefined (creates a new runner)
*/
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 or create a new one
let runner: MultiIndexRunner;
if (config.runner) {
// Use the shared runner provided by the caller
runner = config.runner;
} else {
// Create a new runner for this server instance
// 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 (e.g., in HTTP server), this will update
// the User-Agent for all sessions (last writer wins). This is acceptable
// because the User-Agent is primarily for analytics tracking.
server.oninitialized = () => {
const clientInfo = server.getClientVersion();
if (clientInfo) {
Expand Down