@alcyone-labs/arg-parser 2.6.0 → 2.7.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/dist/index.cjs CHANGED
@@ -35,6 +35,7 @@ var __flags, _throwForDuplicateFlags, _appName, _appCommandName, _subCommandName
35
35
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
36
36
  const fs = require("node:fs");
37
37
  const path = require("node:path");
38
+ const node_url = require("node:url");
38
39
  const magicRegexp = require("magic-regexp");
39
40
  const getTsconfig = require("get-tsconfig");
40
41
  const zod = require("zod");
@@ -3952,22 +3953,41 @@ const _ArgParserBase = class _ArgParserBase {
3952
3953
  console.log("--- End Configuration Dump ---\\n");
3953
3954
  }
3954
3955
  }
3956
+ /**
3957
+ * Detects if the current script is being executed directly (not imported)
3958
+ * Uses a robust method that works across different environments and sandboxes
3959
+ * @param importMetaUrl The import.meta.url from the calling script (optional)
3960
+ * @returns true if the script is being executed directly, false if imported
3961
+ */
3962
+ static isExecutedDirectly(importMetaUrl) {
3963
+ try {
3964
+ if (importMetaUrl) {
3965
+ const currentFile = node_url.fileURLToPath(importMetaUrl);
3966
+ const executedFile = path__namespace.resolve(process.argv[1]);
3967
+ return currentFile === executedFile;
3968
+ }
3969
+ if (typeof process !== "undefined" && process.argv && process.argv[1]) {
3970
+ return false;
3971
+ }
3972
+ return false;
3973
+ } catch {
3974
+ return false;
3975
+ }
3976
+ }
3955
3977
  async parse(processArgs, options) {
3956
- var _a, _b;
3978
+ var _a;
3957
3979
  debug.log("ArgParserBase.parse() called with args:", processArgs);
3980
+ const shouldCheckAutoExecution = (options == null ? void 0 : options.importMetaUrl) && (options == null ? void 0 : options.autoExecute) !== false;
3981
+ if (shouldCheckAutoExecution) {
3982
+ const isDirectExecution = _ArgParserBase.isExecutedDirectly(options.importMetaUrl);
3983
+ if (!isDirectExecution) {
3984
+ debug.log("Auto-execution enabled but script is imported, skipping execution");
3985
+ return {};
3986
+ }
3987
+ }
3958
3988
  if (processArgs === void 0) {
3959
3989
  if (typeof process !== "undefined" && process.argv && Array.isArray(process.argv)) {
3960
3990
  processArgs = process.argv.slice(2);
3961
- const isCliMode = !__privateGet(this, _parentParser) && !!__privateGet(this, _appCommandName);
3962
- const isMcpMode = (options == null ? void 0 : options.isMcp) || ((_a = globalThis.console) == null ? void 0 : _a.mcpError);
3963
- if (isCliMode && !isMcpMode) {
3964
- console.warn(
3965
- `Warning: parse() called without arguments. Auto-detected Node.js environment and using process.argv.slice(2).`
3966
- );
3967
- console.warn(
3968
- `For explicit control, call parse(process.argv.slice(2)) instead.`
3969
- );
3970
- }
3971
3991
  } else {
3972
3992
  throw new Error(
3973
3993
  "parse() called without arguments in non-Node.js environment. Please provide arguments explicitly: parse(['--flag', 'value'])"
@@ -3992,7 +4012,7 @@ const _ArgParserBase = class _ArgParserBase {
3992
4012
  commandChain: identifiedCommandChain,
3993
4013
  parserChain: identifiedParserChain
3994
4014
  } = __privateMethod(this, _ArgParserBase_instances, _identifyCommandChainAndParsers_fn).call(this, processArgs, this, [], [this]);
3995
- const saveToEnvResult = __privateMethod(_b = identifiedFinalParser, _ArgParserBase_instances, _handleSaveToEnvFlag_fn).call(_b, processArgs, identifiedParserChain);
4015
+ const saveToEnvResult = __privateMethod(_a = identifiedFinalParser, _ArgParserBase_instances, _handleSaveToEnvFlag_fn).call(_a, processArgs, identifiedParserChain);
3996
4016
  if (saveToEnvResult !== false) {
3997
4017
  return saveToEnvResult === true ? {} : saveToEnvResult;
3998
4018
  }
@@ -5311,7 +5331,10 @@ _startUnifiedMcpServer_fn = async function(mcpServerConfig, transportOptions) {
5311
5331
  const finalTransportOptions = {
5312
5332
  port: transportOptions.port,
5313
5333
  host: transportOptions.host || "localhost",
5314
- path: transportOptions.path || "/mcp"
5334
+ path: transportOptions.path || "/mcp",
5335
+ // Pass-through for streamable-http only; harmlessly ignored for others
5336
+ cors: transportOptions.cors,
5337
+ auth: transportOptions.auth
5315
5338
  };
5316
5339
  await mcpParser.startMcpServerWithTransport(
5317
5340
  serverInfo,
@@ -5419,6 +5442,27 @@ _parseMcpTransportOptions_fn = function(processArgs) {
5419
5442
  i++;
5420
5443
  }
5421
5444
  break;
5445
+ // Streamable HTTP extras (accept JSON string)
5446
+ case "--s-mcp-cors":
5447
+ if (nextArg && !nextArg.startsWith("-")) {
5448
+ try {
5449
+ options.cors = JSON.parse(nextArg);
5450
+ } catch {
5451
+ options.cors = nextArg;
5452
+ }
5453
+ i++;
5454
+ }
5455
+ break;
5456
+ case "--s-mcp-auth":
5457
+ if (nextArg && !nextArg.startsWith("-")) {
5458
+ try {
5459
+ options.auth = JSON.parse(nextArg);
5460
+ } catch {
5461
+ options.auth = nextArg;
5462
+ }
5463
+ i++;
5464
+ }
5465
+ break;
5422
5466
  // Backward compatibility: support old flags but with deprecation warning
5423
5467
  case "--transport":
5424
5468
  case "--port":
@@ -7456,6 +7500,34 @@ Migration guide: https://github.com/alcyone-labs/arg-parser/blob/main/docs/MCP-M
7456
7500
  parseAsync(processArgs, options) {
7457
7501
  return this.parse(processArgs, options);
7458
7502
  }
7503
+ /**
7504
+ * Convenience method for auto-execution: only runs if the script is executed directly (not imported).
7505
+ * This eliminates the need for boilerplate code to check if the script is being run directly.
7506
+ *
7507
+ * @param importMetaUrl Pass import.meta.url from your script for reliable detection
7508
+ * @param processArgs Optional arguments to parse (defaults to process.argv.slice(2))
7509
+ * @param options Additional parse options
7510
+ * @returns Promise that resolves to the parse result, or empty object if script is imported
7511
+ *
7512
+ * @example
7513
+ * ```typescript
7514
+ * // At the bottom of your CLI script:
7515
+ * await cli.parseIfExecutedDirectly(import.meta.url);
7516
+ *
7517
+ * // With error handling:
7518
+ * await cli.parseIfExecutedDirectly(import.meta.url).catch((error) => {
7519
+ * console.error("Fatal error:", error instanceof Error ? error.message : String(error));
7520
+ * process.exit(1);
7521
+ * });
7522
+ * ```
7523
+ */
7524
+ async parseIfExecutedDirectly(importMetaUrl, processArgs, options) {
7525
+ return this.parse(processArgs, {
7526
+ ...options,
7527
+ autoExecute: true,
7528
+ importMetaUrl
7529
+ });
7530
+ }
7459
7531
  addMcpSubCommand(subCommandName = "mcp-server", serverInfo, optionsOrToolOptions) {
7460
7532
  console.warn(`[DEPRECATED] addMcpSubCommand() is deprecated and will be removed in v2.0.
7461
7533
  Please use withMcp() to configure server metadata and the --s-mcp-serve system flag instead.
@@ -7734,6 +7806,7 @@ registerToolAsSubCommand_fn = function(toolConfig) {
7734
7806
  });
7735
7807
  };
7736
7808
  _startSingleTransport_fn = async function(server, serverInfo, transportConfig, logPath) {
7809
+ var _a, _b, _c, _d, _e, _f;
7737
7810
  const resolvedLogPath = resolveLogPath(logPath || "./logs/mcp.log");
7738
7811
  const logger = simpleMcpLogger.createMcpLogger("MCP Transport", resolvedLogPath);
7739
7812
  try {
@@ -7777,10 +7850,142 @@ _startSingleTransport_fn = async function(server, serverInfo, transportConfig, l
7777
7850
  const express = (await import("express")).default;
7778
7851
  const app = express();
7779
7852
  app.use(express.json());
7853
+ try {
7854
+ (_c = (_b = (_a = this._mcpServerConfig) == null ? void 0 : _a.httpServer) == null ? void 0 : _b.configureExpress) == null ? void 0 : _c.call(_b, app);
7855
+ } catch (e) {
7856
+ }
7780
7857
  const port = transportConfig.port || 3e3;
7781
7858
  const path2 = transportConfig.path || "/mcp";
7859
+ if (transportConfig.cors) {
7860
+ const cors = transportConfig.cors;
7861
+ const allowMethods = ((_d = cors.methods) == null ? void 0 : _d.join(", ")) || "GET,POST,PUT,PATCH,DELETE,OPTIONS";
7862
+ const allowHeaders = (req) => {
7863
+ var _a2;
7864
+ return ((_a2 = cors.headers) == null ? void 0 : _a2.join(", ")) || req.headers["access-control-request-headers"] || "Content-Type, Authorization, MCP-Session-Id";
7865
+ };
7866
+ const exposed = ((_e = cors.exposedHeaders) == null ? void 0 : _e.join(", ")) || void 0;
7867
+ const resolveOrigin = (req) => {
7868
+ const reqOrigin = req.headers.origin;
7869
+ const origins = cors.origins ?? "*";
7870
+ if (origins === "*") return cors.credentials ? reqOrigin : "*";
7871
+ if (!reqOrigin) return void 0;
7872
+ const list = Array.isArray(origins) ? origins : [origins];
7873
+ for (const o of list) {
7874
+ if (typeof o === "string" && o === reqOrigin) return reqOrigin;
7875
+ if (o instanceof RegExp && o.test(reqOrigin)) return reqOrigin;
7876
+ }
7877
+ return void 0;
7878
+ };
7879
+ const applyCorsHeaders = (req, res) => {
7880
+ const origin = resolveOrigin(req);
7881
+ if (origin) res.setHeader("Access-Control-Allow-Origin", origin);
7882
+ if (cors.credentials) res.setHeader("Access-Control-Allow-Credentials", "true");
7883
+ res.setHeader("Vary", "Origin");
7884
+ res.setHeader("Access-Control-Allow-Methods", allowMethods);
7885
+ const hdrs = allowHeaders(req);
7886
+ if (hdrs) res.setHeader("Access-Control-Allow-Headers", hdrs);
7887
+ if (exposed) res.setHeader("Access-Control-Expose-Headers", exposed);
7888
+ if (typeof cors.maxAge === "number") res.setHeader("Access-Control-Max-Age", String(cors.maxAge));
7889
+ };
7890
+ app.options(path2, (req, res) => {
7891
+ applyCorsHeaders(req, res);
7892
+ res.status(204).end();
7893
+ });
7894
+ app.use((req, res, next) => {
7895
+ if (req.path === path2) applyCorsHeaders(req, res);
7896
+ next();
7897
+ });
7898
+ }
7899
+ if ((_f = transportConfig.auth) == null ? void 0 : _f.customMiddleware) {
7900
+ app.use(transportConfig.auth.customMiddleware);
7901
+ }
7902
+ const authOpts = transportConfig.auth;
7903
+ const shouldRequireAuthFor = (req) => {
7904
+ if (!authOpts) return false;
7905
+ const reqPath = req.path;
7906
+ const pub = authOpts.publicPaths || [];
7907
+ const prot = authOpts.protectedPaths;
7908
+ if (pub.includes(reqPath)) return false;
7909
+ if (prot && !prot.includes(reqPath)) return false;
7910
+ return authOpts.required !== false;
7911
+ };
7912
+ const base64urlDecode = (s) => Buffer.from(s.replace(/-/g, "+").replace(/_/g, "/"), "base64");
7913
+ const verifyJwt = async (token) => {
7914
+ if (!(authOpts == null ? void 0 : authOpts.jwt)) return false;
7915
+ const [h, p, sig] = token.split(".");
7916
+ if (!h || !p || !sig) return false;
7917
+ const header = JSON.parse(base64urlDecode(h).toString("utf8"));
7918
+ const payload = JSON.parse(base64urlDecode(p).toString("utf8"));
7919
+ const alg = header.alg;
7920
+ if (authOpts.jwt.algorithms && !authOpts.jwt.algorithms.includes(alg)) return false;
7921
+ const data2 = Buffer.from(`${h}.${p}`);
7922
+ const signature = base64urlDecode(sig);
7923
+ if (alg === "HS256") {
7924
+ const secret = authOpts.jwt.secret;
7925
+ if (!secret) return false;
7926
+ const hmac = (await import("node:crypto")).createHmac("sha256", secret).update(data2).digest();
7927
+ if (!hmac.equals(signature)) return false;
7928
+ } else if (alg === "RS256") {
7929
+ const crypto = await import("node:crypto");
7930
+ let key = authOpts.jwt.publicKey;
7931
+ if (!key && authOpts.jwt.getPublicKey) {
7932
+ key = await authOpts.jwt.getPublicKey(header, payload);
7933
+ }
7934
+ if (!key) return false;
7935
+ const verify = crypto.createVerify("RSA-SHA256");
7936
+ verify.update(data2);
7937
+ verify.end();
7938
+ const ok = verify.verify(key, signature);
7939
+ if (!ok) return false;
7940
+ } else {
7941
+ return false;
7942
+ }
7943
+ if (authOpts.jwt.audience) {
7944
+ const allowed = Array.isArray(authOpts.jwt.audience) ? authOpts.jwt.audience : [authOpts.jwt.audience];
7945
+ if (!allowed.includes(payload.aud)) return false;
7946
+ }
7947
+ if (authOpts.jwt.issuer) {
7948
+ const allowed = Array.isArray(authOpts.jwt.issuer) ? authOpts.jwt.issuer : [authOpts.jwt.issuer];
7949
+ if (!allowed.includes(payload.iss)) return false;
7950
+ }
7951
+ const nowSec = Math.floor(Date.now() / 1e3);
7952
+ const tol = authOpts.jwt.clockToleranceSec || 0;
7953
+ if (payload.nbf && nowSec + tol < payload.nbf) return false;
7954
+ if (payload.exp && nowSec - tol >= payload.exp) return false;
7955
+ return true;
7956
+ };
7957
+ const authenticate = async (req) => {
7958
+ if (!authOpts) return true;
7959
+ const authz = req.headers.authorization;
7960
+ const token = (authz == null ? void 0 : authz.startsWith("Bearer ")) ? authz.slice(7) : void 0;
7961
+ if (!token) {
7962
+ if (authOpts.validator) return !!await authOpts.validator(req, token);
7963
+ return false;
7964
+ }
7965
+ if (authOpts.scheme === "jwt" || authOpts.jwt) {
7966
+ const ok = await verifyJwt(token);
7967
+ if (!ok) return false;
7968
+ } else if (authOpts.scheme === "bearer" || !authOpts.scheme) {
7969
+ if (authOpts.allowedTokens && !authOpts.allowedTokens.includes(token)) {
7970
+ if (authOpts.validator) return !!await authOpts.validator(req, token);
7971
+ return false;
7972
+ }
7973
+ }
7974
+ if (authOpts.validator) {
7975
+ const ok = await authOpts.validator(req, token);
7976
+ if (!ok) return false;
7977
+ }
7978
+ return true;
7979
+ };
7782
7980
  const transports = {};
7783
7981
  app.all(path2, async (req, res) => {
7982
+ if (shouldRequireAuthFor(req)) {
7983
+ const ok = await authenticate(req);
7984
+ if (!ok) {
7985
+ res.status(401).json({ error: "Unauthorized" });
7986
+ return;
7987
+ }
7988
+ }
7784
7989
  const sessionId = req.headers["mcp-session-id"];
7785
7990
  let transport;
7786
7991
  if (sessionId && transports[sessionId]) {
@@ -7793,9 +7998,7 @@ _startSingleTransport_fn = async function(server, serverInfo, transportConfig, l
7793
7998
  }
7794
7999
  });
7795
8000
  transport.onclose = () => {
7796
- if (transport.sessionId) {
7797
- delete transports[transport.sessionId];
7798
- }
8001
+ if (transport.sessionId) delete transports[transport.sessionId];
7799
8002
  };
7800
8003
  await server.connect(transport);
7801
8004
  }