up-mcp-bridge 1.0.0 → 1.0.2

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
@@ -209,9 +209,7 @@ You can specify multiple `--ignore-tool` flags to ignore different patterns. Exa
209
209
  ]
210
210
  ```
211
211
 
212
- ### Auto-Reconnect (Experimental)
213
-
214
- ⚠️ **This feature is experimental and has known issues being investigated.**
212
+ ### Auto-Reconnect
215
213
 
216
214
  To enable automatic reconnection when the remote server restarts, add the `--auto-reconnect` flag:
217
215
 
@@ -231,11 +229,20 @@ To enable automatic reconnection when the remote server restarts, add the `--aut
231
229
  ```
232
230
 
233
231
  This enables:
232
+ - **Session-aware ping**: Detects stale SSE sessions via `/ping?sessionId=XXX` endpoint
234
233
  - Automatic reconnection with exponential backoff when the server becomes unavailable
235
234
  - Message queuing during reconnection attempts
236
235
  - MCP session re-initialization after successful reconnection
237
236
  - Transparent handling for the MCP client
238
237
 
238
+ **How session-aware ping works:**
239
+ 1. When connected, the bridge extracts the `sessionId` from the SSE endpoint URL
240
+ 2. Periodic health checks include the `sessionId` parameter
241
+ 3. If the server returns `410 Gone`, the session has expired (e.g., server restarted)
242
+ 4. The bridge immediately triggers reconnection instead of waiting for SSE errors
243
+
244
+ This ensures fast recovery when the remote server restarts, avoiding tool call timeouts.
245
+
239
246
  **Default parameters:**
240
247
  - Max reconnection attempts: 20
241
248
  - Base delay: 1000ms
@@ -254,18 +261,7 @@ This enables:
254
261
  ]
255
262
  ```
256
263
 
257
- #### Known Issues Under Investigation
258
-
259
- 1. **MCP calls may hang after server restart**: In some scenarios, MCP tool calls hang indefinitely after the remote server restarts, even though the proxy reports a successful reconnection. A full restart cycle (stop client → restart server → start client) resolves the issue, but this defeats the purpose of auto-reconnect.
260
-
261
- 2. **npx execution issues**: When using `npx up-mcp-bridge`, environment variables (like `NODE_TLS_REJECT_UNAUTHORIZED`) may not propagate correctly to the spawned process, causing SSL issues with self-signed certificates.
262
-
263
- 3. **HTTP vs HTTPS behavior**: The proxy behaves differently when connecting via HTTP vs HTTPS. HTTP connections (even on localhost) may exhibit different hanging behavior.
264
-
265
- **Status**: These issues are being actively investigated. If you encounter problems, please report them with:
266
- - Your configuration (sanitized)
267
- - Steps to reproduce
268
- - Logs from the proxy (`--debug` flag)
264
+ **Note:** Session-aware ping requires server support (e.g., ultraPRO Desktop v1.0+). Servers without this feature will still work with standard ping-based health checks.
269
265
 
270
266
  ### Transport Strategies
271
267
 
@@ -18043,7 +18043,7 @@ var Client = class extends Protocol {
18043
18043
  };
18044
18044
 
18045
18045
  // package.json
18046
- var version2 = "1.0.0";
18046
+ var version2 = "1.0.1";
18047
18047
 
18048
18048
  // node_modules/pkce-challenge/dist/index.node.js
18049
18049
  var crypto;
@@ -20026,10 +20026,13 @@ var PING_TIMEOUT_MS = 2e3;
20026
20026
  var MAX_PING_FAILURES = 3;
20027
20027
  var MAX_SSE_ERRORS_BEFORE_RECONNECT = 2;
20028
20028
  var SSE_ERROR_PATTERNS = ["timeout", "terminated", "aborted", "network", "ECONNRESET", "ECONNREFUSED"];
