@pharaoh-so/mcp 0.2.8 → 0.2.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -51,7 +51,7 @@ claude mcp add pharaoh -- npx @pharaoh-so/mcp
51
51
  If you have a browser available (desktop, laptop), you can connect directly via SSE instead:
52
52
 
53
53
  ```bash
54
- claude mcp add --transport sse pharaoh https://mcp.pharaoh.so/sse
54
+ claude mcp add --transport sse --scope user pharaoh https://mcp.pharaoh.so/sse
55
55
  ```
56
56
 
57
57
  This uses OAuth in the browser and doesn't require this package.
package/dist/proxy.d.ts CHANGED
@@ -1,3 +1,19 @@
1
+ /**
2
+ * Stdio ↔ SSE proxy — bridges a local MCP stdio connection to a remote Pharaoh SSE server.
3
+ * Uses SDK transports on both ends: StdioServerTransport (local) and SSEClientTransport (remote).
4
+ *
5
+ * RECONNECTION DESIGN:
6
+ * The MCP SDK's SSEClientTransport wraps an EventSource, which has built-in auto-reconnect
7
+ * (every 3 seconds by default). This auto-reconnect is problematic because:
8
+ * 1. Each reconnect creates a NEW server-side session (new auth, new session ID)
9
+ * 2. The proxy's reconnect loop doesn't know about the internal reconnect
10
+ * 3. Tool calls get lost between the stale and new sessions
11
+ *
12
+ * Solution: On any non-fatal SSE error, we explicitly close() the transport to kill
13
+ * the EventSource and its auto-reconnect timer, then let the proxy's own backoff loop
14
+ * create a fresh SSEClientTransport for the next attempt. This ensures one session
15
+ * at a time and clean reconnection semantics.
16
+ */
1
17
  /** Thrown when the remote server returns 401 — token is expired or revoked. */
2
18
  export declare class TokenExpiredError extends Error {
3
19
  constructor();
package/dist/proxy.js CHANGED
@@ -1,6 +1,18 @@
1
1
  /**
2
2
  * Stdio ↔ SSE proxy — bridges a local MCP stdio connection to a remote Pharaoh SSE server.
3
3
  * Uses SDK transports on both ends: StdioServerTransport (local) and SSEClientTransport (remote).
4
+ *
5
+ * RECONNECTION DESIGN:
6
+ * The MCP SDK's SSEClientTransport wraps an EventSource, which has built-in auto-reconnect
7
+ * (every 3 seconds by default). This auto-reconnect is problematic because:
8
+ * 1. Each reconnect creates a NEW server-side session (new auth, new session ID)
9
+ * 2. The proxy's reconnect loop doesn't know about the internal reconnect
10
+ * 3. Tool calls get lost between the stale and new sessions
11
+ *
12
+ * Solution: On any non-fatal SSE error, we explicitly close() the transport to kill
13
+ * the EventSource and its auto-reconnect timer, then let the proxy's own backoff loop
14
+ * create a fresh SSEClientTransport for the next attempt. This ensures one session
15
+ * at a time and clean reconnection semantics.
4
16
  */
5
17
  import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
6
18
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
@@ -92,8 +104,6 @@ export async function startProxy(sseUrl, token) {
92
104
  });
93
105
  };
94
106
  }
95
- /** Handle SSE errors — delegates to classifySSEError for 401/403 detection. */
96
- const handleSSEError = classifySSEError;
97
107
  // Connect SSE with reconnect loop — stdio starts AFTER first successful connection
98
108
  await connectWithReconnect();
99
109
  /** Attempt SSE connection with exponential backoff on failure. */
@@ -116,18 +126,27 @@ export async function startProxy(sseUrl, token) {
116
126
  await stdio.start();
117
127
  stdioStarted = true;
118
128
  }
129
+ else {
130
+ process.stderr.write("Pharaoh: reconnected\n");
131
+ }
119
132
  // Wait for close — this promise resolves when SSE disconnects
120
133
  await new Promise((resolve, reject) => {
121
134
  sse.onclose = () => resolve();
122
135
  sse.onerror = (err) => {
123
136
  try {
124
- handleSSEError(err);
137
+ classifySSEError(err);
125
138
  }
126
139
  catch (typed) {
127
140
  reject(typed);
128
141
  return;
129
142
  }
130
- // Non-fatal error — SSE will auto-close, onclose will fire
143
+ // Non-fatal error — explicitly close the transport to kill
144
+ // EventSource's built-in auto-reconnect. Without this, EventSource
145
+ // reconnects every ~3s, creating ghost sessions on the server
146
+ // (100+ auth successes, zero tool calls completing).
147
+ // close() fires onclose which resolves this promise, handing
148
+ // control back to the proxy's backoff loop.
149
+ sse.close().catch(() => { });
131
150
  };
132
151
  });
133
152
  // SSE closed — attempt reconnect
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.2.8",
4
+ "version": "0.2.10",
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",
@@ -72,7 +72,7 @@ Full docs at [pharaoh.so/docs](https://pharaoh.so/docs).
72
72
 
73
73
  **Direct SSE** (desktop with browser, uses OAuth):
74
74
  ```
75
- claude mcp add pharaoh https://mcp.pharaoh.so/sse
75
+ claude mcp add --transport sse --scope user pharaoh https://mcp.pharaoh.so/sse
76
76
  ```
77
77
 
78
78
  **Headless** (VPS/SSH/containers, uses device flow — authorize on any device):