@aiwerk/mcp-bridge 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/dist/bin/mcp-bridge.d.ts +2 -0
  2. package/dist/bin/mcp-bridge.js +320 -0
  3. package/dist/src/config.d.ts +19 -0
  4. package/dist/src/config.js +145 -0
  5. package/{src/index.ts → dist/src/index.d.ts} +1 -30
  6. package/dist/src/index.js +21 -0
  7. package/dist/src/mcp-router.d.ts +65 -0
  8. package/dist/src/mcp-router.js +271 -0
  9. package/dist/src/protocol.d.ts +4 -0
  10. package/dist/src/protocol.js +58 -0
  11. package/dist/src/schema-convert.d.ts +11 -0
  12. package/dist/src/schema-convert.js +150 -0
  13. package/dist/src/standalone-server.d.ts +30 -0
  14. package/dist/src/standalone-server.js +312 -0
  15. package/dist/src/tool-naming.d.ts +3 -0
  16. package/dist/src/tool-naming.js +38 -0
  17. package/dist/src/transport-base.d.ts +76 -0
  18. package/dist/src/transport-base.js +163 -0
  19. package/dist/src/transport-sse.d.ts +16 -0
  20. package/dist/src/transport-sse.js +207 -0
  21. package/dist/src/transport-stdio.d.ts +20 -0
  22. package/dist/src/transport-stdio.js +281 -0
  23. package/dist/src/transport-streamable-http.d.ts +11 -0
  24. package/dist/src/transport-streamable-http.js +164 -0
  25. package/dist/src/types.d.ts +72 -0
  26. package/dist/src/types.js +4 -0
  27. package/dist/src/update-checker.d.ts +25 -0
  28. package/dist/src/update-checker.js +132 -0
  29. package/package.json +19 -4
  30. package/scripts/install-server.ps1 +25 -58
  31. package/scripts/install-server.sh +37 -90
  32. package/servers/apify/README.md +6 -6
  33. package/servers/github/README.md +6 -6
  34. package/servers/google-maps/README.md +6 -6
  35. package/servers/hetzner/README.md +6 -6
  36. package/servers/hostinger/README.md +6 -6
  37. package/servers/linear/README.md +6 -6
  38. package/servers/miro/README.md +6 -6
  39. package/servers/notion/README.md +6 -6
  40. package/servers/stripe/README.md +6 -6
  41. package/servers/tavily/README.md +6 -6
  42. package/servers/todoist/README.md +6 -6
  43. package/servers/wise/README.md +6 -6
  44. package/bin/mcp-bridge.js +0 -9
  45. package/bin/mcp-bridge.ts +0 -335
  46. package/src/config.ts +0 -168
  47. package/src/mcp-router.ts +0 -366
  48. package/src/protocol.ts +0 -69
  49. package/src/schema-convert.ts +0 -178
  50. package/src/standalone-server.ts +0 -385
  51. package/src/tool-naming.ts +0 -51
  52. package/src/transport-base.ts +0 -199
  53. package/src/transport-sse.ts +0 -230
  54. package/src/transport-stdio.ts +0 -312
  55. package/src/transport-streamable-http.ts +0 -188
  56. package/src/types.ts +0 -88
  57. package/src/update-checker.ts +0 -155
  58. package/tests/collision.test.ts +0 -60
  59. package/tests/env-resolve.test.ts +0 -68
  60. package/tests/mcp-router.test.ts +0 -301
  61. package/tests/schema-convert.test.ts +0 -70
  62. package/tests/transport-base.test.ts +0 -214
  63. package/tsconfig.json +0 -15
