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