dynmcp 0.1.1 → 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 CHANGED
@@ -102,6 +102,64 @@ When no `--` command is provided, `dynmcp` looks for a config file in this order
102
102
 
103
103
  MCP names (the keys in the config) must match `^[a-z0-9][a-z0-9-]*$`.
104
104
 
105
+ ## Environment Variable Interpolation
106
+
107
+ Config files can reference environment variables in any string-typed leaf value using shell-style syntax. This is useful for keeping secrets (bearer tokens, API keys) and host-specific values (paths, ports) out of the config file itself.
108
+
109
+ ```json
110
+ {
111
+ "mcp": {
112
+ "remote": {
113
+ "transport": "streamable-http",
114
+ "url": "${MCP_URL:-https://example.com/mcp}",
115
+ "headers": {
116
+ "Authorization": "Bearer ${MCP_TOKEN}"
117
+ }
118
+ }
119
+ }
120
+ }
121
+ ```
122
+
123
+ ### Syntax
124
+
125
+ | Form | Behavior |
126
+ |---|---|
127
+ | `${VAR}` | Replaced with the value of `VAR`. Hard error at startup if `VAR` is undefined. |
128
+ | `${VAR:-default}` | Replaced with `VAR` if set and non-empty, otherwise the literal `default` (may contain spaces, colons, etc.). |
129
+ | `$${...}` | Escape — emits a literal `${...}` with no interpolation. |
130
+
131
+ Interpolation only applies to **leaf string values** inside the `mcp` map (and nested objects/arrays within it). Map keys, the top-level `$schema` field, and the top-level `env` field are never interpolated. Partial-string interpolation works — `"Bearer ${TOKEN}"` is valid.
132
+
133
+ If any referenced variables are missing without a default, `dynmcp` exits at startup with an error listing **all** of them at once (not one at a time).
134
+
135
+ ### Sources (`env` field)
136
+
137
+ A top-level `env` field controls where variables are read from:
138
+
139
+ | Value | Behavior |
140
+ |---|---|
141
+ | `"enable"` (default) | Loads `.env` file (if present) and merges with `process.env`. `.env` values take precedence over `process.env` for the same key. |
142
+ | `"dotenv"` | Loads from `.env` file only. `process.env` is ignored. |
143
+ | `"process"` | Reads from `process.env` only. No `.env` file is loaded. |
144
+ | `"disable"` | Disables interpolation entirely — `${VAR}` is left literal. |
145
+
146
+ ```json
147
+ {
148
+ "env": "process",
149
+ "mcp": { /* ... */ }
150
+ }
151
+ ```
152
+
153
+ ### `.env` File Discovery
154
+
155
+ By default, `dynmcp` looks for a file literally named `.env` in the current working directory. To use a different path, pass `--env` / `-e`:
156
+
157
+ ```bash
158
+ dynmcp --env ./secrets.env
159
+ ```
160
+
161
+ Combining `--env` with `env: "disable"` or `env: "process"` is rejected as incoherent (no `.env` would be loaded). If `--env` points to a file that does not exist, `dynmcp` exits with an error.
162
+
105
163
  ## CLI Reference
106
164
 