@@ -0,0 +1,271 @@
1
+ import { SseTransport } from "./transport-sse.js";
2
+ import { StdioTransport } from "./transport-stdio.js";
3
+ import { StreamableHttpTransport } from "./transport-streamable-http.js";
4
+ import { fetchToolsList, initializeProtocol, PACKAGE_VERSION } from "./protocol.js";
5
+ const DEFAULT_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
6
+ const DEFAULT_MAX_CONCURRENT = 5;
7
+ export class McpRouter {
8
+ servers;
9
+ clientConfig;
10
+ logger;
11
+ transportRefs;
12
+ idleTimeoutMs;
13
+ maxConcurrent;
14
+ states = new Map();
15
+ constructor(servers, clientConfig, logger, transportRefs) {
16
+ this.servers = servers;
17
+ this.clientConfig = clientConfig;
18
+ this.logger = logger;
19
+ this.transportRefs = {
20
+ sse: transportRefs?.sse ?? SseTransport,
21
+ stdio: transportRefs?.stdio ?? StdioTransport,
22
+ streamableHttp: transportRefs?.streamableHttp ?? StreamableHttpTransport
23
+ };
24
+ this.idleTimeoutMs = clientConfig.routerIdleTimeoutMs ?? DEFAULT_IDLE_TIMEOUT_MS;
25
+ this.maxConcurrent = clientConfig.routerMaxConcurrent ?? DEFAULT_MAX_CONCURRENT;
26
+ }
27
+ static generateDescription(servers) {
28
+ const serverNames = Object.keys(servers);
29
+ if (serverNames.length === 0) {
30
+ return "Call MCP server tools. No servers configured.";
31
+ }
32
+ const serverList = serverNames
33
+ .map((name) => {
34
+ const desc = servers[name].description;
35
+ return desc ? `${name} (${desc})` : name;
36
+ })
37
+ .join(", ");
38
+ return `Call any MCP server tool. Servers: ${serverList}. Use action='list' to discover tools and required parameters, action='call' to execute a tool, action='refresh' to clear cache and re-discover tools, and action='status' to check server connection states. If the user mentions a specific tool by name, the call action auto-connects and works without listing first.`;
39
+ }
40
+ async dispatch(server, action = "call", tool, params) {
41
+ try {
42
+ const normalizedAction = action || "call";
43
+ // Status action: no server required, shows all server states
44
+ if (normalizedAction === "status") {
45
+ return this.getStatus();
46
+ }
47
+ if (!server) {
48
+ return this.error("invalid_params", "server is required");
49
+ }
50
+ if (!this.servers[server]) {
51
+ return this.error("unknown_server", `Server '${server}' not found`, Object.keys(this.servers));
52
+ }
53
+ if (normalizedAction === "list") {
54
+ try {
55
+ const tools = await this.getToolList(server);
56
+ return { server, action: "list", tools };
57
+ }
58
+ catch (error) {
59
+ return this.error("connection_failed", `Failed to connect to ${server}: ${error instanceof Error ? error.message : String(error)}`);
60
+ }
61
+ }
62
+ if (normalizedAction === "refresh") {
63
+ try {
64
+ const state = await this.ensureConnected(server);
65
+ state.toolsCache = undefined;
66
+ state.toolNames = [];
67
+ const tools = await this.getToolList(server);
68
+ return { server, action: "refresh", refreshed: true, tools };
69
+ }
70
+ catch (error) {
71
+ return this.error("connection_failed", `Failed to connect to ${server}: ${error instanceof Error ? error.message : String(error)}`);
72
+ }
73
+ }
74
+ if (normalizedAction !== "call") {
75
+ return this.error("invalid_params", `action must be one of: list, call, refresh`);
76
+ }
77
+ if (!tool) {
78
+ return this.error("invalid_params", "tool is required for action=call");
79
+ }
80
+ try {
81
+ await this.getToolList(server);
82
+ }
83
+ catch (error) {
84
+ return this.error("connection_failed", `Failed to connect to ${server}: ${error instanceof Error ? error.message : String(error)}`);
85
+ }
86
+ const state = this.states.get(server);
87
+ if (!state.toolNames.includes(tool)) {
88
+ return this.error("unknown_tool", `Tool '${tool}' not found on server '${server}'`, state.toolNames);
89
+ }
90
+ this.markUsed(server);
91
+ const response = await state.transport.sendRequest({
92
+ jsonrpc: "2.0",
93
+ method: "tools/call",
94
+ params: {
95
+ name: tool,
96
+ arguments: params ?? {}
97
+ }
98
+ });
99
+ if (response.error) {
100
+ return this.error("mcp_error", response.error.message, undefined, response.error.code);
101
+ }
102
+ return { server, action: "call", tool, result: response.result };
103
+ }
104
+ catch (error) {
105
+ return this.error("mcp_error", error instanceof Error ? error.message : String(error));
106
+ }
107
+ }
108
+ async getToolList(server) {
109
+ if (!this.servers[server]) {
110
+ throw new Error(`Server '${server}' not found`);
111
+ }
112
+ const state = await this.ensureConnected(server);
113
+ if (state.toolsCache) {
114
+ this.markUsed(server);
115
+ return state.toolsCache;
116
+ }
117
+ const tools = await fetchToolsList(state.transport);
118
+ state.toolNames = tools.map((tool) => tool.name);
119
+ state.toolsCache = tools.map((tool) => ({
120
+ name: tool.name,
121
+ description: tool.description || "",
122
+ requiredParams: this.extractRequiredParams(tool)
123
+ }));
124
+ this.markUsed(server);
125
+ return state.toolsCache;
126
+ }
127
+ getStatus() {
128
+ const serverStatuses = Object.entries(this.servers).map(([name, config]) => {
129
+ const state = this.states.get(name);
130
+ let status = "disconnected";
131
+ if (state?.transport.isConnected()) {
132
+ const idleMs = Date.now() - state.lastUsedAt;
133
+ status = idleMs > 60_000 ? "idle" : "connected";
134
+ }
135
+ return {
136
+ name,
137
+ transport: config.transport,
138
+ status,
139
+ tools: state?.toolNames.length ?? 0,
140
+ ...(state?.lastUsedAt ? { lastUsed: new Date(state.lastUsedAt).toISOString() } : {})
141
+ };
142
+ });
143
+ return { action: "status", servers: serverStatuses };
144
+ }
145
+ async disconnectAll() {
146
+ for (const serverName of Object.keys(this.servers)) {
147
+ await this.disconnectServer(serverName);
148
+ }
149
+ }
150
+ async ensureConnected(server) {
151
+ let state = this.states.get(server);
152
+ if (!state) {
153
+ const transport = this.createTransport(server, this.servers[server]);
154
+ state = {
155
+ transport,
156
+ initialized: false,
157
+ toolNames: [],
158
+ lastUsedAt: Date.now(),
159
+ idleTimer: null
160
+ };
161
+ this.states.set(server, state);
162
+ }
163
+ if (state.initPromise) {
164
+ await state.initPromise;
165
+ return state;
166
+ }
167
+ state.initPromise = (async () => {
168
+ if (!state.transport.isConnected()) {
169
+ await state.transport.connect();
170
+ }
171
+ if (!state.initialized) {
172
+ await initializeProtocol(state.transport, PACKAGE_VERSION);
173
+ state.initialized = true;
174
+ }
175
+ this.markUsed(server);
176
+ await this.enforceMaxConcurrent(server);
177
+ })();
178
+ try {
179
+ await state.initPromise;
180
+ return state;
181
+ }
182
+ finally {
183
+ state.initPromise = undefined;
184
+ }
185
+ }
186
+ async enforceMaxConcurrent(activeServer) {
187
+ const connectedServers = [...this.states.entries()]
188
+ .filter(([_, s]) => s.transport.isConnected())
189
+ .map(([name, s]) => ({ name, lastUsedAt: s.lastUsedAt }));
190
+ if (connectedServers.length <= this.maxConcurrent) {
191
+ return;
192
+ }
193
+ connectedServers.sort((a, b) => a.lastUsedAt - b.lastUsedAt);
194
+ for (const candidate of connectedServers) {
195
+ if (candidate.name === activeServer) {
196
+ continue;
197
+ }
198
+ await this.disconnectServer(candidate.name);
199
+ this.logger.info(`[mcp-bridge] Router evicted idle server via LRU: ${candidate.name}`);
200
+ return;
201
+ }
202
+ }
203
+ async disconnectServer(server) {
204
+ const state = this.states.get(server);
205
+ if (!state)
206
+ return;
207
+ if (state.idleTimer) {
208
+ clearTimeout(state.idleTimer);
209
+ state.idleTimer = null;
210
+ }
211
+ if (state.transport.isConnected()) {
212
+ await state.transport.disconnect();
213
+ }
214
+ state.initialized = false;
215
+ state.toolsCache = undefined;
216
+ state.toolNames = [];
217
+ }
218
+ markUsed(server) {
219
+ const state = this.states.get(server);
220
+ if (!state)
221
+ return;
222
+ state.lastUsedAt = Date.now();
223
+ if (state.idleTimer) {
224
+ clearTimeout(state.idleTimer);
225
+ }
226
+ state.idleTimer = setTimeout(() => {
227
+ this.disconnectServer(server).catch((error) => {
228
+ this.logger.warn(`[mcp-bridge] Router idle disconnect failed for ${server}:`, error);
229
+ });
230
+ }, this.idleTimeoutMs);
231
+ // Don't keep the process alive just for idle disconnect
232
+ if (state.idleTimer && typeof state.idleTimer.unref === "function") {
233
+ state.idleTimer.unref();
234
+ }
235
+ }
236
+ createTransport(serverName, serverConfig) {
237
+ const onReconnected = async () => {
238
+ const state = this.states.get(serverName);
239
+ if (!state)
240
+ return;
241
+ state.initialized = false;
242
+ state.toolsCache = undefined;
243
+ state.toolNames = [];
244
+ };
245
+ if (serverConfig.transport === "sse") {
246
+ return new this.transportRefs.sse(serverConfig, this.clientConfig, this.logger, onReconnected);
247
+ }
248
+ if (serverConfig.transport === "stdio") {
249
+ return new this.transportRefs.stdio(serverConfig, this.clientConfig, this.logger, onReconnected);
250
+ }
251
+ if (serverConfig.transport === "streamable-http") {
252
+ return new this.transportRefs.streamableHttp(serverConfig, this.clientConfig, this.logger, onReconnected);
253
+ }
254
+ throw new Error(`Unsupported transport: ${serverConfig.transport}`);
255
+ }
256
+ extractRequiredParams(tool) {
257
+ const required = tool.inputSchema?.required;
258
+ if (!Array.isArray(required)) {
259
+ return [];
260
+ }
261
+ return required.filter((name) => typeof name === "string");
262
+ }
263
+ error(error, message, available, code) {
264
+ return {
265
+ error,
266
+ message,
267
+ ...(available ? { available } : {}),
268
+ ...(typeof code === "number" ? { code } : {})
269
+ };
270
+ }
271
+ }
@@ -0,0 +1,4 @@
1
+ import { McpTool, McpTransport } from "./types.js";
2
+ export declare const PACKAGE_VERSION: string;
3
+ export declare function initializeProtocol(transport: McpTransport, version: string): Promise<void>;
4
+ export declare function fetchToolsList(transport: McpTransport): Promise<McpTool[]>;
@@ -0,0 +1,58 @@
1
+ import { readFileSync } from "fs";
2
+ import { join, dirname } from "path";
3
+ import { fileURLToPath } from "url";
4
+ const __filename = fileURLToPath(import.meta.url);
5
+ const __dirname = dirname(__filename);
6
+ export const PACKAGE_VERSION = (() => {
7
+ try {
8
+ return JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8")).version;
9
+ }
10
+ catch {
11
+ return "0.0.0";
12
+ }
13
+ })();
14
+ export async function initializeProtocol(transport, version) {
15
+ const initRequest = {
16
+ jsonrpc: "2.0",
17
+ method: "initialize",
18
+ params: {
19
+ protocolVersion: "2024-11-05",
20
+ capabilities: {},
21
+ clientInfo: {
22
+ name: "mcp-bridge",
23
+ version: version || PACKAGE_VERSION
24
+ }
25
+ }
26
+ };
27
+ const response = await transport.sendRequest(initRequest);
28
+ if (response.error) {
29
+ throw new Error(`Initialize failed: ${response.error.message}`);
30
+ }
31
+ await transport.sendNotification({
32
+ jsonrpc: "2.0",
33
+ method: "notifications/initialized"
34
+ });
35
+ }
36
+ export async function fetchToolsList(transport) {
37
+ const allTools = [];
38
+ let cursor;
39
+ while (true) {
40
+ const request = {
41
+ jsonrpc: "2.0",
42
+ method: "tools/list",
43
+ ...(cursor ? { params: { cursor } } : {})
44
+ };
45
+ const response = await transport.sendRequest(request);
46
+ if (response.error) {
47
+ throw new Error(response.error.message);
48
+ }
49
+ const pageTools = Array.isArray(response.result?.tools) ? response.result.tools : [];
50
+ allTools.push(...pageTools);
51
+ const nextCursor = response.result?.nextCursor;
52
+ if (!nextCursor) {
53
+ break;
54
+ }
55
+ cursor = nextCursor;
56
+ }
57
+ return allTools;
58
+ }
@@ -0,0 +1,11 @@
1
+ type TSchema = any;
2
+ type TypeBoxMod = {
3
+ Type: any;
4
+ } | null;
5
+ export declare function setTypeBoxLoader(loader: (() => Promise<TypeBoxMod>) | null): void;
6
+ export declare function setSchemaLogger(logger: {
7
+ warn: (...args: unknown[]) => void;
8
+ }): void;
9
+ export declare function convertJsonSchemaToTypeBox(schema: any, depth?: number): Promise<TSchema>;
10
+ export declare function createToolParameters(inputSchema: any): Promise<TSchema>;
11
+ export {};
@@ -0,0 +1,150 @@
1
+ let cachedTypeBoxPromise = null;
2
+ // Overridable loader for dependency injection (used by tests)
3
+ let typeBoxLoader = null;
4
+ export function setTypeBoxLoader(loader) {
5
+ typeBoxLoader = loader;
6
+ cachedTypeBoxPromise = null; // auto-reset cache
7
+ }
8
+ async function getTypeBox() {
9
+ // If a test loader is set, always use it (bypass cache)
10
+ if (typeBoxLoader) {
11
+ return typeBoxLoader();
12
+ }
13
+ if (cachedTypeBoxPromise) {
14
+ return cachedTypeBoxPromise;
15
+ }
16
+ cachedTypeBoxPromise = (async () => {
17
+ try {
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
+ const mod = await import("@sinclair/typebox");
20
+ const Type = mod?.Type ?? mod?.default?.Type;
21
+ if (!Type) {
22
+ throw new Error("TypeBox module missing Type export");
23
+ }
24
+ return { Type };
25
+ }
26
+ catch (error) {
27
+ return null;
28
+ }
29
+ })();
30
+ return cachedTypeBoxPromise;
31
+ }
32
+ async function anyFallback() {
33
+ const typeBox = await getTypeBox();
34
+ if (typeBox?.Type) {
35
+ return typeBox.Type.Any();
36
+ }
37
+ // Empty schema {} is valid JSON Schema (matches anything), unlike { type: "any" } which is invalid.
38
+ // This only triggers if @sinclair/typebox is missing — run `npm install` in the plugin directory.
39
+ schemaLogger.warn("[mcp-bridge] TypeBox unavailable — using permissive empty schema fallback. Run `npm install` to fix.");
40
+ return {};
41
+ }
42
+ // Logger can be injected via setSchemaLogger(); defaults to console
43
+ let schemaLogger = console;
44
+ export function setSchemaLogger(logger) {
45
+ schemaLogger = logger;
46
+ }
47
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
48
+ export async function convertJsonSchemaToTypeBox(schema, depth = 0) {
49
+ const typeBox = await getTypeBox();
50
+ const Type = typeBox?.Type;
51
+ if (!Type) {
52
+ return anyFallback();
53
+ }
54
+ try {
55
+ if (depth > 10) {
56
+ schemaLogger.warn("[mcp-bridge] JSON schema depth limit exceeded (>10), falling back to Type.Any()");
57
+ return Type.Any();
58
+ }
59
+ if (!schema || typeof schema !== "object") {
60
+ return Type.Any();
61
+ }
62
+ // anyOf and oneOf both map to Type.Union (TypeBox doesn't distinguish)
63
+ const unionSource = schema.anyOf || schema.oneOf;
64
+ if (Array.isArray(unionSource) && unionSource.length > 0) {
65
+ const variants = await Promise.all(unionSource.map((item) => convertJsonSchemaToTypeBox(item, depth + 1)));
66
+ return Type.Union(variants);
67
+ }
68
+ // allOf maps to Type.Intersect
69
+ if (Array.isArray(schema.allOf) && schema.allOf.length > 0) {
70
+ const parts = await Promise.all(schema.allOf.map((item) => convertJsonSchemaToTypeBox(item, depth + 1)));
71
+ return Type.Intersect(parts);
72
+ }
73
+ switch (schema.type) {
74
+ case "string": {
75
+ if (schema.enum) {
76
+ return Type.Union(schema.enum.map((value) => Type.Literal(value)));
77
+ }
78
+ const stringOptions = {};
79
+ if (schema.minLength !== undefined)
80
+ stringOptions.minLength = schema.minLength;
81
+ if (schema.maxLength !== undefined)
82
+ stringOptions.maxLength = schema.maxLength;
83
+ if (schema.pattern !== undefined)
84
+ stringOptions.pattern = schema.pattern;
85
+ return Type.String(stringOptions);
86
+ }
87
+ case "number":
88
+ case "integer": {
89
+ const numberOptions = {};
90
+ if (schema.minimum !== undefined)
91
+ numberOptions.minimum = schema.minimum;
92
+ if (schema.maximum !== undefined)
93
+ numberOptions.maximum = schema.maximum;
94
+ return Type.Number(numberOptions);
95
+ }
96
+ case "boolean":
97
+ return Type.Boolean();
98
+ case "array":
99
+ if (schema.items) {
100
+ return Type.Array(await convertJsonSchemaToTypeBox(schema.items, depth + 1));
101
+ }
102
+ return Type.Array(Type.Any());
103
+ case "object":
104
+ if (schema.properties) {
105
+ const propertyEntries = Object.entries(schema.properties);
106
+ if (propertyEntries.length > 100) {
107
+ schemaLogger.warn("[mcp-bridge] JSON schema object has too many properties (>100), falling back to Type.Any()");
108
+ return Type.Any();
109
+ }
110
+ const properties = {};
111
+ const requiredSet = new Set(Array.isArray(schema.required) ? schema.required : []);
112
+ for (const [key, value] of propertyEntries) {
113
+ const converted = await convertJsonSchemaToTypeBox(value, depth + 1);
114
+ properties[key] = requiredSet.has(key) ? converted : Type.Optional(converted);
115
+ }
116
+ return Type.Object(properties);
117
+ }
118
+ return Type.Object({});
119
+ case "null":
120
+ return Type.Null();
121
+ default:
122
+ return Type.Any();
123
+ }
124
+ }
125
+ catch (error) {
126
+ schemaLogger.warn("[mcp-bridge] Failed to convert JSON schema, falling back to Type.Any()");
127
+ return Type.Any();
128
+ }
129
+ }
130
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
131
+ export async function createToolParameters(inputSchema) {
132
+ const typeBox = await getTypeBox();
133
+ const Type = typeBox?.Type;
134
+ if (!Type) {
135
+ // TypeBox missing — return the raw JSON Schema as-is
136
+ schemaLogger.warn("[mcp-bridge] TypeBox unavailable — passing raw JSON Schema. Run `npm install` to fix.");
137
+ return inputSchema ?? {};
138
+ }
139
+ if (!inputSchema) {
140
+ return Type.Object({});
141
+ }
142
+ // If the inputSchema is already a proper object schema, convert it
143
+ if (inputSchema.type === "object") {
144
+ return convertJsonSchemaToTypeBox(inputSchema, 0);
145
+ }
146
+ // If it's not an object, wrap it in an object
147
+ return Type.Object({
148
+ input: await convertJsonSchemaToTypeBox(inputSchema, 0)
149
+ });
150
+ }
@@ -0,0 +1,30 @@
1
+ import { BridgeConfig, Logger, McpRequest, McpResponse } from "./types.js";
2
+ /**
3
+ * Standalone MCP server that wraps the router.
4
+ * Implements the MCP protocol (initialize, tools/list, tools/call)
5
+ * and forwards tool calls to backend MCP servers.
6
+ */
7
+ export declare class StandaloneServer {
8
+ private config;
9
+ private logger;
10
+ private router;
11
+ private initialized;
12
+ private directTools;
13
+ private directConnections;
14
+ constructor(config: BridgeConfig, logger: Logger);
15
+ private isRouterMode;
16
+ /** Start stdio mode: read JSON-RPC from stdin, write responses to stdout. */
17
+ startStdio(): Promise<void>;
18
+ private processLine;
19
+ private writeResponse;
20
+ /** Handle a single MCP JSON-RPC request. */
21
+ handleRequest(request: McpRequest): Promise<McpResponse>;
22
+ private handleInitialize;
23
+ private handleToolsList;
24
+ private handleToolsCall;
25
+ /** Connect to all backend servers and discover their tools (direct mode). */
26
+ private discoverDirectTools;
27
+ private createTransport;
28
+ /** Graceful shutdown: disconnect all backend servers. */
29
+ shutdown(): Promise<void>;
30
+ }