20029
- async function pingServer(serverUrl) {
20029
+ async function pingServer(serverUrl, sessionId) {
20030
20030
  try {
20031
20031
  const url2 = new URL(serverUrl);
20032
- const pingUrl = `${url2.protocol}//${url2.host}/ping`;
20032
+ let pingUrl = `${url2.protocol}//${url2.host}/ping`;
20033
+ if (sessionId) {
20034
+ pingUrl += `?sessionId=${encodeURIComponent(sessionId)}`;
20035
+ }
20033
20036
  const controller = new AbortController();
20034
20037
  const timeout = setTimeout(() => controller.abort(), PING_TIMEOUT_MS);
20035
20038
  try {
@@ -20038,13 +20041,25 @@ async function pingServer(serverUrl) {
20038
20041
  method: "GET"
20039
20042
  });
20040
20043
  clearTimeout(timeout);
20041
- return response.ok;
20044
+ if (response.status === 410) {
20045
+ return { alive: true, sessionExpired: true };
20046
+ }
20047
+ return { alive: response.ok, sessionExpired: false };
20042
20048
  } catch (e) {
20043
20049
  clearTimeout(timeout);
20044
- return false;
20050
+ return { alive: false, sessionExpired: false };
20045
20051
  }
20046
20052
  } catch (e) {
20047
- return false;
20053
+ return { alive: false, sessionExpired: false };
20054
+ }
20055
+ }
20056
+ function extractSessionId(transport) {
20057
+ try {
20058
+ const endpoint = transport._endpoint;
20059
+ if (!endpoint) return void 0;
20060
+ return endpoint.searchParams?.get("sessionId") || void 0;
20061
+ } catch {
20062
+ return void 0;
20048
20063
  }
20049
20064
  }
20050
20065
  var pid = process.pid;