107
165
  ```
@@ -113,6 +171,7 @@ dynmcp [options] [-- <upstream-command> [upstream-args...]]
113
171
  | `--version` | `-v` | Print the package version and exit |
114
172
  | `--help` | `-h` | Print usage information and exit |
115
173
  | `--config <path>` | `-c` | Path to config file (JSON or YAML) |
174
+ | `--env <path>` | `-e` | Path to a custom `.env` file for variable interpolation |
116
175
  | `--` | | Everything after is the upstream MCP command (single-MCP mode) |
117
176
 
118
177
  ### Mode Resolution
package/dist/index.cjs CHANGED
@@ -23,13 +23,13 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  ));
24
24
 
25
25
  // src/cli.ts
26
- var import_node_process4 = __toESM(require("process"), 1);
26
+ var import_node_process6 = __toESM(require("process"), 1);
27
27
  var import_commander = require("commander");
28
28
 
29
29
  // package.json
30
30
  var package_default = {
31
31
  name: "dynmcp",
32
- version: "0.1.1",
32
+ version: "0.2.0",
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",
@@ -96,6 +96,7 @@ var package_default = {
96
96
  boxen: "^8.0.1",
97
97
  chalk: "^5.6.2",
98
98
  commander: "^14.0.3",
99
+ dotenv: "^17.4.2",
99
100
  enquirer: "^2.4.1",
100
101
  fastmcp: "^4.0.1",
101
102
  figlet: "^1.11.0",
@@ -121,13 +122,16 @@ var import_figlet = __toESM(require("figlet"), 1);
121
122
  var import_chalk = __toESM(require("chalk"), 1);
122
123
 
123
124
  // src/proxy/index.ts
124
- var import_node_process3 = __toESM(require("process"), 1);
125
+ var import_node_process5 = __toESM(require("process"), 1);
125
126
  var import_stdio2 = require("@modelcontextprotocol/sdk/client/stdio.js");
126
127
 
127
128
  // src/config/schema.ts
128
129
  var import_zod = require("zod");
129
130
  var MCP_NAME_PATTERN = /^[a-z0-9][a-z0-9-]*$/;
130
131
  var mcpName = import_zod.z.string().regex(MCP_NAME_PATTERN);
132
+ var envModeSchema = import_zod.z.enum(["enable", "dotenv", "process", "disable"]).describe(
133
+ '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.'
134
+ );
131
135
  var stdioTransport = import_zod.z.object({
132
136
  transport: import_zod.z.literal("stdio"),
133
137
  command: import_zod.z.string(),
@@ -153,51 +157,227 @@ var transportConfig = import_zod.z.discriminatedUnion("transport", [
153
157
  sseTransport
154
158
  ]);
155
159
  var mcpConfigSchema = import_zod.z.object({
160
+ env: envModeSchema.optional(),
156
161
  mcp: import_zod.z.record(mcpName, transportConfig).refine((obj) => Object.keys(obj).length > 0, { message: "At least one MCP must be configured" })
157
162
  });
158
163
 
159
164
  // src/config/loader.ts
160
- var import_node_fs = require("fs");
161
165
  var import_node_fs2 = require("fs");
162
- var import_node_path = require("path");
166
+ var import_node_path2 = require("path");
167
+ var import_node_process2 = __toESM(require("process"), 1);
163
168
  var import_yaml = require("yaml");
169
+
170
+ // src/config/env-sources.ts
171
+ var import_node_fs = require("fs");
172
+ var import_node_path = require("path");
173
+ var import_node_process = __toESM(require("process"), 1);
174
+ var import_dotenv = __toESM(require("dotenv"), 1);
175
+ var DEFAULT_DOTENV_FILENAME = ".env";
176
+ function loadEnv(options) {
177
+ const { mode, envFilePath, cwd = import_node_process.default.cwd(), processEnv = import_node_process.default.env } = options;
178
+ if (envFilePath !== void 0 && (mode === "disable" || mode === "process")) {
179
+ throw new Error(
180
+ `--env flag is incompatible with env mode "${mode}". --env requires env mode "enable" or "dotenv".`
181
+ );
182
+ }
183
+ if (mode === "disable") {
184
+ return { variables: {}, interpolationEnabled: false };
185
+ }
186
+ const dotenvVars = mode === "process" ? {} : readDotenvFile(envFilePath, cwd);
187
+ const processVars = mode === "dotenv" ? {} : filterDefined(processEnv);
188
+ const variables = { ...processVars, ...dotenvVars };
189
+ return { variables, interpolationEnabled: true };
190
+ }
191
+ function readDotenvFile(envFilePath, cwd) {
192
+ const isExplicit = envFilePath !== void 0;
193
+ const resolvedPath = isExplicit ? (0, import_node_path.resolve)(envFilePath) : (0, import_node_path.resolve)(cwd, DEFAULT_DOTENV_FILENAME);
194
+ if (!(0, import_node_fs.existsSync)(resolvedPath)) {
195
+ if (isExplicit) {
196
+ throw new Error(`.env file not found: ${resolvedPath}`);
197
+ }
198
+ return {};
199
+ }
200
+ let raw;
201
+ try {
202
+ raw = (0, import_node_fs.readFileSync)(resolvedPath, "utf-8");
203
+ } catch (readError) {
204
+ const message = readError instanceof Error ? readError.message : String(readError);
205
+ throw new Error(`Failed to read .env file (${resolvedPath}): ${message}`);
206
+ }
207
+ try {
208
+ return import_dotenv.default.parse(raw);
209
+ } catch (parseError) {
210
+ const message = parseError instanceof Error ? parseError.message : String(parseError);
211
+ throw new Error(`Failed to parse .env file (${resolvedPath}): ${message}`);
212
+ }
213
+ }
214
+ function filterDefined(env) {
215
+ const result = {};
216
+ for (const [key, value] of Object.entries(env)) {
217
+ if (value !== void 0) {
218
+ result[key] = value;
219
+ }
220
+ }
221
+ return result;
222
+ }
223
+
224
+ // src/config/interpolate.ts
225
+ var TOP_LEVEL_PASSTHROUGH_KEYS = /* @__PURE__ */ new Set(["$schema", "env"]);
226
+ var MissingEnvVarsError = class extends Error {
227
+ constructor(missingVars) {
228
+ const list = missingVars.join(", ");
229
+ const plural = missingVars.length === 1 ? "" : "s";
230
+ super(`Missing required environment variable${plural}: ${list}`);
231
+ this.missingVars = missingVars;
232
+ this.name = "MissingEnvVarsError";
233
+ }
234
+ missingVars;
235
+ };
236
+ function interpolateConfig(config, env) {
237
+ if (config === null || typeof config !== "object" || Array.isArray(config)) {
238
+ return config;
239
+ }
240
+ const missing = [];
241
+ const result = {};
242
+ for (const [key, value] of Object.entries(config)) {
243
+ if (TOP_LEVEL_PASSTHROUGH_KEYS.has(key)) {
244
+ result[key] = value;
245
+ } else {
246
+ result[key] = walkNode(value, env, missing);
247
+ }
248
+ }
249
+ if (missing.length > 0) {
250
+ const unique = Array.from(new Set(missing)).sort();
251
+ throw new MissingEnvVarsError(unique);
252
+ }
253
+ return result;
254
+ }
255
+ function walkNode(node, env, missing) {
256
+ if (typeof node === "string") {
257
+ return interpolateString(node, env, missing);
258
+ }
259
+ if (Array.isArray(node)) {
260
+ return node.map((item) => walkNode(item, env, missing));
261
+ }
262
+ if (node !== null && typeof node === "object") {
263
+ const result = {};
264
+ for (const [key, value] of Object.entries(node)) {
265
+ result[key] = walkNode(value, env, missing);
266
+ }
267
+ return result;
268
+ }
269
+ return node;
270
+ }
271
+ function interpolateString(value, env, missing) {
272
+ let result = "";
273
+ let i = 0;
274
+ const len = value.length;
275
+ while (i < len) {
276
+ const ch = value[i];
277
+ if (ch === "$" && value[i + 1] === "$" && value[i + 2] === "{") {
278
+ const close = value.indexOf("}", i + 3);
279
+ if (close === -1) {
280
+ result += ch;
281
+ i += 1;
282
+ continue;
283
+ }
284
+ result += value.substring(i + 1, close + 1);
285
+ i = close + 1;
286
+ continue;
287
+ }
288
+ if (ch === "$" && value[i + 1] === "{") {
289
+ const close = value.indexOf("}", i + 2);
290
+ if (close === -1) {
291
+ result += value.substring(i);
292
+ break;
293
+ }
294
+ const expr = value.substring(i + 2, close);
295
+ const { name, defaultValue } = parseExpr(expr);
296
+ const resolved = env[name];
297
+ const hasValue = resolved !== void 0 && resolved !== "";
298
+ if (hasValue) {
299
+ result += resolved;
300
+ } else if (defaultValue !== void 0) {
301
+ result += defaultValue;
302
+ } else if (resolved !== void 0) {
303
+ result += "";
304
+ } else {
305
+ missing.push(name);
306
+ }
307
+ i = close + 1;
308
+ continue;
309
+ }
310
+ result += ch;
311
+ i += 1;
312
+ }
313
+ return result;
314
+ }
315
+ function parseExpr(expr) {
316
+ const sep = expr.indexOf(":-");
317
+ if (sep === -1) {
318
+ return { name: expr, defaultValue: void 0 };
319
+ }
320
+ return {
321
+ name: expr.substring(0, sep),
322
+ defaultValue: expr.substring(sep + 2)
323
+ };
324
+ }
325
+
326
+ // src/config/loader.ts
164
327
  var AUTO_DISCOVER_NAMES = ["mcp.json", ".mcp.json"];
328
+ var DEFAULT_ENV_MODE = "enable";
329
+ var VALID_ENV_MODES = ["enable", "dotenv", "process", "disable"];
165
330
  function resolveConfigPath(explicitPath) {
166
331
  if (explicitPath) {
167
- const resolved = (0, import_node_path.resolve)(explicitPath);
332
+ const resolved = (0, import_node_path2.resolve)(explicitPath);
168
333
  if (!(0, import_node_fs2.existsSync)(resolved)) {
169
334
  throw new Error(`Config file not found: ${resolved}`);
170
335
  }
171
336
  return resolved;
172
337
  }
173
- const cwd = process.cwd();
338
+ const cwd = import_node_process2.default.cwd();
174
339
  for (const name of AUTO_DISCOVER_NAMES) {
175
- const candidate = (0, import_node_path.resolve)(cwd, name);
340
+ const candidate = (0, import_node_path2.resolve)(cwd, name);
176
341
  if ((0, import_node_fs2.existsSync)(candidate)) {
177
342
  return candidate;
178
343
  }
179
344
  }
180
- const searched = AUTO_DISCOVER_NAMES.map((n) => (0, import_node_path.resolve)(cwd, n)).join(", ");
345
+ const searched = AUTO_DISCOVER_NAMES.map((n) => (0, import_node_path2.resolve)(cwd, n)).join(", ");
181
346
  throw new Error(`No config file found. Searched: ${searched}`);
182
347
  }
183
- function loadConfig(explicitPath) {
184
- const configPath = resolveConfigPath(explicitPath);
185
- const raw = (0, import_node_fs.readFileSync)(configPath, "utf-8");
348
+ function loadConfig(options = {}) {
349
+ const { configPath, envFilePath } = options;
350
+ const resolvedPath = resolveConfigPath(configPath);
351
+ const raw = (0, import_node_fs2.readFileSync)(resolvedPath, "utf-8");
186
352
  let content;
187
353
  try {
188
- content = isYamlFile(configPath) ? (0, import_yaml.parse)(raw) : JSON.parse(raw);
354
+ content = isYamlFile(resolvedPath) ? (0, import_yaml.parse)(raw) : JSON.parse(raw);
189
355
  } catch (parseError) {
190
356
  const message = parseError instanceof Error ? parseError.message : String(parseError);
191
- throw new Error(`Failed to parse config file (${configPath}): ${message}`);
357
+ throw new Error(`Failed to parse config file (${resolvedPath}): ${message}`);
192
358
  }
193
- const result = mcpConfigSchema.safeParse(content);
359
+ const envMode = readEnvMode(content);
360
+ const loadedEnv = loadEnv({ mode: envMode, envFilePath });
361
+ const interpolated = loadedEnv.interpolationEnabled ? interpolateConfig(content, loadedEnv.variables) : content;
362
+ const result = mcpConfigSchema.safeParse(interpolated);
194
363
  if (!result.success) {
195
364
  const formatted = result.error.issues.map((issue) => ` - ${issue.path.join(".")}: ${issue.message}`).join("\n");
196
- throw new Error(`Invalid config file (${configPath}):
365
+ throw new Error(`Invalid config file (${resolvedPath}):
197
366
  ${formatted}`);
