@debugg-ai/debugg-ai-mcp 1.0.32 → 1.0.33
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/dist/config/index.js +10 -1
- package/dist/index.js +18 -3
- package/dist/services/ngrok/tunnelManager.js +6 -0
- package/dist/services/posthogProvider.js +32 -0
- package/dist/services/workflows.js +12 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/telemetry.js +57 -0
- package/package.json +4 -3
package/dist/config/index.js
CHANGED
|
@@ -43,6 +43,10 @@ const configSchema = z.object({
|
|
|
43
43
|
level: z.enum(['error', 'warn', 'info', 'debug']).default('info'),
|
|
44
44
|
format: z.enum(['json', 'simple']).default('simple'),
|
|
45
45
|
}),
|
|
46
|
+
telemetry: z.object({
|
|
47
|
+
posthogApiKey: z.string().optional(),
|
|
48
|
+
posthogHost: z.string().optional(),
|
|
49
|
+
}),
|
|
46
50
|
});
|
|
47
51
|
export function loadConfig() {
|
|
48
52
|
const rawConfig = {
|
|
@@ -67,6 +71,10 @@ export function loadConfig() {
|
|
|
67
71
|
level: process.env.LOG_LEVEL || 'info',
|
|
68
72
|
format: process.env.LOG_FORMAT || 'simple',
|
|
69
73
|
},
|
|
74
|
+
telemetry: {
|
|
75
|
+
posthogApiKey: process.env.POSTHOG_API_KEY || undefined,
|
|
76
|
+
posthogHost: process.env.POSTHOG_HOST || undefined,
|
|
77
|
+
},
|
|
70
78
|
};
|
|
71
79
|
try {
|
|
72
80
|
return configSchema.parse(rawConfig);
|
|
@@ -86,7 +94,8 @@ export const config = {
|
|
|
86
94
|
get server() { return getConfig().server; },
|
|
87
95
|
get api() { return getConfig().api; },
|
|
88
96
|
get defaults() { return getConfig().defaults; },
|
|
89
|
-
get logging() { return getConfig().logging; }
|
|
97
|
+
get logging() { return getConfig().logging; },
|
|
98
|
+
get telemetry() { return getConfig().telemetry; },
|
|
90
99
|
};
|
|
91
100
|
function getConfig() {
|
|
92
101
|
if (!_config) {
|
package/dist/index.js
CHANGED
|
@@ -21,7 +21,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
21
21
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
22
22
|
import { config } from "./config/index.js";
|
|
23
23
|
import { tools, getTool } from "./tools/index.js";
|
|
24
|
-
import { Logger, validateInput, createErrorResponse, toMCPError, handleConfigurationError } from "./utils/index.js";
|
|
24
|
+
import { Logger, validateInput, createErrorResponse, toMCPError, handleConfigurationError, Telemetry, TelemetryEvents, } from "./utils/index.js";
|
|
25
25
|
// Initialize logger
|
|
26
26
|
const logger = new Logger({ module: 'main' });
|
|
27
27
|
/**
|
|
@@ -108,8 +108,11 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
108
108
|
const progressCallback = createProgressCallback(typeof progressToken === 'string' || typeof progressToken === 'number' ? String(progressToken) : undefined);
|
|
109
109
|
// Execute tool handler with progress callback
|
|
110
110
|
requestLogger.info(`Executing tool: ${name}`);
|
|
111
|
+
const toolStart = Date.now();
|
|
111
112
|
const result = await tool.handler(validatedInput, context, progressCallback);
|
|
113
|
+
const toolDuration = Date.now() - toolStart;
|
|
112
114
|
requestLogger.info(`Tool execution completed: ${name}`);
|
|
115
|
+
Telemetry.capture(TelemetryEvents.TOOL_EXECUTED, { toolName: name, durationMs: toolDuration, success: true });
|
|
113
116
|
return result;
|
|
114
117
|
}
|
|
115
118
|
catch (error) {
|
|
@@ -121,6 +124,7 @@ server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
|
121
124
|
message: mcpError.message,
|
|
122
125
|
data: mcpError.data
|
|
123
126
|
});
|
|
127
|
+
Telemetry.capture(TelemetryEvents.TOOL_FAILED, { toolName: name, errorCode: mcpError.code });
|
|
124
128
|
return createErrorResponse(mcpError, typedReq.params.name);
|
|
125
129
|
}
|
|
126
130
|
});
|
|
@@ -149,6 +153,15 @@ async function main() {
|
|
|
149
153
|
if (!config.api.key) {
|
|
150
154
|
throw new Error('Missing required environment variable: DEBUGGAI_API_KEY');
|
|
151
155
|
}
|
|
156
|
+
// Initialize telemetry (PostHog when key is set, Noop otherwise)
|
|
157
|
+
Telemetry.setDistinctId(config.api.key);
|
|
158
|
+
if (config.telemetry.posthogApiKey) {
|
|
159
|
+
const { PostHogProvider } = await import('./services/posthogProvider.js');
|
|
160
|
+
Telemetry.configure(new PostHogProvider(config.telemetry.posthogApiKey, {
|
|
161
|
+
host: config.telemetry.posthogHost,
|
|
162
|
+
}));
|
|
163
|
+
logger.info('Telemetry enabled (PostHog)');
|
|
164
|
+
}
|
|
152
165
|
// Create and connect transport
|
|
153
166
|
const transport = new StdioServerTransport();
|
|
154
167
|
await server.connect(transport);
|
|
@@ -168,12 +181,14 @@ async function main() {
|
|
|
168
181
|
/**
|
|
169
182
|
* Handle graceful shutdown
|
|
170
183
|
*/
|
|
171
|
-
process.on('SIGINT', () => {
|
|
184
|
+
process.on('SIGINT', async () => {
|
|
172
185
|
logger.info('Received SIGINT, shutting down gracefully');
|
|
186
|
+
await Telemetry.shutdown();
|
|
173
187
|
process.exit(0);
|
|
174
188
|
});
|
|
175
|
-
process.on('SIGTERM', () => {
|
|
189
|
+
process.on('SIGTERM', async () => {
|
|
176
190
|
logger.info('Received SIGTERM, shutting down gracefully');
|
|
191
|
+
await Telemetry.shutdown();
|
|
177
192
|
process.exit(0);
|
|
178
193
|
});
|
|
179
194
|
/**
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* process recently touched the entry the timer resets instead of stopping.
|
|
18
18
|
*/
|
|
19
19
|
import { Logger } from '../../utils/logger.js';
|
|
20
|
+
import { Telemetry, TelemetryEvents } from '../../utils/telemetry.js';
|
|
20
21
|
import { isLocalhostUrl, extractLocalhostPort, generateTunnelUrl } from '../../utils/urlParser.js';
|
|
21
22
|
import { v4 as uuidv4 } from 'uuid';
|
|
22
23
|
import { getDefaultRegistry, } from './tunnelRegistry.js';
|
|
@@ -128,6 +129,7 @@ class TunnelManager {
|
|
|
128
129
|
if (!tunnelInfo.isOwned) {
|
|
129
130
|
// Borrowed — just drop the local reference; owner manages the real tunnel
|
|
130
131
|
logger.info(`Released borrowed tunnel reference: ${tunnelInfo.publicUrl}`);
|
|
132
|
+
Telemetry.capture(TelemetryEvents.TUNNEL_STOPPED, { port: tunnelInfo.port, reason: 'released', isOwned: false });
|
|
131
133
|
return;
|
|
132
134
|
}
|
|
133
135
|
// Owned — remove from shared registry, then disconnect + revoke
|
|
@@ -183,6 +185,7 @@ class TunnelManager {
|
|
|
183
185
|
const existing = this.getTunnelForPort(port);
|
|
184
186
|
if (existing) {
|
|
185
187
|
logger.info(`Reusing existing tunnel for port ${port}: ${existing.publicUrl}`);
|
|
188
|
+
Telemetry.capture(TelemetryEvents.TUNNEL_PROVISIONED, { port, how: 'reused' });
|
|
186
189
|
return { url: existing.publicUrl, tunnelId: existing.tunnelId, isLocalhost: true };
|
|
187
190
|
}
|
|
188
191
|
// 2. Deduplicate concurrent creation requests for the same port
|
|
@@ -212,6 +215,7 @@ class TunnelManager {
|
|
|
212
215
|
regEntry.lastAccessedAt = now;
|
|
213
216
|
this.reg.write(registry);
|
|
214
217
|
this.resetTunnelTimer(borrowed);
|
|
218
|
+
Telemetry.capture(TelemetryEvents.TUNNEL_PROVISIONED, { port, how: 'borrowed' });
|
|
215
219
|
return { url: regEntry.publicUrl, tunnelId: regEntry.tunnelId, isLocalhost: true };
|
|
216
220
|
}
|
|
217
221
|
// 4. Create a new tunnel (this process becomes the owner)
|
|
@@ -290,6 +294,7 @@ class TunnelManager {
|
|
|
290
294
|
}
|
|
291
295
|
this.resetTunnelTimer(tunnelInfo);
|
|
292
296
|
logger.info(`Tunnel created: ${publicUrl} → localhost:${port}`);
|
|
297
|
+
Telemetry.capture(TelemetryEvents.TUNNEL_PROVISIONED, { port, how: 'created' });
|
|
293
298
|
return tunnelInfo;
|
|
294
299
|
}
|
|
295
300
|
catch (error) {
|
|
@@ -334,6 +339,7 @@ class TunnelManager {
|
|
|
334
339
|
}
|
|
335
340
|
}
|
|
336
341
|
logger.info(`Auto-shutting down tunnel ${tunnelInfo.tunnelId} after inactivity`);
|
|
342
|
+
Telemetry.capture(TelemetryEvents.TUNNEL_STOPPED, { port: tunnelInfo.port, reason: 'auto-shutoff', isOwned: tunnelInfo.isOwned });
|
|
337
343
|
await this.stopTunnel(tunnelInfo.tunnelId).catch((err) => logger.error(`Failed to auto-shutdown tunnel ${tunnelInfo.tunnelId}:`, err));
|
|
338
344
|
}, this.TUNNEL_TIMEOUT_MS);
|
|
339
345
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PostHog telemetry provider.
|
|
3
|
+
* Enabled when POSTHOG_API_KEY is set in the environment.
|
|
4
|
+
*/
|
|
5
|
+
import { PostHog } from 'posthog-node';
|
|
6
|
+
export class PostHogProvider {
|
|
7
|
+
client;
|
|
8
|
+
constructor(apiKey, options) {
|
|
9
|
+
this.client = new PostHog(apiKey, {
|
|
10
|
+
host: options?.host ?? 'https://us.i.posthog.com',
|
|
11
|
+
flushAt: 20,
|
|
12
|
+
flushInterval: 10000,
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
capture(event) {
|
|
16
|
+
this.client.capture({
|
|
17
|
+
distinctId: event.distinctId,
|
|
18
|
+
event: event.event,
|
|
19
|
+
properties: event.properties,
|
|
20
|
+
timestamp: event.timestamp,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
identify(distinctId, properties) {
|
|
24
|
+
this.client.identify({ distinctId, properties });
|
|
25
|
+
}
|
|
26
|
+
async flush() {
|
|
27
|
+
await this.client.flush();
|
|
28
|
+
}
|
|
29
|
+
async shutdown() {
|
|
30
|
+
await this.client.shutdown();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Workflows Service
|
|
3
3
|
* 4-step integration: find template → execute → poll → result
|
|
4
4
|
*/
|
|
5
|
+
import { Telemetry, TelemetryEvents } from '../utils/telemetry.js';
|
|
5
6
|
const TERMINAL_STATUSES = new Set(['completed', 'failed', 'cancelled']);
|
|
6
7
|
const POLL_INTERVAL_MS = 3000;
|
|
7
8
|
const EXECUTION_TIMEOUT_MS = 10 * 60 * 1000; // 10 min
|
|
@@ -44,15 +45,26 @@ export const createWorkflowsService = (tx) => {
|
|
|
44
45
|
},
|
|
45
46
|
async pollExecution(executionUuid, onUpdate, signal) {
|
|
46
47
|
const deadline = Date.now() + EXECUTION_TIMEOUT_MS;
|
|
48
|
+
const pollStart = Date.now();
|
|
49
|
+
let pollCount = 0;
|
|
47
50
|
while (Date.now() < deadline) {
|
|
48
51
|
if (signal?.aborted) {
|
|
49
52
|
throw new Error(`Polling cancelled for execution ${executionUuid}`);
|
|
50
53
|
}
|
|
51
54
|
const execution = await service.getExecution(executionUuid);
|
|
55
|
+
pollCount++;
|
|
52
56
|
if (onUpdate) {
|
|
53
57
|
await onUpdate(execution).catch(() => { });
|
|
54
58
|
}
|
|
55
59
|
if (TERMINAL_STATUSES.has(execution.status)) {
|
|
60
|
+
Telemetry.capture(TelemetryEvents.WORKFLOW_EXECUTED, {
|
|
61
|
+
status: execution.status,
|
|
62
|
+
success: execution.state?.success ?? false,
|
|
63
|
+
outcome: execution.state?.outcome ?? null,
|
|
64
|
+
stepsTaken: execution.state?.stepsTaken ?? 0,
|
|
65
|
+
durationMs: Date.now() - pollStart,
|
|
66
|
+
pollCount,
|
|
67
|
+
});
|
|
56
68
|
return execution;
|
|
57
69
|
}
|
|
58
70
|
await new Promise((resolve, reject) => {
|
package/dist/utils/index.js
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic telemetry abstraction layer.
|
|
3
|
+
* Providers implement TelemetryProvider; call sites use Telemetry.capture().
|
|
4
|
+
* Falls back to NoopProvider when no provider is configured.
|
|
5
|
+
*/
|
|
6
|
+
import { createHash } from 'crypto';
|
|
7
|
+
class NoopProvider {
|
|
8
|
+
capture(_event) { }
|
|
9
|
+
identify(_distinctId, _properties) { }
|
|
10
|
+
async flush() { }
|
|
11
|
+
async shutdown() { }
|
|
12
|
+
}
|
|
13
|
+
let _provider = new NoopProvider();
|
|
14
|
+
let _distinctId = 'anonymous';
|
|
15
|
+
export const Telemetry = {
|
|
16
|
+
configure(provider) {
|
|
17
|
+
_provider = provider;
|
|
18
|
+
},
|
|
19
|
+
/**
|
|
20
|
+
* Derive a stable, anonymous identifier from the API key.
|
|
21
|
+
* Uses SHA-256 so the raw key is never stored or transmitted.
|
|
22
|
+
*/
|
|
23
|
+
setDistinctId(apiKey) {
|
|
24
|
+
_distinctId = createHash('sha256').update(apiKey).digest('hex').slice(0, 16);
|
|
25
|
+
},
|
|
26
|
+
capture(event, properties) {
|
|
27
|
+
try {
|
|
28
|
+
_provider.capture({ event, distinctId: _distinctId, properties, timestamp: new Date() });
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// never let telemetry crash the app
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
async flush() {
|
|
35
|
+
try {
|
|
36
|
+
await _provider.flush();
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// best-effort
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
async shutdown() {
|
|
43
|
+
try {
|
|
44
|
+
await _provider.shutdown();
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
// best-effort
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
export const TelemetryEvents = {
|
|
52
|
+
TOOL_EXECUTED: 'tool.executed',
|
|
53
|
+
TOOL_FAILED: 'tool.failed',
|
|
54
|
+
WORKFLOW_EXECUTED: 'workflow.executed',
|
|
55
|
+
TUNNEL_PROVISIONED: 'tunnel.provisioned',
|
|
56
|
+
TUNNEL_STOPPED: 'tunnel.stopped',
|
|
57
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@debugg-ai/debugg-ai-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.33",
|
|
4
4
|
"description": "Zero-Config, Fully AI-Managed End-to-End Testing for all code gen platforms.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"version:major": "npm version major --no-git-tag-version",
|
|
26
26
|
"publish:check": "npm pack --dry-run",
|
|
27
27
|
"prepublishOnly": "npm test && npm run build",
|
|
28
|
-
"mcp:local": "npm run build && claude mcp remove debugg-ai 2>/dev/null; claude mcp add debugg-ai -s user -e DEBUGGAI_API_KEY=KrvXlzVFXVZO82UErXye5N7CtnmBTu1GKULrJnwXRRU -- node /Users/qosha/Repos/debugg-ai/debugg-ai-mcp/dist/index.js",
|
|
29
|
-
"mcp:npm": "claude mcp remove debugg-ai 2>/dev/null; claude mcp add debugg-ai -s user -e DEBUGGAI_API_KEY=KrvXlzVFXVZO82UErXye5N7CtnmBTu1GKULrJnwXRRU -- npx -y @debugg-ai/debugg-ai-mcp"
|
|
28
|
+
"mcp:local": "npm run build && claude mcp remove debugg-ai 2>/dev/null; claude mcp add debugg-ai -s user -e DEBUGGAI_API_KEY=KrvXlzVFXVZO82UErXye5N7CtnmBTu1GKULrJnwXRRU -e POSTHOG_API_KEY=phc_4h2Yov2P0Vc9UMqfKf3dYKSQ6THOs7N6LZR0VKYopZN -- node /Users/qosha/Repos/debugg-ai/debugg-ai-mcp/dist/index.js",
|
|
29
|
+
"mcp:npm": "claude mcp remove debugg-ai 2>/dev/null; claude mcp add debugg-ai -s user -e DEBUGGAI_API_KEY=KrvXlzVFXVZO82UErXye5N7CtnmBTu1GKULrJnwXRRU -e POSTHOG_API_KEY=phc_4h2Yov2P0Vc9UMqfKf3dYKSQ6THOs7N6LZR0VKYopZN -- npx -y @debugg-ai/debugg-ai-mcp"
|
|
30
30
|
},
|
|
31
31
|
"keywords": [
|
|
32
32
|
"debugg",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"axios": "^1.9.0",
|
|
50
50
|
"mkdirp": "^3.0.1",
|
|
51
51
|
"ngrok": "^5.0.0-beta.2",
|
|
52
|
+
"posthog-node": "^5.26.0",
|
|
52
53
|
"uuid": "^11.1.0",
|
|
53
54
|
"winston": "^3.15.0",
|
|
54
55
|
"zod": "^3.25.32"
|