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