@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
- logger.info(`Reusing existing tunnel for port ${port}: ${existing.publicUrl}`);
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: existing.publicUrl, tunnelId: existing.tunnelId, isLocalhost: true };
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
- return { url: info.publicUrl, tunnelId: info.tunnelId, isLocalhost: true };
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
- return { url: regEntry.publicUrl, tunnelId: regEntry.tunnelId, isLocalhost: true };
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
- return { ...ctx, tunnelId: existing.tunnelId, targetUrl: existing.publicUrl };
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.
@@ -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
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@debugg-ai/debugg-ai-mcp",
3
- "version": "3.7.2",
3
+ "version": "3.7.3",
4
4
  "description": "Zero-Config, Fully AI-Managed End-to-End Testing for all code gen platforms.",
5
5
  "type": "module",
6
6
  "bin": {