@@ -20154,6 +20169,17 @@ function mcpProxy({
20154
20169
  let consecutiveTimeouts = 0;
20155
20170
  let consecutiveSseErrors = 0;
20156
20171
  const pendingRequests = /* @__PURE__ */ new Map();
20172
+ let currentSessionId;
20173
+ let sessionIdExtracted = false;
20174
+ function tryExtractSessionId() {
20175
+ if (sessionIdExtracted) return;
20176
+ const sessionId = extractSessionId(currentTransportToServer);
20177
+ if (sessionId) {
20178
+ currentSessionId = sessionId;
20179
+ sessionIdExtracted = true;
20180
+ log(`[Session] Extracted sessionId from transport: ${sessionId}`);
20181
+ }
20182
+ }
20157
20183
  let savedInitializeMessage = null;
20158
20184
  let initializeIdCounter = 1e6;
20159
20185
  async function reinitializeMcpSession(transport) {
@@ -20287,8 +20313,20 @@ function mcpProxy({
20287
20313
  return;
20288
20314
  }
20289
20315
  debugLog(`[Ping] Checking server health for request ${message.id}...`);
20290
- const isAlive = await pingServer(serverUrl);
20291
- if (isAlive) {
20316
+ const pingResult = await pingServer(serverUrl, currentSessionId);
20317
+ if (pingResult.sessionExpired) {
20318
+ log(`[Ping] Session expired for request ${message.id}, triggering immediate reconnection`);
20319
+ clearInterval(pingInterval);
20320
+ connectionHealthy = false;
20321
+ const pending = pendingRequests.get(message.id);
20322
+ if (pending) {
20323
+ pendingRequests.delete(message.id);
20324
+ queueMessageForRetry(pending.message);
20325
+ }
20326
+ currentTransportToServer.close().catch(onServerError);
20327
+ return;
20328
+ }
20329
+ if (pingResult.alive) {
20292
20330
  if (pingFailures > 0) {
20293
20331
  debugLog(`[Ping] Server recovered, resetting failure counter (was ${pingFailures})`);
20294
20332
  pingFailures = 0;
@@ -20386,6 +20424,7 @@ function mcpProxy({
20386
20424
  serverTransport.onmessage = (_message) => {
20387
20425
  const message = messageTransformer.interceptResponse(_message);
20388
20426
  log("[Remote\u2192Local]", message.method || message.id);
20427
+ tryExtractSessionId();
20389
20428
  if (message.id !== void 0) {
20390
20429
  clearRequestTracking(message.id);
20391
20430
  }
@@ -20437,6 +20476,9 @@ function mcpProxy({
20437
20476
  connectionHealthy = true;
20438
20477
  consecutiveTimeouts = 0;
20439
20478
  consecutiveSseErrors = 0;
20479
+ sessionIdExtracted = false;
20480
+ currentSessionId = void 0;
20481
+ tryExtractSessionId();
20440
20482
  log(`Flushing ${pendingMessages.length} queued messages...`);
20441
20483
  while (pendingMessages.length > 0) {
20442
20484
  const pending = pendingMessages.shift();
@@ -20991,17 +21033,28 @@ async function parseCommandLineArgs(args, usage) {
20991
21033
  };
20992
21034
  }
20993
21035
  function setupSignalHandlers(cleanup) {
20994
- process.on("SIGINT", async () => {
20995
- log("\nShutting down...");
20996
- await cleanup();
20997
- process.exit(0);
20998
- });
21036
+ let isShuttingDown = false;
21037
+ const gracefulShutdown = async (signal) => {
21038
+ if (isShuttingDown) {
21039
+ log(`[Shutdown] Already shutting down, ignoring ${signal}`);
21040
+ return;
21041
+ }
21042
+ isShuttingDown = true;
21043
+ log(`[Shutdown] Received ${signal}, cleaning up...`);
21044
+ try {
21045
+ await cleanup();
21046
+ log("[Shutdown] Cleanup complete, exiting with code 0");
21047
+ process.exit(0);
21048
+ } catch (error2) {
21049
+ log("[Shutdown] Error during cleanup:", error2);
21050
+ process.exit(1);
21051
+ }
21052
+ };
21053
+ process.on("SIGINT", () => gracefulShutdown("SIGINT"));
21054
+ process.on("SIGTERM", () => gracefulShutdown("SIGTERM"));
20999
21055
  process.stdin.resume();
21000
- process.stdin.on("end", async () => {
21001
- log("\nShutting down...");
21002
- await cleanup();
21003
- process.exit(0);
21004
- });
21056
+ process.stdin.on("end", () => gracefulShutdown("stdin-end"));
21057
+ process.stdin.on("close", () => gracefulShutdown("stdin-close"));
21005
21058
  }
21006
21059
  function getServerUrlHash(serverUrl, authorizeResource, headers) {
21007
21060
  const parts = [serverUrl];
package/dist/client.js CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  parseCommandLineArgs,
13
13
  setupSignalHandlers,
14
14
  version
15
- } from "./chunk-EOYXIWZ7.js";
15
+ } from "./chunk-3OI3JDYP.js";
16
16
 
17
17
  // src/client.ts
18
18
  import { EventEmitter } from "events";
package/dist/proxy.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  mcpProxy,
11
11
  parseCommandLineArgs,
12
12
  setupSignalHandlers
13
- } from "./chunk-EOYXIWZ7.js";
13
+ } from "./chunk-3OI3JDYP.js";
14
14
 
15
15
  // src/proxy.ts
16
16
  import { EventEmitter } from "events";
@@ -172,8 +172,8 @@ async function runProxy(serverUrl, callbackPort, headers, transportStrategy = "h
172
172
  }
173
173
  log("Press Ctrl+C to exit");
174
174
  const cleanup = async () => {
175
- await remoteTransport.close();
176
175
  await localTransport.close();
176
+ await remoteTransport.close();
177
177
  if (server) {
178
178
  server.close();
179
179
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "up-mcp-bridge",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Remote proxy for MCP with auto-reconnect support. Fork of mcp-remote with transparent server restart handling.",
5
5
  "keywords": [
6
6
  "mcp",