@kwonye/mcpx 0.1.77 → 0.1.79

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.
@@ -1,7 +1,8 @@
1
1
  import {
2
+ loadConfig,
2
3
  loadMergedConfig,
3
4
  saveConfig
4
- } from "./cli-7bsf6akp.js";
5
+ } from "./cli-7edpy4nk.js";
5
6
  import {
6
7
  SecretsManager,
7
8
  ensureGatewayToken,
@@ -103,6 +104,13 @@ function resolveHeaders(spec, secrets) {
103
104
  }
104
105
  return headers;
105
106
  }
107
+ function isOAuthLikelyChallenge(wwwAuthenticate) {
108
+ if (!wwwAuthenticate) {
109
+ return false;
110
+ }
111
+ const lower = wwwAuthenticate.toLowerCase();
112
+ return lower.includes("bearer") || lower.includes("authorization_uri") || lower.includes("resource_metadata");
113
+ }
106
114
  async function probeHttpAuthRequirement(spec, secrets, timeoutMs = DEFAULT_AUTH_PROBE_TIMEOUT_MS) {
107
115
  const timeoutController = new AbortController;
108
116
  const timeoutHandle = setTimeout(() => timeoutController.abort(), timeoutMs);
@@ -120,10 +128,12 @@ async function probeHttpAuthRequirement(spec, secrets, timeoutMs = DEFAULT_AUTH_
120
128
  signal: timeoutController.signal
121
129
  });
122
130
  if (response.status === 401 || response.status === 403) {
131
+ const wwwAuthenticate = response.headers.get("www-authenticate");
123
132
  return {
124
133
  authRequired: true,
134
+ oauthLikely: isOAuthLikelyChallenge(wwwAuthenticate),
125
135
  status: response.status,
126
- wwwAuthenticate: response.headers.get("www-authenticate") ?? undefined
136
+ wwwAuthenticate: wwwAuthenticate ?? undefined
127
137
  };
128
138
  }