198
367
  }
199
368
  return result.data;
200
369
  }
370
+ function readEnvMode(content) {
371
+ if (content === null || typeof content !== "object" || Array.isArray(content)) {
372
+ return DEFAULT_ENV_MODE;
373
+ }
374
+ const value = content.env;
375
+ if (value === void 0) return DEFAULT_ENV_MODE;
376
+ if (typeof value === "string" && VALID_ENV_MODES.includes(value)) {
377
+ return value;
378
+ }
379
+ return DEFAULT_ENV_MODE;
380
+ }
201
381
  function isYamlFile(filePath) {
202
382
  return filePath.endsWith(".yml") || filePath.endsWith(".yaml");
203
383
  }
@@ -338,7 +518,7 @@ function buildAnnotationLines(tool) {
338
518
  }
339
519
 
340
520
  // src/proxy/upstream-client.ts
341
- var import_node_process = __toESM(require("process"), 1);
521
+ var import_node_process3 = __toESM(require("process"), 1);
342
522
  var import_client = require("@modelcontextprotocol/sdk/client/index.js");
343
523
  var UpstreamClient = class {
344
524
  transport;
@@ -347,7 +527,7 @@ var UpstreamClient = class {
347
527
  constructor({ name, transport, onTransportError }) {
348
528
  this.transport = transport;
349
529
  this.onTransportError = onTransportError ?? ((error) => {
350
- import_node_process.default.stderr.write(`[${name}] Upstream MCP transport error: ${error.message}
530
+ import_node_process3.default.stderr.write(`[${name}] Upstream MCP transport error: ${error.message}
351
531
  `);
352
532
  });
353
533
  }
@@ -459,7 +639,7 @@ var Orchestrator = class {
459
639
  };
460
640
 
461
641
  // src/proxy/server.ts
462
- var import_node_process2 = __toESM(require("process"), 1);
642
+ var import_node_process4 = __toESM(require("process"), 1);
463
643
  var import_fastmcp = require("fastmcp");
464
644
  var import_zod3 = require("zod");
465
645
  var ProxyServer = class {
@@ -497,7 +677,7 @@ var ProxyServer = class {
497
677
  return result;
498
678
  }
499
679
  });
500
- import_node_process2.default.stderr.write("Starting dynamic-discovery-mcp server over stdio\n");
680
+ import_node_process4.default.stderr.write("Starting dynamic-discovery-mcp server over stdio\n");
501
681
  await server.start({ transportType: "stdio" });
502
682
  }
503
683
  };
@@ -510,7 +690,7 @@ async function startProxy(command, args) {
510
690
  name: command,
511
691
  transport,
512
692
  onTransportError: (error) => {
513
- import_node_process3.default.stderr.write(`Upstream MCP transport error: ${error.message}
693
+ import_node_process5.default.stderr.write(`Upstream MCP transport error: ${error.message}
514
694
  `);
515
695
  shutdown(1);
516
696
  }
@@ -519,36 +699,36 @@ async function startProxy(command, args) {
519
699
  if (isShuttingDown) return;
520
700
  isShuttingDown = true;
521
701
  upstreamClient.disconnect().catch((error) => {
522
- import_node_process3.default.stderr.write(
702
+ import_node_process5.default.stderr.write(
523
703
  `dynmcp: error during disconnect: ${error instanceof Error ? error.message : String(error)}
524
704
  `
525
705
  );
526
- }).finally(() => import_node_process3.default.exit(exitCode));
706
+ }).finally(() => import_node_process5.default.exit(exitCode));
527
707
  };
528
708
  try {
529
709
  await upstreamClient.connect();
530
710
  } catch (error) {
531
- import_node_process3.default.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
711
+ import_node_process5.default.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
532
712
  `);
533
- import_node_process3.default.exit(1);
713
+ import_node_process5.default.exit(1);
534
714
  }
535
715
  let tools;
536
716
  try {
537
717
  tools = await upstreamClient.listTools();
538
718
  } catch (error) {
539
- import_node_process3.default.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
719
+ import_node_process5.default.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
540
720
  `);
541
- import_node_process3.default.exit(1);
721
+ import_node_process5.default.exit(1);
542
722
  }
543
723
  const catalog = ToolCatalog.fromFlat(tools);
544
724
  const proxyServer = new ProxyServer({
545
725
  catalog,
546
726
  callTool: (name, input) => upstreamClient.callTool(name, input)
547
727
  });
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));
728
+ import_node_process5.default.on("SIGINT", () => shutdown(0));
729
+ import_node_process5.default.on("SIGTERM", () => shutdown(0));
730
+ import_node_process5.default.stdin.on("end", () => shutdown(0));
731
+ import_node_process5.default.stdin.on("close", () => shutdown(0));
552
732
  try {
553
733
  await proxyServer.start();
554
734
  } catch (error) {
@@ -556,9 +736,9 @@ async function startProxy(command, args) {
556
736
  throw error;
557
737
  }
558
738
  }
559
- async function startProxyFromConfig(configPath) {
739
+ async function startProxyFromConfig(options = {}) {
560
740
  let isShuttingDown = false;
561
- const config = loadConfig(configPath);
741
+ const config = loadConfig(options);
562
742
  const mcps = /* @__PURE__ */ new Map();
563
743
  for (const [name, entry] of Object.entries(config.mcp)) {
564
744
  mcps.set(name, { transport: createTransport(entry) });
@@ -566,7 +746,7 @@ async function startProxyFromConfig(configPath) {
566
746
  const orchestrator = new Orchestrator({
567
747
  mcps,
568
748
  onTransportError: (mcpName2, error) => {
569
- import_node_process3.default.stderr.write(`Upstream MCP "${mcpName2}" transport error: ${error.message}
749
+ import_node_process5.default.stderr.write(`Upstream MCP "${mcpName2}" transport error: ${error.message}
570
750
  `);
571
751
  shutdown(1);
572
752
  }
@@ -575,27 +755,27 @@ async function startProxyFromConfig(configPath) {
575
755
  if (isShuttingDown) return;
576
756
  isShuttingDown = true;
577
757
  orchestrator.disconnectAll().catch((error) => {
578
- import_node_process3.default.stderr.write(
758
+ import_node_process5.default.stderr.write(
579
759
  `dynmcp: error during disconnect: ${error instanceof Error ? error.message : String(error)}
580
760
  `
581
761
  );
582
- }).finally(() => import_node_process3.default.exit(exitCode));
762
+ }).finally(() => import_node_process5.default.exit(exitCode));
583
763
  };
584
764
  try {
585
765
  await orchestrator.connect();
586
766
  } catch (error) {
587
- import_node_process3.default.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
767
+ import_node_process5.default.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
588
768
  `);
589
- import_node_process3.default.exit(1);
769
+ import_node_process5.default.exit(1);
590
770
  }
591
771
  const proxyServer = new ProxyServer({
592
772
  catalog: orchestrator.catalog,
593
773
  callTool: (name, input) => orchestrator.callTool(name, input)
594
774
  });
595
- import_node_process3.default.on("SIGINT", () => shutdown(0));
596
- import_node_process3.default.on("SIGTERM", () => shutdown(0));
597
- import_node_process3.default.stdin.on("end", () => shutdown(0));
598
- import_node_process3.default.stdin.on("close", () => shutdown(0));
775
+ import_node_process5.default.on("SIGINT", () => shutdown(0));
776
+ import_node_process5.default.on("SIGTERM", () => shutdown(0));
777
+ import_node_process5.default.stdin.on("end", () => shutdown(0));
778
+ import_node_process5.default.stdin.on("close", () => shutdown(0));
599
779
  try {
600
780
  await proxyServer.start();
601
781
  } catch (error) {
@@ -615,39 +795,43 @@ var cliBanner = import_chalk.default.bold.magentaBright(
615
795
  var cli = new import_commander.Command(package_default.name).description(package_default.description).version(package_default.version).addHelpText("beforeAll", cliBanner).addHelpText(
616
796
  "after",
617
797
  "\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) => {
619
- const separatorIndex = import_node_process4.default.argv.indexOf("--");
798
+ ).option("-c, --config <path>", "Path to config file (JSON or YAML)").option(
799
+ "-e, --env <path>",
800
+ "Path to a .env file for environment variable interpolation"
801
+ ).allowExcessArguments(true).passThroughOptions(true).action(async (_options, cmd) => {
802
+ const separatorIndex = import_node_process6.default.argv.indexOf("--");
620
803
  const configPath = cmd.opts().config;
804
+ const envFilePath = cmd.opts().env;
621
805
  if (separatorIndex !== -1) {
622
- const [command, ...args] = import_node_process4.default.argv.slice(separatorIndex + 1);
806
+ const [command, ...args] = import_node_process6.default.argv.slice(separatorIndex + 1);
623
807
  if (command === void 0) {
624
- import_node_process4.default.stderr.write(
808
+ import_node_process6.default.stderr.write(
625
809
  "dynmcp: no upstream command provided after --.\nUsage: dynmcp -- <command> [args...]\n"
626
810
  );
627
- import_node_process4.default.exit(1);
811
+ import_node_process6.default.exit(1);
628
812
  }
629
813
  try {
630
814
  await startProxy(command, args);
631
815
  } catch (error) {
632
- import_node_process4.default.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
816
+ import_node_process6.default.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
633
817
  `);
634
- import_node_process4.default.exit(1);
818
+ import_node_process6.default.exit(1);
635
819
  }
636
820
  return;
637
821
  }
638
822
  try {
639
- await startProxyFromConfig(configPath);
823
+ await startProxyFromConfig({ configPath, envFilePath });
640
824
  } catch (error) {
641
- import_node_process4.default.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
825
+ import_node_process6.default.stderr.write(`dynmcp: ${error instanceof Error ? error.message : String(error)}
642
826
  `);
643
- import_node_process4.default.exit(1);
827
+ import_node_process6.default.exit(1);
644
828
  }
645
829
  });
646
830
 
647
831
  // src/index.ts
648
- var import_node_process5 = __toESM(require("process"), 1);
832
+ var import_node_process7 = __toESM(require("process"), 1);
649
833
  async function main() {
650
- cli.parse(import_node_process5.default.argv);
834
+ cli.parse(import_node_process7.default.argv);
651
835
  }
652
836
  main();
653
837
  //# sourceMappingURL=index.cjs.map