@debugg-ai/debugg-ai-mcp 1.0.24 → 1.0.25
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.
|
@@ -76,9 +76,12 @@ export async function startLiveSessionHandler(input, context, progressCallback)
|
|
|
76
76
|
if (!session) {
|
|
77
77
|
throw new Error('Failed to start browser session: No session returned');
|
|
78
78
|
}
|
|
79
|
-
// If we need a tunnel, create it now using the
|
|
79
|
+
// If we need a tunnel, create it now using the tunnel key from the session response
|
|
80
80
|
if (isLocalhost && tunnelId) {
|
|
81
|
-
|
|
81
|
+
if (!session.tunnelKey) {
|
|
82
|
+
throw new Error('Backend did not return a tunnel key for localhost session');
|
|
83
|
+
}
|
|
84
|
+
const tunnelAuthToken = session.tunnelKey;
|
|
82
85
|
logger.info(`Creating tunnel with backend-provided key for ${input.url} -> ${sessionUrl}`);
|
|
83
86
|
// Create the tunnel using the original localhost URL and the generated tunnel ID
|
|
84
87
|
const port = extractLocalhostPort(input.url);
|
|
@@ -35,29 +35,13 @@ export async function testPageChangesHandler(input, context, progressCallback) {
|
|
|
35
35
|
const client = new DebuggAIServerClient(config.api.key);
|
|
36
36
|
await client.init();
|
|
37
37
|
let tunnelId;
|
|
38
|
+
let ngrokKeyId;
|
|
38
39
|
try {
|
|
39
40
|
const targetUrlRaw = resolveTargetUrl(input);
|
|
40
|
-
|
|
41
|
-
// --- Localhost tunneling ---
|
|
42
|
-
if (isLocalhostUrl(targetUrlRaw)) {
|
|
43
|
-
if (progressCallback) {
|
|
44
|
-
await progressCallback({ progress: 1, total: 10, message: 'Creating secure tunnel for localhost...' });
|
|
45
|
-
}
|
|
46
|
-
const port = extractLocalhostPort(targetUrlRaw);
|
|
47
|
-
if (!port) {
|
|
48
|
-
throw new Error(`Could not extract port from localhost URL: ${targetUrlRaw}`);
|
|
49
|
-
}
|
|
50
|
-
const { v4: uuidv4 } = await import('uuid');
|
|
51
|
-
tunnelId = uuidv4();
|
|
52
|
-
const tunnelPublicUrl = `https://${tunnelId}.ngrok.debugg.ai`;
|
|
53
|
-
const tunnelAuthToken = await client.getNgrokAuthToken();
|
|
54
|
-
const tunnelResult = await tunnelManager.processUrl(targetUrlRaw, tunnelAuthToken, tunnelId);
|
|
55
|
-
targetUrl = tunnelResult.url;
|
|
56
|
-
logger.info(`Tunnel ready: ${targetUrl}`);
|
|
57
|
-
}
|
|
41
|
+
const isLocalhost = isLocalhostUrl(targetUrlRaw);
|
|
58
42
|
// --- Find workflow template ---
|
|
59
43
|
if (progressCallback) {
|
|
60
|
-
await progressCallback({ progress:
|
|
44
|
+
await progressCallback({ progress: 1, total: 10, message: 'Locating evaluation workflow template...' });
|
|
61
45
|
}
|
|
62
46
|
if (!cachedTemplateUuid) {
|
|
63
47
|
const template = await client.workflows.findEvaluationTemplate();
|
|
@@ -70,15 +54,33 @@ export async function testPageChangesHandler(input, context, progressCallback) {
|
|
|
70
54
|
}
|
|
71
55
|
// --- Build context data ---
|
|
72
56
|
const contextData = {
|
|
73
|
-
targetUrl:
|
|
57
|
+
targetUrl: targetUrlRaw,
|
|
74
58
|
question: input.description,
|
|
75
59
|
};
|
|
76
60
|
// --- Execute ---
|
|
77
61
|
if (progressCallback) {
|
|
78
|
-
await progressCallback({ progress:
|
|
62
|
+
await progressCallback({ progress: 2, total: 10, message: 'Queuing workflow execution...' });
|
|
79
63
|
}
|
|
80
|
-
const
|
|
64
|
+
const executeResponse = await client.workflows.executeWorkflow(cachedTemplateUuid, contextData);
|
|
65
|
+
const executionUuid = executeResponse.executionUuid;
|
|
66
|
+
ngrokKeyId = executeResponse.ngrokKeyId ?? undefined;
|
|
81
67
|
logger.info(`Execution queued: ${executionUuid}`);
|
|
68
|
+
// --- Localhost tunneling (after execute, using tunnel_key + executionUuid as subdomain) ---
|
|
69
|
+
if (isLocalhost) {
|
|
70
|
+
if (progressCallback) {
|
|
71
|
+
await progressCallback({ progress: 3, total: 10, message: 'Creating secure tunnel for localhost...' });
|
|
72
|
+
}
|
|
73
|
+
const port = extractLocalhostPort(targetUrlRaw);
|
|
74
|
+
if (!port) {
|
|
75
|
+
throw new Error(`Could not extract port from localhost URL: ${targetUrlRaw}`);
|
|
76
|
+
}
|
|
77
|
+
if (!executeResponse.tunnelKey) {
|
|
78
|
+
throw new Error('Backend did not return a tunnel key for localhost execution');
|
|
79
|
+
}
|
|
80
|
+
tunnelId = executionUuid;
|
|
81
|
+
const tunnelResult = await tunnelManager.processUrl(targetUrlRaw, executeResponse.tunnelKey, tunnelId);
|
|
82
|
+
logger.info(`Tunnel ready: ${tunnelResult.url}`);
|
|
83
|
+
}
|
|
82
84
|
// --- Poll ---
|
|
83
85
|
let lastSteps = 0;
|
|
84
86
|
const finalExecution = await client.workflows.pollExecution(executionUuid, async (exec) => {
|
|
@@ -105,7 +107,7 @@ export async function testPageChangesHandler(input, context, progressCallback) {
|
|
|
105
107
|
success: finalExecution.state?.success ?? false,
|
|
106
108
|
status: finalExecution.status,
|
|
107
109
|
stepsTaken: finalExecution.state?.stepsTaken ?? surferNode?.outputData?.stepsTaken ?? 0,
|
|
108
|
-
targetUrl,
|
|
110
|
+
targetUrl: targetUrlRaw,
|
|
109
111
|
executionId: executionUuid,
|
|
110
112
|
durationMs: finalExecution.durationMs ?? duration,
|
|
111
113
|
};
|
|
@@ -142,6 +144,10 @@ export async function testPageChangesHandler(input, context, progressCallback) {
|
|
|
142
144
|
throw handleExternalServiceError(error, 'DebuggAI', 'test execution');
|
|
143
145
|
}
|
|
144
146
|
finally {
|
|
147
|
+
// Revoke the short-lived ngrok key
|
|
148
|
+
if (ngrokKeyId) {
|
|
149
|
+
client.revokeNgrokKey(ngrokKeyId).catch(err => logger.warn(`Failed to revoke ngrok key ${ngrokKeyId}: ${err}`));
|
|
150
|
+
}
|
|
145
151
|
// Clean up tunnel if we created one
|
|
146
152
|
if (tunnelId) {
|
|
147
153
|
tunnelManager.stopTunnel(tunnelId).catch(err => logger.warn(`Failed to stop tunnel ${tunnelId}: ${err}`));
|
package/dist/services/index.js
CHANGED
|
@@ -38,8 +38,6 @@ export class DebuggAIServerClient {
|
|
|
38
38
|
e2es;
|
|
39
39
|
browserSessions;
|
|
40
40
|
workflows;
|
|
41
|
-
// Cached ngrok auth token — stable per account, fetched once per server session
|
|
42
|
-
_ngrokAuthToken;
|
|
43
41
|
constructor(userApiKey) {
|
|
44
42
|
this.userApiKey = userApiKey;
|
|
45
43
|
// Note: init() is async and should be called separately
|
|
@@ -53,31 +51,13 @@ export class DebuggAIServerClient {
|
|
|
53
51
|
this.workflows = createWorkflowsService(this.tx);
|
|
54
52
|
}
|
|
55
53
|
/**
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* and cached for the lifetime of this server session.
|
|
54
|
+
* Revoke an ngrok API key by its key ID.
|
|
55
|
+
* Call this after workflow execution completes to clean up the short-lived key.
|
|
59
56
|
*/
|
|
60
|
-
async
|
|
61
|
-
if (this._ngrokAuthToken)
|
|
62
|
-
return this._ngrokAuthToken;
|
|
57
|
+
async revokeNgrokKey(ngrokKeyId) {
|
|
63
58
|
if (!this.tx)
|
|
64
59
|
throw new Error('Client not initialized — call init() first');
|
|
65
|
-
|
|
66
|
-
// We create a minimal probe test, extract the key, then delete the test.
|
|
67
|
-
const created = await this.tx.post('api/v1/e2e-tests/', {
|
|
68
|
-
description: '_mcp_tunnel_probe',
|
|
69
|
-
repoName: '_mcp',
|
|
70
|
-
branchName: 'main',
|
|
71
|
-
});
|
|
72
|
-
const token = created?.tunnelKey;
|
|
73
|
-
if (!token)
|
|
74
|
-
throw new Error('Backend did not return a tunnel auth token');
|
|
75
|
-
// Clean up the probe test
|
|
76
|
-
if (created?.uuid) {
|
|
77
|
-
this.tx.delete(`api/v1/e2e-tests/${created.uuid}/`).catch(() => { });
|
|
78
|
-
}
|
|
79
|
-
this._ngrokAuthToken = token;
|
|
80
|
-
return token;
|
|
60
|
+
await this.tx.post('api/v1/ngrok/revoke/', { ngrokKeyId });
|
|
81
61
|
}
|
|
82
62
|
}
|
|
83
63
|
/**
|
|
@@ -13,12 +13,23 @@ export const createWorkflowsService = (tx) => {
|
|
|
13
13
|
const evalTemplate = templates.find(t => t.name.toLowerCase().includes('app evaluation'));
|
|
14
14
|
return evalTemplate ?? templates[0] ?? null;
|
|
15
15
|
},
|
|
16
|
-
async executeWorkflow(workflowUuid, contextData) {
|
|
17
|
-
const
|
|
16
|
+
async executeWorkflow(workflowUuid, contextData, env) {
|
|
17
|
+
const body = { contextData };
|
|
18
|
+
if (env && Object.keys(env).length > 0) {
|
|
19
|
+
body.env = env;
|
|
20
|
+
}
|
|
21
|
+
const response = await tx.post(`api/v1/workflows/${workflowUuid}/execute/`, body);
|
|
18
22
|
if (!response?.resourceUuid) {
|
|
19
23
|
throw new Error('Workflow execution failed: no execution UUID returned');
|
|
20
24
|
}
|
|
21
|
-
return
|
|
25
|
+
return {
|
|
26
|
+
executionUuid: response.resourceUuid,
|
|
27
|
+
tunnelKey: response.tunnelKey ?? null,
|
|
28
|
+
ngrokKeyId: response.ngrokKeyId ?? null,
|
|
29
|
+
ngrokExpiresAt: response.ngrokExpiresAt ?? null,
|
|
30
|
+
resolvedEnvironmentId: response.resolvedEnvironmentId ?? null,
|
|
31
|
+
resolvedCredentialId: response.resolvedCredentialId ?? null,
|
|
32
|
+
};
|
|
22
33
|
},
|
|
23
34
|
async getExecution(executionUuid) {
|
|
24
35
|
const response = await tx.get(`api/v1/workflows/executions/${executionUuid}/`);
|