@vfarcic/dot-ai
Version:
AI-powered development productivity platform that enhances software development workflows through intelligent automation and AI-driven assistance
91 lines (90 loc) • 3.85 kB
JavaScript
;
/**
* Tool Execution Tracing for MCP Tools
*
* Provides generic tracing wrapper for all MCP tool executions,
* creating INTERNAL spans with GenAI semantic conventions.
*
* Supports both STDIO (MCP) and HTTP (REST) transports transparently.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.withToolTracing = withToolTracing;
const api_1 = require("@opentelemetry/api");
const telemetry_1 = require("../telemetry");
/**
* Wraps a tool handler with OpenTelemetry tracing
*
* Creates an INTERNAL span for tool execution with:
* - Tool name and input arguments
* - Execution duration and success status
* - Exception tracking for errors
* - GenAI semantic conventions (gen_ai.tool.*)
*
* @param toolName - Name of the MCP tool being executed
* @param args - Tool input arguments (will be serialized to JSON)
* @param handler - Async function that implements the tool logic
* @param options - Optional tracing options (e.g., MCP client info)
* @returns Promise resolving to the tool handler result
*
* @example
* ```typescript
* const result = await withToolTracing('recommend', { intent: 'deploy postgres' }, async (args) => {
* return await handleRecommendTool(args);
* }, { mcpClient: { name: 'claude-code', version: '1.0.0' } });
* ```
*/
async function withToolTracing(toolName, args, handler, options) {
const tracer = api_1.trace.getTracer('dot-ai-mcp');
// Create INTERNAL span for tool execution
// Using INTERNAL kind since this is business logic within the server process
const span = tracer.startSpan(`execute_tool ${toolName}`, {
kind: api_1.SpanKind.INTERNAL,
attributes: {
// GenAI semantic conventions for tool execution
'gen_ai.tool.name': toolName,
'gen_ai.tool.input': JSON.stringify(args, null, 2),
// MCP client info (if available)
...(options?.mcpClient && {
'mcp.client.name': options.mcpClient.name,
'mcp.client.version': options.mcpClient.version,
}),
},
});
// Execute handler within active span context
// This ensures any child spans (AI calls, K8s operations) become children of this span
return await api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), async () => {
const startTime = Date.now();
try {
const result = await handler(args);
const duration = Date.now() - startTime;
// Record success metrics
span.setAttributes({
'gen_ai.tool.duration_ms': duration,
'gen_ai.tool.success': true,
});
span.setStatus({ code: api_1.SpanStatusCode.OK });
// Track telemetry (fire-and-forget, async)
(0, telemetry_1.getTelemetry)().trackToolExecution(toolName, true, duration, options?.mcpClient);
return result;
}
catch (error) {
const duration = Date.now() - startTime;
const errorType = error.constructor?.name || 'Error';
// Record error details without disrupting original error flow
span.recordException(error);
span.setStatus({
code: api_1.SpanStatusCode.ERROR,
message: error.message,
});
// Track telemetry for failed execution (fire-and-forget, async)
(0, telemetry_1.getTelemetry)().trackToolExecution(toolName, false, duration, options?.mcpClient);
(0, telemetry_1.getTelemetry)().trackToolError(toolName, errorType, options?.mcpClient);
// Re-throw to preserve original error handling behavior
throw error;
}
finally {
// Always end span regardless of success/failure
span.end();
}
});
}