UNPKG

@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
"use strict"; /** * 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(); } }); }