Skip to content

Commit e2aeb36

Browse files
author
Augment Agent
committed
Fix HTTP session inefficiency by sharing MultiIndexRunner across sessions
Currently, every new HTTP client session creates a fresh MultiIndexRunner instance via createMCPServer(). This causes redundant store.list() and store.loadSearch() calls for every index on every session, which is wasteful especially for S3-backed stores. This change shares a single MultiIndexRunner instance across all HTTP sessions while maintaining per-session MCP Server instances (required by MCP protocol). Changes: - Add optional 'runner' field to MCPServerConfig interface - Update createMCPServer() to use provided runner if available, otherwise create new one - Modify createMCPHttpServer() to create shared runner once and pass to all sessions - Add comments explaining the optimization and updateClientUserAgent behavior The shared MultiIndexRunner is safe because it already has lazy client caching. All existing tests pass and backward compatibility is maintained. Related: https://slack.com/archives/C0ACX5SLML7/p1770850068455299
1 parent f7d6472 commit e2aeb36

File tree

2 files changed

+48
-11
lines changed

2 files changed

+48
-11
lines changed

src/clients/mcp-http-server.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
2828
import { createServer, IncomingMessage, ServerResponse } from "node:http";
2929
import { randomUUID, timingSafeEqual } from "node:crypto";
3030
import { createMCPServer, MCPServerConfig } from "./mcp-server.js";
31+
import { MultiIndexRunner } from "./multi-index-runner.js";
32+
import { buildClientUserAgent } from "../core/utils.js";
3133

3234
/**
3335
* HTTP error with status code for proper client error responses.
@@ -165,9 +167,26 @@ export async function createMCPHttpServer(
165167
// Store transports by session ID
166168
const transports: Map<string, StreamableHTTPServerTransport> = new Map();
167169

170+
// Create a shared MultiIndexRunner for all HTTP sessions to avoid redundant
171+
// store.list() and store.loadSearch() calls on every session initialization.
172+
// This is safe because MultiIndexRunner already has lazy client caching.
173+
// Each session still gets its own MCP Server instance (required by MCP protocol),
174+
// but they all share the same underlying runner and search clients.
175+
const clientUserAgent = buildClientUserAgent("mcp");
176+
const sharedRunner = await MultiIndexRunner.create({
177+
store: config.store,
178+
indexNames: config.indexNames,
179+
searchOnly: config.searchOnly,
180+
clientUserAgent,
181+
});
182+
168183
// Create the underlying MCP server factory (creates new instance per session)
184+
// Each session gets its own Server instance but shares the MultiIndexRunner
169185
const createServerInstance = async (): Promise<Server> => {
170-
return createMCPServer(config);
186+
return createMCPServer({
187+
...config,
188+
runner: sharedRunner,
189+
});
171190
};
172191

173192
/**

src/clients/mcp-server.ts

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,14 @@ export interface MCPServerConfig {
7171
* @default "0.1.0"
7272
*/
7373
version?: string;
74+
/**
75+
* Optional pre-initialized MultiIndexRunner to share across sessions.
76+
* When provided, this runner is used instead of creating a new one.
77+
* This is useful for HTTP servers to avoid redundant store.list() and
78+
* store.loadSearch() calls for every session.
79+
* @internal Used by mcp-http-server for session sharing optimization
80+
*/
81+
runner?: MultiIndexRunner;
7482
}
7583
/**
7684
* Create an MCP server instance.
@@ -94,16 +102,23 @@ export interface MCPServerConfig {
94102
export async function createMCPServer(
95103
config: MCPServerConfig
96104
): Promise<Server> {
97-
// Create shared runner for multi-index operations
98-
// Build User-Agent for analytics tracking
99-
const clientUserAgent = buildClientUserAgent("mcp");
100-
101-
const runner = await MultiIndexRunner.create({
102-
store: config.store,
103-
indexNames: config.indexNames,
104-
searchOnly: config.searchOnly,
105-
clientUserAgent,
106-
});
105+
// Use provided runner if available (for HTTP session sharing),
106+
// otherwise create a new one (for stdio server)
107+
let runner: MultiIndexRunner;
108+
if (config.runner) {
109+
runner = config.runner;
110+
} else {
111+
// Build User-Agent for analytics tracking
112+
const clientUserAgent = buildClientUserAgent("mcp");
113+
114+
runner = await MultiIndexRunner.create({
115+
store: config.store,
116+
indexNames: config.indexNames,
117+
searchOnly: config.searchOnly,
118+
clientUserAgent,
119+
});
120+
}
121+
107122
const { indexNames, indexes } = runner;
108123
const searchOnly = !runner.hasFileOperations();
109124
// Format index list for tool descriptions
@@ -122,6 +137,9 @@ export async function createMCPServer(
122137
);
123138
// Use the SDK's oninitialized callback to capture MCP client info
124139
// This preserves the SDK's protocol version negotiation
140+
// Note: When using a shared runner (HTTP sessions), this updates the runner
141+
// for all sessions (last writer wins). This is intentional - we want to track
142+
// the most recent client info for analytics.
125143
server.oninitialized = () => {
126144
const clientInfo = server.getClientVersion();
127145
if (clientInfo) {

0 commit comments

Comments
 (0)