129
139
  if (response.status === 200) {
@@ -501,20 +511,22 @@ class ClaudeAdapter {
501
511
  try {
502
512
  const raw = readJsonFile(configPath, {});
503
513
  const managedNames = options.managedEntries.map((entry) => entry.name);
504
- const serverEntries = Object.fromEntries(options.managedEntries.map((entry) => [entry.name, {
514
+ const enabledEntries = options.managedEntries.filter((entry) => entry.enabled);
515
+ const serverEntries = Object.fromEntries(enabledEntries.map((entry) => [entry.name, {
505
516
  type: "http",
506
517
  url: entry.url,
507
518
  headers: entry.headers
508
519
  }]));
520
+ const enabledManagedNames = enabledEntries.map((entry) => entry.name);
509
521
  const topLevelServers = raw.mcpServers ?? {};
510
522
  removeSourceEntries(topLevelServers, options.sourceEntriesToRemove);
511
- for (const name of managedNames) {
523
+ for (const name of enabledManagedNames) {
512
524
  const topLevelConflict = ensureManagedEntryWritable(options.managedIndex, this.id, name, topLevelServers[name]);
513
525
  if (topLevelConflict) {
514
526
  return errorResult(this.id, configPath, topLevelConflict);
515
527
  }
516
528
  }
517
- pruneStaleManagedEntries(options.managedIndex, this.id, topLevelServers, managedNames);
529
+ pruneStaleManagedEntries(options.managedIndex, this.id, topLevelServers, enabledManagedNames);
518
530
  for (const [name, entry] of Object.entries(serverEntries)) {
519
531
  topLevelServers[name] = entry;
520
532
  }
@@ -656,9 +668,9 @@ class ClaudeDesktopAdapter {
656
668
  }
657
669
  raw.mcpServers = topLevelServers;
658
670
  writeJsonAtomic(configPath, raw);
659
- setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(options.managedEntries.map((entry) => {
671
+ setManagedEntries(options.managedIndex, this.id, configPath, Object.fromEntries(enabledEntries.map((entry) => {
660
672
  const { command, args } = buildProxyEntry(entry);
661
- return [entry.name, JSON.stringify({ command, args, enabled: entry.enabled })];
673
+ return [entry.name, JSON.stringify({ command, args })];
662
674
  })));
663
675
  return okResult(this.id, configPath);
664
676
  } catch (error) {
@@ -1029,6 +1041,9 @@ class ClineAdapter {
1029
1041
  if (!configPath) {
1030
1042
  return errorResult(this.id, undefined, "Unable to resolve Cline MCP config path.");
1031
1043
  }
1044
+ if (!fs3.existsSync(configPath)) {
1045
+ return okResult(this.id, configPath, "Cline config not found; skipping sync.");
1046
+ }
1032
1047
  try {
1033
1048
  const raw = readJsonFile(configPath, {});
1034
1049
  const servers = {
@@ -1640,6 +1655,9 @@ function buildManagedEntries(config, gatewayUrl, localToken) {
1640
1655
  enabled: isServerEnabled(spec)
1641
1656
  }));
1642
1657
  }
1658
+ function isClientInternalServerCommand(candidate) {
1659
+ return candidate.spec.transport === "stdio" && /\.app\/Contents\//.test(candidate.spec.command ?? "");
1660
+ }
1643
1661
  function syncAllClients(config, secrets, targetClients) {
1644
1662
  const adapters = getAdapters();
1645
1663
  const managedIndexPath = getManagedIndexPath();
@@ -1658,6 +1676,16 @@ function syncAllClients(config, secrets, targetClients) {
1658
1676
  const scan = adapter.scanForImports(config, managedIndex);
1659
1677
  imports.skipped.push(...scan.skipped);
1660
1678
  for (const candidate of scan.candidates) {
1679
+ if (isClientInternalServerCommand(candidate)) {
1680
+ imports.skipped.push({
1681
+ clientId: candidate.clientId,
1682
+ configPath: candidate.configPath,
1683
+ sourceEntryName: candidate.sourceEntryName,
1684
+ serverName: candidate.serverName,
1685
+ reason: "client-internal server"
1686
+ });
1687
+ continue;
1688
+ }
1661
1689
  const existing = config.servers[candidate.serverName];
1662
1690
  if (!existing) {
1663
1691
  config.servers[candidate.serverName] = candidate.spec;
@@ -1857,12 +1885,298 @@ function maybePrefixBearer(target, value, raw = false) {
1857
1885
  return `Bearer ${value}`;
1858
1886
  }
1859
1887
 
1888
+ // src/core/spawn-env.ts
1889
+ import os from "node:os";
1890
+ import path11 from "node:path";
1891
+ import fs6 from "node:fs";
1892
+ function existingNvmCurrentBin() {
1893
+ const nvmDir = process.env.NVM_DIR ?? path11.join(os.homedir(), ".nvm");
1894
+ const current = path11.join(nvmDir, "current", "bin");
1895
+ return fs6.existsSync(current) ? current : null;
1896
+ }
1897
+ function buildEnrichedPath(current = process.env.PATH ?? "") {
1898
+ const candidates = [
1899
+ ...current.split(":"),
1900
+ "/opt/homebrew/bin",
1901
+ "/usr/local/bin",
1902
+ path11.join(os.homedir(), ".bun", "bin"),
1903
+ path11.join(os.homedir(), ".local", "bin"),
1904
+ existingNvmCurrentBin()
1905
+ ].filter((entry) => Boolean(entry));
1906
+ const seen = new Set;
1907
+ const entries = [];
1908
+ for (const candidate of candidates) {
1909
+ if (!candidate || seen.has(candidate)) {
1910
+ continue;
1911
+ }
1912
+ seen.add(candidate);
1913
+ entries.push(candidate);
1914
+ }
1915
+ return entries.join(":");
1916
+ }
1917
+
1918
+ // src/core/oauth.ts
1919
+ import http from "node:http";
1920
+ import crypto from "node:crypto";
1921
+ import {
1922
+ auth,
1923
+ discoverOAuthServerInfo,
1924
+ refreshAuthorization
1925
+ } from "@modelcontextprotocol/sdk/client/auth.js";
1926
+ function oauthSecretName(serverName, suffix) {
1927
+ return `oauth_${serverName.toLowerCase().replace(/[^a-z0-9._-]/g, "_")}_${suffix}`;
1928
+ }
1929
+ function readJsonSecret(secrets, name) {
1930
+ const raw = secrets.getSecret(name);
1931
+ if (!raw) {
1932
+ return;
1933
+ }
1934
+ try {
1935
+ return JSON.parse(raw);
1936
+ } catch {
1937
+ return;
1938
+ }
1939
+ }
1940
+ function writeJsonSecret(secrets, name, value) {
1941
+ secrets.setSecret(name, JSON.stringify(value));
1942
+ }
1943
+ function isOAuthReference(value) {
1944
+ return value.startsWith("oauth://");
1945
+ }
1946
+ function oauthReferenceServerName(value) {
1947
+ if (!isOAuthReference(value)) {
1948
+ throw new Error(`Not an OAuth reference: ${value}`);
1949
+ }
1950
+ return decodeURIComponent(value.slice("oauth://".length));
1951
+ }
1952
+ function tokensAreExpiring(tokens) {
1953
+ if (!tokens.tokens.expires_in) {
1954
+ return false;
1955
+ }
1956
+ const expiresAt = tokens.obtainedAt + tokens.tokens.expires_in * 1000;
1957
+ return Date.now() >= expiresAt - 60000;
1958
+ }
1959
+
1960
+ class McpxOAuthProvider {
1961
+ #serverName;
1962
+ #secrets;
1963
+ #redirectUrlValue;
1964
+ #stateValue;
1965
+ constructor(serverName, secrets, redirectUrl, state = crypto.randomUUID()) {
1966
+ this.#serverName = serverName;
1967
+ this.#secrets = secrets;
1968
+ this.#redirectUrlValue = redirectUrl;
1969
+ this.#stateValue = state;
1970
+ }
1971
+ get redirectUrl() {
1972
+ return this.#redirectUrlValue;
1973
+ }
1974
+ get clientMetadata() {
1975
+ if (!this.#redirectUrlValue) {
1976
+ return {
1977
+ redirect_uris: [],
1978
+ token_endpoint_auth_method: "none",
1979
+ grant_types: ["authorization_code", "refresh_token"],
1980
+ response_types: ["code"],
1981
+ client_name: "mcpx"
1982
+ };
1983
+ }
1984
+ return {
1985
+ redirect_uris: [this.#redirectUrlValue],
1986
+ token_endpoint_auth_method: "none",
1987
+ grant_types: ["authorization_code", "refresh_token"],
1988
+ response_types: ["code"],
1989
+ client_name: "mcpx"
1990
+ };
1991
+ }
1992
+ state() {
1993
+ return this.#stateValue;
1994
+ }
1995
+ clientInformation() {
1996
+ return readJsonSecret(this.#secrets, oauthSecretName(this.#serverName, "client"));
1997
+ }
1998
+ saveClientInformation(clientInformation) {
1999
+ writeJsonSecret(this.#secrets, oauthSecretName(this.#serverName, "client"), clientInformation);
2000
+ }
2001
+ tokens() {
2002
+ return readJsonSecret(this.#secrets, oauthSecretName(this.#serverName, "tokens"))?.tokens;
2003
+ }
2004
+ saveTokens(tokens) {
2005
+ writeJsonSecret(this.#secrets, oauthSecretName(this.#serverName, "tokens"), {
2006
+ tokens,
2007
+ obtainedAt: Date.now()
2008
+ });
2009
+ }
2010
+ redirectToAuthorization(_authorizationUrl) {}
2011
+ saveCodeVerifier(codeVerifier) {
2012
+ this.#secrets.setSecret(oauthSecretName(this.#serverName, "verifier"), codeVerifier);
2013
+ }
2014
+ codeVerifier() {
2015
+ const verifier = this.#secrets.getSecret(oauthSecretName(this.#serverName, "verifier"));
2016
+ if (!verifier) {
2017
+ throw new Error(`Missing OAuth code verifier for "${this.#serverName}".`);
2018
+ }
2019
+ return verifier;
2020
+ }
2021
+ saveDiscoveryState(state) {
2022
+ writeJsonSecret(this.#secrets, oauthSecretName(this.#serverName, "discovery"), state);
2023
+ }
2024
+ discoveryState() {
2025
+ return readJsonSecret(this.#secrets, oauthSecretName(this.#serverName, "discovery"));
2026
+ }
2027
+ invalidateCredentials(scope) {
2028
+ const suffixes = scope === "all" ? ["client", "tokens", "verifier", "discovery"] : [scope];
2029
+ for (const suffix of suffixes) {
2030
+ this.#secrets.removeSecret(oauthSecretName(this.#serverName, suffix));
2031
+ }
2032
+ }
2033
+ }
2034
+
2035
+ class InteractiveOAuthProvider extends McpxOAuthProvider {
2036
+ #openUrl;
2037
+ constructor(serverName, secrets, redirectUrl, state, openUrl) {
2038
+ super(serverName, secrets, redirectUrl, state);
2039
+ this.#openUrl = openUrl;
2040
+ }
2041
+ async redirectToAuthorization(authorizationUrl) {
2042
+ await this.#openUrl(authorizationUrl.toString());
2043
+ }
2044
+ }
2045
+ function listen(server) {
2046
+ return new Promise((resolve, reject) => {
2047
+ server.once("error", reject);
2048
+ server.listen(0, "127.0.0.1", () => {
2049
+ const address = server.address();
2050
+ if (!address || typeof address === "string") {
2051
+ reject(new Error("Failed to start OAuth callback server."));
2052
+ return;
2053
+ }
2054
+ resolve(address.port);
2055
+ });
2056
+ });
2057
+ }
2058
+ function closeServer(server) {
2059
+ return new Promise((resolve) => {
2060
+ server.close(() => resolve());
2061
+ });
2062
+ }
2063
+ function waitForAuthorizationCode(server, expectedState, timeoutMs = 120000) {
2064
+ return new Promise((resolve, reject) => {
2065
+ const timeout = setTimeout(() => {
2066
+ reject(new Error(`OAuth login timed out after ${timeoutMs}ms.`));
2067
+ }, timeoutMs);
2068
+ server.on("request", (request, response) => {
2069
+ const url = new URL(request.url ?? "/", "http://127.0.0.1");
2070
+ const code = url.searchParams.get("code");
2071
+ const state = url.searchParams.get("state");
2072
+ const error = url.searchParams.get("error");
2073
+ response.setHeader("content-type", "text/html; charset=utf-8");
2074
+ if (error) {
2075
+ response.statusCode = 400;
2076
+ response.end("<html><body><h1>mcpx OAuth failed</h1><p>You can close this window.</p></body></html>");
2077
+ clearTimeout(timeout);
2078
+ reject(new Error(`OAuth authorization failed: ${error}`));
2079
+ return;
2080
+ }
2081
+ if (!code) {
2082
+ response.statusCode = 400;
2083
+ response.end("<html><body><h1>Missing OAuth code</h1><p>You can close this window.</p></body></html>");
2084
+ return;
2085
+ }
2086
+ if (state !== expectedState) {
2087
+ response.statusCode = 400;
2088
+ response.end("<html><body><h1>OAuth state mismatch</h1><p>You can close this window.</p></body></html>");
2089
+ clearTimeout(timeout);
2090
+ reject(new Error("OAuth state mismatch."));
2091
+ return;
2092
+ }
2093
+ response.end("<html><body><h1>mcpx OAuth complete</h1><p>You can close this window.</p></body></html>");
2094
+ clearTimeout(timeout);
2095
+ resolve(code);
2096
+ });
2097
+ });
2098
+ }
2099
+ function bindOAuthReference(config, serverName) {
2100
+ const spec = config.servers[serverName];
2101
+ if (!spec) {
2102
+ throw new Error(`Server "${serverName}" not found.`);
2103
+ }
2104
+ if (spec.transport !== "http") {
2105
+ throw new Error(`OAuth login only supports HTTP servers.`);
2106
+ }
2107
+ spec.headers = {
2108
+ ...spec.headers ?? {},
2109
+ Authorization: `oauth://${encodeURIComponent(serverName)}`
2110
+ };
2111
+ }
2112
+ async function runOAuthLogin(serverName, spec, secrets, openUrl, configPath) {
2113
+ const state = crypto.randomUUID();
2114
+ const callbackServer = http.createServer();
2115
+ const port = await listen(callbackServer);
2116
+ const redirectUrl = `http://127.0.0.1:${port}/callback`;
2117
+ const provider = new InteractiveOAuthProvider(serverName, secrets, redirectUrl, state, openUrl);
2118
+ const codePromise = waitForAuthorizationCode(callbackServer, state);
2119
+ try {
2120
+ const initial = await auth(provider, {
2121
+ serverUrl: spec.url
2122
+ });
2123
+ if (initial !== "REDIRECT") {
2124
+ throw new Error(`Expected OAuth redirect for "${serverName}", got ${initial}.`);
2125
+ }
2126
+ const authorizationCode = await codePromise;
2127
+ const result = await auth(provider, {
2128
+ serverUrl: spec.url,
2129
+ authorizationCode
2130
+ });
2131
+ if (result !== "AUTHORIZED") {
2132
+ throw new Error(`OAuth login did not authorize "${serverName}".`);
2133
+ }
2134
+ const config = loadConfig(configPath);
2135
+ bindOAuthReference(config, serverName);
2136
+ saveConfig(config, configPath);
2137
+ syncAllClients(config, secrets);
2138
+ return { serverName, authorized: true };
2139
+ } finally {
2140
+ await closeServer(callbackServer);
2141
+ }
2142
+ }
2143
+ async function getOAuthAccessToken(serverName, spec, secrets, options = {}) {
2144
+ const stored = readJsonSecret(secrets, oauthSecretName(serverName, "tokens"));
2145
+ if (!stored) {
2146
+ throw new Error(`No OAuth tokens stored for "${serverName}". Run mcpx auth login ${serverName}.`);
2147
+ }
2148
+ if (!options.forceRefresh && !tokensAreExpiring(stored)) {
2149
+ return stored.tokens.access_token;
2150
+ }
2151
+ if (!stored.tokens.refresh_token) {
2152
+ if (!options.forceRefresh) {
2153
+ return stored.tokens.access_token;
2154
+ }
2155
+ throw new Error(`No OAuth refresh token stored for "${serverName}". Re-authentication required.`);
2156
+ }
2157
+ const provider = new McpxOAuthProvider(serverName, secrets);
2158
+ const clientInformation = provider.clientInformation();
2159
+ if (!clientInformation) {
2160
+ throw new Error(`No OAuth client information stored for "${serverName}". Re-authentication required.`);
2161
+ }
2162
+ const cachedDiscovery = provider.discoveryState();
2163
+ const serverInfo = cachedDiscovery ?? await discoverOAuthServerInfo(spec.url);
2164
+ const refreshed = await refreshAuthorization(serverInfo.authorizationServerUrl, {
2165
+ metadata: serverInfo.authorizationServerMetadata,
2166
+ clientInformation,
2167
+ refreshToken: stored.tokens.refresh_token,
2168
+ resource: serverInfo.resourceMetadata?.resource ? new URL(serverInfo.resourceMetadata.resource) : undefined
2169
+ });
2170
+ provider.saveTokens(refreshed);
2171
+ return refreshed.access_token;
2172
+ }
2173
+
1860
2174
  // src/version.ts
1861
2175
  var APP_VERSION = "0.1.3";
1862
2176
 
1863
2177
  // src/core/update.ts
1864
- import fs6 from "node:fs";
1865
- import path11 from "node:path";
2178
+ import fs7 from "node:fs";
2179
+ import path12 from "node:path";
1866
2180
  import { execFileSync as execFileSync2 } from "node:child_process";
1867
2181
  function compareVersions(a, b) {
1868
2182
  const partsA = a.split(".").map(Number);
@@ -1901,17 +2215,17 @@ async function checkForUpdates() {
1901
2215
  }
1902
2216
  function getStagedUpdate() {
1903
2217
  const stagedPath = getStagedVersionPath();
1904
- if (!fs6.existsSync(stagedPath)) {
2218
+ if (!fs7.existsSync(stagedPath)) {
1905
2219
  return null;
1906
2220
  }
1907
2221
  try {
1908
- const raw = fs6.readFileSync(stagedPath, "utf8");
2222
+ const raw = fs7.readFileSync(stagedPath, "utf8");
1909
2223
  const info = JSON.parse(raw);
1910
2224
  if (!info.version || !info.cliPath) {
1911
2225
  return null;
1912
2226
  }
1913
- const absoluteCliPath = path11.isAbsolute(info.cliPath) ? info.cliPath : path11.join(getUpdatesDir(), info.cliPath);
1914
- if (!fs6.existsSync(absoluteCliPath)) {
2227
+ const absoluteCliPath = path12.isAbsolute(info.cliPath) ? info.cliPath : path12.join(getUpdatesDir(), info.cliPath);
2228
+ if (!fs7.existsSync(absoluteCliPath)) {
1915
2229
  return null;
1916
2230
  }
1917
2231
  return {
@@ -1927,15 +2241,15 @@ function stageUpdate(version, cliPath) {
1927
2241
  ensureDir(updatesDir);
1928
2242
  const stagedInfo = {
1929
2243
  version,
1930
- cliPath: path11.isAbsolute(cliPath) ? path11.relative(updatesDir, cliPath) : cliPath,
2244
+ cliPath: path12.isAbsolute(cliPath) ? path12.relative(updatesDir, cliPath) : cliPath,
1931
2245
  stagedAt: new Date().toISOString()
1932
2246
  };
1933
- fs6.writeFileSync(getStagedVersionPath(), JSON.stringify(stagedInfo, null, 2), "utf8");
2247
+ fs7.writeFileSync(getStagedVersionPath(), JSON.stringify(stagedInfo, null, 2), "utf8");
1934
2248
  }
1935
2249
  function clearStagedUpdate() {
1936
2250
  const stagedPath = getStagedVersionPath();
1937
- if (fs6.existsSync(stagedPath)) {
1938
- fs6.unlinkSync(stagedPath);
2251
+ if (fs7.existsSync(stagedPath)) {
2252
+ fs7.unlinkSync(stagedPath);
1939
2253
  }
1940
2254
  }
1941
2255
  function getStagedCliPath() {
@@ -1944,31 +2258,31 @@ function getStagedCliPath() {
1944
2258
  }
1945
2259
  function removeOldVersions(keepCount = 2) {
1946
2260
  const updatesDir = getUpdatesDir();
1947
- if (!fs6.existsSync(updatesDir)) {
2261
+ if (!fs7.existsSync(updatesDir)) {
1948
2262
  return;
1949
2263
  }
1950
- const entries = fs6.readdirSync(updatesDir, { withFileTypes: true });
2264
+ const entries = fs7.readdirSync(updatesDir, { withFileTypes: true });
1951
2265
  const versionDirs = entries.filter((entry) => entry.isDirectory() && entry.name.startsWith("v")).map((entry) => entry.name).sort((a, b) => compareVersions(b.slice(1), a.slice(1)));
1952
2266
  if (versionDirs.length <= keepCount) {
1953
2267
  return;
1954
2268
  }
1955
2269
  for (const oldVersion of versionDirs.slice(keepCount)) {
1956
- const oldPath = path11.join(updatesDir, oldVersion);
2270
+ const oldPath = path12.join(updatesDir, oldVersion);
1957
2271
  try {
1958
- fs6.rmSync(oldPath, { recursive: true, force: true });
2272
+ fs7.rmSync(oldPath, { recursive: true, force: true });
1959
2273
  } catch {}
1960
2274
  }
1961
2275
  }
1962
2276
 
1963
2277
  // src/core/update-manager.ts
1964
- import fs7 from "node:fs";
1965
- import path12 from "node:path";
2278
+ import fs8 from "node:fs";
2279
+ import path13 from "node:path";
1966
2280
  import { execSync, spawn } from "node:child_process";
1967
2281
  function acquireLock() {
1968
2282
  const lockPath = getUpdateLockPath();
1969
2283
  try {
1970
- ensureDir(path12.dirname(lockPath));
1971
- fs7.writeFileSync(lockPath, `${process.pid}
2284
+ ensureDir(path13.dirname(lockPath));
2285
+ fs8.writeFileSync(lockPath, `${process.pid}
1972
2286
  `, { flag: "wx" });
1973
2287
  return true;
1974
2288
  } catch {
@@ -1978,25 +2292,25 @@ function acquireLock() {
1978
2292
  function releaseLock() {
1979
2293
  const lockPath = getUpdateLockPath();
1980
2294
  try {
1981
- fs7.unlinkSync(lockPath);
2295
+ fs8.unlinkSync(lockPath);
1982
2296
  } catch {}
1983
2297
  }
1984
2298
  function isUpdateInProgress() {
1985
2299
  const lockPath = getUpdateLockPath();
1986
- if (!fs7.existsSync(lockPath)) {
2300
+ if (!fs8.existsSync(lockPath)) {
1987
2301
  return false;
1988
2302
  }
1989
2303
  try {
1990
- const raw = fs7.readFileSync(lockPath, "utf8").trim();
2304
+ const raw = fs8.readFileSync(lockPath, "utf8").trim();
1991
2305
  const pid = Number(raw);
1992
2306
  if (!Number.isFinite(pid) || pid <= 0) {
1993
- fs7.unlinkSync(lockPath);
2307
+ fs8.unlinkSync(lockPath);
1994
2308
  return false;
1995
2309
  }
1996
2310
  process.kill(pid, 0);
1997
2311
  return true;
1998
2312
  } catch {
1999
- fs7.unlinkSync(lockPath);
2313
+ fs8.unlinkSync(lockPath);
2000
2314
  return false;
2001
2315
  }
2002
2316
  }
@@ -2007,8 +2321,8 @@ async function downloadAndStageUpdate() {
2007
2321
  }
2008
2322
  const targetVersion = updateStatus.latestVersion;
2009
2323
  const updatesDir = getUpdatesDir();
2010
- const versionDir = path12.join(updatesDir, `v${targetVersion}`);
2011
- if (fs7.existsSync(versionDir)) {
2324
+ const versionDir = path13.join(updatesDir, `v${targetVersion}`);
2325
+ if (fs8.existsSync(versionDir)) {
2012
2326
  const existingStaged = getStagedUpdate();
2013
2327
  if (existingStaged?.version === targetVersion) {
2014
2328
  return { success: false, error: `Version ${targetVersion} already staged` };
@@ -2020,8 +2334,8 @@ async function downloadAndStageUpdate() {
2020
2334
  cwd: versionDir,
2021
2335
  stdio: ["pipe", "pipe", "pipe"]
2022
2336
  });
2023
- const tarballPath = path12.join(versionDir, `kwonye-mcpx-${targetVersion}.tgz`);
2024
- if (!fs7.existsSync(tarballPath)) {
2337
+ const tarballPath = path13.join(versionDir, `kwonye-mcpx-${targetVersion}.tgz`);
2338
+ if (!fs8.existsSync(tarballPath)) {
2025
2339
  throw new Error("Downloaded tarball not found");
2026
2340
  }
2027
2341
  execSync(`tar -xzf ${tarballPath} --strip-components=1`, {
@@ -2032,8 +2346,8 @@ async function downloadAndStageUpdate() {
2032
2346
  cwd: versionDir,
2033
2347
  stdio: ["pipe", "pipe", "pipe"]
2034
2348
  });
2035
- const cliPath = path12.join(versionDir, "dist", "cli.js");
2036
- if (!fs7.existsSync(cliPath)) {
2349
+ const cliPath = path13.join(versionDir, "dist", "cli.js");
2350
+ if (!fs8.existsSync(cliPath)) {
2037
2351
  throw new Error("CLI entry point not found in downloaded package");
2038
2352
  }
2039
2353
  stageUpdate(targetVersion, cliPath);
@@ -2041,7 +2355,7 @@ async function downloadAndStageUpdate() {
2041
2355
  return { success: true, version: targetVersion };
2042
2356
  } catch (error) {
2043
2357
  try {
2044
- fs7.rmSync(versionDir, { recursive: true, force: true });
2358
+ fs8.rmSync(versionDir, { recursive: true, force: true });
2045
2359
  } catch {}
2046
2360
  return {
2047
2361
  success: false,
@@ -2119,14 +2433,14 @@ function performRollback() {
2119
2433
  }
2120
2434
 
2121
2435
  // src/core/daemon.ts
2122
- import fs8 from "node:fs";
2436
+ import fs9 from "node:fs";
2123
2437
  import net from "node:net";
2124
2438
  import { spawn as spawn2 } from "node:child_process";
2125
2439
 
2126
2440
  // src/gateway/server.ts
2127
- import http from "node:http";
2128
- import { URL } from "node:url";
2129
- import crypto from "node:crypto";
2441
+ import http2 from "node:http";
2442
+ import { URL as URL2 } from "node:url";
2443
+ import crypto2 from "node:crypto";
2130
2444
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2131
2445
  import {
2132
2446
  StdioClientTransport,
@@ -2215,16 +2529,16 @@ function getWellKnownPrefix(pathname) {
2215
2529
  return null;
2216
2530
  }
2217
2531
  function getUpstreamPathSuffixForWellKnown(upstream) {
2218
- const upstreamUrl = new URL(upstream.spec.url);
2532
+ const upstreamUrl = new URL2(upstream.spec.url);
2219
2533
  if (upstreamUrl.pathname === "/") {
2220
2534
  return "";
2221
2535
  }
2222
2536
  return upstreamUrl.pathname.endsWith("/") ? upstreamUrl.pathname.slice(0, -1) : upstreamUrl.pathname;
2223
2537
  }
2224
2538
  function buildWellKnownUpstreamUrl(upstream, prefix) {
2225
- const upstreamUrl = new URL(upstream.spec.url);
2539
+ const upstreamUrl = new URL2(upstream.spec.url);
2226
2540
  const suffix = getUpstreamPathSuffixForWellKnown(upstream);
2227
- return new URL(`${prefix}${suffix}`, upstreamUrl.origin);
2541
+ return new URL2(`${prefix}${suffix}`, upstreamUrl.origin);
2228
2542
  }
2229
2543
  function getLocalOriginFromRequest(request) {
2230
2544
  return `http://${request.headers.host ?? "127.0.0.1"}`;
@@ -2240,7 +2554,7 @@ function appendUpstreamQuery(url, upstream) {
2240
2554
  if (!upstream) {
2241
2555
  return url;
2242
2556
  }
2243
- const parsed = new URL(url);
2557
+ const parsed = new URL2(url);
2244
2558
  parsed.searchParams.set("upstream", upstream);
2245
2559
  return parsed.toString();
2246
2560
  }
@@ -2312,51 +2626,52 @@ async function getUpstreamTokenCounts(config, secrets, runtime) {
2312
2626
  const results = {};
2313
2627
  const upstreams = listUpstreams(config);
2314
2628
  for (const upstream of upstreams) {
2315
- if (runtime.tokenCache.has(upstream.name)) {
2316
- results[upstream.name] = runtime.tokenCache.get(upstream.name);
2629
+ const fingerprint = specFingerprint(upstream.spec);
2630
+ const cached = runtime.tokenCache.get(upstream.name);
2631
+ if (cached?.fingerprint === fingerprint) {
2632
+ results[upstream.name] = cached.count;
2317
2633
  continue;
2318
2634
  }
2635
+ let toolsCount = 0;
2636
+ let resourcesCount = 0;
2637
+ let promptsCount = 0;
2638
+ const errors = [];
2319
2639
  try {
2320
- const prevTimeout = process.env.MCPX_UPSTREAM_TIMEOUT_MS;
2321
- process.env.MCPX_UPSTREAM_TIMEOUT_MS = "3000";
2322
- let toolsCount = 0;
2323
- let resourcesCount = 0;
2324
- let promptsCount = 0;
2325
- try {
2326
- const toolsResult = await callUpstream(upstream, "tools/list", {}, "token-tools", secrets, runtime);
2327
- if (toolsResult && toolsResult.tools) {
2328
- toolsCount = Math.ceil(JSON.stringify(toolsResult.tools).length / 4);
2329
- }
2330
- } catch (err) {}
2331
- try {
2332
- const resourcesResult = await callUpstream(upstream, "resources/list", {}, "token-resources", secrets, runtime);
2333
- if (resourcesResult && resourcesResult.resources) {
2334
- resourcesCount = Math.ceil(JSON.stringify(resourcesResult.resources).length / 4);
2335
- }
2336
- } catch (err) {}
2337
- try {
2338
- const promptsResult = await callUpstream(upstream, "prompts/list", {}, "token-prompts", secrets, runtime);
2339
- if (promptsResult && promptsResult.prompts) {
2340
- promptsCount = Math.ceil(JSON.stringify(promptsResult.prompts).length / 4);
2341
- }
2342
- } catch (err) {}
2343
- if (prevTimeout !== undefined) {
2344
- process.env.MCPX_UPSTREAM_TIMEOUT_MS = prevTimeout;
2345
- } else {
2346
- delete process.env.MCPX_UPSTREAM_TIMEOUT_MS;
2347
- }
2348
- const total = toolsCount + resourcesCount + promptsCount;
2349
- const countObj = {
2350
- tools: toolsCount,
2351
- resources: resourcesCount,
2352
- prompts: promptsCount,
2353
- total
2354
- };
2355
- runtime.tokenCache.set(upstream.name, countObj);
2356
- results[upstream.name] = countObj;
2640
+ const toolsResult = await callUpstream(upstream, "tools/list", {}, "token-tools", secrets, runtime);
2641
+ if (toolsResult && toolsResult.tools) {
2642
+ toolsCount = Math.ceil(JSON.stringify(toolsResult.tools).length / 4);
2643
+ }
2357
2644
  } catch (error) {
2358
- results[upstream.name] = { tools: 0, resources: 0, prompts: 0, total: 0 };
2645
+ errors.push(`tools/list: ${error instanceof Error ? error.message : String(error)}`);
2359
2646
  }
2647
+ try {
2648
+ const resourcesResult = await callUpstream(upstream, "resources/list", {}, "token-resources", secrets, runtime);
2649
+ if (resourcesResult && resourcesResult.resources) {
2650
+ resourcesCount = Math.ceil(JSON.stringify(resourcesResult.resources).length / 4);
2651
+ }
2652
+ } catch (error) {
2653
+ errors.push(`resources/list: ${error instanceof Error ? error.message : String(error)}`);
2654
+ }
2655
+ try {
2656
+ const promptsResult = await callUpstream(upstream, "prompts/list", {}, "token-prompts", secrets, runtime);
2657
+ if (promptsResult && promptsResult.prompts) {
2658
+ promptsCount = Math.ceil(JSON.stringify(promptsResult.prompts).length / 4);
2659
+ }
2660
+ } catch (error) {
2661
+ errors.push(`prompts/list: ${error instanceof Error ? error.message : String(error)}`);
2662
+ }
2663
+ const total = toolsCount + resourcesCount + promptsCount;
2664
+ const countObj = {
2665
+ tools: toolsCount,
2666
+ resources: resourcesCount,
2667
+ prompts: promptsCount,
2668
+ total,
2669
+ error: errors.length > 0 ? errors.join("; ") : undefined
2670
+ };
2671
+ if (errors.length === 0) {
2672
+ runtime.tokenCache.set(upstream.name, { fingerprint, count: countObj });
2673
+ }
2674
+ results[upstream.name] = countObj;
2360
2675
  }
2361
2676
  return results;
2362
2677
  }
@@ -2380,11 +2695,9 @@ function specFingerprint(spec) {
2380
2695
  return JSON.stringify(spec);
2381
2696
  }
2382
2697
  function resolveStdioEnv(spec, secrets) {
2383
- if (!spec.env || Object.keys(spec.env).length === 0) {
2384
- return;
2385
- }
2386
2698
  const env = getDefaultEnvironment();
2387
- for (const [key, value] of Object.entries(spec.env)) {
2699
+ env.PATH = buildEnrichedPath(env.PATH);
2700
+ for (const [key, value] of Object.entries(spec.env ?? {})) {
2388
2701
  env[key] = secrets.resolveMaybeSecret(value);
2389
2702
  }
2390
2703
  return env;
@@ -2493,12 +2806,21 @@ async function callStdioUpstream(upstream, method, params, secrets, runtime) {
2493
2806
  }
2494
2807
  }
2495
2808
  async function callHttpUpstream(upstream, method, params, id, secrets, passthroughAuthorizationHeader) {
2809
+ return callHttpUpstreamOnce(upstream, method, params, id, secrets, passthroughAuthorizationHeader, false);
2810
+ }
2811
+ async function callHttpUpstreamOnce(upstream, method, params, id, secrets, passthroughAuthorizationHeader, forceOAuthRefresh) {
2496
2812
  const headers = {
2497
2813
  "content-type": "application/json",
2498
2814
  accept: "application/json, text/event-stream"
2499
2815
  };
2500
2816
  for (const [key, value] of Object.entries(upstream.spec.headers ?? {})) {
2501
- headers[key] = secrets.resolveMaybeSecret(value);
2817
+ if (key.toLowerCase() === "authorization" && isOAuthReference(value)) {
2818
+ const oauthServerName = oauthReferenceServerName(value);
2819
+ const accessToken = await getOAuthAccessToken(oauthServerName, upstream.spec, secrets, { forceRefresh: forceOAuthRefresh });
2820
+ headers[key] = `Bearer ${accessToken}`;
2821
+ } else {
2822
+ headers[key] = secrets.resolveMaybeSecret(value);
2823
+ }
2502
2824
  }
2503
2825
  if (passthroughAuthorizationHeader) {
2504
2826
  headers.Authorization = passthroughAuthorizationHeader;
@@ -2530,6 +2852,9 @@ async function callHttpUpstream(upstream, method, params, id, secrets, passthrou
2530
2852
  }
2531
2853
  if (!response.ok) {
2532
2854
  const responseText = await response.text();
2855
+ if (!forceOAuthRefresh && (response.status === 401 || response.status === 403) && Object.values(upstream.spec.headers ?? {}).some((value) => isOAuthReference(value))) {
2856
+ return callHttpUpstreamOnce(upstream, method, params, id, secrets, passthroughAuthorizationHeader, true);
2857
+ }
2533
2858
  throw new UpstreamHttpError(upstream.name, response.status, responseText, response.headers.get("www-authenticate") ?? undefined);
2534
2859
  }
2535
2860
  let payload = null;
@@ -2671,10 +2996,12 @@ async function handleListTools(config, secrets, runtime, upstreamFilter, clientA
2671
2996
  if (!runtime.tokenCache) {
2672
2997
  runtime.tokenCache = new Map;
2673
2998
  }
2674
- const existing = runtime.tokenCache.get(upstream.name) ?? { tools: 0, resources: 0, prompts: 0, total: 0 };
2999
+ const cached = runtime.tokenCache.get(upstream.name);
3000
+ const existing = cached?.fingerprint === specFingerprint(upstream.spec) ? cached.count : { tools: 0, resources: 0, prompts: 0, total: 0 };
2675
3001
  existing.tools = Math.ceil(JSON.stringify(result.tools ?? []).length / 4);
2676
3002
  existing.total = existing.tools + existing.resources + existing.prompts;
2677
- runtime.tokenCache.set(upstream.name, existing);
3003
+ delete existing.error;
3004
+ runtime.tokenCache.set(upstream.name, { fingerprint: specFingerprint(upstream.spec), count: existing });
2678
3005
  for (const tool of result.tools ?? []) {
2679
3006
  const name = typeof tool.name === "string" ? tool.name : "tool";
2680
3007
  tools.push({
@@ -2683,9 +3010,10 @@ async function handleListTools(config, secrets, runtime, upstreamFilter, clientA
2683
3010
  });
2684
3011
  }
2685
3012
  } catch (error) {
2686
- if (upstreams.length === 1 && isAuthChallenge(error)) {
3013
+ if (upstreams.length === 1) {
2687
3014
  throw error;
2688
3015
  }
3016
+ runtime.upstreamErrors?.set(upstream.name, error instanceof Error ? error.message : String(error));
2689
3017
  }
2690
3018
  }
2691
3019
  return { tools };
@@ -2701,10 +3029,12 @@ async function handleListResources(config, secrets, runtime, upstreamFilter, cli
2701
3029
  if (!runtime.tokenCache) {
2702
3030
  runtime.tokenCache = new Map;
2703
3031
  }
2704
- const existing = runtime.tokenCache.get(upstream.name) ?? { tools: 0, resources: 0, prompts: 0, total: 0 };
3032
+ const cached = runtime.tokenCache.get(upstream.name);
3033
+ const existing = cached?.fingerprint === specFingerprint(upstream.spec) ? cached.count : { tools: 0, resources: 0, prompts: 0, total: 0 };
2705
3034
  existing.resources = Math.ceil(JSON.stringify(result.resources ?? []).length / 4);
2706
3035
  existing.total = existing.tools + existing.resources + existing.prompts;
2707
- runtime.tokenCache.set(upstream.name, existing);
3036
+ delete existing.error;
3037
+ runtime.tokenCache.set(upstream.name, { fingerprint: specFingerprint(upstream.spec), count: existing });
2708
3038
  for (const resource of result.resources ?? []) {
2709
3039
  const originalUri = typeof resource.uri === "string" ? resource.uri : "";
2710
3040
  const originalName = typeof resource.name === "string" ? resource.name : originalUri;
@@ -2715,9 +3045,10 @@ async function handleListResources(config, secrets, runtime, upstreamFilter, cli
2715
3045
  });
2716
3046
  }
2717
3047
  } catch (error) {
2718
- if (upstreams.length === 1 && isAuthChallenge(error)) {
3048
+ if (upstreams.length === 1) {
2719
3049
  throw error;
2720
3050
  }
3051
+ runtime.upstreamErrors?.set(upstream.name, error instanceof Error ? error.message : String(error));
2721
3052
  }
2722
3053
  }
2723
3054
  return { resources };
@@ -2733,10 +3064,12 @@ async function handleListPrompts(config, secrets, runtime, upstreamFilter, clien
2733
3064
  if (!runtime.tokenCache) {
2734
3065
  runtime.tokenCache = new Map;
2735
3066
  }
2736
- const existing = runtime.tokenCache.get(upstream.name) ?? { tools: 0, resources: 0, prompts: 0, total: 0 };
3067
+ const cached = runtime.tokenCache.get(upstream.name);
3068
+ const existing = cached?.fingerprint === specFingerprint(upstream.spec) ? cached.count : { tools: 0, resources: 0, prompts: 0, total: 0 };
2737
3069
  existing.prompts = Math.ceil(JSON.stringify(result.prompts ?? []).length / 4);
2738
3070
  existing.total = existing.tools + existing.resources + existing.prompts;
2739
- runtime.tokenCache.set(upstream.name, existing);
3071
+ delete existing.error;
3072
+ runtime.tokenCache.set(upstream.name, { fingerprint: specFingerprint(upstream.spec), count: existing });
2740
3073
  for (const prompt of result.prompts ?? []) {
2741
3074
  const name = typeof prompt.name === "string" ? prompt.name : "prompt";
2742
3075
  prompts.push({
@@ -2745,9 +3078,10 @@ async function handleListPrompts(config, secrets, runtime, upstreamFilter, clien
2745
3078
  });
2746
3079
  }
2747
3080
  } catch (error) {
2748
- if (upstreams.length === 1 && isAuthChallenge(error)) {
3081
+ if (upstreams.length === 1) {
2749
3082
  throw error;
2750
3083
  }
3084
+ runtime.upstreamErrors?.set(upstream.name, error instanceof Error ? error.message : String(error));
2751
3085
  }
2752
3086
  }
2753
3087
  return { prompts };
@@ -2887,6 +3221,11 @@ async function handleRequestObject(request, secrets, runtime, upstreamFilter, cl
2887
3221
  const result = await getUpstreamTokenCounts(config, secrets, runtime);
2888
3222
  return makeResult(id, result);
2889
3223
  }
3224
+ if (request.method === "custom/refreshTokenCounts") {
3225
+ runtime.tokenCache?.clear();
3226
+ const result = await getUpstreamTokenCounts(config, secrets, runtime);
3227
+ return makeResult(id, result);
3228
+ }
2890
3229
  if (request.method === "tools/list") {
2891
3230
  const result = await handleListTools(config, secrets, runtime, upstreamFilter, clientAuthorizationHeader);
2892
3231
  return makeResult(id, result);
@@ -2973,12 +3312,13 @@ async function maybeHandleWellKnownOAuthRequest(request, response, requestUrl, s
2973
3312
  function createGatewayServer(options) {
2974
3313
  const debug = process.env.MCPX_GATEWAY_DEBUG === "1";
2975
3314
  const runtime = {
2976
- stdioConnections: new Map
3315
+ stdioConnections: new Map,
3316
+ upstreamErrors: new Map
2977
3317
  };
2978
- const server = http.createServer(async (request, response) => {
2979
- let requestUrl = new URL(request.url ?? "/", `http://${request.headers.host ?? "127.0.0.1"}`);
3318
+ const server = http2.createServer(async (request, response) => {
3319
+ let requestUrl = new URL2(request.url ?? "/", `http://${request.headers.host ?? "127.0.0.1"}`);
2980
3320
  try {
2981
- requestUrl = new URL(request.url ?? "/", `http://${request.headers.host ?? "127.0.0.1"}`);
3321
+ requestUrl = new URL2(request.url ?? "/", `http://${request.headers.host ?? "127.0.0.1"}`);
2982
3322
  const upstreamFilter = getRequestedUpstream(requestUrl);
2983
3323
  if (debug) {
2984
3324
  console.error(`[mcpx gateway] ${request.method ?? "?"} ${requestUrl.pathname} auth=${request.headers.authorization ? "yes" : "no"} accept=${request.headers.accept ?? ""}`);
@@ -3089,7 +3429,7 @@ function createGatewayServer(options) {
3089
3429
  response.setHeader("content-type", "application/json");
3090
3430
  }
3091
3431
  if (hasInitialize) {
3092
- const sessionId = crypto.randomUUID();
3432
+ const sessionId = crypto2.randomUUID();
3093
3433
  response.setHeader("mcp-session-id", sessionId);
3094
3434
  response.setHeader("MCP-Session-Id", sessionId);
3095
3435
  }
@@ -3119,7 +3459,11 @@ function createGatewayServer(options) {
3119
3459
  const localResourceMetadataUrl = appendUpstreamQuery(`${getLocalOriginFromRequest(request)}/.well-known/oauth-protected-resource`, getRequestedUpstream(requestUrl));
3120
3460
  response.setHeader("www-authenticate", rewriteWwwAuthenticateResourceMetadata(error.wwwAuthenticate, localResourceMetadataUrl));
3121
3461
  }
3122
- response.end(error.bodyText.length > 0 ? error.bodyText : JSON.stringify({ error: "upstream_auth_required" }));
3462
+ response.end(JSON.stringify(makeError(null, -32001, "Upstream authentication required.", {
3463
+ status: error.status,
3464
+ body: error.bodyText,
3465
+ wwwAuthenticate: error.wwwAuthenticate
3466
+ })));
3123
3467
  if (debug) {
3124
3468
  console.error(`[mcpx gateway] -> ${error.status} upstream auth challenge`);
3125
3469
  }
@@ -3145,10 +3489,10 @@ function createGatewayServer(options) {
3145
3489
 
3146
3490
  // src/core/daemon.ts
3147
3491
  function readPidFromFile(pidPath = getPidPath()) {
3148
- if (!fs8.existsSync(pidPath)) {
3492
+ if (!fs9.existsSync(pidPath)) {
3149
3493
  return null;
3150
3494
  }
3151
- const raw = fs8.readFileSync(pidPath, "utf8").trim();
3495
+ const raw = fs9.readFileSync(pidPath, "utf8").trim();
3152
3496
  if (!raw) {
3153
3497
  return null;
3154
3498
  }
@@ -3195,6 +3539,29 @@ async function resolveGatewayPort(config) {
3195
3539
  }
3196
3540
  throw new Error(`No available local port found near ${config.gateway.port}.`);
3197
3541
  }
3542
+ async function waitForGatewayReady(port, token, timeoutMs = 5000) {
3543
+ const deadline = Date.now() + timeoutMs;
3544
+ let lastError;
3545
+ while (Date.now() < deadline) {
3546
+ try {
3547
+ const response = await fetch(`http://127.0.0.1:${port}/mcp`, {
3548
+ method: "GET",
3549
+ headers: {
3550
+ Authorization: `Bearer ${token}`
3551
+ },
3552
+ signal: AbortSignal.timeout(500)
3553
+ });
3554
+ if (response.ok) {
3555
+ return;
3556
+ }
3557
+ lastError = new Error(`Gateway returned HTTP ${response.status}.`);
3558
+ } catch (error) {
3559
+ lastError = error;
3560
+ }
3561
+ await new Promise((resolve) => setTimeout(resolve, 100));
3562
+ }
3563
+ throw new Error(`Gateway did not become ready within ${timeoutMs}ms: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
3564
+ }
3198
3565
  function getDaemonStatus(config) {
3199
3566
  const pidPath = getPidPath();
3200
3567
  const pid = readPidFromFile(pidPath);
@@ -3227,16 +3594,16 @@ async function startDaemon(config, cliPath, secrets) {
3227
3594
  };
3228
3595
  }
3229
3596
  try {
3230
- fs8.unlinkSync(getPidPath());
3597
+ fs9.unlinkSync(getPidPath());
3231
3598
  } catch {}
3232
3599
  }
3233
3600
  const port = await resolveGatewayPort(config);
3234
- ensureGatewayToken(config, secrets);
3601
+ const token = ensureGatewayToken(config, secrets);
3235
3602
  const pidPath = getPidPath();
3236
3603
  const logPath = getLogPath();
3237
3604
  ensureParentDir(pidPath);
3238
3605
  ensureParentDir(logPath);
3239
- const logFd = fs8.openSync(logPath, "a", 384);
3606
+ const logFd = fs9.openSync(logPath, "a", 384);
3240
3607
  const child = spawn2(process.execPath, [cliPath, "daemon", "run", "--port", String(port)], {
3241
3608
  detached: true,
3242
3609
  stdio: ["ignore", logFd, logFd],
@@ -3246,9 +3613,10 @@ async function startDaemon(config, cliPath, secrets) {
3246
3613
  }
3247
3614
  });
3248
3615
  child.unref();
3249
- fs8.writeFileSync(pidPath, `${child.pid}
3616
+ fs9.writeFileSync(pidPath, `${child.pid}
3250
3617
  `, { mode: 384 });
3251
3618
  startBackgroundUpdateCheck();
3619
+ await waitForGatewayReady(port, token);
3252
3620
  return {
3253
3621
  started: true,
3254
3622
  pid: child.pid ?? -1,
@@ -3269,7 +3637,7 @@ function stopDaemon() {
3269
3637
  process.kill(pid, "SIGTERM");
3270
3638
  } catch {}
3271
3639
  try {
3272
- fs8.unlinkSync(pidPath);
3640
+ fs9.unlinkSync(pidPath);
3273
3641
  } catch {}
3274
3642
  return {
3275
3643
  stopped: true,
@@ -3282,10 +3650,10 @@ async function restartDaemon(config, cliPath, secrets) {
3282
3650
  }
3283
3651
  function readDaemonLogs(maxLines = 200) {
3284
3652
  const logPath = getLogPath();
3285
- if (!fs8.existsSync(logPath)) {
3653
+ if (!fs9.existsSync(logPath)) {
3286
3654
  return "";
3287
3655
  }
3288
- const lines = fs8.readFileSync(logPath, "utf8").split(`
3656
+ const lines = fs9.readFileSync(logPath, "utf8").split(`
3289
3657
  `);
3290
3658
  return lines.slice(Math.max(lines.length - maxLines, 0)).join(`
3291
3659
  `);
@@ -3367,7 +3735,8 @@ async function fetchTokenCounts(gatewayUrl, token) {
3367
3735
  id: "status-tokens",
3368
3736
  method: "custom/tokenCounts",
3369
3737
  params: {}
3370
- })
3738
+ }),
3739
+ signal: AbortSignal.timeout(4000)
3371
3740
  });
3372
3741
  if (!res.ok) {
3373
3742
  return {};
@@ -3424,4 +3793,4 @@ async function buildStatusReport(config, managedIndex, daemon = getDaemonStatus(
3424
3793
  };
3425
3794
  }
3426
3795
 
3427
- export { listSkills, getSkill, saveSkill, deleteSkill, probeHttpAuthRequirement, fetchRegistryServerDetail, selectBestPackage, extractRequiredInputs, mapRegistryToSpec, getAdapters, loadManagedIndex, getGatewayUrl, syncAllClients, toSecretRef, secretRefName, defaultAuthSecretName, resolveAuthTarget, applyAuthReference, removeAuthReference, listAuthBindings, maybePrefixBearer, APP_VERSION, compareVersions, checkForUpdates, getStagedUpdate, clearStagedUpdate, getStagedCliPath, startBackgroundUpdateCheck, runBackgroundUpdate, performUpdate, performRollback, getDaemonStatus, startDaemon, stopDaemon, restartDaemon, readDaemonLogs, runDaemonForeground, STATUS_CLIENTS, buildStatusReport };
3796
+ export { listSkills, getSkill, saveSkill, deleteSkill, probeHttpAuthRequirement, fetchRegistryServerDetail, selectBestPackage, extractRequiredInputs, mapRegistryToSpec, getAdapters, loadManagedIndex, getGatewayUrl, syncAllClients, toSecretRef, secretRefName, defaultAuthSecretName, resolveAuthTarget, applyAuthReference, removeAuthReference, listAuthBindings, maybePrefixBearer, buildEnrichedPath, isOAuthReference, oauthReferenceServerName, runOAuthLogin, getOAuthAccessToken, APP_VERSION, compareVersions, checkForUpdates, getStagedUpdate, clearStagedUpdate, getStagedCliPath, startBackgroundUpdateCheck, runBackgroundUpdate, performUpdate, performRollback, getDaemonStatus, startDaemon, stopDaemon, restartDaemon, readDaemonLogs, runDaemonForeground, STATUS_CLIENTS, buildStatusReport };