@ai-ecoverse/mcpcat 0.31.17 → 0.31.18

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.
@@ -6558,6 +6558,12 @@ function agentIgnoresConfig(platform2, path) {
6558
6558
  function projectConfigPath(platform2) {
6559
6559
  return `${platform2.cwd}/.pi/mcp.json`;
6560
6560
  }
6561
+ function cacheDir(platform2) {
6562
+ if (platform2.env.XDG_CACHE_HOME) return `${platform2.env.XDG_CACHE_HOME}/mcpcat`;
6563
+ const home = platform2.env.HOME ?? platform2.env.USERPROFILE ?? "/";
6564
+ if (platform2.env.LOCALAPPDATA) return `${platform2.env.LOCALAPPDATA}\\mcpcat\\cache`;
6565
+ return `${home}/Library/Caches/mcpcat`;
6566
+ }
6561
6567
  function stateDir(platform2) {
6562
6568
  if (platform2.env.XDG_STATE_HOME) return `${platform2.env.XDG_STATE_HOME}/mcpcat`;
6563
6569
  const home = platform2.env.HOME ?? platform2.env.USERPROFILE ?? "/";
@@ -17441,6 +17447,142 @@ function parseLauncher(text) {
17441
17447
  return m ? { node: m[1], entry: m[2] } : void 0;
17442
17448
  }
17443
17449
 
17450
+ // src/auth/callback-port.ts
17451
+ function hasFixedCallback(oauth) {
17452
+ if (oauth?.callbackPort !== void 0 && oauth.callbackPort > 0) return true;
17453
+ if (!oauth?.callbackUrl) return false;
17454
+ try {
17455
+ const port = Number(new URL(oauth.callbackUrl).port);
17456
+ return port > 0;
17457
+ } catch {
17458
+ return false;
17459
+ }
17460
+ }
17461
+ function needsInjectedCallbackPort(options) {
17462
+ return options.namedEntry && !hasFixedCallback(options.oauth);
17463
+ }
17464
+ async function withInjectedCallbackPortIfNeeded(platform2, name, options, fn) {
17465
+ if (!needsInjectedCallbackPort(options)) return fn(void 0);
17466
+ const port = await allocateCallbackPort();
17467
+ return withTempCallbackPortConfig(platform2, name, port, fn);
17468
+ }
17469
+ async function allocateCallbackPort() {
17470
+ const callback = await OAuthCallbackServer.listen({
17471
+ host: "127.0.0.1",
17472
+ port: 0,
17473
+ path: "/callback"
17474
+ });
17475
+ try {
17476
+ return Number(new URL(callback.redirectUrl).port);
17477
+ } finally {
17478
+ await callback.close();
17479
+ }
17480
+ }
17481
+ function isRecord7(value) {
17482
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17483
+ }
17484
+ function hostEnv(platform2) {
17485
+ const out = {};
17486
+ for (const [key, value] of Object.entries(platform2.env)) {
17487
+ if (value !== void 0) out[key] = value;
17488
+ }
17489
+ return out;
17490
+ }
17491
+ async function makeTempDir(platform2) {
17492
+ const base = `${cacheDir(platform2)}/mcp-cb`;
17493
+ await platform2.fs.mkdir(base, { mode: 448 });
17494
+ for (let i = 0; i < 32; i++) {
17495
+ const dir = `${base}/${Date.now().toString(36)}-${i.toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
17496
+ try {
17497
+ await platform2.fs.mkdirExclusive(dir);
17498
+ return dir;
17499
+ } catch {
17500
+ }
17501
+ }
17502
+ throw new Error("unable to create temporary mcp config directory");
17503
+ }
17504
+ async function removeTempDir(platform2, dir) {
17505
+ try {
17506
+ if (platform2.fs.unlink) await platform2.fs.unlink(`${dir}/mcp.json`);
17507
+ } catch {
17508
+ }
17509
+ try {
17510
+ await platform2.fs.rmdir(dir);
17511
+ } catch {
17512
+ }
17513
+ }
17514
+ async function withTempCallbackPortConfig(platform2, name, port, fn) {
17515
+ const path = userConfigPath(platform2);
17516
+ let raw;
17517
+ try {
17518
+ raw = new TextDecoder().decode(await platform2.fs.readFile(path));
17519
+ } catch {
17520
+ return fn(void 0);
17521
+ }
17522
+ let parsed;
17523
+ try {
17524
+ parsed = JSON.parse(raw);
17525
+ } catch {
17526
+ return fn(void 0);
17527
+ }
17528
+ if (!isRecord7(parsed) || !isRecord7(parsed.mcpServers) || !isRecord7(parsed.mcpServers[name])) {
17529
+ return fn(void 0);
17530
+ }
17531
+ const entry = { ...parsed.mcpServers[name] };
17532
+ const prevOauth = isRecord7(entry.oauth) ? { ...entry.oauth } : {};
17533
+ const oauth = { ...prevOauth, callbackPort: port };
17534
+ if (typeof oauth.callbackUrl === "string") {
17535
+ try {
17536
+ const url = new URL(oauth.callbackUrl);
17537
+ if (!(Number(url.port) > 0)) {
17538
+ url.port = String(port);
17539
+ oauth.callbackUrl = url.href;
17540
+ }
17541
+ } catch {
17542
+ }
17543
+ }
17544
+ entry.oauth = oauth;
17545
+ const tempServers = { ...parsed.mcpServers, [name]: entry };
17546
+ const merged = { ...parsed, mcpServers: tempServers };
17547
+ const tempDir = await makeTempDir(platform2);
17548
+ const tempPath = `${tempDir}/mcp.json`;
17549
+ await platform2.fs.writeFile(tempPath, `${JSON.stringify(merged, null, 2)}
17550
+ `, { mode: 384 });
17551
+ const env = { ...hostEnv(platform2), PI_MCP_CONFIG: tempPath };
17552
+ let cleaned = false;
17553
+ const cleanup = async () => {
17554
+ if (cleaned) return;
17555
+ cleaned = true;
17556
+ await removeTempDir(platform2, tempDir);
17557
+ };
17558
+ const exitFor = (signal) => signal === "SIGINT" ? 130 : signal === "SIGTERM" ? 143 : 129;
17559
+ let signalCode;
17560
+ let wake;
17561
+ const stopped = new Promise((resolve) => {
17562
+ wake = resolve;
17563
+ });
17564
+ if (platform2.onSignal) {
17565
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
17566
+ await platform2.onSignal(signal, () => {
17567
+ void cleanup().then(() => {
17568
+ signalCode = exitFor(signal);
17569
+ wake?.();
17570
+ });
17571
+ });
17572
+ }
17573
+ }
17574
+ try {
17575
+ const result = await Promise.race([
17576
+ fn(env).then((value) => ({ ok: true, value })),
17577
+ stopped.then(() => ({ ok: false }))
17578
+ ]);
17579
+ if (!result.ok) platform2.exit(signalCode ?? 130);
17580
+ return result.value;
17581
+ } finally {
17582
+ await cleanup();
17583
+ }
17584
+ }
17585
+
17444
17586
  // src/auth/oauth.ts
17445
17587
  var CALLBACK_HOST2 = "127.0.0.1";
17446
17588
  var CALLBACK_PATH2 = "/callback";
@@ -17456,14 +17598,23 @@ function timedFetch2(fetchImpl) {
17456
17598
  });
17457
17599
  };
17458
17600
  }
17601
+ function nonzeroPort(value) {
17602
+ return value !== void 0 && value > 0 ? value : void 0;
17603
+ }
17604
+ function preferredPortFromRegistration(configured, registered) {
17605
+ if (configured !== void 0) return configured;
17606
+ if (!registered[0]) return void 0;
17607
+ return nonzeroPort(Number(new URL(registered[0]).port));
17608
+ }
17459
17609
  function callbackSettings(oauth) {
17460
17610
  const raw = oauth?.callbackUrl ?? `http://${CALLBACK_HOST2}${CALLBACK_PATH2}`;
17461
17611
  const url = new URL(raw);
17462
17612
  const address = url.hostname.replace(/^\[|\]$/g, "");
17463
- const port = url.port ? Number(url.port) : oauth?.callbackPort;
17613
+ const port = nonzeroPort(url.port ? Number(url.port) : oauth?.callbackPort);
17464
17614
  let fixedRedirectUrl;
17465
- if (url.port) fixedRedirectUrl = oauth?.callbackUrl;
17466
- else if (port !== void 0) {
17615
+ if (nonzeroPort(url.port ? Number(url.port) : void 0) !== void 0 && oauth?.callbackUrl) {
17616
+ fixedRedirectUrl = oauth.callbackUrl;
17617
+ } else if (port !== void 0) {
17467
17618
  url.port = String(port);
17468
17619
  fixedRedirectUrl = url.href;
17469
17620
  }
@@ -17475,6 +17626,24 @@ function callbackSettings(oauth) {
17475
17626
  fixedRedirectUrl
17476
17627
  };
17477
17628
  }
17629
+ async function listenCallback(cbSettings, preferredPort) {
17630
+ try {
17631
+ return await OAuthCallbackServer.listen({
17632
+ host: cbSettings.host,
17633
+ redirectHost: cbSettings.redirectHost,
17634
+ port: preferredPort ?? 0,
17635
+ path: cbSettings.path
17636
+ });
17637
+ } catch (err) {
17638
+ if (preferredPort === void 0 || cbSettings.fixedRedirectUrl) throw err;
17639
+ return OAuthCallbackServer.listen({
17640
+ host: cbSettings.host,
17641
+ redirectHost: cbSettings.redirectHost,
17642
+ port: 0,
17643
+ path: cbSettings.path
17644
+ });
17645
+ }
17646
+ }
17478
17647
  function cimdForServer(metadata, redirectUrl) {
17479
17648
  if (!metadata?.client_id_metadata_document_supported) return void 0;
17480
17649
  if (!metadata.token_endpoint_auth_methods_supported?.includes("none")) return void 0;
@@ -17579,24 +17748,17 @@ async function signIn(options) {
17579
17748
  const stored = await store.load();
17580
17749
  const cbSettings = callbackSettings(oauth);
17581
17750
  const registered = stored?.clientInformation && "redirect_uris" in stored.clientInformation ? stored.clientInformation.redirect_uris : [];
17582
- const preferredPort = cbSettings.port ?? (registered[0] ? Number(new URL(registered[0]).port) || void 0 : void 0);
17583
- const callback = await OAuthCallbackServer.listen({
17584
- host: cbSettings.host,
17585
- redirectHost: cbSettings.redirectHost,
17586
- port: preferredPort ?? 0,
17587
- path: cbSettings.path
17588
- });
17751
+ const preferredPort = preferredPortFromRegistration(cbSettings.port, registered);
17752
+ const callback = await listenCallback(cbSettings, preferredPort);
17589
17753
  const redirectUrl = cbSettings.fixedRedirectUrl ?? callback.redirectUrl;
17590
17754
  try {
17591
17755
  if (stored) {
17592
17756
  const next = { ...stored };
17593
17757
  delete next.oauthState;
17594
- const hasCimd = buildClientMetadataDocumentFn(oauth, redirectUrl) !== void 0;
17595
- const keepClient = oauth?.clientId || (hasCimd ? !stored.clientInformation : registered.includes(redirectUrl));
17596
- if (!keepClient) {
17758
+ delete next.tokens;
17759
+ delete next.tokensExpireAt;
17760
+ if (!oauth?.clientId) {
17597
17761
  delete next.clientInformation;
17598
- delete next.tokens;
17599
- delete next.tokensExpireAt;
17600
17762
  }
17601
17763
  await store.save(next);
17602
17764
  }
@@ -17679,13 +17841,14 @@ async function resolveLoginTarget(platform2, target) {
17679
17841
  return {
17680
17842
  name: target,
17681
17843
  serverUrl: httpConfig.url,
17682
- oauth: httpConfig.oauth
17844
+ oauth: httpConfig.oauth,
17845
+ namedEntry: true
17683
17846
  };
17684
17847
  }
17685
17848
  const resolved = resolveServerName(target);
17686
17849
  if (resolved.isUrl) {
17687
17850
  const url = withScheme(target);
17688
- return { name: resolved.name, serverUrl: url, oauth: void 0 };
17851
+ return { name: resolved.name, serverUrl: url, oauth: void 0, namedEntry: false };
17689
17852
  }
17690
17853
  throw new Error(`unknown target '${target}': not a URL and not found in mcp.json`);
17691
17854
  }
@@ -17716,24 +17879,31 @@ async function jerkLogin(platform2, target, resolved, args2) {
17716
17879
  if (args2.includes("--no-browser")) {
17717
17880
  argv.push("--no-browser");
17718
17881
  }
17719
- const proc = await platform2.spawn({ argv });
17720
- proc.stdin.close().catch(() => {
17721
- });
17722
- const [, errText, result] = await Promise.all([
17723
- readStream2(proc.stdout),
17724
- readStream2(proc.stderr),
17725
- proc.wait()
17726
- ]);
17727
- if (result.status === 0) {
17728
- await platform2.write(1, `Signed in to ${target}
17882
+ return withInjectedCallbackPortIfNeeded(
17883
+ platform2,
17884
+ resolved.name,
17885
+ { namedEntry: resolved.namedEntry, oauth: resolved.oauth },
17886
+ async (env) => {
17887
+ const proc = await platform2.spawn({ argv, env });
17888
+ proc.stdin.close().catch(() => {
17889
+ });
17890
+ const [, errText, result] = await Promise.all([
17891
+ readStream2(proc.stdout),
17892
+ readStream2(proc.stderr),
17893
+ proc.wait()
17894
+ ]);
17895
+ if (result.status === 0) {
17896
+ await platform2.write(1, `Signed in to ${target}
17729
17897
  `);
17730
- return 0;
17731
- }
17732
- if (errText.trim()) {
17733
- await platform2.write(2, sanitizeTerminal(errText));
17734
- }
17735
- if (result.status === JERK_EXIT_SIGN_IN) return 4;
17736
- return result.status;
17898
+ return 0;
17899
+ }
17900
+ if (errText.trim()) {
17901
+ await platform2.write(2, sanitizeTerminal(errText));
17902
+ }
17903
+ if (result.status === JERK_EXIT_SIGN_IN) return 4;
17904
+ return result.status;
17905
+ }
17906
+ );
17737
17907
  }
17738
17908
  async function jerkLogout(platform2, target, resolved, source) {
17739
17909
  const id = jerkId(resolved.name, resolved.serverUrl);
@@ -17910,11 +18080,11 @@ function concatUint8Arrays(arrays) {
17910
18080
  }
17911
18081
 
17912
18082
  // src/config/import.ts
17913
- function isRecord7(value) {
18083
+ function isRecord8(value) {
17914
18084
  return typeof value === "object" && value !== null && !Array.isArray(value);
17915
18085
  }
17916
18086
  function isStringRecord2(value) {
17917
- return isRecord7(value) && Object.values(value).every((v) => typeof v === "string");
18087
+ return isRecord8(value) && Object.values(value).every((v) => typeof v === "string");
17918
18088
  }
17919
18089
  function tryParse(text) {
17920
18090
  try {
@@ -17944,17 +18114,17 @@ function importClaude(text, names) {
17944
18114
  result.errors.push(error);
17945
18115
  return result;
17946
18116
  }
17947
- if (!isRecord7(parsed)) {
18117
+ if (!isRecord8(parsed)) {
17948
18118
  result.errors.push("expected an object");
17949
18119
  return result;
17950
18120
  }
17951
18121
  const mcpServers = parsed.mcpServers;
17952
- if (!isRecord7(mcpServers)) {
18122
+ if (!isRecord8(mcpServers)) {
17953
18123
  result.errors.push('no "mcpServers" object');
17954
18124
  return result;
17955
18125
  }
17956
18126
  for (const [name, entry] of filterNames(Object.entries(mcpServers), names)) {
17957
- if (!isRecord7(entry)) {
18127
+ if (!isRecord8(entry)) {
17958
18128
  result.errors.push(`${name}: not an object`);
17959
18129
  continue;
17960
18130
  }
@@ -17969,17 +18139,17 @@ function importVSCode(text, names) {
17969
18139
  result.errors.push(error);
17970
18140
  return result;
17971
18141
  }
17972
- if (!isRecord7(parsed)) {
18142
+ if (!isRecord8(parsed)) {
17973
18143
  result.errors.push("expected an object");
17974
18144
  return result;
17975
18145
  }
17976
18146
  const servers = parsed.servers ?? parsed.mcpServers;
17977
- if (!isRecord7(servers)) {
18147
+ if (!isRecord8(servers)) {
17978
18148
  result.errors.push('no "servers" or "mcpServers" object');
17979
18149
  return result;
17980
18150
  }
17981
18151
  for (const [name, entry] of filterNames(Object.entries(servers), names)) {
17982
- if (!isRecord7(entry)) {
18152
+ if (!isRecord8(entry)) {
17983
18153
  result.errors.push(`${name}: not an object`);
17984
18154
  continue;
17985
18155
  }
@@ -18013,17 +18183,17 @@ function importSixServers(text, names) {
18013
18183
  result.errors.push(error);
18014
18184
  return result;
18015
18185
  }
18016
- if (!isRecord7(parsed)) {
18186
+ if (!isRecord8(parsed)) {
18017
18187
  result.errors.push("expected an object");
18018
18188
  return result;
18019
18189
  }
18020
18190
  const servers = parsed.servers;
18021
- if (!isRecord7(servers)) {
18191
+ if (!isRecord8(servers)) {
18022
18192
  result.errors.push('no "servers" object');
18023
18193
  return result;
18024
18194
  }
18025
18195
  for (const [name, entry] of filterNames(Object.entries(servers), names)) {
18026
- if (!isRecord7(entry)) {
18196
+ if (!isRecord8(entry)) {
18027
18197
  result.errors.push(`${name}: not an object`);
18028
18198
  continue;
18029
18199
  }
@@ -18031,7 +18201,7 @@ function importSixServers(text, names) {
18031
18201
  if (typeof entry.url === "string") config.url = entry.url;
18032
18202
  if (isStringRecord2(entry.headers)) config.headers = entry.headers;
18033
18203
  if (typeof entry.exposure === "string") config.exposure = entry.exposure;
18034
- if (isRecord7(entry.toolExposure)) config.toolExposure = entry.toolExposure;
18204
+ if (isRecord8(entry.toolExposure)) config.toolExposure = entry.toolExposure;
18035
18205
  result.servers.push({ name, config });
18036
18206
  }
18037
18207
  return result;
@@ -18048,7 +18218,7 @@ function importCodexToml(text, names) {
18048
18218
  if (fields.command) config.command = fields.command;
18049
18219
  if (fields.args) config.args = fields.args;
18050
18220
  if (fields.url) config.url = fields.url;
18051
- if (fields.env && isRecord7(fields.env)) config.env = fields.env;
18221
+ if (fields.env && isRecord8(fields.env)) config.env = fields.env;
18052
18222
  result.servers.push({ name, config });
18053
18223
  }
18054
18224
  return result;
@@ -18100,7 +18270,7 @@ function convertOpenCodeLocal(entry) {
18100
18270
  } else if (typeof entry.command === "string") {
18101
18271
  config.command = entry.command;
18102
18272
  }
18103
- if (isRecord7(entry.environment)) {
18273
+ if (isRecord8(entry.environment)) {
18104
18274
  const env = {};
18105
18275
  for (const [k, v] of Object.entries(entry.environment)) {
18106
18276
  if (typeof v === "string")
@@ -18123,17 +18293,17 @@ function importOpenCode(text, names) {
18123
18293
  result.errors.push(error);
18124
18294
  return result;
18125
18295
  }
18126
- if (!isRecord7(parsed)) {
18296
+ if (!isRecord8(parsed)) {
18127
18297
  result.errors.push("expected an object");
18128
18298
  return result;
18129
18299
  }
18130
18300
  const mcpServers = parsed.mcpServers ?? parsed.mcp;
18131
- if (!isRecord7(mcpServers)) {
18301
+ if (!isRecord8(mcpServers)) {
18132
18302
  result.errors.push("no mcp servers found");
18133
18303
  return result;
18134
18304
  }
18135
18305
  for (const [name, entry] of filterNames(Object.entries(mcpServers), names)) {
18136
- if (!isRecord7(entry)) {
18306
+ if (!isRecord8(entry)) {
18137
18307
  result.errors.push(`${name}: not an object`);
18138
18308
  continue;
18139
18309
  }
@@ -18149,9 +18319,9 @@ function detectFormat(text, filename) {
18149
18319
  if (filename.includes(".vscode")) return "vscode";
18150
18320
  try {
18151
18321
  const parsed = JSON.parse(text);
18152
- if (isRecord7(parsed)) {
18153
- if (parsed.servers && isRecord7(parsed.servers) && parsed.version !== void 0) return "six";
18154
- if (parsed.servers && isRecord7(parsed.servers)) return "vscode";
18322
+ if (isRecord8(parsed)) {
18323
+ if (parsed.servers && isRecord8(parsed.servers) && parsed.version !== void 0) return "six";
18324
+ if (parsed.servers && isRecord8(parsed.servers)) return "vscode";
18155
18325
  if (parsed.mcpServers) return "claude";
18156
18326
  if (parsed.mcp) return "opencode";
18157
18327
  }
@@ -22315,5 +22485,5 @@ try {
22315
22485
  });
22316
22486
  } catch {
22317
22487
  }
22318
- var code = await run(platform, "0.31.17", args);
22488
+ var code = await run(platform, "0.31.18", args);
22319
22489
  process.exit(code);
@@ -6179,6 +6179,12 @@ function agentIgnoresConfig(platform2, path) {
6179
6179
  function projectConfigPath(platform2) {
6180
6180
  return `${platform2.cwd}/.pi/mcp.json`;
6181
6181
  }
6182
+ function cacheDir(platform2) {
6183
+ if (platform2.env.XDG_CACHE_HOME) return `${platform2.env.XDG_CACHE_HOME}/mcpcat`;
6184
+ const home = platform2.env.HOME ?? platform2.env.USERPROFILE ?? "/";
6185
+ if (platform2.env.LOCALAPPDATA) return `${platform2.env.LOCALAPPDATA}\\mcpcat\\cache`;
6186
+ return `${home}/Library/Caches/mcpcat`;
6187
+ }
6182
6188
  function stateDir(platform2) {
6183
6189
  if (platform2.env.XDG_STATE_HOME) return `${platform2.env.XDG_STATE_HOME}/mcpcat`;
6184
6190
  const home = platform2.env.HOME ?? platform2.env.USERPROFILE ?? "/";
@@ -16968,6 +16974,142 @@ function parseUpgradeArgs(args) {
16968
16974
  return { kind: "run", check, force };
16969
16975
  }
16970
16976
 
16977
+ // src/auth/callback-port.ts
16978
+ function hasFixedCallback(oauth) {
16979
+ if (oauth?.callbackPort !== void 0 && oauth.callbackPort > 0) return true;
16980
+ if (!oauth?.callbackUrl) return false;
16981
+ try {
16982
+ const port = Number(new URL(oauth.callbackUrl).port);
16983
+ return port > 0;
16984
+ } catch {
16985
+ return false;
16986
+ }
16987
+ }
16988
+ function needsInjectedCallbackPort(options) {
16989
+ return options.namedEntry && !hasFixedCallback(options.oauth);
16990
+ }
16991
+ async function withInjectedCallbackPortIfNeeded(platform2, name, options, fn) {
16992
+ if (!needsInjectedCallbackPort(options)) return fn(void 0);
16993
+ const port = await allocateCallbackPort();
16994
+ return withTempCallbackPortConfig(platform2, name, port, fn);
16995
+ }
16996
+ async function allocateCallbackPort() {
16997
+ const callback = await OAuthCallbackServer.listen({
16998
+ host: "127.0.0.1",
16999
+ port: 0,
17000
+ path: "/callback"
17001
+ });
17002
+ try {
17003
+ return Number(new URL(callback.redirectUrl).port);
17004
+ } finally {
17005
+ await callback.close();
17006
+ }
17007
+ }
17008
+ function isRecord7(value) {
17009
+ return typeof value === "object" && value !== null && !Array.isArray(value);
17010
+ }
17011
+ function hostEnv(platform2) {
17012
+ const out = {};
17013
+ for (const [key, value] of Object.entries(platform2.env)) {
17014
+ if (value !== void 0) out[key] = value;
17015
+ }
17016
+ return out;
17017
+ }
17018
+ async function makeTempDir(platform2) {
17019
+ const base = `${cacheDir(platform2)}/mcp-cb`;
17020
+ await platform2.fs.mkdir(base, { mode: 448 });
17021
+ for (let i = 0; i < 32; i++) {
17022
+ const dir = `${base}/${Date.now().toString(36)}-${i.toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
17023
+ try {
17024
+ await platform2.fs.mkdirExclusive(dir);
17025
+ return dir;
17026
+ } catch {
17027
+ }
17028
+ }
17029
+ throw new Error("unable to create temporary mcp config directory");
17030
+ }
17031
+ async function removeTempDir(platform2, dir) {
17032
+ try {
17033
+ if (platform2.fs.unlink) await platform2.fs.unlink(`${dir}/mcp.json`);
17034
+ } catch {
17035
+ }
17036
+ try {
17037
+ await platform2.fs.rmdir(dir);
17038
+ } catch {
17039
+ }
17040
+ }
17041
+ async function withTempCallbackPortConfig(platform2, name, port, fn) {
17042
+ const path = userConfigPath(platform2);
17043
+ let raw;
17044
+ try {
17045
+ raw = new TextDecoder().decode(await platform2.fs.readFile(path));
17046
+ } catch {
17047
+ return fn(void 0);
17048
+ }
17049
+ let parsed;
17050
+ try {
17051
+ parsed = JSON.parse(raw);
17052
+ } catch {
17053
+ return fn(void 0);
17054
+ }
17055
+ if (!isRecord7(parsed) || !isRecord7(parsed.mcpServers) || !isRecord7(parsed.mcpServers[name])) {
17056
+ return fn(void 0);
17057
+ }
17058
+ const entry = { ...parsed.mcpServers[name] };
17059
+ const prevOauth = isRecord7(entry.oauth) ? { ...entry.oauth } : {};
17060
+ const oauth = { ...prevOauth, callbackPort: port };
17061
+ if (typeof oauth.callbackUrl === "string") {
17062
+ try {
17063
+ const url = new URL(oauth.callbackUrl);
17064
+ if (!(Number(url.port) > 0)) {
17065
+ url.port = String(port);
17066
+ oauth.callbackUrl = url.href;
17067
+ }
17068
+ } catch {
17069
+ }
17070
+ }
17071
+ entry.oauth = oauth;
17072
+ const tempServers = { ...parsed.mcpServers, [name]: entry };
17073
+ const merged = { ...parsed, mcpServers: tempServers };
17074
+ const tempDir = await makeTempDir(platform2);
17075
+ const tempPath = `${tempDir}/mcp.json`;
17076
+ await platform2.fs.writeFile(tempPath, `${JSON.stringify(merged, null, 2)}
17077
+ `, { mode: 384 });
17078
+ const env2 = { ...hostEnv(platform2), PI_MCP_CONFIG: tempPath };
17079
+ let cleaned = false;
17080
+ const cleanup = async () => {
17081
+ if (cleaned) return;
17082
+ cleaned = true;
17083
+ await removeTempDir(platform2, tempDir);
17084
+ };
17085
+ const exitFor = (signal) => signal === "SIGINT" ? 130 : signal === "SIGTERM" ? 143 : 129;
17086
+ let signalCode;
17087
+ let wake;
17088
+ const stopped = new Promise((resolve2) => {
17089
+ wake = resolve2;
17090
+ });
17091
+ if (platform2.onSignal) {
17092
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
17093
+ await platform2.onSignal(signal, () => {
17094
+ void cleanup().then(() => {
17095
+ signalCode = exitFor(signal);
17096
+ wake?.();
17097
+ });
17098
+ });
17099
+ }
17100
+ }
17101
+ try {
17102
+ const result = await Promise.race([
17103
+ fn(env2).then((value) => ({ ok: true, value })),
17104
+ stopped.then(() => ({ ok: false }))
17105
+ ]);
17106
+ if (!result.ok) platform2.exit(signalCode ?? 130);
17107
+ return result.value;
17108
+ } finally {
17109
+ await cleanup();
17110
+ }
17111
+ }
17112
+
16971
17113
  // src/auth/oauth.ts
16972
17114
  var CALLBACK_HOST2 = "127.0.0.1";
16973
17115
  var CALLBACK_PATH2 = "/callback";
@@ -16983,14 +17125,23 @@ function timedFetch2(fetchImpl) {
16983
17125
  });
16984
17126
  };
16985
17127
  }
17128
+ function nonzeroPort(value) {
17129
+ return value !== void 0 && value > 0 ? value : void 0;
17130
+ }
17131
+ function preferredPortFromRegistration(configured, registered) {
17132
+ if (configured !== void 0) return configured;
17133
+ if (!registered[0]) return void 0;
17134
+ return nonzeroPort(Number(new URL(registered[0]).port));
17135
+ }
16986
17136
  function callbackSettings(oauth) {
16987
17137
  const raw = oauth?.callbackUrl ?? `http://${CALLBACK_HOST2}${CALLBACK_PATH2}`;
16988
17138
  const url = new URL(raw);
16989
17139
  const address = url.hostname.replace(/^\[|\]$/g, "");
16990
- const port = url.port ? Number(url.port) : oauth?.callbackPort;
17140
+ const port = nonzeroPort(url.port ? Number(url.port) : oauth?.callbackPort);
16991
17141
  let fixedRedirectUrl;
16992
- if (url.port) fixedRedirectUrl = oauth?.callbackUrl;
16993
- else if (port !== void 0) {
17142
+ if (nonzeroPort(url.port ? Number(url.port) : void 0) !== void 0 && oauth?.callbackUrl) {
17143
+ fixedRedirectUrl = oauth.callbackUrl;
17144
+ } else if (port !== void 0) {
16994
17145
  url.port = String(port);
16995
17146
  fixedRedirectUrl = url.href;
16996
17147
  }
@@ -17002,6 +17153,24 @@ function callbackSettings(oauth) {
17002
17153
  fixedRedirectUrl
17003
17154
  };
17004
17155
  }
17156
+ async function listenCallback(cbSettings, preferredPort) {
17157
+ try {
17158
+ return await OAuthCallbackServer.listen({
17159
+ host: cbSettings.host,
17160
+ redirectHost: cbSettings.redirectHost,
17161
+ port: preferredPort ?? 0,
17162
+ path: cbSettings.path
17163
+ });
17164
+ } catch (err) {
17165
+ if (preferredPort === void 0 || cbSettings.fixedRedirectUrl) throw err;
17166
+ return OAuthCallbackServer.listen({
17167
+ host: cbSettings.host,
17168
+ redirectHost: cbSettings.redirectHost,
17169
+ port: 0,
17170
+ path: cbSettings.path
17171
+ });
17172
+ }
17173
+ }
17005
17174
  function cimdForServer(metadata, redirectUrl) {
17006
17175
  if (!metadata?.client_id_metadata_document_supported) return void 0;
17007
17176
  if (!metadata.token_endpoint_auth_methods_supported?.includes("none")) return void 0;
@@ -17106,24 +17275,17 @@ async function signIn(options) {
17106
17275
  const stored = await store.load();
17107
17276
  const cbSettings = callbackSettings(oauth);
17108
17277
  const registered = stored?.clientInformation && "redirect_uris" in stored.clientInformation ? stored.clientInformation.redirect_uris : [];
17109
- const preferredPort = cbSettings.port ?? (registered[0] ? Number(new URL(registered[0]).port) || void 0 : void 0);
17110
- const callback = await OAuthCallbackServer.listen({
17111
- host: cbSettings.host,
17112
- redirectHost: cbSettings.redirectHost,
17113
- port: preferredPort ?? 0,
17114
- path: cbSettings.path
17115
- });
17278
+ const preferredPort = preferredPortFromRegistration(cbSettings.port, registered);
17279
+ const callback = await listenCallback(cbSettings, preferredPort);
17116
17280
  const redirectUrl = cbSettings.fixedRedirectUrl ?? callback.redirectUrl;
17117
17281
  try {
17118
17282
  if (stored) {
17119
17283
  const next = { ...stored };
17120
17284
  delete next.oauthState;
17121
- const hasCimd = buildClientMetadataDocumentFn(oauth, redirectUrl) !== void 0;
17122
- const keepClient = oauth?.clientId || (hasCimd ? !stored.clientInformation : registered.includes(redirectUrl));
17123
- if (!keepClient) {
17285
+ delete next.tokens;
17286
+ delete next.tokensExpireAt;
17287
+ if (!oauth?.clientId) {
17124
17288
  delete next.clientInformation;
17125
- delete next.tokens;
17126
- delete next.tokensExpireAt;
17127
17289
  }
17128
17290
  await store.save(next);
17129
17291
  }
@@ -17206,13 +17368,14 @@ async function resolveLoginTarget(platform2, target) {
17206
17368
  return {
17207
17369
  name: target,
17208
17370
  serverUrl: httpConfig.url,
17209
- oauth: httpConfig.oauth
17371
+ oauth: httpConfig.oauth,
17372
+ namedEntry: true
17210
17373
  };
17211
17374
  }
17212
17375
  const resolved = resolveServerName(target);
17213
17376
  if (resolved.isUrl) {
17214
17377
  const url = withScheme(target);
17215
- return { name: resolved.name, serverUrl: url, oauth: void 0 };
17378
+ return { name: resolved.name, serverUrl: url, oauth: void 0, namedEntry: false };
17216
17379
  }
17217
17380
  throw new Error(`unknown target '${target}': not a URL and not found in mcp.json`);
17218
17381
  }
@@ -17243,24 +17406,31 @@ async function jerkLogin(platform2, target, resolved, args) {
17243
17406
  if (args.includes("--no-browser")) {
17244
17407
  argv.push("--no-browser");
17245
17408
  }
17246
- const proc = await platform2.spawn({ argv });
17247
- proc.stdin.close().catch(() => {
17248
- });
17249
- const [, errText, result] = await Promise.all([
17250
- readStream2(proc.stdout),
17251
- readStream2(proc.stderr),
17252
- proc.wait()
17253
- ]);
17254
- if (result.status === 0) {
17255
- await platform2.write(1, `Signed in to ${target}
17409
+ return withInjectedCallbackPortIfNeeded(
17410
+ platform2,
17411
+ resolved.name,
17412
+ { namedEntry: resolved.namedEntry, oauth: resolved.oauth },
17413
+ async (env2) => {
17414
+ const proc = await platform2.spawn({ argv, env: env2 });
17415
+ proc.stdin.close().catch(() => {
17416
+ });
17417
+ const [, errText, result] = await Promise.all([
17418
+ readStream2(proc.stdout),
17419
+ readStream2(proc.stderr),
17420
+ proc.wait()
17421
+ ]);
17422
+ if (result.status === 0) {
17423
+ await platform2.write(1, `Signed in to ${target}
17256
17424
  `);
17257
- return 0;
17258
- }
17259
- if (errText.trim()) {
17260
- await platform2.write(2, sanitizeTerminal(errText));
17261
- }
17262
- if (result.status === JERK_EXIT_SIGN_IN) return 4;
17263
- return result.status;
17425
+ return 0;
17426
+ }
17427
+ if (errText.trim()) {
17428
+ await platform2.write(2, sanitizeTerminal(errText));
17429
+ }
17430
+ if (result.status === JERK_EXIT_SIGN_IN) return 4;
17431
+ return result.status;
17432
+ }
17433
+ );
17264
17434
  }
17265
17435
  async function jerkLogout(platform2, target, resolved, source) {
17266
17436
  const id = jerkId(resolved.name, resolved.serverUrl);
@@ -17437,11 +17607,11 @@ function concatUint8Arrays(arrays) {
17437
17607
  }
17438
17608
 
17439
17609
  // src/config/import.ts
17440
- function isRecord7(value) {
17610
+ function isRecord8(value) {
17441
17611
  return typeof value === "object" && value !== null && !Array.isArray(value);
17442
17612
  }
17443
17613
  function isStringRecord2(value) {
17444
- return isRecord7(value) && Object.values(value).every((v) => typeof v === "string");
17614
+ return isRecord8(value) && Object.values(value).every((v) => typeof v === "string");
17445
17615
  }
17446
17616
  function tryParse(text) {
17447
17617
  try {
@@ -17471,17 +17641,17 @@ function importClaude(text, names) {
17471
17641
  result.errors.push(error);
17472
17642
  return result;
17473
17643
  }
17474
- if (!isRecord7(parsed)) {
17644
+ if (!isRecord8(parsed)) {
17475
17645
  result.errors.push("expected an object");
17476
17646
  return result;
17477
17647
  }
17478
17648
  const mcpServers = parsed.mcpServers;
17479
- if (!isRecord7(mcpServers)) {
17649
+ if (!isRecord8(mcpServers)) {
17480
17650
  result.errors.push('no "mcpServers" object');
17481
17651
  return result;
17482
17652
  }
17483
17653
  for (const [name, entry] of filterNames(Object.entries(mcpServers), names)) {
17484
- if (!isRecord7(entry)) {
17654
+ if (!isRecord8(entry)) {
17485
17655
  result.errors.push(`${name}: not an object`);
17486
17656
  continue;
17487
17657
  }
@@ -17496,17 +17666,17 @@ function importVSCode(text, names) {
17496
17666
  result.errors.push(error);
17497
17667
  return result;
17498
17668
  }
17499
- if (!isRecord7(parsed)) {
17669
+ if (!isRecord8(parsed)) {
17500
17670
  result.errors.push("expected an object");
17501
17671
  return result;
17502
17672
  }
17503
17673
  const servers = parsed.servers ?? parsed.mcpServers;
17504
- if (!isRecord7(servers)) {
17674
+ if (!isRecord8(servers)) {
17505
17675
  result.errors.push('no "servers" or "mcpServers" object');
17506
17676
  return result;
17507
17677
  }
17508
17678
  for (const [name, entry] of filterNames(Object.entries(servers), names)) {
17509
- if (!isRecord7(entry)) {
17679
+ if (!isRecord8(entry)) {
17510
17680
  result.errors.push(`${name}: not an object`);
17511
17681
  continue;
17512
17682
  }
@@ -17540,17 +17710,17 @@ function importSixServers(text, names) {
17540
17710
  result.errors.push(error);
17541
17711
  return result;
17542
17712
  }
17543
- if (!isRecord7(parsed)) {
17713
+ if (!isRecord8(parsed)) {
17544
17714
  result.errors.push("expected an object");
17545
17715
  return result;
17546
17716
  }
17547
17717
  const servers = parsed.servers;
17548
- if (!isRecord7(servers)) {
17718
+ if (!isRecord8(servers)) {
17549
17719
  result.errors.push('no "servers" object');
17550
17720
  return result;
17551
17721
  }
17552
17722
  for (const [name, entry] of filterNames(Object.entries(servers), names)) {
17553
- if (!isRecord7(entry)) {
17723
+ if (!isRecord8(entry)) {
17554
17724
  result.errors.push(`${name}: not an object`);
17555
17725
  continue;
17556
17726
  }
@@ -17558,7 +17728,7 @@ function importSixServers(text, names) {
17558
17728
  if (typeof entry.url === "string") config.url = entry.url;
17559
17729
  if (isStringRecord2(entry.headers)) config.headers = entry.headers;
17560
17730
  if (typeof entry.exposure === "string") config.exposure = entry.exposure;
17561
- if (isRecord7(entry.toolExposure)) config.toolExposure = entry.toolExposure;
17731
+ if (isRecord8(entry.toolExposure)) config.toolExposure = entry.toolExposure;
17562
17732
  result.servers.push({ name, config });
17563
17733
  }
17564
17734
  return result;
@@ -17575,7 +17745,7 @@ function importCodexToml(text, names) {
17575
17745
  if (fields.command) config.command = fields.command;
17576
17746
  if (fields.args) config.args = fields.args;
17577
17747
  if (fields.url) config.url = fields.url;
17578
- if (fields.env && isRecord7(fields.env)) config.env = fields.env;
17748
+ if (fields.env && isRecord8(fields.env)) config.env = fields.env;
17579
17749
  result.servers.push({ name, config });
17580
17750
  }
17581
17751
  return result;
@@ -17627,7 +17797,7 @@ function convertOpenCodeLocal(entry) {
17627
17797
  } else if (typeof entry.command === "string") {
17628
17798
  config.command = entry.command;
17629
17799
  }
17630
- if (isRecord7(entry.environment)) {
17800
+ if (isRecord8(entry.environment)) {
17631
17801
  const env2 = {};
17632
17802
  for (const [k, v] of Object.entries(entry.environment)) {
17633
17803
  if (typeof v === "string")
@@ -17650,17 +17820,17 @@ function importOpenCode(text, names) {
17650
17820
  result.errors.push(error);
17651
17821
  return result;
17652
17822
  }
17653
- if (!isRecord7(parsed)) {
17823
+ if (!isRecord8(parsed)) {
17654
17824
  result.errors.push("expected an object");
17655
17825
  return result;
17656
17826
  }
17657
17827
  const mcpServers = parsed.mcpServers ?? parsed.mcp;
17658
- if (!isRecord7(mcpServers)) {
17828
+ if (!isRecord8(mcpServers)) {
17659
17829
  result.errors.push("no mcp servers found");
17660
17830
  return result;
17661
17831
  }
17662
17832
  for (const [name, entry] of filterNames(Object.entries(mcpServers), names)) {
17663
- if (!isRecord7(entry)) {
17833
+ if (!isRecord8(entry)) {
17664
17834
  result.errors.push(`${name}: not an object`);
17665
17835
  continue;
17666
17836
  }
@@ -17676,9 +17846,9 @@ function detectFormat(text, filename) {
17676
17846
  if (filename.includes(".vscode")) return "vscode";
17677
17847
  try {
17678
17848
  const parsed = JSON.parse(text);
17679
- if (isRecord7(parsed)) {
17680
- if (parsed.servers && isRecord7(parsed.servers) && parsed.version !== void 0) return "six";
17681
- if (parsed.servers && isRecord7(parsed.servers)) return "vscode";
17849
+ if (isRecord8(parsed)) {
17850
+ if (parsed.servers && isRecord8(parsed.servers) && parsed.version !== void 0) return "six";
17851
+ if (parsed.servers && isRecord8(parsed.servers)) return "vscode";
17682
17852
  if (parsed.mcpServers) return "claude";
17683
17853
  if (parsed.mcp) return "opencode";
17684
17854
  }
@@ -21684,7 +21854,7 @@ async function main(ctx) {
21684
21854
  packageDir: "/node_modules/@ai-ecoverse/mcpcat"
21685
21855
  });
21686
21856
  const args = ctx.argv.slice(1);
21687
- return run(platform2, "0.31.17", args);
21857
+ return run(platform2, "0.31.18", args);
21688
21858
  }
21689
21859
  export {
21690
21860
  main as default
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/mcpcat",
3
- "version": "0.31.17",
3
+ "version": "0.31.18",
4
4
  "description": "A curl for the Model Context Protocol",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -58,16 +58,16 @@
58
58
  "trust:dry": "npx fledgling sync --dry-run -y --skip-publish"
59
59
  },
60
60
  "devDependencies": {
61
- "@ai-ecoverse/slicc-kernel": "1.173.0",
61
+ "@ai-ecoverse/slicc-kernel": "1.174.0",
62
62
  "@ai-ecoverse/slicc-shared-web": "2.13.10",
63
- "@ai-ecoverse/slicc-vault": "1.21.2",
63
+ "@ai-ecoverse/slicc-vault": "1.22.0",
64
64
  "@biomejs/biome": "2.6.3",
65
65
  "@earendil-works/pi-codemode": "1.4.0",
66
66
  "@earendil-works/pi-mcp": "1.4.0",
67
67
  "@types/node": "26.6.5",
68
68
  "esbuild": "0.28.2",
69
69
  "lefthook": "2.2.2",
70
- "monocart-coverage-reports": "3.1.0",
70
+ "monocart-coverage-reports": "3.2.0",
71
71
  "proper-lockfile": "4.1.2",
72
72
  "quickjs-wasi": "3.6.3",
73
73
  "semantic-release": "25.0.9",