dynmcp 0.0.1 → 0.1.1
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.
- package/README.md +104 -7
- package/dist/index.cjs +355 -98
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +372 -115
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
- package/schema/mcp-config.json +116 -0
package/dist/index.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import
|
|
4
|
+
import process5 from "process";
|
|
5
5
|
import { Command } from "commander";
|
|
6
6
|
|
|
7
7
|
// package.json
|
|
8
8
|
var package_default = {
|
|
9
9
|
name: "dynmcp",
|
|
10
|
-
version: "0.
|
|
10
|
+
version: "0.1.1",
|
|
11
11
|
description: "Dynamic MCP context management tool for AI MCP-enabled agents and clients.",
|
|
12
12
|
author: "Brandon Burrus <brandon@burrus.io>",
|
|
13
13
|
license: "MIT",
|
|
@@ -52,9 +52,12 @@ var package_default = {
|
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
54
|
files: [
|
|
55
|
-
"dist"
|
|
55
|
+
"dist",
|
|
56
|
+
"schema"
|
|
56
57
|
],
|
|
57
58
|
scripts: {
|
|
59
|
+
"generate:schema": "tsx scripts/generate-schema.ts",
|
|
60
|
+
prebuild: "tsx scripts/generate-schema.ts",
|
|
58
61
|
build: "tsup",
|
|
59
62
|
dev: "tsx src/index.ts",
|
|
60
63
|
typecheck: "tsc --noEmit",
|
|
@@ -75,6 +78,7 @@ var package_default = {
|
|
|
75
78
|
fastmcp: "^4.0.1",
|
|
76
79
|
figlet: "^1.11.0",
|
|
77
80
|
figures: "^6.1.0",
|
|
81
|
+
yaml: "^2.9.0",
|
|
78
82
|
zod: "^4.4.3"
|
|
79
83
|
},
|
|
80
84
|
devDependencies: {
|
|
@@ -95,22 +99,147 @@ import figlet from "figlet";
|
|
|
95
99
|
import chalk from "chalk";
|
|
96
100
|
|
|
97
101
|
// src/proxy/index.ts
|
|
98
|
-
import
|
|
102
|
+
import process4 from "process";
|
|
103
|
+
import { StdioClientTransport as StdioClientTransport2 } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
104
|
+
|
|
105
|
+
// src/config/schema.ts
|
|
106
|
+
import { z } from "zod";
|
|
107
|
+
var MCP_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
108
|
+
var mcpName = z.string().regex(MCP_NAME_PATTERN);
|
|
109
|
+
var stdioTransport = z.object({
|
|
110
|
+
transport: z.literal("stdio"),
|
|
111
|
+
command: z.string(),
|
|
112
|
+
args: z.array(z.string()).optional(),
|
|
113
|
+
env: z.record(z.string(), z.string()).optional()
|
|
114
|
+
}).strict();
|
|
115
|
+
var httpUrl = z.string().url().refine((u) => u.startsWith("http://") || u.startsWith("https://"), {
|
|
116
|
+
message: "URL must use http:// or https:// scheme"
|
|
117
|
+
});
|
|
118
|
+
var streamableHttpTransport = z.object({
|
|
119
|
+
transport: z.literal("streamable-http"),
|
|
120
|
+
url: httpUrl,
|
|
121
|
+
headers: z.record(z.string(), z.string()).optional()
|
|
122
|
+
}).strict();
|
|
123
|
+
var sseTransport = z.object({
|
|
124
|
+
transport: z.literal("sse"),
|
|
125
|
+
url: httpUrl,
|
|
126
|
+
headers: z.record(z.string(), z.string()).optional()
|
|
127
|
+
}).strict();
|
|
128
|
+
var transportConfig = z.discriminatedUnion("transport", [
|
|
129
|
+
stdioTransport,
|
|
130
|
+
streamableHttpTransport,
|
|
131
|
+
sseTransport
|
|
132
|
+
]);
|
|
133
|
+
var mcpConfigSchema = z.object({
|
|
134
|
+
mcp: z.record(mcpName, transportConfig).refine((obj) => Object.keys(obj).length > 0, { message: "At least one MCP must be configured" })
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// src/config/loader.ts
|
|
138
|
+
import { readFileSync } from "fs";
|
|
139
|
+
import { existsSync } from "fs";
|
|
140
|
+
import { resolve } from "path";
|
|
141
|
+
import { parse as parseYaml } from "yaml";
|
|
142
|
+
var AUTO_DISCOVER_NAMES = ["mcp.json", ".mcp.json"];
|
|
143
|
+
function resolveConfigPath(explicitPath) {
|
|
144
|
+
if (explicitPath) {
|
|
145
|
+
const resolved = resolve(explicitPath);
|
|
146
|
+
if (!existsSync(resolved)) {
|
|
147
|
+
throw new Error(`Config file not found: ${resolved}`);
|
|
148
|
+
}
|
|
149
|
+
return resolved;
|
|
150
|
+
}
|
|
151
|
+
const cwd = process.cwd();
|
|
152
|
+
for (const name of AUTO_DISCOVER_NAMES) {
|
|
153
|
+
const candidate = resolve(cwd, name);
|
|
154
|
+
if (existsSync(candidate)) {
|
|
155
|
+
return candidate;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
const searched = AUTO_DISCOVER_NAMES.map((n) => resolve(cwd, n)).join(", ");
|
|
159
|
+
throw new Error(`No config file found. Searched: ${searched}`);
|
|
160
|
+
}
|
|
161
|
+
function loadConfig(explicitPath) {
|
|
162
|
+
const configPath = resolveConfigPath(explicitPath);
|
|
163
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
164
|
+
let content;
|
|
165
|
+
try {
|
|
166
|
+
content = isYamlFile(configPath) ? parseYaml(raw) : JSON.parse(raw);
|
|
167
|
+
} catch (parseError) {
|
|
168
|
+
const message = parseError instanceof Error ? parseError.message : String(parseError);
|
|
169
|
+
throw new Error(`Failed to parse config file (${configPath}): ${message}`);
|
|
170
|
+
}
|
|
171
|
+
const result = mcpConfigSchema.safeParse(content);
|
|
172
|
+
if (!result.success) {
|
|
173
|
+
const formatted = result.error.issues.map((issue) => ` - ${issue.path.join(".")}: ${issue.message}`).join("\n");
|
|
174
|
+
throw new Error(`Invalid config file (${configPath}):
|
|
175
|
+
${formatted}`);
|
|
176
|
+
}
|
|
177
|
+
return result.data;
|
|
178
|
+
}
|
|
179
|
+
function isYamlFile(filePath) {
|
|
180
|
+
return filePath.endsWith(".yml") || filePath.endsWith(".yaml");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// src/config/json-schema.ts
|
|
184
|
+
import { z as z2 } from "zod";
|
|
185
|
+
|
|
186
|
+
// src/proxy/transport-factory.ts
|
|
187
|
+
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
188
|
+
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
189
|
+
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
|
|
190
|
+
function createTransport(config) {
|
|
191
|
+
switch (config.transport) {
|
|
192
|
+
case "stdio":
|
|
193
|
+
return new StdioClientTransport({
|
|
194
|
+
command: config.command,
|
|
195
|
+
args: config.args,
|
|
196
|
+
env: config.env
|
|
197
|
+
});
|
|
198
|
+
case "streamable-http":
|
|
199
|
+
return new StreamableHTTPClientTransport(
|
|
200
|
+
new URL(config.url),
|
|
201
|
+
config.headers ? { requestInit: { headers: config.headers } } : void 0
|
|
202
|
+
);
|
|
203
|
+
case "sse":
|
|
204
|
+
return new SSEClientTransport(
|
|
205
|
+
new URL(config.url),
|
|
206
|
+
config.headers ? { requestInit: { headers: config.headers } } : void 0
|
|
207
|
+
);
|
|
208
|
+
default: {
|
|
209
|
+
const _exhaustive = config;
|
|
210
|
+
return _exhaustive;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
99
214
|
|
|
100
215
|
// src/proxy/tool-catalog.ts
|
|
101
216
|
var DISCOVER_TOOL_PREAMBLE = `Use this tool to look up the full schema of a tool before calling it with use_tool.
|
|
102
217
|
Call discover_tool with a tool name from the list below to get its complete description,
|
|
103
218
|
input parameters, and output schema. Always discover a tool before using it.`;
|
|
104
|
-
var ToolCatalog = class {
|
|
219
|
+
var ToolCatalog = class _ToolCatalog {
|
|
105
220
|
tools;
|
|
106
221
|
discoverToolDescription;
|
|
107
|
-
constructor(
|
|
222
|
+
constructor(tools, description) {
|
|
223
|
+
this.tools = tools;
|
|
224
|
+
this.discoverToolDescription = description;
|
|
225
|
+
}
|
|
226
|
+
static fromFlat(upstreamTools) {
|
|
108
227
|
const toolMap = /* @__PURE__ */ new Map();
|
|
109
228
|
for (const tool of upstreamTools) {
|
|
110
229
|
toolMap.set(tool.name, tool);
|
|
111
230
|
}
|
|
112
|
-
|
|
113
|
-
|
|
231
|
+
const description = buildFlatDescription(upstreamTools);
|
|
232
|
+
return new _ToolCatalog(toolMap, description);
|
|
233
|
+
}
|
|
234
|
+
static fromGrouped(groups) {
|
|
235
|
+
const toolMap = /* @__PURE__ */ new Map();
|
|
236
|
+
for (const [mcpName2, tools] of groups) {
|
|
237
|
+
for (const tool of tools) {
|
|
238
|
+
toolMap.set(`${mcpName2}/${tool.name}`, tool);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const description = buildGroupedDescription(groups);
|
|
242
|
+
return new _ToolCatalog(toolMap, description);
|
|
114
243
|
}
|
|
115
244
|
getToolDetails(toolName) {
|
|
116
245
|
const tool = this.tools.get(toolName);
|
|
@@ -118,10 +247,10 @@ var ToolCatalog = class {
|
|
|
118
247
|
const sortedNames = [...this.tools.keys()].sort().join(", ");
|
|
119
248
|
return `Unknown tool: "${toolName}". Available tools: ${sortedNames}`;
|
|
120
249
|
}
|
|
121
|
-
return buildToolDetailsString(tool);
|
|
250
|
+
return buildToolDetailsString(toolName, tool);
|
|
122
251
|
}
|
|
123
252
|
};
|
|
124
|
-
function
|
|
253
|
+
function buildFlatDescription(tools) {
|
|
125
254
|
const sortedTools = [...tools].sort((a, b) => a.name.localeCompare(b.name));
|
|
126
255
|
const toolLines = sortedTools.map((tool) => `- ${tool.name}: ${tool.description}`).join("\n");
|
|
127
256
|
return `${DISCOVER_TOOL_PREAMBLE}
|
|
@@ -130,9 +259,24 @@ function buildDiscoverToolDescription(tools) {
|
|
|
130
259
|
${toolLines}
|
|
131
260
|
</tools>`;
|
|
132
261
|
}
|
|
133
|
-
function
|
|
262
|
+
function buildGroupedDescription(groups) {
|
|
263
|
+
const sortedMcpNames = [...groups.keys()].sort();
|
|
264
|
+
const sections = sortedMcpNames.map((mcpName2) => {
|
|
265
|
+
const tools = groups.get(mcpName2);
|
|
266
|
+
const sortedTools = [...tools].sort((a, b) => a.name.localeCompare(b.name));
|
|
267
|
+
const toolLines = sortedTools.map((tool) => `- ${mcpName2}/${tool.name}: ${tool.description}`).join("\n");
|
|
268
|
+
return `${mcpName2}:
|
|
269
|
+
${toolLines}`;
|
|
270
|
+
});
|
|
271
|
+
return `${DISCOVER_TOOL_PREAMBLE}
|
|
272
|
+
|
|
273
|
+
<tools>
|
|
274
|
+
${sections.join("\n\n")}
|
|
275
|
+
</tools>`;
|
|
276
|
+
}
|
|
277
|
+
function buildToolDetailsString(displayName, tool) {
|
|
134
278
|
const lines = [
|
|
135
|
-
`Tool: ${
|
|
279
|
+
`Tool: ${displayName}`,
|
|
136
280
|
`Description: ${tool.description}`,
|
|
137
281
|
"",
|
|
138
282
|
"Input Schema:",
|
|
@@ -171,73 +315,21 @@ function buildAnnotationLines(tool) {
|
|
|
171
315
|
return lines;
|
|
172
316
|
}
|
|
173
317
|
|
|
174
|
-
// src/proxy/server.ts
|
|
175
|
-
import process from "process";
|
|
176
|
-
import { FastMCP } from "fastmcp";
|
|
177
|
-
import { z } from "zod";
|
|
178
|
-
var ProxyServer = class {
|
|
179
|
-
catalog;
|
|
180
|
-
upstreamClient;
|
|
181
|
-
constructor({ catalog, upstreamClient }) {
|
|
182
|
-
this.catalog = catalog;
|
|
183
|
-
this.upstreamClient = upstreamClient;
|
|
184
|
-
}
|
|
185
|
-
async start() {
|
|
186
|
-
const server = new FastMCP({
|
|
187
|
-
name: "dynamic-discovery-mcp",
|
|
188
|
-
version: package_default.version
|
|
189
|
-
});
|
|
190
|
-
server.addTool({
|
|
191
|
-
name: "discover_tool",
|
|
192
|
-
description: this.catalog.discoverToolDescription,
|
|
193
|
-
parameters: z.object({ tool_name: z.string() }),
|
|
194
|
-
execute: async ({ tool_name }) => {
|
|
195
|
-
return this.catalog.getToolDetails(tool_name);
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
server.addTool({
|
|
199
|
-
name: "use_tool",
|
|
200
|
-
description: "Use a tool that was previously discovered with the discover_tool tool.",
|
|
201
|
-
parameters: z.object({
|
|
202
|
-
tool_name: z.string(),
|
|
203
|
-
tool_input: z.record(z.string(), z.unknown()).default({})
|
|
204
|
-
}),
|
|
205
|
-
execute: async ({ tool_name, tool_input }) => {
|
|
206
|
-
if (!this.catalog.tools.has(tool_name)) {
|
|
207
|
-
return this.catalog.getToolDetails(tool_name);
|
|
208
|
-
}
|
|
209
|
-
const result = await this.upstreamClient.callTool(tool_name, tool_input);
|
|
210
|
-
return result;
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
process.stderr.write("Starting dynamic-discovery-mcp server over stdio\n");
|
|
214
|
-
await server.start({ transportType: "stdio" });
|
|
215
|
-
}
|
|
216
|
-
};
|
|
217
|
-
|
|
218
318
|
// src/proxy/upstream-client.ts
|
|
219
319
|
import process2 from "process";
|
|
220
320
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
221
|
-
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
222
321
|
var UpstreamClient = class {
|
|
223
|
-
|
|
224
|
-
args;
|
|
322
|
+
transport;
|
|
225
323
|
onTransportError;
|
|
226
324
|
client = null;
|
|
227
|
-
transport
|
|
228
|
-
|
|
229
|
-
this.command = command;
|
|
230
|
-
this.args = args;
|
|
325
|
+
constructor({ name, transport, onTransportError }) {
|
|
326
|
+
this.transport = transport;
|
|
231
327
|
this.onTransportError = onTransportError ?? ((error) => {
|
|
232
|
-
process2.stderr.write(`Upstream MCP transport error: ${error.message}
|
|
328
|
+
process2.stderr.write(`[${name}] Upstream MCP transport error: ${error.message}
|
|
233
329
|
`);
|
|
234
330
|
});
|
|
235
331
|
}
|
|
236
332
|
async connect() {
|
|
237
|
-
this.transport = new StdioClientTransport({
|
|
238
|
-
command: this.command,
|
|
239
|
-
args: this.args
|
|
240
|
-
});
|
|
241
333
|
this.transport.onerror = this.onTransportError;
|
|
242
334
|
this.client = new Client({ name: "dynamic-discovery-mcp", version: "1.0.0" });
|
|
243
335
|
await this.client.connect(this.transport);
|
|
@@ -272,10 +364,8 @@ var UpstreamClient = class {
|
|
|
272
364
|
if (this.client === null) {
|
|
273
365
|
throw new Error("Client is not connected. Call connect() first.");
|
|
274
366
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
arguments: input
|
|
278
|
-
});
|
|
367
|
+
const result = await this.client.callTool({ name, arguments: input });
|
|
368
|
+
return result;
|
|
279
369
|
}
|
|
280
370
|
async disconnect() {
|
|
281
371
|
if (this.client === null) {
|
|
@@ -283,54 +373,213 @@ var UpstreamClient = class {
|
|
|
283
373
|
}
|
|
284
374
|
await this.client.close();
|
|
285
375
|
this.client = null;
|
|
286
|
-
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
// src/proxy/orchestrator.ts
|
|
380
|
+
var Orchestrator = class {
|
|
381
|
+
config;
|
|
382
|
+
clients = /* @__PURE__ */ new Map();
|
|
383
|
+
toolCatalog = null;
|
|
384
|
+
constructor(config) {
|
|
385
|
+
this.config = config;
|
|
386
|
+
}
|
|
387
|
+
async connect() {
|
|
388
|
+
const groups = /* @__PURE__ */ new Map();
|
|
389
|
+
try {
|
|
390
|
+
for (const [mcpName2, { transport }] of this.config.mcps) {
|
|
391
|
+
const client = new UpstreamClient({
|
|
392
|
+
name: mcpName2,
|
|
393
|
+
transport,
|
|
394
|
+
onTransportError: (error) => {
|
|
395
|
+
this.config.onTransportError?.(mcpName2, error);
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
await client.connect();
|
|
399
|
+
const tools = await client.listTools();
|
|
400
|
+
this.clients.set(mcpName2, client);
|
|
401
|
+
groups.set(mcpName2, tools);
|
|
402
|
+
}
|
|
403
|
+
} catch (error) {
|
|
404
|
+
await this.disconnectAll();
|
|
405
|
+
throw error;
|
|
406
|
+
}
|
|
407
|
+
this.toolCatalog = ToolCatalog.fromGrouped(groups);
|
|
408
|
+
}
|
|
409
|
+
get catalog() {
|
|
410
|
+
if (this.toolCatalog === null) {
|
|
411
|
+
throw new Error("Orchestrator is not connected. Call connect() first.");
|
|
412
|
+
}
|
|
413
|
+
return this.toolCatalog;
|
|
414
|
+
}
|
|
415
|
+
async callTool(namespacedName, input) {
|
|
416
|
+
const separatorIndex = namespacedName.indexOf("/");
|
|
417
|
+
if (separatorIndex === -1) {
|
|
418
|
+
throw new Error(
|
|
419
|
+
`Invalid namespaced tool name: "${namespacedName}". Expected format: "mcpName/toolName".`
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
const mcpName2 = namespacedName.slice(0, separatorIndex);
|
|
423
|
+
const toolName = namespacedName.slice(separatorIndex + 1);
|
|
424
|
+
const client = this.clients.get(mcpName2);
|
|
425
|
+
if (client === void 0) {
|
|
426
|
+
const available = [...this.clients.keys()].sort().join(", ");
|
|
427
|
+
throw new Error(`Unknown MCP: "${mcpName2}". Available MCPs: ${available}`);
|
|
428
|
+
}
|
|
429
|
+
return client.callTool(toolName, input);
|
|
430
|
+
}
|
|
431
|
+
async disconnectAll() {
|
|
432
|
+
const disconnections = [...this.clients.values()].map((client) => client.disconnect());
|
|
433
|
+
await Promise.all(disconnections);
|
|
434
|
+
this.clients.clear();
|
|
435
|
+
this.toolCatalog = null;
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
// src/proxy/server.ts
|
|
440
|
+
import process3 from "process";
|
|
441
|
+
import { FastMCP } from "fastmcp";
|
|
442
|
+
import { z as z3 } from "zod";
|
|
443
|
+
var ProxyServer = class {
|
|
444
|
+
catalog;
|
|
445
|
+
callTool;
|
|
446
|
+
constructor({ catalog, callTool }) {
|
|
447
|
+
this.catalog = catalog;
|
|
448
|
+
this.callTool = callTool;
|
|
449
|
+
}
|
|
450
|
+
async start() {
|
|
451
|
+
const server = new FastMCP({
|
|
452
|
+
name: "dynamic-discovery-mcp",
|
|
453
|
+
version: package_default.version
|
|
454
|
+
});
|
|
455
|
+
server.addTool({
|
|
456
|
+
name: "discover_tool",
|
|
457
|
+
description: this.catalog.discoverToolDescription,
|
|
458
|
+
parameters: z3.object({ tool_name: z3.string() }),
|
|
459
|
+
execute: async ({ tool_name }) => {
|
|
460
|
+
return this.catalog.getToolDetails(tool_name);
|
|
461
|
+
}
|
|
462
|
+
});
|
|
463
|
+
server.addTool({
|
|
464
|
+
name: "use_tool",
|
|
465
|
+
description: "Use a tool that was previously discovered with the discover_tool tool.",
|
|
466
|
+
parameters: z3.object({
|
|
467
|
+
tool_name: z3.string(),
|
|
468
|
+
tool_input: z3.record(z3.string(), z3.unknown()).default({})
|
|
469
|
+
}),
|
|
470
|
+
execute: async ({ tool_name, tool_input }) => {
|
|
471
|
+
if (!this.catalog.tools.has(tool_name)) {
|
|
472
|
+
return this.catalog.getToolDetails(tool_name);
|
|
473
|
+
}
|
|
474
|
+
const result = await this.callTool(tool_name, tool_input);
|
|
475
|
+
return result;
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
process3.stderr.write("Starting dynamic-discovery-mcp server over stdio\n");
|
|
479
|
+
await server.start({ transportType: "stdio" });
|
|
287
480
|
}
|
|
288
481
|
};
|
|
289
482
|
|
|
290
483
|
// src/proxy/index.ts
|
|
291
484
|
async function startProxy(command, args) {
|
|
292
485
|
let isShuttingDown = false;
|
|
486
|
+
const transport = new StdioClientTransport2({ command, args });
|
|
487
|
+
const upstreamClient = new UpstreamClient({
|
|
488
|
+
name: command,
|
|
489
|
+
transport,
|
|
490
|
+
onTransportError: (error) => {
|
|
491
|
+
process4.stderr.write(`Upstream MCP transport error: ${error.message}
|
|
492
|
+
`);
|
|
493
|
+
shutdown(1);
|
|
494
|
+
}
|
|
495
|
+
});
|
|
293
496
|
const shutdown = (exitCode) => {
|
|
294
497
|
if (isShuttingDown) return;
|
|
295
498
|
isShuttingDown = true;
|
|
296
499
|
upstreamClient.disconnect().catch((error) => {
|
|
297
|
-
|
|
500
|
+
process4.stderr.write(
|
|
298
501
|
`dynmcp: error during disconnect: ${error instanceof Error ? error.message : String(error)}
|
|
299
502
|
`
|
|
300
503
|
);
|
|
301
|
-
}).finally(() =>
|
|
504
|
+
}).finally(() => process4.exit(exitCode));
|
|
302
505
|
};
|
|
303
|
-
const upstreamClient = new UpstreamClient({
|
|
304
|
-
command,
|
|
305
|
-
args,
|
|
306
|
-
onTransportError: (error) => {
|
|
307
|
-
process3.stderr.write(`Upstream MCP transport error: ${error.message}
|
|
308
|
-
`);
|
|
309
|
-
shutdown(1);
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
506
|
try {
|
|
313
507
|
await upstreamClient.connect();
|
|
314
508
|
} catch (error) {
|
|
315
|
-
|
|
509
|
+
process4.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
|
|
316
510
|
`);
|
|
317
|
-
|
|
511
|
+
process4.exit(1);
|
|
318
512
|
}
|
|
319
513
|
let tools;
|
|
320
514
|
try {
|
|
321
515
|
tools = await upstreamClient.listTools();
|
|
322
516
|
} catch (error) {
|
|
323
|
-
|
|
517
|
+
process4.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
|
|
518
|
+
`);
|
|
519
|
+
process4.exit(1);
|
|
520
|
+
}
|
|
521
|
+
const catalog = ToolCatalog.fromFlat(tools);
|
|
522
|
+
const proxyServer = new ProxyServer({
|
|
523
|
+
catalog,
|
|
524
|
+
callTool: (name, input) => upstreamClient.callTool(name, input)
|
|
525
|
+
});
|
|
526
|
+
process4.on("SIGINT", () => shutdown(0));
|
|
527
|
+
process4.on("SIGTERM", () => shutdown(0));
|
|
528
|
+
process4.stdin.on("end", () => shutdown(0));
|
|
529
|
+
process4.stdin.on("close", () => shutdown(0));
|
|
530
|
+
try {
|
|
531
|
+
await proxyServer.start();
|
|
532
|
+
} catch (error) {
|
|
533
|
+
shutdown(1);
|
|
534
|
+
throw error;
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
async function startProxyFromConfig(configPath) {
|
|
538
|
+
let isShuttingDown = false;
|
|
539
|
+
const config = loadConfig(configPath);
|
|
540
|
+
const mcps = /* @__PURE__ */ new Map();
|
|
541
|
+
for (const [name, entry] of Object.entries(config.mcp)) {
|
|
542
|
+
mcps.set(name, { transport: createTransport(entry) });
|
|
543
|
+
}
|
|
544
|
+
const orchestrator = new Orchestrator({
|
|
545
|
+
mcps,
|
|
546
|
+
onTransportError: (mcpName2, error) => {
|
|
547
|
+
process4.stderr.write(`Upstream MCP "${mcpName2}" transport error: ${error.message}
|
|
548
|
+
`);
|
|
549
|
+
shutdown(1);
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
const shutdown = (exitCode) => {
|
|
553
|
+
if (isShuttingDown) return;
|
|
554
|
+
isShuttingDown = true;
|
|
555
|
+
orchestrator.disconnectAll().catch((error) => {
|
|
556
|
+
process4.stderr.write(
|
|
557
|
+
`dynmcp: error during disconnect: ${error instanceof Error ? error.message : String(error)}
|
|
558
|
+
`
|
|
559
|
+
);
|
|
560
|
+
}).finally(() => process4.exit(exitCode));
|
|
561
|
+
};
|
|
562
|
+
try {
|
|
563
|
+
await orchestrator.connect();
|
|
564
|
+
} catch (error) {
|
|
565
|
+
process4.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
|
|
324
566
|
`);
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
567
|
+
process4.exit(1);
|
|
568
|
+
}
|
|
569
|
+
const proxyServer = new ProxyServer({
|
|
570
|
+
catalog: orchestrator.catalog,
|
|
571
|
+
callTool: (name, input) => orchestrator.callTool(name, input)
|
|
572
|
+
});
|
|
573
|
+
process4.on("SIGINT", () => shutdown(0));
|
|
574
|
+
process4.on("SIGTERM", () => shutdown(0));
|
|
575
|
+
process4.stdin.on("end", () => shutdown(0));
|
|
576
|
+
process4.stdin.on("close", () => shutdown(0));
|
|
577
|
+
try {
|
|
578
|
+
await proxyServer.start();
|
|
579
|
+
} catch (error) {
|
|
580
|
+
shutdown(1);
|
|
581
|
+
throw error;
|
|
582
|
+
}
|
|
334
583
|
}
|
|
335
584
|
|
|
336
585
|
// src/cli.ts
|
|
@@ -341,34 +590,42 @@ var cliBanner = chalk.bold.magentaBright(
|
|
|
341
590
|
verticalLayout: "fitted"
|
|
342
591
|
})
|
|
343
592
|
);
|
|
344
|
-
var cli = new Command(package_default.name).description(package_default.description).version(package_default.version).addHelpText("beforeAll", cliBanner).addHelpText(
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
593
|
+
var cli = new Command(package_default.name).description(package_default.description).version(package_default.version).addHelpText("beforeAll", cliBanner).addHelpText(
|
|
594
|
+
"after",
|
|
595
|
+
"\nExamples:\n dynmcp -- npx -y chrome-devtools-mcp@latest\n dynmcp --config ./mcp.json\n"
|
|
596
|
+
).option("-c, --config <path>", "Path to config file (JSON or YAML)").allowExcessArguments(true).passThroughOptions(true).action(async (_options, cmd) => {
|
|
597
|
+
const separatorIndex = process5.argv.indexOf("--");
|
|
598
|
+
const configPath = cmd.opts().config;
|
|
599
|
+
if (separatorIndex !== -1) {
|
|
600
|
+
const [command, ...args] = process5.argv.slice(separatorIndex + 1);
|
|
601
|
+
if (command === void 0) {
|
|
602
|
+
process5.stderr.write(
|
|
603
|
+
"dynmcp: no upstream command provided after --.\nUsage: dynmcp -- <command> [args...]\n"
|
|
604
|
+
);
|
|
605
|
+
process5.exit(1);
|
|
606
|
+
}
|
|
607
|
+
try {
|
|
608
|
+
await startProxy(command, args);
|
|
609
|
+
} catch (error) {
|
|
610
|
+
process5.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
|
|
611
|
+
`);
|
|
612
|
+
process5.exit(1);
|
|
613
|
+
}
|
|
614
|
+
return;
|
|
358
615
|
}
|
|
359
616
|
try {
|
|
360
|
-
await
|
|
617
|
+
await startProxyFromConfig(configPath);
|
|
361
618
|
} catch (error) {
|
|
362
|
-
|
|
619
|
+
process5.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
|
|
363
620
|
`);
|
|
364
|
-
|
|
621
|
+
process5.exit(1);
|
|
365
622
|
}
|
|
366
623
|
});
|
|
367
624
|
|
|
368
625
|
// src/index.ts
|
|
369
|
-
import
|
|
626
|
+
import process6 from "process";
|
|
370
627
|
async function main() {
|
|
371
|
-
cli.parse(
|
|
628
|
+
cli.parse(process6.argv);
|
|
372
629
|
}
|
|
373
630
|
main();
|
|
374
631
|
//# sourceMappingURL=index.js.map
|