@pharaoh-so/mcp 0.3.23 → 0.3.24

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/auth.js CHANGED
@@ -52,6 +52,13 @@ export async function pollOnce(baseUrl, deviceCode) {
52
52
  const token = (await res.json());
53
53
  return { status: "authorized", token };
54
54
  }
55
+ // 5xx is an upstream/transport issue (CDN 503, Render cold-start 502,
56
+ // gateway 504 with an HTML error page). Treat as network_error so the
57
+ // gateway keeps the pending cycle alive and retries on the next tool call
58
+ // — a transient hiccup must not surface to the user as "auth denied".
59
+ if (res.status >= 500) {
60
+ return { status: "network_error", error: `HTTP ${res.status}` };
61
+ }
55
62
  const body = (await res.json().catch(() => ({ error: "unknown" })));
56
63
  if (body.error === "authorization_pending") {
57
64
  return { status: "pending" };
package/dist/backend.d.ts CHANGED
@@ -51,6 +51,8 @@ export declare class AuthRequiredBackend implements Backend {
51
51
  listTools(): Promise<Tool[]>;
52
52
  callTool(_name: string, _args: Record<string, unknown>): Promise<CallToolResult>;
53
53
  }
54
+ /** Backoff schedule for reconnect attempts (milliseconds). Exported for tests. */
55
+ export declare const BACKOFF_MS: number[];
54
56
  /**
55
57
  * Real SSE-backed backend. Owns an MCP Client + SSEClientTransport and a
56
58
  * reconnect loop. Forwards listTools/callTool to the remote server.
@@ -62,7 +64,7 @@ export declare class AuthRequiredBackend implements Backend {
62
64
  * let this loop create a fresh transport for the next attempt. 5 retries
63
65
  * (2s, 4s, 8s, 16s, 32s) before giving up.
64
66
  */
65
- declare class SseProxyBackend implements Backend {
67
+ export declare class SseProxyBackend implements Backend {
66
68
  private readonly sseUrl;
67
69
  private readonly token;
68
70
  readonly name: "sse";
@@ -93,4 +95,3 @@ declare class SseProxyBackend implements Backend {
93
95
  * depend on its internal reconnect state.
94
96
  */
95
97
  export declare function createSseBackend(sseUrl: string, token: string): SseProxyBackend;
96
- export {};
package/dist/backend.js CHANGED
@@ -81,8 +81,8 @@ export class AuthRequiredBackend {
81
81
  return response;
82
82
  }
83
83
  }
84
- /** Backoff schedule for reconnect attempts (milliseconds). */
85
- const BACKOFF_MS = [2000, 4000, 8000, 16000, 32000];
84
+ /** Backoff schedule for reconnect attempts (milliseconds). Exported for tests. */
85
+ export const BACKOFF_MS = [2000, 4000, 8000, 16000, 32000];
86
86
  /**
87
87
  * Build the SSEClientTransport with Bearer token auth. Injects the
88
88
  * Authorization header into both the SSE connection (via custom fetch) and
@@ -117,7 +117,7 @@ function sleep(ms) {
117
117
  * let this loop create a fresh transport for the next attempt. 5 retries
118
118
  * (2s, 4s, 8s, 16s, 32s) before giving up.
119
119
  */
120
- class SseProxyBackend {
120
+ export class SseProxyBackend {
121
121
  sseUrl;
122
122
  token;
123
123
  name = "sse";
@@ -207,7 +207,12 @@ class SseProxyBackend {
207
207
  if (err instanceof TokenExpiredError || err instanceof TenantSuspendedError) {
208
208
  throw err;
209
209
  }
210
- attempt++;
210
+ // Do NOT increment attempt here — the try block already incremented
211
+ // it before calling reconnectAfter. A second bump would halve the
212
+ // effective retry budget so only ~3 of the 5 BACKOFF_MS slots
213
+ // would fire (skipping the 4s and 16s delays). The guard below
214
+ // catches the max-retries case where reconnectAfter's own throw
215
+ // arrives after we've already spent the budget in the try block.
211
216
  if (attempt > BACKOFF_MS.length) {
212
217
  throw err instanceof Error
213
218
  ? err
@@ -87,12 +87,17 @@ export class ProxyServer {
87
87
  */
88
88
  async setBackend(next) {
89
89
  const prev = this.backend;
90
- this.backend = next;
90
+ // Start the new backend BEFORE swapping the reference. If start() rejects
91
+ // (e.g. SSE connect fails), `prev` stays active — otherwise the proxy
92
+ // would be wedged with a not-yet-started backend that throws on every
93
+ // list/call until the caller tried again. This ordering also closes the
94
+ // race window where an in-flight request could arrive between the
95
+ // reference swap and a slow SSE handshake.
91
96
  if (next.start)
92
97
  await next.start();
93
- // Only emit after backend reference is swapped — if the client races
94
- // a list request against the notification, we want them to see the
95
- // new catalog.
98
+ this.backend = next;
99
+ // Fire the notification after the swap so a client that races a list
100
+ // request against it sees the new catalog.
96
101
  await this.server.sendToolListChanged();
97
102
  // Fire-and-forget stop of the old backend. Errors here don't affect
98
103
  // the running session — the swap has already happened.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@pharaoh-so/mcp",
3
3
  "mcpName": "so.pharaoh/pharaoh",
4
- "version": "0.3.23",
4
+ "version": "0.3.24",
5
5
  "description": "MCP proxy for Pharaoh — maps codebases into queryable knowledge graphs for AI agents. Enables Claude Code in headless environments (VPS, SSH, CI) via device flow auth.",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",