dynmcp 0.1.0 → 0.2.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 +59 -0
- package/dist/index.cjs +250 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +253 -63
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
- package/schema/mcp-config.json +126 -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 process6 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.2.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",
|
|
@@ -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",
|
|
@@ -71,6 +74,7 @@ var package_default = {
|
|
|
71
74
|
boxen: "^8.0.1",
|
|
72
75
|
chalk: "^5.6.2",
|
|
73
76
|
commander: "^14.0.3",
|
|
77
|
+
dotenv: "^17.4.2",
|
|
74
78
|
enquirer: "^2.4.1",
|
|
75
79
|
fastmcp: "^4.0.1",
|
|
76
80
|
figlet: "^1.11.0",
|
|
@@ -96,13 +100,16 @@ import figlet from "figlet";
|
|
|
96
100
|
import chalk from "chalk";
|
|
97
101
|
|
|
98
102
|
// src/proxy/index.ts
|
|
99
|
-
import
|
|
103
|
+
import process5 from "process";
|
|
100
104
|
import { StdioClientTransport as StdioClientTransport2 } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
101
105
|
|
|
102
106
|
// src/config/schema.ts
|
|
103
107
|
import { z } from "zod";
|
|
104
108
|
var MCP_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
|
|
105
109
|
var mcpName = z.string().regex(MCP_NAME_PATTERN);
|
|
110
|
+
var envModeSchema = z.enum(["enable", "dotenv", "process", "disable"]).describe(
|
|
111
|
+
'Controls environment variable interpolation in config values. "enable" (default) merges .env and process.env (.env wins). "dotenv" loads .env only. "process" uses process.env only. "disable" turns interpolation off.'
|
|
112
|
+
);
|
|
106
113
|
var stdioTransport = z.object({
|
|
107
114
|
transport: z.literal("stdio"),
|
|
108
115
|
command: z.string(),
|
|
@@ -128,55 +135,234 @@ var transportConfig = z.discriminatedUnion("transport", [
|
|
|
128
135
|
sseTransport
|
|
129
136
|
]);
|
|
130
137
|
var mcpConfigSchema = z.object({
|
|
138
|
+
env: envModeSchema.optional(),
|
|
131
139
|
mcp: z.record(mcpName, transportConfig).refine((obj) => Object.keys(obj).length > 0, { message: "At least one MCP must be configured" })
|
|
132
140
|
});
|
|
133
141
|
|
|
134
142
|
// src/config/loader.ts
|
|
135
|
-
import { readFileSync } from "fs";
|
|
136
|
-
import {
|
|
137
|
-
import
|
|
143
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
144
|
+
import { resolve as resolve2 } from "path";
|
|
145
|
+
import process2 from "process";
|
|
138
146
|
import { parse as parseYaml } from "yaml";
|
|
147
|
+
|
|
148
|
+
// src/config/env-sources.ts
|
|
149
|
+
import { existsSync, readFileSync } from "fs";
|
|
150
|
+
import { resolve } from "path";
|
|
151
|
+
import process from "process";
|
|
152
|
+
import dotenv from "dotenv";
|
|
153
|
+
var DEFAULT_DOTENV_FILENAME = ".env";
|
|
154
|
+
function loadEnv(options) {
|
|
155
|
+
const { mode, envFilePath, cwd = process.cwd(), processEnv = process.env } = options;
|
|
156
|
+
if (envFilePath !== void 0 && (mode === "disable" || mode === "process")) {
|
|
157
|
+
throw new Error(
|
|
158
|
+
`--env flag is incompatible with env mode "${mode}". --env requires env mode "enable" or "dotenv".`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
if (mode === "disable") {
|
|
162
|
+
return { variables: {}, interpolationEnabled: false };
|
|
163
|
+
}
|
|
164
|
+
const dotenvVars = mode === "process" ? {} : readDotenvFile(envFilePath, cwd);
|
|
165
|
+
const processVars = mode === "dotenv" ? {} : filterDefined(processEnv);
|
|
166
|
+
const variables = { ...processVars, ...dotenvVars };
|
|
167
|
+
return { variables, interpolationEnabled: true };
|
|
168
|
+
}
|
|
169
|
+
function readDotenvFile(envFilePath, cwd) {
|
|
170
|
+
const isExplicit = envFilePath !== void 0;
|
|
171
|
+
const resolvedPath = isExplicit ? resolve(envFilePath) : resolve(cwd, DEFAULT_DOTENV_FILENAME);
|
|
172
|
+
if (!existsSync(resolvedPath)) {
|
|
173
|
+
if (isExplicit) {
|
|
174
|
+
throw new Error(`.env file not found: ${resolvedPath}`);
|
|
175
|
+
}
|
|
176
|
+
return {};
|
|
177
|
+
}
|
|
178
|
+
let raw;
|
|
179
|
+
try {
|
|
180
|
+
raw = readFileSync(resolvedPath, "utf-8");
|
|
181
|
+
} catch (readError) {
|
|
182
|
+
const message = readError instanceof Error ? readError.message : String(readError);
|
|
183
|
+
throw new Error(`Failed to read .env file (${resolvedPath}): ${message}`);
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
return dotenv.parse(raw);
|
|
187
|
+
} catch (parseError) {
|
|
188
|
+
const message = parseError instanceof Error ? parseError.message : String(parseError);
|
|
189
|
+
throw new Error(`Failed to parse .env file (${resolvedPath}): ${message}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
function filterDefined(env) {
|
|
193
|
+
const result = {};
|
|
194
|
+
for (const [key, value] of Object.entries(env)) {
|
|
195
|
+
if (value !== void 0) {
|
|
196
|
+
result[key] = value;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return result;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/config/interpolate.ts
|
|
203
|
+
var TOP_LEVEL_PASSTHROUGH_KEYS = /* @__PURE__ */ new Set(["$schema", "env"]);
|
|
204
|
+
var MissingEnvVarsError = class extends Error {
|
|
205
|
+
constructor(missingVars) {
|
|
206
|
+
const list = missingVars.join(", ");
|
|
207
|
+
const plural = missingVars.length === 1 ? "" : "s";
|
|
208
|
+
super(`Missing required environment variable${plural}: ${list}`);
|
|
209
|
+
this.missingVars = missingVars;
|
|
210
|
+
this.name = "MissingEnvVarsError";
|
|
211
|
+
}
|
|
212
|
+
missingVars;
|
|
213
|
+
};
|
|
214
|
+
function interpolateConfig(config, env) {
|
|
215
|
+
if (config === null || typeof config !== "object" || Array.isArray(config)) {
|
|
216
|
+
return config;
|
|
217
|
+
}
|
|
218
|
+
const missing = [];
|
|
219
|
+
const result = {};
|
|
220
|
+
for (const [key, value] of Object.entries(config)) {
|
|
221
|
+
if (TOP_LEVEL_PASSTHROUGH_KEYS.has(key)) {
|
|
222
|
+
result[key] = value;
|
|
223
|
+
} else {
|
|
224
|
+
result[key] = walkNode(value, env, missing);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (missing.length > 0) {
|
|
228
|
+
const unique = Array.from(new Set(missing)).sort();
|
|
229
|
+
throw new MissingEnvVarsError(unique);
|
|
230
|
+
}
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
function walkNode(node, env, missing) {
|
|
234
|
+
if (typeof node === "string") {
|
|
235
|
+
return interpolateString(node, env, missing);
|
|
236
|
+
}
|
|
237
|
+
if (Array.isArray(node)) {
|
|
238
|
+
return node.map((item) => walkNode(item, env, missing));
|
|
239
|
+
}
|
|
240
|
+
if (node !== null && typeof node === "object") {
|
|
241
|
+
const result = {};
|
|
242
|
+
for (const [key, value] of Object.entries(node)) {
|
|
243
|
+
result[key] = walkNode(value, env, missing);
|
|
244
|
+
}
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
return node;
|
|
248
|
+
}
|
|
249
|
+
function interpolateString(value, env, missing) {
|
|
250
|
+
let result = "";
|
|
251
|
+
let i = 0;
|
|
252
|
+
const len = value.length;
|
|
253
|
+
while (i < len) {
|
|
254
|
+
const ch = value[i];
|
|
255
|
+
if (ch === "$" && value[i + 1] === "$" && value[i + 2] === "{") {
|
|
256
|
+
const close = value.indexOf("}", i + 3);
|
|
257
|
+
if (close === -1) {
|
|
258
|
+
result += ch;
|
|
259
|
+
i += 1;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
result += value.substring(i + 1, close + 1);
|
|
263
|
+
i = close + 1;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (ch === "$" && value[i + 1] === "{") {
|
|
267
|
+
const close = value.indexOf("}", i + 2);
|
|
268
|
+
if (close === -1) {
|
|
269
|
+
result += value.substring(i);
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
const expr = value.substring(i + 2, close);
|
|
273
|
+
const { name, defaultValue } = parseExpr(expr);
|
|
274
|
+
const resolved = env[name];
|
|
275
|
+
const hasValue = resolved !== void 0 && resolved !== "";
|
|
276
|
+
if (hasValue) {
|
|
277
|
+
result += resolved;
|
|
278
|
+
} else if (defaultValue !== void 0) {
|
|
279
|
+
result += defaultValue;
|
|
280
|
+
} else if (resolved !== void 0) {
|
|
281
|
+
result += "";
|
|
282
|
+
} else {
|
|
283
|
+
missing.push(name);
|
|
284
|
+
}
|
|
285
|
+
i = close + 1;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
result += ch;
|
|
289
|
+
i += 1;
|
|
290
|
+
}
|
|
291
|
+
return result;
|
|
292
|
+
}
|
|
293
|
+
function parseExpr(expr) {
|
|
294
|
+
const sep = expr.indexOf(":-");
|
|
295
|
+
if (sep === -1) {
|
|
296
|
+
return { name: expr, defaultValue: void 0 };
|
|
297
|
+
}
|
|
298
|
+
return {
|
|
299
|
+
name: expr.substring(0, sep),
|
|
300
|
+
defaultValue: expr.substring(sep + 2)
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/config/loader.ts
|
|
139
305
|
var AUTO_DISCOVER_NAMES = ["mcp.json", ".mcp.json"];
|
|
306
|
+
var DEFAULT_ENV_MODE = "enable";
|
|
307
|
+
var VALID_ENV_MODES = ["enable", "dotenv", "process", "disable"];
|
|
140
308
|
function resolveConfigPath(explicitPath) {
|
|
141
309
|
if (explicitPath) {
|
|
142
|
-
const resolved =
|
|
143
|
-
if (!
|
|
310
|
+
const resolved = resolve2(explicitPath);
|
|
311
|
+
if (!existsSync2(resolved)) {
|
|
144
312
|
throw new Error(`Config file not found: ${resolved}`);
|
|
145
313
|
}
|
|
146
314
|
return resolved;
|
|
147
315
|
}
|
|
148
|
-
const cwd =
|
|
316
|
+
const cwd = process2.cwd();
|
|
149
317
|
for (const name of AUTO_DISCOVER_NAMES) {
|
|
150
|
-
const candidate =
|
|
151
|
-
if (
|
|
318
|
+
const candidate = resolve2(cwd, name);
|
|
319
|
+
if (existsSync2(candidate)) {
|
|
152
320
|
return candidate;
|
|
153
321
|
}
|
|
154
322
|
}
|
|
155
|
-
const searched = AUTO_DISCOVER_NAMES.map((n) =>
|
|
323
|
+
const searched = AUTO_DISCOVER_NAMES.map((n) => resolve2(cwd, n)).join(", ");
|
|
156
324
|
throw new Error(`No config file found. Searched: ${searched}`);
|
|
157
325
|
}
|
|
158
|
-
function loadConfig(
|
|
159
|
-
const configPath =
|
|
160
|
-
const
|
|
326
|
+
function loadConfig(options = {}) {
|
|
327
|
+
const { configPath, envFilePath } = options;
|
|
328
|
+
const resolvedPath = resolveConfigPath(configPath);
|
|
329
|
+
const raw = readFileSync2(resolvedPath, "utf-8");
|
|
161
330
|
let content;
|
|
162
331
|
try {
|
|
163
|
-
content = isYamlFile(
|
|
332
|
+
content = isYamlFile(resolvedPath) ? parseYaml(raw) : JSON.parse(raw);
|
|
164
333
|
} catch (parseError) {
|
|
165
334
|
const message = parseError instanceof Error ? parseError.message : String(parseError);
|
|
166
|
-
throw new Error(`Failed to parse config file (${
|
|
335
|
+
throw new Error(`Failed to parse config file (${resolvedPath}): ${message}`);
|
|
167
336
|
}
|
|
168
|
-
const
|
|
337
|
+
const envMode = readEnvMode(content);
|
|
338
|
+
const loadedEnv = loadEnv({ mode: envMode, envFilePath });
|
|
339
|
+
const interpolated = loadedEnv.interpolationEnabled ? interpolateConfig(content, loadedEnv.variables) : content;
|
|
340
|
+
const result = mcpConfigSchema.safeParse(interpolated);
|
|
169
341
|
if (!result.success) {
|
|
170
342
|
const formatted = result.error.issues.map((issue) => ` - ${issue.path.join(".")}: ${issue.message}`).join("\n");
|
|
171
|
-
throw new Error(`Invalid config file (${
|
|
343
|
+
throw new Error(`Invalid config file (${resolvedPath}):
|
|
172
344
|
${formatted}`);
|
|
173
345
|
}
|
|
174
346
|
return result.data;
|
|
175
347
|
}
|
|
348
|
+
function readEnvMode(content) {
|
|
349
|
+
if (content === null || typeof content !== "object" || Array.isArray(content)) {
|
|
350
|
+
return DEFAULT_ENV_MODE;
|
|
351
|
+
}
|
|
352
|
+
const value = content.env;
|
|
353
|
+
if (value === void 0) return DEFAULT_ENV_MODE;
|
|
354
|
+
if (typeof value === "string" && VALID_ENV_MODES.includes(value)) {
|
|
355
|
+
return value;
|
|
356
|
+
}
|
|
357
|
+
return DEFAULT_ENV_MODE;
|
|
358
|
+
}
|
|
176
359
|
function isYamlFile(filePath) {
|
|
177
360
|
return filePath.endsWith(".yml") || filePath.endsWith(".yaml");
|
|
178
361
|
}
|
|
179
362
|
|
|
363
|
+
// src/config/json-schema.ts
|
|
364
|
+
import { z as z2 } from "zod";
|
|
365
|
+
|
|
180
366
|
// src/proxy/transport-factory.ts
|
|
181
367
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
182
368
|
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
|
|
@@ -310,7 +496,7 @@ function buildAnnotationLines(tool) {
|
|
|
310
496
|
}
|
|
311
497
|
|
|
312
498
|
// src/proxy/upstream-client.ts
|
|
313
|
-
import
|
|
499
|
+
import process3 from "process";
|
|
314
500
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
315
501
|
var UpstreamClient = class {
|
|
316
502
|
transport;
|
|
@@ -319,7 +505,7 @@ var UpstreamClient = class {
|
|
|
319
505
|
constructor({ name, transport, onTransportError }) {
|
|
320
506
|
this.transport = transport;
|
|
321
507
|
this.onTransportError = onTransportError ?? ((error) => {
|
|
322
|
-
|
|
508
|
+
process3.stderr.write(`[${name}] Upstream MCP transport error: ${error.message}
|
|
323
509
|
`);
|
|
324
510
|
});
|
|
325
511
|
}
|
|
@@ -431,9 +617,9 @@ var Orchestrator = class {
|
|
|
431
617
|
};
|
|
432
618
|
|
|
433
619
|
// src/proxy/server.ts
|
|
434
|
-
import
|
|
620
|
+
import process4 from "process";
|
|
435
621
|
import { FastMCP } from "fastmcp";
|
|
436
|
-
import { z as
|
|
622
|
+
import { z as z3 } from "zod";
|
|
437
623
|
var ProxyServer = class {
|
|
438
624
|
catalog;
|
|
439
625
|
callTool;
|
|
@@ -449,7 +635,7 @@ var ProxyServer = class {
|
|
|
449
635
|
server.addTool({
|
|
450
636
|
name: "discover_tool",
|
|
451
637
|
description: this.catalog.discoverToolDescription,
|
|
452
|
-
parameters:
|
|
638
|
+
parameters: z3.object({ tool_name: z3.string() }),
|
|
453
639
|
execute: async ({ tool_name }) => {
|
|
454
640
|
return this.catalog.getToolDetails(tool_name);
|
|
455
641
|
}
|
|
@@ -457,9 +643,9 @@ var ProxyServer = class {
|
|
|
457
643
|
server.addTool({
|
|
458
644
|
name: "use_tool",
|
|
459
645
|
description: "Use a tool that was previously discovered with the discover_tool tool.",
|
|
460
|
-
parameters:
|
|
461
|
-
tool_name:
|
|
462
|
-
tool_input:
|
|
646
|
+
parameters: z3.object({
|
|
647
|
+
tool_name: z3.string(),
|
|
648
|
+
tool_input: z3.record(z3.string(), z3.unknown()).default({})
|
|
463
649
|
}),
|
|
464
650
|
execute: async ({ tool_name, tool_input }) => {
|
|
465
651
|
if (!this.catalog.tools.has(tool_name)) {
|
|
@@ -469,7 +655,7 @@ var ProxyServer = class {
|
|
|
469
655
|
return result;
|
|
470
656
|
}
|
|
471
657
|
});
|
|
472
|
-
|
|
658
|
+
process4.stderr.write("Starting dynamic-discovery-mcp server over stdio\n");
|
|
473
659
|
await server.start({ transportType: "stdio" });
|
|
474
660
|
}
|
|
475
661
|
};
|
|
@@ -482,7 +668,7 @@ async function startProxy(command, args) {
|
|
|
482
668
|
name: command,
|
|
483
669
|
transport,
|
|
484
670
|
onTransportError: (error) => {
|
|
485
|
-
|
|
671
|
+
process5.stderr.write(`Upstream MCP transport error: ${error.message}
|
|
486
672
|
`);
|
|
487
673
|
shutdown(1);
|
|
488
674
|
}
|
|
@@ -491,36 +677,36 @@ async function startProxy(command, args) {
|
|
|
491
677
|
if (isShuttingDown) return;
|
|
492
678
|
isShuttingDown = true;
|
|
493
679
|
upstreamClient.disconnect().catch((error) => {
|
|
494
|
-
|
|
680
|
+
process5.stderr.write(
|
|
495
681
|
`dynmcp: error during disconnect: ${error instanceof Error ? error.message : String(error)}
|
|
496
682
|
`
|
|
497
683
|
);
|
|
498
|
-
}).finally(() =>
|
|
684
|
+
}).finally(() => process5.exit(exitCode));
|
|
499
685
|
};
|
|
500
686
|
try {
|
|
501
687
|
await upstreamClient.connect();
|
|
502
688
|
} catch (error) {
|
|
503
|
-
|
|
689
|
+
process5.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
|
|
504
690
|
`);
|
|
505
|
-
|
|
691
|
+
process5.exit(1);
|
|
506
692
|
}
|
|
507
693
|
let tools;
|
|
508
694
|
try {
|
|
509
695
|
tools = await upstreamClient.listTools();
|
|
510
696
|
} catch (error) {
|
|
511
|
-
|
|
697
|
+
process5.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
|
|
512
698
|
`);
|
|
513
|
-
|
|
699
|
+
process5.exit(1);
|
|
514
700
|
}
|
|
515
701
|
const catalog = ToolCatalog.fromFlat(tools);
|
|
516
702
|
const proxyServer = new ProxyServer({
|
|
517
703
|
catalog,
|
|
518
704
|
callTool: (name, input) => upstreamClient.callTool(name, input)
|
|
519
705
|
});
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
706
|
+
process5.on("SIGINT", () => shutdown(0));
|
|
707
|
+
process5.on("SIGTERM", () => shutdown(0));
|
|
708
|
+
process5.stdin.on("end", () => shutdown(0));
|
|
709
|
+
process5.stdin.on("close", () => shutdown(0));
|
|
524
710
|
try {
|
|
525
711
|
await proxyServer.start();
|
|
526
712
|
} catch (error) {
|
|
@@ -528,9 +714,9 @@ async function startProxy(command, args) {
|
|
|
528
714
|
throw error;
|
|
529
715
|
}
|
|
530
716
|
}
|
|
531
|
-
async function startProxyFromConfig(
|
|
717
|
+
async function startProxyFromConfig(options = {}) {
|
|
532
718
|
let isShuttingDown = false;
|
|
533
|
-
const config = loadConfig(
|
|
719
|
+
const config = loadConfig(options);
|
|
534
720
|
const mcps = /* @__PURE__ */ new Map();
|
|
535
721
|
for (const [name, entry] of Object.entries(config.mcp)) {
|
|
536
722
|
mcps.set(name, { transport: createTransport(entry) });
|
|
@@ -538,7 +724,7 @@ async function startProxyFromConfig(configPath) {
|
|
|
538
724
|
const orchestrator = new Orchestrator({
|
|
539
725
|
mcps,
|
|
540
726
|
onTransportError: (mcpName2, error) => {
|
|
541
|
-
|
|
727
|
+
process5.stderr.write(`Upstream MCP "${mcpName2}" transport error: ${error.message}
|
|
542
728
|
`);
|
|
543
729
|
shutdown(1);
|
|
544
730
|
}
|
|
@@ -547,27 +733,27 @@ async function startProxyFromConfig(configPath) {
|
|
|
547
733
|
if (isShuttingDown) return;
|
|
548
734
|
isShuttingDown = true;
|
|
549
735
|
orchestrator.disconnectAll().catch((error) => {
|
|
550
|
-
|
|
736
|
+
process5.stderr.write(
|
|
551
737
|
`dynmcp: error during disconnect: ${error instanceof Error ? error.message : String(error)}
|
|
552
738
|
`
|
|
553
739
|
);
|
|
554
|
-
}).finally(() =>
|
|
740
|
+
}).finally(() => process5.exit(exitCode));
|
|
555
741
|
};
|
|
556
742
|
try {
|
|
557
743
|
await orchestrator.connect();
|
|
558
744
|
} catch (error) {
|
|
559
|
-
|
|
745
|
+
process5.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
|
|
560
746
|
`);
|
|
561
|
-
|
|
747
|
+
process5.exit(1);
|
|
562
748
|
}
|
|
563
749
|
const proxyServer = new ProxyServer({
|
|
564
750
|
catalog: orchestrator.catalog,
|
|
565
751
|
callTool: (name, input) => orchestrator.callTool(name, input)
|
|
566
752
|
});
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
753
|
+
process5.on("SIGINT", () => shutdown(0));
|
|
754
|
+
process5.on("SIGTERM", () => shutdown(0));
|
|
755
|
+
process5.stdin.on("end", () => shutdown(0));
|
|
756
|
+
process5.stdin.on("close", () => shutdown(0));
|
|
571
757
|
try {
|
|
572
758
|
await proxyServer.start();
|
|
573
759
|
} catch (error) {
|
|
@@ -587,39 +773,43 @@ var cliBanner = chalk.bold.magentaBright(
|
|
|
587
773
|
var cli = new Command(package_default.name).description(package_default.description).version(package_default.version).addHelpText("beforeAll", cliBanner).addHelpText(
|
|
588
774
|
"after",
|
|
589
775
|
"\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)").
|
|
591
|
-
|
|
776
|
+
).option("-c, --config <path>", "Path to config file (JSON or YAML)").option(
|
|
777
|
+
"-e, --env <path>",
|
|
778
|
+
"Path to a .env file for environment variable interpolation"
|
|
779
|
+
).allowExcessArguments(true).passThroughOptions(true).action(async (_options, cmd) => {
|
|
780
|
+
const separatorIndex = process6.argv.indexOf("--");
|
|
592
781
|
const configPath = cmd.opts().config;
|
|
782
|
+
const envFilePath = cmd.opts().env;
|
|
593
783
|
if (separatorIndex !== -1) {
|
|
594
|
-
const [command, ...args] =
|
|
784
|
+
const [command, ...args] = process6.argv.slice(separatorIndex + 1);
|
|
595
785
|
if (command === void 0) {
|
|
596
|
-
|
|
786
|
+
process6.stderr.write(
|
|
597
787
|
"dynmcp: no upstream command provided after --.\nUsage: dynmcp -- <command> [args...]\n"
|
|
598
788
|
);
|
|
599
|
-
|
|
789
|
+
process6.exit(1);
|
|
600
790
|
}
|
|
601
791
|
try {
|
|
602
792
|
await startProxy(command, args);
|
|
603
793
|
} catch (error) {
|
|
604
|
-
|
|
794
|
+
process6.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
|
|
605
795
|
`);
|
|
606
|
-
|
|
796
|
+
process6.exit(1);
|
|
607
797
|
}
|
|
608
798
|
return;
|
|
609
799
|
}
|
|
610
800
|
try {
|
|
611
|
-
await startProxyFromConfig(configPath);
|
|
801
|
+
await startProxyFromConfig({ configPath, envFilePath });
|
|
612
802
|
} catch (error) {
|
|
613
|
-
|
|
803
|
+
process6.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
|
|
614
804
|
`);
|
|
615
|
-
|
|
805
|
+
process6.exit(1);
|
|
616
806
|
}
|
|
617
807
|
});
|
|
618
808
|
|
|
619
809
|
// src/index.ts
|
|
620
|
-
import
|
|
810
|
+
import process7 from "process";
|
|
621
811
|
async function main() {
|
|
622
|
-
cli.parse(
|
|
812
|
+
cli.parse(process7.argv);
|
|
623
813
|
}
|
|
624
814
|
main();
|
|
625
815
|
//# sourceMappingURL=index.js.map
|