@debugg-ai/debugg-ai-mcp 3.7.2 → 3.7.3
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.
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { Logger } from '../../utils/logger.js';
|
|
20
20
|
import { Telemetry, TelemetryEvents } from '../../utils/telemetry.js';
|
|
21
|
-
import { isLocalhostUrl, extractLocalhostPort, generateTunnelUrl } from '../../utils/urlParser.js';
|
|
21
|
+
import { isLocalhostUrl, extractLocalhostPort, generateTunnelUrl, retargetTunnelUrl } from '../../utils/urlParser.js';
|
|
22
22
|
import { v4 as uuidv4 } from 'uuid';
|
|
23
23
|
import { FaultInjector, TunnelTrace, getFaultModeFromEnv } from './tunnelFaultInjection.js';
|
|
24
24
|
import { getDefaultRegistry, } from './tunnelRegistry.js';
|
|
@@ -262,9 +262,11 @@ class TunnelManager {
|
|
|
262
262
|
// 1. Check local in-process map (handles owned + borrowed with liveness check)
|
|
263
263
|
const existing = this.getTunnelForPort(port);
|
|
264
264
|
if (existing) {
|
|
265
|
-
|
|
265
|
+
// Bead zmc9: retarget to THIS caller's path; publicUrl carries the creator's.
|
|
266
|
+
const url_ = retargetTunnelUrl(existing.tunnelUrl, url);
|
|
267
|
+
logger.info(`Reusing existing tunnel for port ${port}: ${url_}`);
|
|
266
268
|
Telemetry.capture(TelemetryEvents.TUNNEL_PROVISIONED, { port, how: 'reused' });
|
|
267
|
-
return { url:
|
|
269
|
+
return { url: url_, tunnelId: existing.tunnelId, isLocalhost: true };
|
|
268
270
|
}
|
|
269
271
|
// 2. Deduplicate concurrent creation requests for the same port
|
|
270
272
|
const pending = this.pendingTunnels.get(port);
|
|
@@ -277,7 +279,8 @@ class TunnelManager {
|
|
|
277
279
|
revokeKey().catch((err) => logger.warn(`Failed to revoke redundant key while joining pending tunnel for port ${port}:`, err));
|
|
278
280
|
}
|
|
279
281
|
const info = await pending;
|
|
280
|
-
|
|
282
|
+
// Bead zmc9: retarget to THIS caller's path, not the in-flight creator's.
|
|
283
|
+
return { url: retargetTunnelUrl(info.tunnelUrl, url), tunnelId: info.tunnelId, isLocalhost: true };
|
|
281
284
|
}
|
|
282
285
|
// 3. Check cross-process registry — another MCP instance may own a tunnel.
|
|
283
286
|
// Borrow only if the entry is fresh (PID alive AND touched within
|
|
@@ -303,7 +306,9 @@ class TunnelManager {
|
|
|
303
306
|
this.reg.write(registry);
|
|
304
307
|
this.resetTunnelTimer(borrowed);
|
|
305
308
|
Telemetry.capture(TelemetryEvents.TUNNEL_PROVISIONED, { port, how: 'borrowed' });
|
|
306
|
-
|
|
309
|
+
// Bead zmc9: retarget to THIS caller's path; regEntry.publicUrl carries the
|
|
310
|
+
// owning PID's creating-call path — replaying it is the cross-session poison.
|
|
311
|
+
return { url: retargetTunnelUrl(regEntry.tunnelUrl, url), tunnelId: regEntry.tunnelId, isLocalhost: true };
|
|
307
312
|
}
|
|
308
313
|
// 4. Create a new tunnel (this process becomes the owner)
|
|
309
314
|
const creationPromise = this.createTunnel(url, port, tunnelId, authToken, keyId, revokeKey);
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - sanitizing backend responses so callers only ever see the original URL
|
|
8
8
|
*/
|
|
9
9
|
import { tunnelManager } from '../services/ngrok/tunnelManager.js';
|
|
10
|
-
import { isLocalhostUrl, replaceTunnelUrls, extractLocalhostPort } from './urlParser.js';
|
|
10
|
+
import { isLocalhostUrl, replaceTunnelUrls, extractLocalhostPort, retargetTunnelUrl } from './urlParser.js';
|
|
11
11
|
// ─── URL resolution ──────────────────────────────────────────────────────────
|
|
12
12
|
/**
|
|
13
13
|
* Resolve tool input to a concrete URL string.
|
|
@@ -43,7 +43,13 @@ export function findExistingTunnel(ctx) {
|
|
|
43
43
|
if (!existing)
|
|
44
44
|
return null;
|
|
45
45
|
tunnelManager.touchTunnel(existing.tunnelId);
|
|
46
|
-
|
|
46
|
+
// Bead zmc9: retarget to THIS caller's path — never replay existing.publicUrl,
|
|
47
|
+
// which carries the path of whichever call created the (port-keyed) tunnel.
|
|
48
|
+
return {
|
|
49
|
+
...ctx,
|
|
50
|
+
tunnelId: existing.tunnelId,
|
|
51
|
+
targetUrl: retargetTunnelUrl(existing.tunnelUrl, ctx.originalUrl),
|
|
52
|
+
};
|
|
47
53
|
}
|
|
48
54
|
/**
|
|
49
55
|
* Create (or reuse) a tunnel for a localhost URL.
|
package/dist/utils/urlParser.js
CHANGED
|
@@ -107,6 +107,28 @@ export function replaceTunnelUrls(value, localhostOrigin) {
|
|
|
107
107
|
}
|
|
108
108
|
return value;
|
|
109
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Re-point a REUSED tunnel at the current caller's path (bead zmc9).
|
|
112
|
+
*
|
|
113
|
+
* A tunnel is origin-scoped: its `tunnelUrl` (scheme://host from ngrok) forwards
|
|
114
|
+
* every path. The path is request-scoped. On reuse we must compose the reused
|
|
115
|
+
* tunnel's ORIGIN with THIS request's path/search/hash — never replay the
|
|
116
|
+
* path-bearing `publicUrl` baked in by whichever call created the tunnel.
|
|
117
|
+
*
|
|
118
|
+
* @param tunnelOrigin the reused tunnel's path-free origin (TunnelInfo.tunnelUrl)
|
|
119
|
+
* @param requestedUrl the localhost URL the current caller asked for
|
|
120
|
+
*/
|
|
121
|
+
export function retargetTunnelUrl(tunnelOrigin, requestedUrl) {
|
|
122
|
+
try {
|
|
123
|
+
const origin = new URL(tunnelOrigin);
|
|
124
|
+
const parsed = parseUrl(requestedUrl);
|
|
125
|
+
return `${origin.protocol}//${origin.host}${parsed.pathname}${parsed.search}${parsed.hash}`;
|
|
126
|
+
}
|
|
127
|
+
catch {
|
|
128
|
+
// Never fabricate a path: fall back to the bare origin, not a foreign path.
|
|
129
|
+
return tunnelOrigin;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
110
132
|
/**
|
|
111
133
|
* Generate a tunneled URL for a localhost URL
|
|
112
134
|
*/
|