diff --git a/.changeset/mcp-handler-tracing.md b/.changeset/mcp-handler-tracing.md new file mode 100644 index 000000000..9261417a0 --- /dev/null +++ b/.changeset/mcp-handler-tracing.md @@ -0,0 +1,5 @@ +--- +"braintrust": minor +--- + +feat: Add `mcp-handler` tracing diff --git a/e2e/config/pr-comment-scenarios.json b/e2e/config/pr-comment-scenarios.json index 5f2101f01..56638ab42 100644 --- a/e2e/config/pr-comment-scenarios.json +++ b/e2e/config/pr-comment-scenarios.json @@ -592,6 +592,17 @@ } ] }, + { + "scenarioDirName": "mcp-handler-instrumentation", + "label": "MCP Handler Instrumentation", + "metadataScenario": "mcp-handler-instrumentation", + "variants": [ + { "variantKey": "mcp-handler-v1", "label": "v1 pinned" }, + { "variantKey": "mcp-handler-v1-latest", "label": "v1 latest" }, + { "variantKey": "mcp-handler-v2", "label": "v2 pinned" }, + { "variantKey": "mcp-handler-v2-latest", "label": "v2 latest" } + ] + }, { "scenarioDirName": "mcp-instrumentation", "label": "MCP Instrumentation", diff --git a/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1-latest-no-content.span-tree.json b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1-latest-no-content.span-tree.json new file mode 100644 index 000000000..52163a434 --- /dev/null +++ b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1-latest-no-content.span-tree.json @@ -0,0 +1,337 @@ +{ + "span_tree": [ + { + "name": "mcp-handler-instrumentation-root", + "type": "task", + "children": [ + { + "name": "initialize", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.client.name": "braintrust-e2e-client", + "mcp.client.version": "1.0.0", + "mcp.method.name": "initialize", + "mcp.protocol.version": "", + "mcp.server.name": "braintrust-e2e-weather", + "mcp.server.version": "1.0.0", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [ + { + "name": "weather-lookup", + "children": [], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [ + { + "name": "weather-lookup", + "children": [], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call report_progress", + "type": "tool", + "children": [ + { + "name": "notifications/progress", + "type": "function", + "children": [], + "metadata": { + "mcp.method.name": "notifications/progress", + "mcp.notification.direction": "outgoing", + "mcp.progress.current": 1, + "mcp.progress.percentage": 100, + "mcp.progress.token": "progress", + "mcp.progress.total": 1, + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "report_progress", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/list", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/list", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "resources/read", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "resources/read", + "mcp.resource.uri": "notes://forecast", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "prompts/get forecast", + "type": "function", + "children": [], + "metadata": { + "gen_ai.prompt.name": "forecast", + "jsonrpc.request.id": "1", + "mcp.method.name": "prompts/get", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_alerts", + "type": "tool", + "children": [], + "metadata": { + "error.type": "tool_error", + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_alerts", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "error": "Tool returned an error result", + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [], + "metadata": { + "error.type": "tool_error", + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "error": "Tool returned an error result", + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "acme/missing", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "acme/missing", + "network.protocol.name": "http", + "rpc.response.status_code": "-32601", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "ping", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "ping", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ] +} diff --git a/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1-latest-no-content.span-tree.txt b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1-latest-no-content.span-tree.txt new file mode 100644 index 000000000..561a25f02 --- /dev/null +++ b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1-latest-no-content.span-tree.txt @@ -0,0 +1,272 @@ +span_tree: +├── mcp-handler-instrumentation-root [task] +│ metadata: { +│ "scenario": "mcp-handler-instrumentation", +│ "testRunId": "" +│ } +│ metrics: { +│ "end": 0, +│ "start": 0 +│ } +│ context: { +│ "caller_filename": "", +│ "caller_functionname": "", +│ "caller_lineno": 0 +│ } +│ ├── initialize [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.client.name": "braintrust-e2e-client", +│ │ "mcp.client.version": "1.0.0", +│ │ "mcp.method.name": "initialize", +│ │ "mcp.protocol.version": "", +│ │ "mcp.server.name": "braintrust-e2e-weather", +│ │ "mcp.server.version": "1.0.0", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── weather-lookup +│ │ metadata: { +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── weather-lookup +│ │ metadata: { +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call report_progress [tool] +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "report_progress", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── notifications/progress [function] +│ │ metadata: { +│ │ "mcp.method.name": "notifications/progress", +│ │ "mcp.notification.direction": "outgoing", +│ │ "mcp.progress.current": 1, +│ │ "mcp.progress.percentage": 100, +│ │ "mcp.progress.token": "progress", +│ │ "mcp.progress.total": 1, +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/list [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/list", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── resources/read [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "resources/read", +│ │ "mcp.resource.uri": "notes://forecast", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── prompts/get forecast [function] +│ │ metadata: { +│ │ "gen_ai.prompt.name": "forecast", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "prompts/get", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_alerts [tool] +│ │ metadata: { +│ │ "error.type": "tool_error", +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_alerts", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ error: "Tool returned an error result" +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ metadata: { +│ │ "error.type": "tool_error", +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ error: "Tool returned an error result" +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ └── acme/missing [function] +│ metadata: { +│ "jsonrpc.request.id": "1", +│ "mcp.method.name": "acme/missing", +│ "network.protocol.name": "http", +│ "rpc.response.status_code": "-32601", +│ "scenario": "mcp-handler-instrumentation", +│ "testRunId": "" +│ } +│ metrics: { +│ "end": 0, +│ "start": 0 +│ } +│ context: { +│ "caller_filename": "", +│ "caller_functionname": "", +│ "caller_lineno": 0 +│ } +└── ping [function] + metadata: { + "jsonrpc.request.id": "1", + "mcp.method.name": "ping", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + } + metrics: { + "end": 0, + "start": 0 + } + context: { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } diff --git a/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1-latest.span-tree.json b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1-latest.span-tree.json new file mode 100644 index 000000000..08a7d77d9 --- /dev/null +++ b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1-latest.span-tree.json @@ -0,0 +1,420 @@ +{ + "span_tree": [ + { + "name": "mcp-handler-instrumentation-root", + "type": "task", + "children": [ + { + "name": "initialize", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.client.name": "braintrust-e2e-client", + "mcp.client.version": "1.0.0", + "mcp.method.name": "initialize", + "mcp.protocol.version": "", + "mcp.server.name": "braintrust-e2e-weather", + "mcp.server.version": "1.0.0", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [ + { + "name": "weather-lookup", + "children": [], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "input": { + "city": "Vienna" + }, + "output": { + "content": [ + { + "text": "Sunny in Vienna", + "type": "text" + } + ] + }, + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [ + { + "name": "weather-lookup", + "children": [], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "input": { + "city": "Paris" + }, + "output": { + "content": [ + { + "text": "Sunny in Paris", + "type": "text" + } + ] + }, + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call report_progress", + "type": "tool", + "children": [ + { + "name": "notifications/progress", + "type": "function", + "children": [], + "output": { + "message": "done", + "progress": 1, + "progressToken": "progress", + "total": 1 + }, + "metadata": { + "mcp.method.name": "notifications/progress", + "mcp.notification.direction": "outgoing", + "mcp.progress.current": 1, + "mcp.progress.message": "done", + "mcp.progress.percentage": 100, + "mcp.progress.token": "progress", + "mcp.progress.total": 1, + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "output": { + "content": [ + { + "text": "done", + "type": "text" + } + ] + }, + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "report_progress", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/list", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/list", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "resources/read", + "type": "function", + "children": [], + "input": { + "uri": "notes://forecast" + }, + "output": { + "contents": [ + { + "text": "Bring an umbrella.", + "uri": "notes://forecast" + } + ] + }, + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "resources/read", + "mcp.resource.uri": "notes://forecast", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "prompts/get forecast", + "type": "function", + "children": [], + "input": { + "city": "Vienna" + }, + "output": { + "messages": [ + { + "content": { + "text": "Forecast for Vienna?", + "type": "text" + }, + "role": "user" + } + ] + }, + "metadata": { + "gen_ai.prompt.name": "forecast", + "jsonrpc.request.id": "1", + "mcp.method.name": "prompts/get", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_alerts", + "type": "tool", + "children": [], + "output": { + "content": [ + { + "text": "Alert service unavailable", + "type": "text" + } + ], + "isError": true + }, + "metadata": { + "error.type": "tool_error", + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_alerts", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "error": "Alert service unavailable", + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [], + "input": { + "city": 42 + }, + "output": { + "content": [ + { + "text": "MCP error -32602: Input validation error: Invalid arguments for tool get_weather: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"city\"\n ],\n \"message\": \"Invalid input: expected string, received number\"\n }\n]", + "type": "text" + } + ], + "isError": true + }, + "metadata": { + "error.type": "tool_error", + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "error": "MCP error -32602: Input validation error: Invalid arguments for tool get_weather: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"city\"\n ],\n \"message\": \"Invalid input: expected string, received number\"\n }\n]", + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "acme/missing", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "acme/missing", + "network.protocol.name": "http", + "rpc.response.status_code": "-32601", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "ping", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "ping", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ] +} diff --git a/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1-latest.span-tree.txt b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1-latest.span-tree.txt new file mode 100644 index 000000000..1db735e57 --- /dev/null +++ b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1-latest.span-tree.txt @@ -0,0 +1,355 @@ +span_tree: +├── mcp-handler-instrumentation-root [task] +│ metadata: { +│ "scenario": "mcp-handler-instrumentation", +│ "testRunId": "" +│ } +│ metrics: { +│ "end": 0, +│ "start": 0 +│ } +│ context: { +│ "caller_filename": "", +│ "caller_functionname": "", +│ "caller_lineno": 0 +│ } +│ ├── initialize [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.client.name": "braintrust-e2e-client", +│ │ "mcp.client.version": "1.0.0", +│ │ "mcp.method.name": "initialize", +│ │ "mcp.protocol.version": "", +│ │ "mcp.server.name": "braintrust-e2e-weather", +│ │ "mcp.server.version": "1.0.0", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ input: { +│ │ "city": "Vienna" +│ │ } +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "text": "Sunny in Vienna", +│ │ "type": "text" +│ │ } +│ │ ] +│ │ } +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── weather-lookup +│ │ metadata: { +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ input: { +│ │ "city": "Paris" +│ │ } +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "text": "Sunny in Paris", +│ │ "type": "text" +│ │ } +│ │ ] +│ │ } +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── weather-lookup +│ │ metadata: { +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call report_progress [tool] +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "text": "done", +│ │ "type": "text" +│ │ } +│ │ ] +│ │ } +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "report_progress", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── notifications/progress [function] +│ │ output: { +│ │ "message": "done", +│ │ "progress": 1, +│ │ "progressToken": "progress", +│ │ "total": 1 +│ │ } +│ │ metadata: { +│ │ "mcp.method.name": "notifications/progress", +│ │ "mcp.notification.direction": "outgoing", +│ │ "mcp.progress.current": 1, +│ │ "mcp.progress.message": "done", +│ │ "mcp.progress.percentage": 100, +│ │ "mcp.progress.token": "progress", +│ │ "mcp.progress.total": 1, +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/list [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/list", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── resources/read [function] +│ │ input: { +│ │ "uri": "notes://forecast" +│ │ } +│ │ output: { +│ │ "contents": [ +│ │ { +│ │ "text": "Bring an umbrella.", +│ │ "uri": "notes://forecast" +│ │ } +│ │ ] +│ │ } +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "resources/read", +│ │ "mcp.resource.uri": "notes://forecast", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── prompts/get forecast [function] +│ │ input: { +│ │ "city": "Vienna" +│ │ } +│ │ output: { +│ │ "messages": [ +│ │ { +│ │ "content": { +│ │ "text": "Forecast for Vienna?", +│ │ "type": "text" +│ │ }, +│ │ "role": "user" +│ │ } +│ │ ] +│ │ } +│ │ metadata: { +│ │ "gen_ai.prompt.name": "forecast", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "prompts/get", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_alerts [tool] +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "text": "Alert service unavailable", +│ │ "type": "text" +│ │ } +│ │ ], +│ │ "isError": true +│ │ } +│ │ metadata: { +│ │ "error.type": "tool_error", +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_alerts", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ error: "Alert service unavailable" +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ input: { +│ │ "city": 42 +│ │ } +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "text": "MCP error -32602: Input validation error: Invalid arguments for tool get_weather: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"city\"\n ],\n \"message\": \"Invalid input: expected string, received number\"\n }\n]", +│ │ "type": "text" +│ │ } +│ │ ], +│ │ "isError": true +│ │ } +│ │ metadata: { +│ │ "error.type": "tool_error", +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ error: "MCP error -32602: Input validation error: Invalid arguments for tool get_weather: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"city\"\n ],\n \"message\": \"Invalid input: expected string, received number\"\n }\n]" +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ └── acme/missing [function] +│ metadata: { +│ "jsonrpc.request.id": "1", +│ "mcp.method.name": "acme/missing", +│ "network.protocol.name": "http", +│ "rpc.response.status_code": "-32601", +│ "scenario": "mcp-handler-instrumentation", +│ "testRunId": "" +│ } +│ metrics: { +│ "end": 0, +│ "start": 0 +│ } +│ context: { +│ "caller_filename": "", +│ "caller_functionname": "", +│ "caller_lineno": 0 +│ } +└── ping [function] + metadata: { + "jsonrpc.request.id": "1", + "mcp.method.name": "ping", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + } + metrics: { + "end": 0, + "start": 0 + } + context: { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } diff --git a/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1-no-content.span-tree.json b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1-no-content.span-tree.json new file mode 100644 index 000000000..52163a434 --- /dev/null +++ b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1-no-content.span-tree.json @@ -0,0 +1,337 @@ +{ + "span_tree": [ + { + "name": "mcp-handler-instrumentation-root", + "type": "task", + "children": [ + { + "name": "initialize", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.client.name": "braintrust-e2e-client", + "mcp.client.version": "1.0.0", + "mcp.method.name": "initialize", + "mcp.protocol.version": "", + "mcp.server.name": "braintrust-e2e-weather", + "mcp.server.version": "1.0.0", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [ + { + "name": "weather-lookup", + "children": [], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [ + { + "name": "weather-lookup", + "children": [], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call report_progress", + "type": "tool", + "children": [ + { + "name": "notifications/progress", + "type": "function", + "children": [], + "metadata": { + "mcp.method.name": "notifications/progress", + "mcp.notification.direction": "outgoing", + "mcp.progress.current": 1, + "mcp.progress.percentage": 100, + "mcp.progress.token": "progress", + "mcp.progress.total": 1, + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "report_progress", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/list", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/list", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "resources/read", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "resources/read", + "mcp.resource.uri": "notes://forecast", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "prompts/get forecast", + "type": "function", + "children": [], + "metadata": { + "gen_ai.prompt.name": "forecast", + "jsonrpc.request.id": "1", + "mcp.method.name": "prompts/get", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_alerts", + "type": "tool", + "children": [], + "metadata": { + "error.type": "tool_error", + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_alerts", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "error": "Tool returned an error result", + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [], + "metadata": { + "error.type": "tool_error", + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "error": "Tool returned an error result", + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "acme/missing", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "acme/missing", + "network.protocol.name": "http", + "rpc.response.status_code": "-32601", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "ping", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "ping", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ] +} diff --git a/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1-no-content.span-tree.txt b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1-no-content.span-tree.txt new file mode 100644 index 000000000..561a25f02 --- /dev/null +++ b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1-no-content.span-tree.txt @@ -0,0 +1,272 @@ +span_tree: +├── mcp-handler-instrumentation-root [task] +│ metadata: { +│ "scenario": "mcp-handler-instrumentation", +│ "testRunId": "" +│ } +│ metrics: { +│ "end": 0, +│ "start": 0 +│ } +│ context: { +│ "caller_filename": "", +│ "caller_functionname": "", +│ "caller_lineno": 0 +│ } +│ ├── initialize [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.client.name": "braintrust-e2e-client", +│ │ "mcp.client.version": "1.0.0", +│ │ "mcp.method.name": "initialize", +│ │ "mcp.protocol.version": "", +│ │ "mcp.server.name": "braintrust-e2e-weather", +│ │ "mcp.server.version": "1.0.0", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── weather-lookup +│ │ metadata: { +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── weather-lookup +│ │ metadata: { +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call report_progress [tool] +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "report_progress", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── notifications/progress [function] +│ │ metadata: { +│ │ "mcp.method.name": "notifications/progress", +│ │ "mcp.notification.direction": "outgoing", +│ │ "mcp.progress.current": 1, +│ │ "mcp.progress.percentage": 100, +│ │ "mcp.progress.token": "progress", +│ │ "mcp.progress.total": 1, +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/list [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/list", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── resources/read [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "resources/read", +│ │ "mcp.resource.uri": "notes://forecast", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── prompts/get forecast [function] +│ │ metadata: { +│ │ "gen_ai.prompt.name": "forecast", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "prompts/get", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_alerts [tool] +│ │ metadata: { +│ │ "error.type": "tool_error", +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_alerts", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ error: "Tool returned an error result" +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ metadata: { +│ │ "error.type": "tool_error", +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ error: "Tool returned an error result" +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ └── acme/missing [function] +│ metadata: { +│ "jsonrpc.request.id": "1", +│ "mcp.method.name": "acme/missing", +│ "network.protocol.name": "http", +│ "rpc.response.status_code": "-32601", +│ "scenario": "mcp-handler-instrumentation", +│ "testRunId": "" +│ } +│ metrics: { +│ "end": 0, +│ "start": 0 +│ } +│ context: { +│ "caller_filename": "", +│ "caller_functionname": "", +│ "caller_lineno": 0 +│ } +└── ping [function] + metadata: { + "jsonrpc.request.id": "1", + "mcp.method.name": "ping", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + } + metrics: { + "end": 0, + "start": 0 + } + context: { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } diff --git a/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1.span-tree.json b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1.span-tree.json new file mode 100644 index 000000000..08a7d77d9 --- /dev/null +++ b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1.span-tree.json @@ -0,0 +1,420 @@ +{ + "span_tree": [ + { + "name": "mcp-handler-instrumentation-root", + "type": "task", + "children": [ + { + "name": "initialize", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.client.name": "braintrust-e2e-client", + "mcp.client.version": "1.0.0", + "mcp.method.name": "initialize", + "mcp.protocol.version": "", + "mcp.server.name": "braintrust-e2e-weather", + "mcp.server.version": "1.0.0", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [ + { + "name": "weather-lookup", + "children": [], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "input": { + "city": "Vienna" + }, + "output": { + "content": [ + { + "text": "Sunny in Vienna", + "type": "text" + } + ] + }, + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [ + { + "name": "weather-lookup", + "children": [], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "input": { + "city": "Paris" + }, + "output": { + "content": [ + { + "text": "Sunny in Paris", + "type": "text" + } + ] + }, + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call report_progress", + "type": "tool", + "children": [ + { + "name": "notifications/progress", + "type": "function", + "children": [], + "output": { + "message": "done", + "progress": 1, + "progressToken": "progress", + "total": 1 + }, + "metadata": { + "mcp.method.name": "notifications/progress", + "mcp.notification.direction": "outgoing", + "mcp.progress.current": 1, + "mcp.progress.message": "done", + "mcp.progress.percentage": 100, + "mcp.progress.token": "progress", + "mcp.progress.total": 1, + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "output": { + "content": [ + { + "text": "done", + "type": "text" + } + ] + }, + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "report_progress", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/list", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/list", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "resources/read", + "type": "function", + "children": [], + "input": { + "uri": "notes://forecast" + }, + "output": { + "contents": [ + { + "text": "Bring an umbrella.", + "uri": "notes://forecast" + } + ] + }, + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "resources/read", + "mcp.resource.uri": "notes://forecast", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "prompts/get forecast", + "type": "function", + "children": [], + "input": { + "city": "Vienna" + }, + "output": { + "messages": [ + { + "content": { + "text": "Forecast for Vienna?", + "type": "text" + }, + "role": "user" + } + ] + }, + "metadata": { + "gen_ai.prompt.name": "forecast", + "jsonrpc.request.id": "1", + "mcp.method.name": "prompts/get", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_alerts", + "type": "tool", + "children": [], + "output": { + "content": [ + { + "text": "Alert service unavailable", + "type": "text" + } + ], + "isError": true + }, + "metadata": { + "error.type": "tool_error", + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_alerts", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "error": "Alert service unavailable", + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [], + "input": { + "city": 42 + }, + "output": { + "content": [ + { + "text": "MCP error -32602: Input validation error: Invalid arguments for tool get_weather: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"city\"\n ],\n \"message\": \"Invalid input: expected string, received number\"\n }\n]", + "type": "text" + } + ], + "isError": true + }, + "metadata": { + "error.type": "tool_error", + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "error": "MCP error -32602: Input validation error: Invalid arguments for tool get_weather: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"city\"\n ],\n \"message\": \"Invalid input: expected string, received number\"\n }\n]", + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "acme/missing", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "acme/missing", + "network.protocol.name": "http", + "rpc.response.status_code": "-32601", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "ping", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "ping", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ] +} diff --git a/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1.span-tree.txt b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1.span-tree.txt new file mode 100644 index 000000000..1db735e57 --- /dev/null +++ b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v1.span-tree.txt @@ -0,0 +1,355 @@ +span_tree: +├── mcp-handler-instrumentation-root [task] +│ metadata: { +│ "scenario": "mcp-handler-instrumentation", +│ "testRunId": "" +│ } +│ metrics: { +│ "end": 0, +│ "start": 0 +│ } +│ context: { +│ "caller_filename": "", +│ "caller_functionname": "", +│ "caller_lineno": 0 +│ } +│ ├── initialize [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.client.name": "braintrust-e2e-client", +│ │ "mcp.client.version": "1.0.0", +│ │ "mcp.method.name": "initialize", +│ │ "mcp.protocol.version": "", +│ │ "mcp.server.name": "braintrust-e2e-weather", +│ │ "mcp.server.version": "1.0.0", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ input: { +│ │ "city": "Vienna" +│ │ } +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "text": "Sunny in Vienna", +│ │ "type": "text" +│ │ } +│ │ ] +│ │ } +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── weather-lookup +│ │ metadata: { +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ input: { +│ │ "city": "Paris" +│ │ } +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "text": "Sunny in Paris", +│ │ "type": "text" +│ │ } +│ │ ] +│ │ } +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── weather-lookup +│ │ metadata: { +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call report_progress [tool] +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "text": "done", +│ │ "type": "text" +│ │ } +│ │ ] +│ │ } +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "report_progress", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── notifications/progress [function] +│ │ output: { +│ │ "message": "done", +│ │ "progress": 1, +│ │ "progressToken": "progress", +│ │ "total": 1 +│ │ } +│ │ metadata: { +│ │ "mcp.method.name": "notifications/progress", +│ │ "mcp.notification.direction": "outgoing", +│ │ "mcp.progress.current": 1, +│ │ "mcp.progress.message": "done", +│ │ "mcp.progress.percentage": 100, +│ │ "mcp.progress.token": "progress", +│ │ "mcp.progress.total": 1, +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/list [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/list", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── resources/read [function] +│ │ input: { +│ │ "uri": "notes://forecast" +│ │ } +│ │ output: { +│ │ "contents": [ +│ │ { +│ │ "text": "Bring an umbrella.", +│ │ "uri": "notes://forecast" +│ │ } +│ │ ] +│ │ } +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "resources/read", +│ │ "mcp.resource.uri": "notes://forecast", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── prompts/get forecast [function] +│ │ input: { +│ │ "city": "Vienna" +│ │ } +│ │ output: { +│ │ "messages": [ +│ │ { +│ │ "content": { +│ │ "text": "Forecast for Vienna?", +│ │ "type": "text" +│ │ }, +│ │ "role": "user" +│ │ } +│ │ ] +│ │ } +│ │ metadata: { +│ │ "gen_ai.prompt.name": "forecast", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "prompts/get", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_alerts [tool] +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "text": "Alert service unavailable", +│ │ "type": "text" +│ │ } +│ │ ], +│ │ "isError": true +│ │ } +│ │ metadata: { +│ │ "error.type": "tool_error", +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_alerts", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ error: "Alert service unavailable" +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ input: { +│ │ "city": 42 +│ │ } +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "text": "MCP error -32602: Input validation error: Invalid arguments for tool get_weather: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"city\"\n ],\n \"message\": \"Invalid input: expected string, received number\"\n }\n]", +│ │ "type": "text" +│ │ } +│ │ ], +│ │ "isError": true +│ │ } +│ │ metadata: { +│ │ "error.type": "tool_error", +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ error: "MCP error -32602: Input validation error: Invalid arguments for tool get_weather: [\n {\n \"expected\": \"string\",\n \"code\": \"invalid_type\",\n \"path\": [\n \"city\"\n ],\n \"message\": \"Invalid input: expected string, received number\"\n }\n]" +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ └── acme/missing [function] +│ metadata: { +│ "jsonrpc.request.id": "1", +│ "mcp.method.name": "acme/missing", +│ "network.protocol.name": "http", +│ "rpc.response.status_code": "-32601", +│ "scenario": "mcp-handler-instrumentation", +│ "testRunId": "" +│ } +│ metrics: { +│ "end": 0, +│ "start": 0 +│ } +│ context: { +│ "caller_filename": "", +│ "caller_functionname": "", +│ "caller_lineno": 0 +│ } +└── ping [function] + metadata: { + "jsonrpc.request.id": "1", + "mcp.method.name": "ping", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + } + metrics: { + "end": 0, + "start": 0 + } + context: { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } diff --git a/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2-latest-no-content.span-tree.json b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2-latest-no-content.span-tree.json new file mode 100644 index 000000000..54841c65f --- /dev/null +++ b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2-latest-no-content.span-tree.json @@ -0,0 +1,383 @@ +{ + "span_tree": [ + { + "name": "mcp-handler-instrumentation-root", + "type": "task", + "children": [ + { + "name": "initialize", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.client.name": "braintrust-e2e-client", + "mcp.client.version": "1.0.0", + "mcp.method.name": "initialize", + "mcp.protocol.version": "", + "mcp.server.name": "braintrust-e2e-weather", + "mcp.server.version": "1.0.0", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [ + { + "name": "weather-lookup", + "children": [], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [ + { + "name": "weather-lookup", + "children": [], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call report_progress", + "type": "tool", + "children": [ + { + "name": "notifications/progress", + "type": "function", + "children": [], + "metadata": { + "mcp.method.name": "notifications/progress", + "mcp.notification.direction": "outgoing", + "mcp.progress.current": 1, + "mcp.progress.percentage": 100, + "mcp.progress.token": "progress", + "mcp.progress.total": 1, + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "report_progress", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/list", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/list", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "resources/read", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "resources/read", + "mcp.resource.uri": "notes://forecast", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "prompts/get forecast", + "type": "function", + "children": [], + "metadata": { + "gen_ai.prompt.name": "forecast", + "jsonrpc.request.id": "1", + "mcp.method.name": "prompts/get", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_alerts", + "type": "tool", + "children": [], + "metadata": { + "error.type": "tool_error", + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_alerts", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "error": "Tool returned an error result", + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [], + "metadata": { + "error.type": "tool_error", + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "error": "Tool returned an error result", + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "acme/missing", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "acme/missing", + "network.protocol.name": "http", + "rpc.response.status_code": "-32601", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [ + { + "name": "weather-lookup", + "children": [], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.client.name": "braintrust-e2e-client", + "mcp.client.version": "1.0.0", + "mcp.method.name": "tools/call", + "mcp.protocol.version": "", + "mcp.server.name": "braintrust-e2e-weather", + "mcp.server.version": "1.0.0", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "ping", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "ping", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ] +} diff --git a/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2-latest-no-content.span-tree.txt b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2-latest-no-content.span-tree.txt new file mode 100644 index 000000000..bf67983fd --- /dev/null +++ b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2-latest-no-content.span-tree.txt @@ -0,0 +1,310 @@ +span_tree: +├── mcp-handler-instrumentation-root [task] +│ metadata: { +│ "scenario": "mcp-handler-instrumentation", +│ "testRunId": "" +│ } +│ metrics: { +│ "end": 0, +│ "start": 0 +│ } +│ context: { +│ "caller_filename": "", +│ "caller_functionname": "", +│ "caller_lineno": 0 +│ } +│ ├── initialize [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.client.name": "braintrust-e2e-client", +│ │ "mcp.client.version": "1.0.0", +│ │ "mcp.method.name": "initialize", +│ │ "mcp.protocol.version": "", +│ │ "mcp.server.name": "braintrust-e2e-weather", +│ │ "mcp.server.version": "1.0.0", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── weather-lookup +│ │ metadata: { +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── weather-lookup +│ │ metadata: { +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call report_progress [tool] +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "report_progress", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── notifications/progress [function] +│ │ metadata: { +│ │ "mcp.method.name": "notifications/progress", +│ │ "mcp.notification.direction": "outgoing", +│ │ "mcp.progress.current": 1, +│ │ "mcp.progress.percentage": 100, +│ │ "mcp.progress.token": "progress", +│ │ "mcp.progress.total": 1, +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/list [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/list", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── resources/read [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "resources/read", +│ │ "mcp.resource.uri": "notes://forecast", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── prompts/get forecast [function] +│ │ metadata: { +│ │ "gen_ai.prompt.name": "forecast", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "prompts/get", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_alerts [tool] +│ │ metadata: { +│ │ "error.type": "tool_error", +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_alerts", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ error: "Tool returned an error result" +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ metadata: { +│ │ "error.type": "tool_error", +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ error: "Tool returned an error result" +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── acme/missing [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "acme/missing", +│ │ "network.protocol.name": "http", +│ │ "rpc.response.status_code": "-32601", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ └── tools/call get_weather [tool] +│ metadata: { +│ "gen_ai.operation.name": "execute_tool", +│ "gen_ai.tool.name": "get_weather", +│ "jsonrpc.request.id": "1", +│ "mcp.client.name": "braintrust-e2e-client", +│ "mcp.client.version": "1.0.0", +│ "mcp.method.name": "tools/call", +│ "mcp.protocol.version": "", +│ "mcp.server.name": "braintrust-e2e-weather", +│ "mcp.server.version": "1.0.0", +│ "network.protocol.name": "http", +│ "scenario": "mcp-handler-instrumentation", +│ "testRunId": "" +│ } +│ metrics: { +│ "end": 0, +│ "start": 0 +│ } +│ context: { +│ "caller_filename": "", +│ "caller_functionname": "", +│ "caller_lineno": 0 +│ } +│ └── weather-lookup +│ metadata: { +│ "scenario": "mcp-handler-instrumentation", +│ "testRunId": "" +│ } +│ metrics: { +│ "end": 0, +│ "start": 0 +│ } +│ context: { +│ "caller_filename": "", +│ "caller_functionname": "", +│ "caller_lineno": 0 +│ } +└── ping [function] + metadata: { + "jsonrpc.request.id": "1", + "mcp.method.name": "ping", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + } + metrics: { + "end": 0, + "start": 0 + } + context: { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } diff --git a/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2-latest.span-tree.json b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2-latest.span-tree.json new file mode 100644 index 000000000..7adf6354a --- /dev/null +++ b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2-latest.span-tree.json @@ -0,0 +1,478 @@ +{ + "span_tree": [ + { + "name": "mcp-handler-instrumentation-root", + "type": "task", + "children": [ + { + "name": "initialize", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.client.name": "braintrust-e2e-client", + "mcp.client.version": "1.0.0", + "mcp.method.name": "initialize", + "mcp.protocol.version": "", + "mcp.server.name": "braintrust-e2e-weather", + "mcp.server.version": "1.0.0", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [ + { + "name": "weather-lookup", + "children": [], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "input": { + "city": "Vienna" + }, + "output": { + "content": [ + { + "text": "Sunny in Vienna", + "type": "text" + } + ] + }, + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [ + { + "name": "weather-lookup", + "children": [], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "input": { + "city": "Paris" + }, + "output": { + "content": [ + { + "text": "Sunny in Paris", + "type": "text" + } + ] + }, + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call report_progress", + "type": "tool", + "children": [ + { + "name": "notifications/progress", + "type": "function", + "children": [], + "output": { + "message": "done", + "progress": 1, + "progressToken": "progress", + "total": 1 + }, + "metadata": { + "mcp.method.name": "notifications/progress", + "mcp.notification.direction": "outgoing", + "mcp.progress.current": 1, + "mcp.progress.message": "done", + "mcp.progress.percentage": 100, + "mcp.progress.token": "progress", + "mcp.progress.total": 1, + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "output": { + "content": [ + { + "text": "done", + "type": "text" + } + ] + }, + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "report_progress", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/list", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/list", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "resources/read", + "type": "function", + "children": [], + "input": { + "uri": "notes://forecast" + }, + "output": { + "contents": [ + { + "text": "Bring an umbrella.", + "uri": "notes://forecast" + } + ] + }, + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "resources/read", + "mcp.resource.uri": "notes://forecast", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "prompts/get forecast", + "type": "function", + "children": [], + "input": { + "city": "Vienna" + }, + "output": { + "messages": [ + { + "content": { + "text": "Forecast for Vienna?", + "type": "text" + }, + "role": "user" + } + ] + }, + "metadata": { + "gen_ai.prompt.name": "forecast", + "jsonrpc.request.id": "1", + "mcp.method.name": "prompts/get", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_alerts", + "type": "tool", + "children": [], + "output": { + "content": [ + { + "text": "Alert service unavailable", + "type": "text" + } + ], + "isError": true + }, + "metadata": { + "error.type": "tool_error", + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_alerts", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "error": "Alert service unavailable", + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [], + "input": { + "city": 42 + }, + "output": { + "content": [ + { + "text": "Input validation error: Invalid arguments for tool get_weather: city: Invalid input: expected string, received number", + "type": "text" + } + ], + "isError": true + }, + "metadata": { + "error.type": "tool_error", + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "error": "Input validation error: Invalid arguments for tool get_weather: city: Invalid input: expected string, received number", + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "acme/missing", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "acme/missing", + "network.protocol.name": "http", + "rpc.response.status_code": "-32601", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [ + { + "name": "weather-lookup", + "children": [], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "input": { + "city": "Tokyo" + }, + "output": { + "content": [ + { + "text": "Sunny in Tokyo", + "type": "text" + } + ], + "resultType": "complete" + }, + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.client.name": "braintrust-e2e-client", + "mcp.client.version": "1.0.0", + "mcp.method.name": "tools/call", + "mcp.protocol.version": "", + "mcp.server.name": "braintrust-e2e-weather", + "mcp.server.version": "1.0.0", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "ping", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "ping", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ] +} diff --git a/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2-latest.span-tree.txt b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2-latest.span-tree.txt new file mode 100644 index 000000000..1a6d2ed9b --- /dev/null +++ b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2-latest.span-tree.txt @@ -0,0 +1,405 @@ +span_tree: +├── mcp-handler-instrumentation-root [task] +│ metadata: { +│ "scenario": "mcp-handler-instrumentation", +│ "testRunId": "" +│ } +│ metrics: { +│ "end": 0, +│ "start": 0 +│ } +│ context: { +│ "caller_filename": "", +│ "caller_functionname": "", +│ "caller_lineno": 0 +│ } +│ ├── initialize [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.client.name": "braintrust-e2e-client", +│ │ "mcp.client.version": "1.0.0", +│ │ "mcp.method.name": "initialize", +│ │ "mcp.protocol.version": "", +│ │ "mcp.server.name": "braintrust-e2e-weather", +│ │ "mcp.server.version": "1.0.0", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ input: { +│ │ "city": "Vienna" +│ │ } +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "text": "Sunny in Vienna", +│ │ "type": "text" +│ │ } +│ │ ] +│ │ } +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── weather-lookup +│ │ metadata: { +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ input: { +│ │ "city": "Paris" +│ │ } +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "text": "Sunny in Paris", +│ │ "type": "text" +│ │ } +│ │ ] +│ │ } +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── weather-lookup +│ │ metadata: { +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call report_progress [tool] +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "text": "done", +│ │ "type": "text" +│ │ } +│ │ ] +│ │ } +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "report_progress", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── notifications/progress [function] +│ │ output: { +│ │ "message": "done", +│ │ "progress": 1, +│ │ "progressToken": "progress", +│ │ "total": 1 +│ │ } +│ │ metadata: { +│ │ "mcp.method.name": "notifications/progress", +│ │ "mcp.notification.direction": "outgoing", +│ │ "mcp.progress.current": 1, +│ │ "mcp.progress.message": "done", +│ │ "mcp.progress.percentage": 100, +│ │ "mcp.progress.token": "progress", +│ │ "mcp.progress.total": 1, +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/list [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/list", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── resources/read [function] +│ │ input: { +│ │ "uri": "notes://forecast" +│ │ } +│ │ output: { +│ │ "contents": [ +│ │ { +│ │ "text": "Bring an umbrella.", +│ │ "uri": "notes://forecast" +│ │ } +│ │ ] +│ │ } +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "resources/read", +│ │ "mcp.resource.uri": "notes://forecast", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── prompts/get forecast [function] +│ │ input: { +│ │ "city": "Vienna" +│ │ } +│ │ output: { +│ │ "messages": [ +│ │ { +│ │ "content": { +│ │ "text": "Forecast for Vienna?", +│ │ "type": "text" +│ │ }, +│ │ "role": "user" +│ │ } +│ │ ] +│ │ } +│ │ metadata: { +│ │ "gen_ai.prompt.name": "forecast", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "prompts/get", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_alerts [tool] +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "text": "Alert service unavailable", +│ │ "type": "text" +│ │ } +│ │ ], +│ │ "isError": true +│ │ } +│ │ metadata: { +│ │ "error.type": "tool_error", +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_alerts", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ error: "Alert service unavailable" +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ input: { +│ │ "city": 42 +│ │ } +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "text": "Input validation error: Invalid arguments for tool get_weather: city: Invalid input: expected string, received number", +│ │ "type": "text" +│ │ } +│ │ ], +│ │ "isError": true +│ │ } +│ │ metadata: { +│ │ "error.type": "tool_error", +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ error: "Input validation error: Invalid arguments for tool get_weather: city: Invalid input: expected string, received number" +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── acme/missing [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "acme/missing", +│ │ "network.protocol.name": "http", +│ │ "rpc.response.status_code": "-32601", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ └── tools/call get_weather [tool] +│ input: { +│ "city": "Tokyo" +│ } +│ output: { +│ "content": [ +│ { +│ "text": "Sunny in Tokyo", +│ "type": "text" +│ } +│ ], +│ "resultType": "complete" +│ } +│ metadata: { +│ "gen_ai.operation.name": "execute_tool", +│ "gen_ai.tool.name": "get_weather", +│ "jsonrpc.request.id": "1", +│ "mcp.client.name": "braintrust-e2e-client", +│ "mcp.client.version": "1.0.0", +│ "mcp.method.name": "tools/call", +│ "mcp.protocol.version": "", +│ "mcp.server.name": "braintrust-e2e-weather", +│ "mcp.server.version": "1.0.0", +│ "network.protocol.name": "http", +│ "scenario": "mcp-handler-instrumentation", +│ "testRunId": "" +│ } +│ metrics: { +│ "end": 0, +│ "start": 0 +│ } +│ context: { +│ "caller_filename": "", +│ "caller_functionname": "", +│ "caller_lineno": 0 +│ } +│ └── weather-lookup +│ metadata: { +│ "scenario": "mcp-handler-instrumentation", +│ "testRunId": "" +│ } +│ metrics: { +│ "end": 0, +│ "start": 0 +│ } +│ context: { +│ "caller_filename": "", +│ "caller_functionname": "", +│ "caller_lineno": 0 +│ } +└── ping [function] + metadata: { + "jsonrpc.request.id": "1", + "mcp.method.name": "ping", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + } + metrics: { + "end": 0, + "start": 0 + } + context: { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } diff --git a/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2-no-content.span-tree.json b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2-no-content.span-tree.json new file mode 100644 index 000000000..54841c65f --- /dev/null +++ b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2-no-content.span-tree.json @@ -0,0 +1,383 @@ +{ + "span_tree": [ + { + "name": "mcp-handler-instrumentation-root", + "type": "task", + "children": [ + { + "name": "initialize", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.client.name": "braintrust-e2e-client", + "mcp.client.version": "1.0.0", + "mcp.method.name": "initialize", + "mcp.protocol.version": "", + "mcp.server.name": "braintrust-e2e-weather", + "mcp.server.version": "1.0.0", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [ + { + "name": "weather-lookup", + "children": [], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [ + { + "name": "weather-lookup", + "children": [], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call report_progress", + "type": "tool", + "children": [ + { + "name": "notifications/progress", + "type": "function", + "children": [], + "metadata": { + "mcp.method.name": "notifications/progress", + "mcp.notification.direction": "outgoing", + "mcp.progress.current": 1, + "mcp.progress.percentage": 100, + "mcp.progress.token": "progress", + "mcp.progress.total": 1, + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "report_progress", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/list", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/list", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "resources/read", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "resources/read", + "mcp.resource.uri": "notes://forecast", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "prompts/get forecast", + "type": "function", + "children": [], + "metadata": { + "gen_ai.prompt.name": "forecast", + "jsonrpc.request.id": "1", + "mcp.method.name": "prompts/get", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_alerts", + "type": "tool", + "children": [], + "metadata": { + "error.type": "tool_error", + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_alerts", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "error": "Tool returned an error result", + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [], + "metadata": { + "error.type": "tool_error", + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "error": "Tool returned an error result", + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "acme/missing", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "acme/missing", + "network.protocol.name": "http", + "rpc.response.status_code": "-32601", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [ + { + "name": "weather-lookup", + "children": [], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.client.name": "braintrust-e2e-client", + "mcp.client.version": "1.0.0", + "mcp.method.name": "tools/call", + "mcp.protocol.version": "", + "mcp.server.name": "braintrust-e2e-weather", + "mcp.server.version": "1.0.0", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "ping", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "ping", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ] +} diff --git a/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2-no-content.span-tree.txt b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2-no-content.span-tree.txt new file mode 100644 index 000000000..bf67983fd --- /dev/null +++ b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2-no-content.span-tree.txt @@ -0,0 +1,310 @@ +span_tree: +├── mcp-handler-instrumentation-root [task] +│ metadata: { +│ "scenario": "mcp-handler-instrumentation", +│ "testRunId": "" +│ } +│ metrics: { +│ "end": 0, +│ "start": 0 +│ } +│ context: { +│ "caller_filename": "", +│ "caller_functionname": "", +│ "caller_lineno": 0 +│ } +│ ├── initialize [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.client.name": "braintrust-e2e-client", +│ │ "mcp.client.version": "1.0.0", +│ │ "mcp.method.name": "initialize", +│ │ "mcp.protocol.version": "", +│ │ "mcp.server.name": "braintrust-e2e-weather", +│ │ "mcp.server.version": "1.0.0", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── weather-lookup +│ │ metadata: { +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── weather-lookup +│ │ metadata: { +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call report_progress [tool] +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "report_progress", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── notifications/progress [function] +│ │ metadata: { +│ │ "mcp.method.name": "notifications/progress", +│ │ "mcp.notification.direction": "outgoing", +│ │ "mcp.progress.current": 1, +│ │ "mcp.progress.percentage": 100, +│ │ "mcp.progress.token": "progress", +│ │ "mcp.progress.total": 1, +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/list [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/list", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── resources/read [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "resources/read", +│ │ "mcp.resource.uri": "notes://forecast", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── prompts/get forecast [function] +│ │ metadata: { +│ │ "gen_ai.prompt.name": "forecast", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "prompts/get", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_alerts [tool] +│ │ metadata: { +│ │ "error.type": "tool_error", +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_alerts", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ error: "Tool returned an error result" +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ metadata: { +│ │ "error.type": "tool_error", +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ error: "Tool returned an error result" +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── acme/missing [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "acme/missing", +│ │ "network.protocol.name": "http", +│ │ "rpc.response.status_code": "-32601", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ └── tools/call get_weather [tool] +│ metadata: { +│ "gen_ai.operation.name": "execute_tool", +│ "gen_ai.tool.name": "get_weather", +│ "jsonrpc.request.id": "1", +│ "mcp.client.name": "braintrust-e2e-client", +│ "mcp.client.version": "1.0.0", +│ "mcp.method.name": "tools/call", +│ "mcp.protocol.version": "", +│ "mcp.server.name": "braintrust-e2e-weather", +│ "mcp.server.version": "1.0.0", +│ "network.protocol.name": "http", +│ "scenario": "mcp-handler-instrumentation", +│ "testRunId": "" +│ } +│ metrics: { +│ "end": 0, +│ "start": 0 +│ } +│ context: { +│ "caller_filename": "", +│ "caller_functionname": "", +│ "caller_lineno": 0 +│ } +│ └── weather-lookup +│ metadata: { +│ "scenario": "mcp-handler-instrumentation", +│ "testRunId": "" +│ } +│ metrics: { +│ "end": 0, +│ "start": 0 +│ } +│ context: { +│ "caller_filename": "", +│ "caller_functionname": "", +│ "caller_lineno": 0 +│ } +└── ping [function] + metadata: { + "jsonrpc.request.id": "1", + "mcp.method.name": "ping", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + } + metrics: { + "end": 0, + "start": 0 + } + context: { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } diff --git a/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2.span-tree.json b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2.span-tree.json new file mode 100644 index 000000000..7adf6354a --- /dev/null +++ b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2.span-tree.json @@ -0,0 +1,478 @@ +{ + "span_tree": [ + { + "name": "mcp-handler-instrumentation-root", + "type": "task", + "children": [ + { + "name": "initialize", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.client.name": "braintrust-e2e-client", + "mcp.client.version": "1.0.0", + "mcp.method.name": "initialize", + "mcp.protocol.version": "", + "mcp.server.name": "braintrust-e2e-weather", + "mcp.server.version": "1.0.0", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [ + { + "name": "weather-lookup", + "children": [], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "input": { + "city": "Vienna" + }, + "output": { + "content": [ + { + "text": "Sunny in Vienna", + "type": "text" + } + ] + }, + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [ + { + "name": "weather-lookup", + "children": [], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "input": { + "city": "Paris" + }, + "output": { + "content": [ + { + "text": "Sunny in Paris", + "type": "text" + } + ] + }, + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call report_progress", + "type": "tool", + "children": [ + { + "name": "notifications/progress", + "type": "function", + "children": [], + "output": { + "message": "done", + "progress": 1, + "progressToken": "progress", + "total": 1 + }, + "metadata": { + "mcp.method.name": "notifications/progress", + "mcp.notification.direction": "outgoing", + "mcp.progress.current": 1, + "mcp.progress.message": "done", + "mcp.progress.percentage": 100, + "mcp.progress.token": "progress", + "mcp.progress.total": 1, + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "output": { + "content": [ + { + "text": "done", + "type": "text" + } + ] + }, + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "report_progress", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/list", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/list", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "resources/read", + "type": "function", + "children": [], + "input": { + "uri": "notes://forecast" + }, + "output": { + "contents": [ + { + "text": "Bring an umbrella.", + "uri": "notes://forecast" + } + ] + }, + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "resources/read", + "mcp.resource.uri": "notes://forecast", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "prompts/get forecast", + "type": "function", + "children": [], + "input": { + "city": "Vienna" + }, + "output": { + "messages": [ + { + "content": { + "text": "Forecast for Vienna?", + "type": "text" + }, + "role": "user" + } + ] + }, + "metadata": { + "gen_ai.prompt.name": "forecast", + "jsonrpc.request.id": "1", + "mcp.method.name": "prompts/get", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_alerts", + "type": "tool", + "children": [], + "output": { + "content": [ + { + "text": "Alert service unavailable", + "type": "text" + } + ], + "isError": true + }, + "metadata": { + "error.type": "tool_error", + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_alerts", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "error": "Alert service unavailable", + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [], + "input": { + "city": 42 + }, + "output": { + "content": [ + { + "text": "Input validation error: Invalid arguments for tool get_weather: city: Invalid input: expected string, received number", + "type": "text" + } + ], + "isError": true + }, + "metadata": { + "error.type": "tool_error", + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.method.name": "tools/call", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "error": "Input validation error: Invalid arguments for tool get_weather: city: Invalid input: expected string, received number", + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "acme/missing", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "acme/missing", + "network.protocol.name": "http", + "rpc.response.status_code": "-32601", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "tools/call get_weather", + "type": "tool", + "children": [ + { + "name": "weather-lookup", + "children": [], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "input": { + "city": "Tokyo" + }, + "output": { + "content": [ + { + "text": "Sunny in Tokyo", + "type": "text" + } + ], + "resultType": "complete" + }, + "metadata": { + "gen_ai.operation.name": "execute_tool", + "gen_ai.tool.name": "get_weather", + "jsonrpc.request.id": "1", + "mcp.client.name": "braintrust-e2e-client", + "mcp.client.version": "1.0.0", + "mcp.method.name": "tools/call", + "mcp.protocol.version": "", + "mcp.server.name": "braintrust-e2e-weather", + "mcp.server.version": "1.0.0", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ], + "metadata": { + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + }, + { + "name": "ping", + "type": "function", + "children": [], + "metadata": { + "jsonrpc.request.id": "1", + "mcp.method.name": "ping", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + }, + "metrics": { + "end": 0, + "start": 0 + }, + "context": { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } + } + ] +} diff --git a/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2.span-tree.txt b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2.span-tree.txt new file mode 100644 index 000000000..1a6d2ed9b --- /dev/null +++ b/e2e/scenarios/mcp-handler-instrumentation/__snapshots__/mcp-handler-v2.span-tree.txt @@ -0,0 +1,405 @@ +span_tree: +├── mcp-handler-instrumentation-root [task] +│ metadata: { +│ "scenario": "mcp-handler-instrumentation", +│ "testRunId": "" +│ } +│ metrics: { +│ "end": 0, +│ "start": 0 +│ } +│ context: { +│ "caller_filename": "", +│ "caller_functionname": "", +│ "caller_lineno": 0 +│ } +│ ├── initialize [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.client.name": "braintrust-e2e-client", +│ │ "mcp.client.version": "1.0.0", +│ │ "mcp.method.name": "initialize", +│ │ "mcp.protocol.version": "", +│ │ "mcp.server.name": "braintrust-e2e-weather", +│ │ "mcp.server.version": "1.0.0", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ input: { +│ │ "city": "Vienna" +│ │ } +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "text": "Sunny in Vienna", +│ │ "type": "text" +│ │ } +│ │ ] +│ │ } +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── weather-lookup +│ │ metadata: { +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ input: { +│ │ "city": "Paris" +│ │ } +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "text": "Sunny in Paris", +│ │ "type": "text" +│ │ } +│ │ ] +│ │ } +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── weather-lookup +│ │ metadata: { +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call report_progress [tool] +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "text": "done", +│ │ "type": "text" +│ │ } +│ │ ] +│ │ } +│ │ metadata: { +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "report_progress", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ │ └── notifications/progress [function] +│ │ output: { +│ │ "message": "done", +│ │ "progress": 1, +│ │ "progressToken": "progress", +│ │ "total": 1 +│ │ } +│ │ metadata: { +│ │ "mcp.method.name": "notifications/progress", +│ │ "mcp.notification.direction": "outgoing", +│ │ "mcp.progress.current": 1, +│ │ "mcp.progress.message": "done", +│ │ "mcp.progress.percentage": 100, +│ │ "mcp.progress.token": "progress", +│ │ "mcp.progress.total": 1, +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/list [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/list", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── resources/read [function] +│ │ input: { +│ │ "uri": "notes://forecast" +│ │ } +│ │ output: { +│ │ "contents": [ +│ │ { +│ │ "text": "Bring an umbrella.", +│ │ "uri": "notes://forecast" +│ │ } +│ │ ] +│ │ } +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "resources/read", +│ │ "mcp.resource.uri": "notes://forecast", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── prompts/get forecast [function] +│ │ input: { +│ │ "city": "Vienna" +│ │ } +│ │ output: { +│ │ "messages": [ +│ │ { +│ │ "content": { +│ │ "text": "Forecast for Vienna?", +│ │ "type": "text" +│ │ }, +│ │ "role": "user" +│ │ } +│ │ ] +│ │ } +│ │ metadata: { +│ │ "gen_ai.prompt.name": "forecast", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "prompts/get", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_alerts [tool] +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "text": "Alert service unavailable", +│ │ "type": "text" +│ │ } +│ │ ], +│ │ "isError": true +│ │ } +│ │ metadata: { +│ │ "error.type": "tool_error", +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_alerts", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ error: "Alert service unavailable" +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── tools/call get_weather [tool] +│ │ input: { +│ │ "city": 42 +│ │ } +│ │ output: { +│ │ "content": [ +│ │ { +│ │ "text": "Input validation error: Invalid arguments for tool get_weather: city: Invalid input: expected string, received number", +│ │ "type": "text" +│ │ } +│ │ ], +│ │ "isError": true +│ │ } +│ │ metadata: { +│ │ "error.type": "tool_error", +│ │ "gen_ai.operation.name": "execute_tool", +│ │ "gen_ai.tool.name": "get_weather", +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "tools/call", +│ │ "network.protocol.name": "http", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ error: "Input validation error: Invalid arguments for tool get_weather: city: Invalid input: expected string, received number" +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ ├── acme/missing [function] +│ │ metadata: { +│ │ "jsonrpc.request.id": "1", +│ │ "mcp.method.name": "acme/missing", +│ │ "network.protocol.name": "http", +│ │ "rpc.response.status_code": "-32601", +│ │ "scenario": "mcp-handler-instrumentation", +│ │ "testRunId": "" +│ │ } +│ │ metrics: { +│ │ "end": 0, +│ │ "start": 0 +│ │ } +│ │ context: { +│ │ "caller_filename": "", +│ │ "caller_functionname": "", +│ │ "caller_lineno": 0 +│ │ } +│ └── tools/call get_weather [tool] +│ input: { +│ "city": "Tokyo" +│ } +│ output: { +│ "content": [ +│ { +│ "text": "Sunny in Tokyo", +│ "type": "text" +│ } +│ ], +│ "resultType": "complete" +│ } +│ metadata: { +│ "gen_ai.operation.name": "execute_tool", +│ "gen_ai.tool.name": "get_weather", +│ "jsonrpc.request.id": "1", +│ "mcp.client.name": "braintrust-e2e-client", +│ "mcp.client.version": "1.0.0", +│ "mcp.method.name": "tools/call", +│ "mcp.protocol.version": "", +│ "mcp.server.name": "braintrust-e2e-weather", +│ "mcp.server.version": "1.0.0", +│ "network.protocol.name": "http", +│ "scenario": "mcp-handler-instrumentation", +│ "testRunId": "" +│ } +│ metrics: { +│ "end": 0, +│ "start": 0 +│ } +│ context: { +│ "caller_filename": "", +│ "caller_functionname": "", +│ "caller_lineno": 0 +│ } +│ └── weather-lookup +│ metadata: { +│ "scenario": "mcp-handler-instrumentation", +│ "testRunId": "" +│ } +│ metrics: { +│ "end": 0, +│ "start": 0 +│ } +│ context: { +│ "caller_filename": "", +│ "caller_functionname": "", +│ "caller_lineno": 0 +│ } +└── ping [function] + metadata: { + "jsonrpc.request.id": "1", + "mcp.method.name": "ping", + "network.protocol.name": "http", + "scenario": "mcp-handler-instrumentation", + "testRunId": "" + } + metrics: { + "end": 0, + "start": 0 + } + context: { + "caller_filename": "", + "caller_functionname": "", + "caller_lineno": 0 + } diff --git a/e2e/scenarios/mcp-handler-instrumentation/configure.mjs b/e2e/scenarios/mcp-handler-instrumentation/configure.mjs new file mode 100644 index 000000000..8702c5df5 --- /dev/null +++ b/e2e/scenarios/mcp-handler-instrumentation/configure.mjs @@ -0,0 +1,23 @@ +import { configureInstrumentation } from "braintrust/instrumentation"; + +configureInstrumentation({ + integrations: { + mcp: process.env.MCP_ENABLED === "1", + mcpHandler: + process.env.MCP_HANDLER_DISABLED === "1" + ? false + : { captureContent: process.env.MCP_CAPTURE_CONTENT === "1" }, + }, + spanCustomizers: [ + { + onSpanExport(data) { + data.metadata = { + ...data.metadata, + scenario: "mcp-handler-instrumentation", + testRunId: process.env.BRAINTRUST_E2E_RUN_ID, + }; + return data; + }, + }, + ], +}); diff --git a/e2e/scenarios/mcp-handler-instrumentation/package.json b/e2e/scenarios/mcp-handler-instrumentation/package.json new file mode 100644 index 000000000..9aff0b4bf --- /dev/null +++ b/e2e/scenarios/mcp-handler-instrumentation/package.json @@ -0,0 +1,27 @@ +{ + "name": "@braintrust/e2e-mcp-handler-instrumentation", + "private": true, + "braintrustScenario": { + "bump": { + "dependencies": { + "mcp-handler-v1-latest": { + "package": "mcp-handler", + "range": "1" + }, + "mcp-handler-v2-latest": { + "package": "mcp-handler", + "range": "2" + } + } + } + }, + "dependencies": { + "@modelcontextprotocol/sdk": "1.26.0", + "@modelcontextprotocol/server": "2.4.0", + "mcp-handler-v1": "npm:mcp-handler@1.0.5", + "mcp-handler-v1-latest": "npm:mcp-handler@1.1.0", + "mcp-handler-v2": "npm:mcp-handler@2.0.0", + "mcp-handler-v2-latest": "npm:mcp-handler@2.3.0", + "zod": "4.3.6" + } +} diff --git a/e2e/scenarios/mcp-handler-instrumentation/pnpm-lock.yaml b/e2e/scenarios/mcp-handler-instrumentation/pnpm-lock.yaml new file mode 100644 index 000000000..2430a81aa --- /dev/null +++ b/e2e/scenarios/mcp-handler-instrumentation/pnpm-lock.yaml @@ -0,0 +1,985 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@modelcontextprotocol/sdk': + specifier: 1.26.0 + version: 1.26.0(zod@4.3.6) + '@modelcontextprotocol/server': + specifier: 2.4.0 + version: 2.4.0 + mcp-handler-v1: + specifier: npm:mcp-handler@1.0.5 + version: mcp-handler@1.0.5(@modelcontextprotocol/sdk@1.26.0(zod@4.3.6)) + mcp-handler-v1-latest: + specifier: npm:mcp-handler@1.1.0 + version: mcp-handler@1.1.0(@modelcontextprotocol/sdk@1.26.0(zod@4.3.6)) + mcp-handler-v2: + specifier: npm:mcp-handler@2.0.0 + version: mcp-handler@2.0.0(@modelcontextprotocol/server@2.4.0) + mcp-handler-v2-latest: + specifier: npm:mcp-handler@2.3.0 + version: mcp-handler@2.3.0(@modelcontextprotocol/server@2.4.0) + zod: + specifier: 4.3.6 + version: 4.3.6 + +packages: + + '@hono/node-server@1.19.17': + resolution: {integrity: sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ==} + engines: {node: '>=18.14.1'} + peerDependencies: + hono: ^4 + + '@modelcontextprotocol/core@2.4.0': + resolution: {integrity: sha512-z8QBKqdfXLPzkLQuG4xp8hrVEMm1PZ1IgxMN2sMbJiFiAKOWXnAzogXn2W4aqIBlP1ea6U5yiqVquvUNM1Cj6Q==} + engines: {node: '>=20'} + + '@modelcontextprotocol/sdk@1.26.0': + resolution: {integrity: sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==} + engines: {node: '>=18'} + peerDependencies: + '@cfworker/json-schema': ^4.1.1 + zod: ^3.25 || ^4.0 + peerDependenciesMeta: + '@cfworker/json-schema': + optional: true + + '@modelcontextprotocol/server@2.4.0': + resolution: {integrity: sha512-rgR9yxSaAJ+bcsi/XroT/S2AfPiIWloXAnrdfYSCms9ivVju0ishu/wW0J21Ho6+ZOR1r7RyeRbjiRuF87ix5Q==} + engines: {node: '>=20'} + + '@redis/bloom@1.2.0': + resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/client@1.6.1': + resolution: {integrity: sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==} + engines: {node: '>=14'} + + '@redis/graph@1.1.1': + resolution: {integrity: sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/json@1.0.7': + resolution: {integrity: sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/search@1.2.0': + resolution: {integrity: sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==} + peerDependencies: + '@redis/client': ^1.0.0 + + '@redis/time-series@1.1.0': + resolution: {integrity: sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==} + peerDependencies: + '@redis/client': ^1.0.0 + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + ajv-formats@3.0.1: + resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + body-parser@2.4.0: + resolution: {integrity: sha512-Ifk1KcLrxaGt1occQagQNFNNOqRt1LFuehEnHHzB8Z6cRgIxRU1TBaiIsUiCdA+gy5PL7Y1IX31DNKkRmxtXjQ==} + engines: {node: '>=18'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + cluster-key-slot@1.1.2: + resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==} + engines: {node: '>=0.10.0'} + + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + + content-disposition@2.0.1: + resolution: {integrity: sha512-e+H0ZXHSWYrENhQzw1LPuP4oF5MzVKmDU6d3hxlvaPEYLLg62MxtQNPRx4SYSuYJSBUgnQIG4HIN2tEtNv7Dog==} + engines: {node: '>=18'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + content-type@2.1.0: + resolution: {integrity: sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==} + engines: {node: '>=18'} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + cors@2.8.6: + resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} + engines: {node: '>= 0.10'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + encodeurl@2.0.1: + resolution: {integrity: sha512-Ev/hIyePLZtpL98x562lvPtenrUfGr0lfaf44HFZ+hDEwso7ccV9QAZrEVLg0hxNAzygaAFxy2dYaAKKM0G06A==} + engines: {node: '>= 0.8'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventsource-parser@3.1.1: + resolution: {integrity: sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==} + engines: {node: '>=18.0.0'} + + eventsource@3.0.7: + resolution: {integrity: sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==} + engines: {node: '>=18.0.0'} + + express-rate-limit@8.7.1: + resolution: {integrity: sha512-2FXbqkygil3mZiXzEtM0IjE/UBmBNot0J1GEVrJT4JbrvwOSoEtqtO+wHbiMQELWe1eNvHoYRg2dkjOid+6JkA==} + engines: {node: '>= 16'} + peerDependencies: + express: '>= 4.11' + + express@5.3.0: + resolution: {integrity: sha512-qCqy1xSoaugxOD4IVHb1ZWdkvCF2W3aE2okKouWj8VwJ+ztskSiXoBnCSJqfKT+cVPJhuJqe9YLbY425+Zh00g==} + engines: {node: '>= 18'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-uri@3.1.9: + resolution: {integrity: sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==} + + finalhandler@2.1.1: + resolution: {integrity: sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==} + engines: {node: '>= 18.0.0'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fresh@2.0.0: + resolution: {integrity: sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==} + engines: {node: '>= 0.8'} + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + generic-pool@3.9.0: + resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==} + engines: {node: '>= 4'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + hono@4.13.14: + resolution: {integrity: sha512-yhRLQ21U6qa9Hsu2N7Qcbh8tE+WKEcoErjIo56LeSGiGXGp9sA7UpE86Ui9uexb61KakiGwuHMn2QdV7w6i6kQ==} + engines: {node: '>=16.9.0'} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + iconv-lite@0.7.3: + resolution: {integrity: sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==} + engines: {node: '>=0.10.0'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ip-address@10.7.5: + resolution: {integrity: sha512-t2v0rkILYv9WXmSL7TpAaU3HXgaFYOPUcH3QHgwQ++gpJuH3bqyMy4Fvt/DJCTKwzH3Cf+bAp5W/mFL39KPLzA==} + engines: {node: '>= 12'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-promise@4.0.0: + resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jose@6.2.14: + resolution: {integrity: sha512-3JasLuki1NRLQvY4wCoC37KZk4qxoBx2k2aFqLxT84IezqW0bkJKnNtxnZj/Fu1xsQivoc/lytxaRHJoGLjJ1A==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json-schema-typed@8.0.2: + resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mcp-handler@1.0.5: + resolution: {integrity: sha512-ZJwo35WK8h4UGyADpXTSWYC6xkiy06/wTqqWxn1lCHv7fRlHxo5LRqpe20dlACJhRH8oLm2D/PlrukGPSjTVyg==} + hasBin: true + peerDependencies: + '@modelcontextprotocol/sdk': ^1.25.1 + next: '>=13.0.0' + peerDependenciesMeta: + next: + optional: true + + mcp-handler@1.1.0: + resolution: {integrity: sha512-MVCES7g18gcoZy+R/3v5nadkUMzMAWdos8jRl6DyljOKvd2/ZKDmwlCjL6zp4vo+7FeCXOYL1uWinHWlkKAAUg==} + hasBin: true + peerDependencies: + '@modelcontextprotocol/sdk': 1.26.0 + next: '>=13.0.0' + peerDependenciesMeta: + next: + optional: true + + mcp-handler@2.0.0: + resolution: {integrity: sha512-gvERNrkalUMO1M3KHwFGi+bdVx6ec5QZ5uu2zku+1DrHjF5upaN8KYQo8QGN7WFYBlrY7njbzEiQoFnoVxY/xQ==} + engines: {node: '>=20'} + hasBin: true + peerDependencies: + '@modelcontextprotocol/server': ^2.0.0 + next: '>=13.0.0' + peerDependenciesMeta: + next: + optional: true + + mcp-handler@2.3.0: + resolution: {integrity: sha512-mvs+fJTLTa6kFv71CKPr+2ey+yXeuooM6WHz96pHhSvXe9m83qPizswU5NJ0yrUnJ2T+70VFPjsdstZtdB3evQ==} + engines: {node: '>=20'} + hasBin: true + peerDependencies: + '@modelcontextprotocol/server': ^2.0.0 + next: '>=13.0.0' + peerDependenciesMeta: + next: + optional: true + + media-typer@1.1.1: + resolution: {integrity: sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==} + engines: {node: '>= 0.8'} + + merge-descriptors@2.0.0: + resolution: {integrity: sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==} + engines: {node: '>=18'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + negotiator@1.1.0: + resolution: {integrity: sha512-NMPBRMJgiQHjbd8phG3Vebdx4kZ1H121rbl5IkMqeOsahptB9BKo/d7oJ3zTXqTgagn2bWlNSXkh0QUGM31RYg==} + engines: {node: '>=18'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + on-finished@2.4.2: + resolution: {integrity: sha512-ATWUv387i+nLD49EiDoOft5scf32Vsdz3ktrQJG9bi7jRFmaXTssvyvj1EmeKVVgvesbqUtvQQvaS2TpTukAZQ==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-to-regexp@8.4.2: + resolution: {integrity: sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==} + + pkce-challenge@5.0.1: + resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} + engines: {node: '>=16.20.0'} + + proxy-addr@2.0.8: + resolution: {integrity: sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==} + engines: {node: '>= 0.10'} + + qs@6.17.1: + resolution: {integrity: sha512-jd3COkaKVgGUW+ZHyEHCG9bH7RoaaiWuMgzsIBjUMVjLAaWGWpoUAVwljVvBHxRu/2qEHnadLGZrkutYUBPQJA==} + engines: {node: '>=0.6'} + + range-parser@1.3.0: + resolution: {integrity: sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==} + engines: {node: '>= 0.6'} + + raw-body@3.0.2: + resolution: {integrity: sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==} + engines: {node: '>= 0.10'} + + redis@4.7.1: + resolution: {integrity: sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + router@2.2.0: + resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} + engines: {node: '>= 18'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + send@1.2.1: + resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} + engines: {node: '>= 18'} + + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + type-is@2.1.0: + resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} + engines: {node: '>= 18'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + zod-to-json-schema@3.25.2: + resolution: {integrity: sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==} + peerDependencies: + zod: ^3.25.28 || ^4 + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + +snapshots: + + '@hono/node-server@1.19.17(hono@4.13.14)': + dependencies: + hono: 4.13.14 + + '@modelcontextprotocol/core@2.4.0': + dependencies: + zod: 4.3.6 + + '@modelcontextprotocol/sdk@1.26.0(zod@4.3.6)': + dependencies: + '@hono/node-server': 1.19.17(hono@4.13.14) + ajv: 8.20.0 + ajv-formats: 3.0.1(ajv@8.20.0) + content-type: 1.0.5 + cors: 2.8.6 + cross-spawn: 7.0.6 + eventsource: 3.0.7 + eventsource-parser: 3.1.1 + express: 5.3.0 + express-rate-limit: 8.7.1(express@5.3.0) + hono: 4.13.14 + jose: 6.2.14 + json-schema-typed: 8.0.2 + pkce-challenge: 5.0.1 + raw-body: 3.0.2 + zod: 4.3.6 + zod-to-json-schema: 3.25.2(zod@4.3.6) + transitivePeerDependencies: + - supports-color + + '@modelcontextprotocol/server@2.4.0': + dependencies: + '@modelcontextprotocol/core': 2.4.0 + zod: 4.3.6 + + '@redis/bloom@1.2.0(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + + '@redis/client@1.6.1': + dependencies: + cluster-key-slot: 1.1.2 + generic-pool: 3.9.0 + yallist: 4.0.0 + + '@redis/graph@1.1.1(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + + '@redis/json@1.0.7(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + + '@redis/search@1.2.0(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + + '@redis/time-series@1.1.0(@redis/client@1.6.1)': + dependencies: + '@redis/client': 1.6.1 + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.1.0 + + ajv-formats@3.0.1(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.9 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + body-parser@2.4.0: + dependencies: + bytes: 3.1.2 + content-type: 2.1.0 + debug: 4.4.3 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + on-finished: 2.4.2 + qs: 6.17.1 + raw-body: 3.0.2 + type-is: 2.1.0 + transitivePeerDependencies: + - supports-color + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + chalk@5.6.2: {} + + cluster-key-slot@1.1.2: {} + + commander@11.1.0: {} + + content-disposition@2.0.1: {} + + content-type@1.0.5: {} + + content-type@2.1.0: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + cors@2.8.6: + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + depd@2.0.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + encodeurl@2.0.1: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + escape-html@1.0.3: {} + + etag@1.8.1: {} + + eventsource-parser@3.1.1: {} + + eventsource@3.0.7: + dependencies: + eventsource-parser: 3.1.1 + + express-rate-limit@8.7.1(express@5.3.0): + dependencies: + debug: 4.4.3 + express: 5.3.0 + ip-address: 10.7.5 + transitivePeerDependencies: + - supports-color + + express@5.3.0: + dependencies: + accepts: 2.0.0 + body-parser: 2.4.0 + content-disposition: 2.0.1 + content-type: 2.1.0 + cookie: 0.7.2 + cookie-signature: 1.2.2 + debug: 4.4.3 + depd: 2.0.0 + encodeurl: 2.0.1 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 2.1.1 + fresh: 2.0.0 + http-errors: 2.0.1 + merge-descriptors: 2.0.0 + mime-types: 3.0.2 + on-finished: 2.4.2 + once: 1.4.0 + parseurl: 1.3.3 + proxy-addr: 2.0.8 + qs: 6.17.1 + range-parser: 1.3.0 + router: 2.2.0 + send: 1.2.1 + serve-static: 2.2.1 + statuses: 2.0.2 + type-is: 2.1.0 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-deep-equal@3.1.3: {} + + fast-uri@3.1.9: {} + + finalhandler@2.1.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.1 + escape-html: 1.0.3 + on-finished: 2.4.2 + parseurl: 1.3.3 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + forwarded@0.2.0: {} + + fresh@2.0.0: {} + + function-bind@1.1.2: {} + + generic-pool@3.9.0: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + hono@4.13.14: {} + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + iconv-lite@0.7.3: + dependencies: + safer-buffer: 2.1.2 + + inherits@2.0.4: {} + + ip-address@10.7.5: {} + + ipaddr.js@1.9.1: {} + + is-promise@4.0.0: {} + + isexe@2.0.0: {} + + jose@6.2.14: {} + + json-schema-traverse@1.0.0: {} + + json-schema-typed@8.0.2: {} + + math-intrinsics@1.1.0: {} + + mcp-handler@1.0.5(@modelcontextprotocol/sdk@1.26.0(zod@4.3.6)): + dependencies: + '@modelcontextprotocol/sdk': 1.26.0(zod@4.3.6) + chalk: 5.6.2 + commander: 11.1.0 + redis: 4.7.1 + + mcp-handler@1.1.0(@modelcontextprotocol/sdk@1.26.0(zod@4.3.6)): + dependencies: + '@modelcontextprotocol/sdk': 1.26.0(zod@4.3.6) + chalk: 5.6.2 + commander: 11.1.0 + redis: 4.7.1 + + mcp-handler@2.0.0(@modelcontextprotocol/server@2.4.0): + dependencies: + '@modelcontextprotocol/server': 2.4.0 + chalk: 5.6.2 + commander: 11.1.0 + + mcp-handler@2.3.0(@modelcontextprotocol/server@2.4.0): + dependencies: + '@modelcontextprotocol/server': 2.4.0 + chalk: 5.6.2 + commander: 11.1.0 + + media-typer@1.1.1: {} + + merge-descriptors@2.0.0: {} + + mime-db@1.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + ms@2.1.3: {} + + negotiator@1.1.0: + dependencies: + content-type: 2.1.0 + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + on-finished@2.4.2: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + parseurl@1.3.3: {} + + path-key@3.1.1: {} + + path-to-regexp@8.4.2: {} + + pkce-challenge@5.0.1: {} + + proxy-addr@2.0.8: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + qs@6.17.1: + dependencies: + es-define-property: 1.0.1 + side-channel: 1.1.1 + + range-parser@1.3.0: {} + + raw-body@3.0.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.7.3 + unpipe: 1.0.0 + + redis@4.7.1: + dependencies: + '@redis/bloom': 1.2.0(@redis/client@1.6.1) + '@redis/client': 1.6.1 + '@redis/graph': 1.1.1(@redis/client@1.6.1) + '@redis/json': 1.0.7(@redis/client@1.6.1) + '@redis/search': 1.2.0(@redis/client@1.6.1) + '@redis/time-series': 1.1.0(@redis/client@1.6.1) + + require-from-string@2.0.2: {} + + router@2.2.0: + dependencies: + debug: 4.4.3 + depd: 2.0.0 + is-promise: 4.0.0 + parseurl: 1.3.3 + path-to-regexp: 8.4.2 + transitivePeerDependencies: + - supports-color + + safer-buffer@2.1.2: {} + + send@1.2.1: + dependencies: + debug: 4.4.3 + encodeurl: 2.0.1 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 2.0.0 + http-errors: 2.0.1 + mime-types: 3.0.2 + ms: 2.1.3 + on-finished: 2.4.2 + range-parser: 1.3.0 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.1 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.1 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + statuses@2.0.2: {} + + toidentifier@1.0.1: {} + + type-is@2.1.0: + dependencies: + content-type: 2.1.0 + media-typer: 1.1.1 + mime-types: 3.0.2 + + unpipe@1.0.0: {} + + vary@1.1.2: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wrappy@1.0.2: {} + + yallist@4.0.0: {} + + zod-to-json-schema@3.25.2(zod@4.3.6): + dependencies: + zod: 4.3.6 + + zod@4.3.6: {} diff --git a/e2e/scenarios/mcp-handler-instrumentation/scenario.mjs b/e2e/scenarios/mcp-handler-instrumentation/scenario.mjs new file mode 100644 index 000000000..b4a042226 --- /dev/null +++ b/e2e/scenarios/mcp-handler-instrumentation/scenario.mjs @@ -0,0 +1,225 @@ +import "./configure.mjs"; +import assert from "node:assert/strict"; +import { createRequire } from "node:module"; +import { injectTraceContext, traced, wrapMCPHandler } from "braintrust"; +import { z } from "zod"; +import { runMain, runTracedScenario } from "../../helpers/provider-runtime.mjs"; + +const packageName = process.env.MCP_HANDLER_PACKAGE; +const { createMcpHandler } = + process.env.MCP_HANDLER_CJS === "1" + ? createRequire(import.meta.url)(packageName) + : await import(packageName); +const createHandler = + process.env.MCP_HANDLER_WRAPPED === "1" + ? wrapMCPHandler(createMcpHandler) + : createMcpHandler; +const v1 = packageName.includes("v1"); +const schema = (shape) => (v1 ? shape : z.object(shape)); + +runMain(async () => { + await runTracedScenario({ + projectNameBase: "tmp-luca", + rootName: "mcp-handler-instrumentation-root", + metadata: { scenario: "mcp-handler-instrumentation" }, + callback: async () => { + const createServerHandler = () => + createHandler( + async (server) => { + await Promise.resolve(); + server.registerTool( + "get_weather", + { + inputSchema: schema({ city: z.string() }), + }, + async ({ city }) => + traced( + async () => { + await Promise.resolve(); + return { + content: [ + { + type: "text", + text: `Sunny in ${city}`, + _meta: { private: "hidden" }, + }, + ], + }; + }, + { name: "weather-lookup" }, + ), + ); + server.registerTool("get_alerts", {}, async () => { + throw new Error("Alert service unavailable"); + }); + server.registerTool( + "report_progress", + { inputSchema: schema({}) }, + async (_args, extra) => { + await server.server.notification( + { + method: "notifications/progress", + params: { + progressToken: "progress", + progress: 1, + total: 1, + message: "done", + }, + }, + { relatedRequestId: extra.mcpReq?.id ?? extra.requestId }, + ); + return { content: [{ type: "text", text: "done" }] }; + }, + ); + server.registerResource( + "forecast-notes", + "notes://forecast", + {}, + async (uri) => ({ + contents: [{ uri: uri.href, text: "Bring an umbrella." }], + }), + ); + server.registerPrompt( + "forecast", + { + argsSchema: schema({ city: z.string() }), + }, + ({ city }) => ({ + messages: [ + { + role: "user", + content: { type: "text", text: `Forecast for ${city}?` }, + }, + ], + }), + ); + }, + { serverInfo: { name: "braintrust-e2e-weather", version: "1.0.0" } }, + ...(v1 ? [{ disableSse: true }] : []), + ); + // Older v1 adapters retain a single-use SDK transport on the handler. + const handler = + packageName === "mcp-handler-v1" + ? (request) => createServerHandler()(request) + : createServerHandler(); + + async function request( + method, + params, + { modern = false, propagate = true } = {}, + ) { + const response = await handler( + new Request("http://mcp.test/mcp", { + method: "POST", + headers: { + "content-type": "application/json", + accept: "application/json, text/event-stream", + "mcp-protocol-version": modern ? "2026-07-28" : "2025-11-25", + ...(modern + ? { "mcp-method": method, "mcp-name": params.name } + : {}), + authorization: "Bearer private-token", + }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method, + params: { + ...params, + _meta: { + ...(modern + ? { + "io.modelcontextprotocol/protocolVersion": "2026-07-28", + "io.modelcontextprotocol/clientCapabilities": {}, + "io.modelcontextprotocol/clientInfo": { + name: "braintrust-e2e-client", + version: "1.0.0", + }, + } + : {}), + ...(propagate + ? injectTraceContext() + : { traceparent: "invalid" }), + private: "hidden", + }, + }, + }), + }), + ); + const text = await response.text(); + assert.equal(response.status, 200, text); + const messages = response.headers + .get("content-type") + .includes("text/event-stream") + ? text + .split("\n") + .filter((line) => line.startsWith("data:")) + .map((line) => JSON.parse(line.slice(5))) + : [JSON.parse(text)]; + return messages.find((message) => message.id === 1); + } + + const initialized = await request("initialize", { + protocolVersion: "2025-11-25", + capabilities: {}, + clientInfo: { name: "braintrust-e2e-client", version: "1.0.0" }, + }); + assert.equal( + initialized.result.serverInfo.name, + "braintrust-e2e-weather", + ); + // Both requests use id=1 on independent stateless transports. + const weather = await Promise.all( + ["Vienna", "Paris"].map((city) => + request("tools/call", { name: "get_weather", arguments: { city } }), + ), + ); + assert.equal(weather[0].result.content[0].text, "Sunny in Vienna"); + assert.equal(weather[1].result.content[0].text, "Sunny in Paris"); + assert.equal( + ( + await request("tools/call", { + name: "report_progress", + arguments: {}, + }) + ).result.content[0].text, + "done", + ); + assert.ok((await request("tools/list", {})).result.tools.length >= 2); + assert.ok( + (await request("resources/read", { uri: "notes://forecast" })).result + .contents, + ); + assert.ok( + ( + await request("prompts/get", { + name: "forecast", + arguments: { city: "Vienna" }, + }) + ).result.messages, + ); + assert.equal( + (await request("tools/call", { name: "get_alerts", arguments: {} })) + .result.isError, + true, + ); + const invalid = await request("tools/call", { + name: "get_weather", + arguments: { city: 42 }, + }); + assert.ok(invalid.error || invalid.result.isError); + assert.equal((await request("acme/missing", {})).error.code, -32601); + await request("ping", {}, { propagate: false }); + if (!v1) { + const modern = await request( + "tools/call", + { name: "get_weather", arguments: { city: "Tokyo" } }, + { modern: true }, + ); + assert.equal(modern.result.content[0].text, "Sunny in Tokyo"); + } + }, + }); + // mcp-handler v1 retains a server cleanup interval after all requests finish. + process.exit(0); +}); diff --git a/e2e/scenarios/mcp-handler-instrumentation/scenario.test.ts b/e2e/scenarios/mcp-handler-instrumentation/scenario.test.ts new file mode 100644 index 000000000..7ae9484e4 --- /dev/null +++ b/e2e/scenarios/mcp-handler-instrumentation/scenario.test.ts @@ -0,0 +1,211 @@ +import { describe, expect, test } from "vitest"; +import { describeVariant } from "../../helpers/describe-variant"; +import { resolveFileSnapshotPath } from "../../helpers/file-snapshot"; +import { + prepareScenarioDir, + readInstalledPackageVersion, + resolveScenarioDir, + withScenarioHarness, +} from "../../helpers/scenario-harness"; +import { matchSpanTreeSnapshot, spanTreeFields } from "../../helpers/span-tree"; +import { + findChildSpans, + findLatestSpan, + spanInstrumentationName, +} from "../../helpers/trace-selectors"; + +const originalScenarioDir = resolveScenarioDir(import.meta.url); +const scenarioDir = await prepareScenarioDir({ + scenarioDir: originalScenarioDir, +}); +const variants = await Promise.all( + [ + "mcp-handler-v1", + "mcp-handler-v1-latest", + "mcp-handler-v2", + "mcp-handler-v2-latest", + ].map(async (dependencyName) => ({ + dependencyName, + version: await readInstalledPackageVersion(scenarioDir, dependencyName), + })), +); + +describe.concurrent("variants", () => { + for (const { dependencyName, version } of variants) { + for (const captureContent of [false, true]) { + describeVariant( + `${dependencyName} ${version}, captureContent=${captureContent}`, + () => { + for (const mode of [ + "wrapped", + "auto", + "both", + "both-plugins", + "disabled", + ]) { + test(`${mode} instrumentation`, async ({ + expect: snapshotExpect, + }) => { + await withScenarioHarness(async (harness) => { + await harness.runNodeScenarioDir({ + scenarioDir, + entry: "scenario.mjs", + env: { + MCP_HANDLER_PACKAGE: dependencyName, + MCP_HANDLER_CJS: dependencyName.endsWith("latest") + ? "0" + : "1", + MCP_HANDLER_WRAPPED: mode === "auto" ? "0" : "1", + MCP_CAPTURE_CONTENT: captureContent ? "1" : "0", + MCP_ENABLED: mode === "both-plugins" ? "1" : "0", + MCP_HANDLER_DISABLED: mode === "disabled" ? "1" : "0", + }, + nodeArgs: + mode === "wrapped" + ? [] + : [ + "--import", + `${scenarioDir}/configure.mjs`, + "--import", + "braintrust/hook.mjs", + ], + runContext: { + cassette: false, + originalScenarioDir, + variantKey: dependencyName, + }, + }); + const events = harness.events(); + const root = findLatestSpan( + events, + "mcp-handler-instrumentation-root", + ); + expect(root).toBeDefined(); + if (mode === "disabled") { + expect( + events.filter((event) => + ["mcp", "mcp-handler"].includes( + spanInstrumentationName(event) ?? "", + ), + ), + ).toHaveLength(0); + return; + } + const spans = events.filter( + (event) => spanInstrumentationName(event) === "mcp-handler", + ); + expect(spans).toHaveLength( + dependencyName.includes("v1") ? 12 : 13, + ); + expect(spans.every((span) => span.span.ended)).toBe(true); + const progress = findLatestSpan( + events, + "tools/call report_progress", + ); + const [notice] = findChildSpans( + events, + "notifications/progress", + progress?.span.id, + ); + expect(notice?.row.metadata).toMatchObject({ + "mcp.notification.direction": "outgoing", + "mcp.progress.token": "progress", + "mcp.progress.current": 1, + "mcp.progress.total": 1, + }); + expect(spanInstrumentationName(notice!)).toBe("mcp-handler"); + const weather = findChildSpans( + events, + "tools/call get_weather", + root!.span.id, + ); + expect(weather).toHaveLength( + dependencyName.includes("v1") ? 3 : 4, + ); + const successfulWeather = weather.filter( + (span) => !span.row.error, + ); + expect(successfulWeather).toHaveLength( + dependencyName.includes("v1") ? 2 : 3, + ); + for (const span of successfulWeather) { + expect(span.span.type).toBe("tool"); + expect(span.row.metadata).toMatchObject({ + "mcp.method.name": "tools/call", + "gen_ai.tool.name": "get_weather", + "network.protocol.name": "http", + }); + expect( + findChildSpans(events, "weather-lookup", span.span.id), + ).toHaveLength(1); + if (captureContent) { + expect(span.input).toMatchObject({ + city: expect.any(String), + }); + expect(span.output).toMatchObject({ + content: [ + { + type: "text", + text: expect.stringMatching(/^Sunny in /), + }, + ], + }); + } + } + const alerts = findLatestSpan(events, "tools/call get_alerts"); + expect(alerts?.row.metadata?.["error.type"]).toBe("tool_error"); + expect(alerts?.row.error).toContain( + captureContent + ? "Alert service unavailable" + : "Tool returned an error result", + ); + expect( + findLatestSpan(events, "acme/missing")?.row.metadata?.[ + "rpc.response.status_code" + ], + ).toBe("-32601"); + const ping = findLatestSpan(events, "ping"); + expect(ping).toBeDefined(); + expect(ping!.span.rootId).not.toBe(root!.span.rootId); + expect( + findLatestSpan(events, "tools/list")?.output, + ).toBeUndefined(); + if (!captureContent) { + for (const span of spans) { + expect(span.input).toBeUndefined(); + expect(span.output).toBeUndefined(); + } + } + expect(JSON.stringify(events)).not.toContain("private-token"); + expect(JSON.stringify(spans)).not.toContain("hidden"); + await matchSpanTreeSnapshot( + events.map((event) => ({ + event, + fields: { + ...spanTreeFields(event), + context: event.row.context && { + ...event.row.context, + ...(event.row.context.caller_filename !== undefined + ? { caller_filename: "" } + : {}), + ...(event.row.context.caller_functionname !== undefined + ? { caller_functionname: "" } + : {}), + }, + metrics: event.metrics, + }, + })), + resolveFileSnapshotPath( + import.meta.url, + `${dependencyName}${captureContent ? "" : "-no-content"}.span-tree.json`, + ), + { snapshotExpect }, + ); + }); + }); + } + }, + ); + } + } +}); diff --git a/js/src/auto-instrumentations/configs/all.ts b/js/src/auto-instrumentations/configs/all.ts index 751e0b8ac..ed7c2c10a 100644 --- a/js/src/auto-instrumentations/configs/all.ts +++ b/js/src/auto-instrumentations/configs/all.ts @@ -38,6 +38,7 @@ import { strandsAgentSDKConfigs } from "./strands-agent-sdk"; import { elevenLabsConfigs } from "./elevenlabs"; import { voyageAIConfigs } from "./voyageai"; import { mcpConfigs } from "./mcp"; +import { mcpHandlerConfigs } from "./mcp-handler"; import { typeSafeConfigs } from "./typesafe"; interface InstrumentationConfigGroup { @@ -146,6 +147,10 @@ const defaultInstrumentationConfigGroups: readonly InstrumentationConfigGroup[] integrations: ["mcp"], configs: mcpConfigs, }, + { + integrations: ["mcpHandler"], + configs: mcpHandlerConfigs, + }, { integrations: ["typesafe"], configs: typeSafeConfigs, diff --git a/js/src/auto-instrumentations/configs/mcp-handler.ts b/js/src/auto-instrumentations/configs/mcp-handler.ts new file mode 100644 index 000000000..c5a987bcd --- /dev/null +++ b/js/src/auto-instrumentations/configs/mcp-handler.ts @@ -0,0 +1,16 @@ +import type { InstrumentationConfig } from "../orchestrion-js"; +import { mcpHandlerChannels } from "../../instrumentation/plugins/mcp-handler-interceptors"; + +export const mcpHandlerConfigs: InstrumentationConfig[] = [ + "dist/index.mjs", + "dist/index.js", +].map((filePath) => ({ + channelName: mcpHandlerChannels.createMcpHandler.channelName, + module: { + name: "mcp-handler", + versionRange: ">=1.0.0 <3.0.0", + filePath, + }, + // The public createMcpHandler export uses this name in both ESM and CJS builds. + functionQuery: { functionName: "createMcpRouteHandler" }, +})); diff --git a/js/src/exports.ts b/js/src/exports.ts index 9cf8b897b..a6dd30303 100644 --- a/js/src/exports.ts +++ b/js/src/exports.ts @@ -244,6 +244,7 @@ export { wrapOllama } from "./wrappers/ollama"; export { wrapCohere } from "./wrappers/cohere"; export { wrapVoyageAI } from "./wrappers/voyageai"; export { wrapMCPServer } from "./wrappers/mcp"; +export { wrapMCPHandler } from "./wrappers/mcp-handler"; export { wrapWorkersAI } from "./wrappers/workers-ai"; export { wrapTypeSafe } from "./wrappers/typesafe"; export { wrapGroq } from "./wrappers/groq"; diff --git a/js/src/instrumentation/braintrust-plugin.test.ts b/js/src/instrumentation/braintrust-plugin.test.ts index c979690ec..7a4ebb1a4 100644 --- a/js/src/instrumentation/braintrust-plugin.test.ts +++ b/js/src/instrumentation/braintrust-plugin.test.ts @@ -24,6 +24,7 @@ import { PiCodingAgentPlugin } from "./plugins/pi-coding-agent-plugin"; import { StrandsAgentSDKPlugin } from "./plugins/strands-agent-sdk-plugin"; import { VoyageAIPlugin } from "./plugins/voyageai-plugin"; import { MCPPlugin } from "./plugins/mcp-plugin"; +import { MCPHandlerPlugin } from "./plugins/mcp-handler-plugin"; import { TypeSafePlugin } from "./plugins/typesafe-plugin"; import { CloudflareAIChatPlugin } from "./plugins/cloudflare-ai-chat-plugin"; import { CloudflareAgentsPlugin } from "./plugins/cloudflare-agents-plugin"; @@ -144,6 +145,10 @@ vi.mock("./plugins/mcp-plugin", () => ({ MCPPlugin: createPluginClassMock(), })); +vi.mock("./plugins/mcp-handler-plugin", () => ({ + MCPHandlerPlugin: createPluginClassMock(), +})); + vi.mock("./plugins/typesafe-plugin", () => ({ TypeSafePlugin: createPluginClassMock(), })); @@ -305,6 +310,28 @@ describe("BraintrustPlugin", () => { expect(mockInstance.enable).toHaveBeenCalledTimes(1); }); + it("configures MCP Handler independently of MCP and disables it on teardown", () => { + const plugin = new BraintrustPlugin({ + integrations: { mcp: false, mcpHandler: { captureContent: false } }, + }); + plugin.enable(); + expect(MCPPlugin).not.toHaveBeenCalled(); + expect(MCPHandlerPlugin).toHaveBeenCalledWith({ captureContent: false }); + const instance = vi.mocked(MCPHandlerPlugin).mock.results[0].value; + expect(instance.enable).toHaveBeenCalledTimes(1); + plugin.disable(); + expect(instance.disable).toHaveBeenCalledTimes(1); + }); + + it("can disable MCP Handler while keeping MCP enabled", () => { + const plugin = new BraintrustPlugin({ + integrations: { mcpHandler: false }, + }); + plugin.enable(); + expect(MCPHandlerPlugin).not.toHaveBeenCalled(); + expect(MCPPlugin).toHaveBeenCalledTimes(1); + }); + it("should create and enable TypeSafe plugin by default", () => { const plugin = new BraintrustPlugin(); plugin.enable(); diff --git a/js/src/instrumentation/braintrust-plugin.ts b/js/src/instrumentation/braintrust-plugin.ts index 5de60a49a..435985f96 100644 --- a/js/src/instrumentation/braintrust-plugin.ts +++ b/js/src/instrumentation/braintrust-plugin.ts @@ -30,6 +30,7 @@ import { StrandsAgentSDKPlugin } from "./plugins/strands-agent-sdk-plugin"; import { ElevenLabsPlugin } from "./plugins/elevenlabs-plugin"; import { LiveKitPlugin } from "./plugins/livekit-plugin"; import { MCPPlugin } from "./plugins/mcp-plugin"; +import { MCPHandlerPlugin } from "./plugins/mcp-handler-plugin"; import { VoyageAIPlugin } from "./plugins/voyageai-plugin"; import { WorkersAIPlugin } from "./plugins/workers-ai-plugin"; import { TypeSafePlugin } from "./plugins/typesafe-plugin"; @@ -97,6 +98,7 @@ export class BraintrustPlugin extends BasePlugin { private liveKitPlugin: LiveKitPlugin | null = null; private voyageAIPlugin: VoyageAIPlugin | null = null; private mcpPlugin: MCPPlugin | null = null; + private mcpHandlerPlugin: MCPHandlerPlugin | null = null; private workersAIPlugin: WorkersAIPlugin | null = null; private typeSafePlugin: TypeSafePlugin | null = null; private cloudflareAIChatPlugin: CloudflareAIChatPlugin | null = null; @@ -231,6 +233,15 @@ export class BraintrustPlugin extends BasePlugin { this.voyageAIPlugin.enable(); } + if (integrations.mcpHandler !== false) { + this.mcpHandlerPlugin = new MCPHandlerPlugin( + typeof integrations.mcpHandler === "object" + ? integrations.mcpHandler + : {}, + ); + this.mcpHandlerPlugin.enable(); + } + if (integrations.mcp !== false) { this.mcpPlugin = new MCPPlugin( typeof integrations.mcp === "object" ? integrations.mcp : {}, @@ -431,6 +442,11 @@ export class BraintrustPlugin extends BasePlugin { this.mcpPlugin = null; } + if (this.mcpHandlerPlugin) { + this.mcpHandlerPlugin.disable(); + this.mcpHandlerPlugin = null; + } + if (this.workersAIPlugin) { this.workersAIPlugin.disable(); this.workersAIPlugin = null; diff --git a/js/src/instrumentation/config.ts b/js/src/instrumentation/config.ts index 5f336cf3b..58cd1edb9 100644 --- a/js/src/instrumentation/config.ts +++ b/js/src/instrumentation/config.ts @@ -67,6 +67,8 @@ export interface InstrumentationIntegrationsConfig { livekit?: boolean | LiveKitIntegrationConfig; /** Set to false to disable, or pass options; options keep MCP enabled. */ mcp?: boolean | MCPIntegrationConfig; + /** Set to false to disable, or pass options; options keep MCP Handler enabled. */ + mcpHandler?: boolean | MCPIntegrationConfig; } export interface MCPIntegrationConfig { @@ -242,6 +244,8 @@ const envIntegrationAliases: Record< modelcontextprotocol: "mcp", "@modelcontextprotocol/sdk": "mcp", "@modelcontextprotocol/server": "mcp", + mcphandler: "mcpHandler", + "mcp-handler": "mcpHandler", }; export function getDefaultInstrumentationIntegrations(): Record< @@ -288,6 +292,7 @@ export function getDefaultInstrumentationIntegrations(): Record< elevenlabs: true, livekit: true, mcp: true, + mcpHandler: true, piCodingAgent: true, strandsAgentSDK: true, cloudflareAgents: true, diff --git a/js/src/instrumentation/plugins/mcp-handler-interceptors.ts b/js/src/instrumentation/plugins/mcp-handler-interceptors.ts new file mode 100644 index 000000000..4c7e058e0 --- /dev/null +++ b/js/src/instrumentation/plugins/mcp-handler-interceptors.ts @@ -0,0 +1,11 @@ +import { channel, defineInterceptor } from "../core/interceptor-definitions"; +import type { MCPHandlerCreator } from "../../vendor-sdk-types/mcp-handler"; +import { mcpChannelDefinitions } from "./mcp-interceptors"; + +export const mcpHandlerChannels = defineInterceptor("mcp-handler", { + ...mcpChannelDefinitions, + createMcpHandler: channel< + Parameters, + ReturnType + >({ channelName: "createMcpHandler" }), +}); diff --git a/js/src/instrumentation/plugins/mcp-handler-plugin.ts b/js/src/instrumentation/plugins/mcp-handler-plugin.ts new file mode 100644 index 000000000..b62554597 --- /dev/null +++ b/js/src/instrumentation/plugins/mcp-handler-plugin.ts @@ -0,0 +1,114 @@ +import type { MCPProtocol } from "../../vendor-sdk-types/mcp"; +import { INSTRUMENTATION_NAMES } from "../../span-origin"; +import type { MCPIntegrationConfig } from "../config"; +import { BasePlugin } from "../core"; +import { runInstrumentation } from "../core/observe-result"; +import { unsubscribeAll } from "../core/tracing"; +import { mcpHandlerChannels } from "./mcp-handler-interceptors"; +import { wrapMCPProtocol, wrapMCPTransport } from "./mcp-interceptors"; +import { + traceMCPMessage, + traceMCPSend, + traceMCPClose, + traceMCPHandler, + traceMCPProtocolError, + traceMCPNotificationHandler, + traceMCPHttpRequest, +} from "./mcp-tracing"; + +export class MCPHandlerPlugin extends BasePlugin { + constructor(private readonly config: MCPIntegrationConfig = {}) { + super(); + } + + protected onEnable(): void { + this.unsubscribers.push( + mcpHandlerChannels.createMcpHandler.intercept( + (target, receiver, args) => { + const [initializeServer, ...options] = args; + return Reflect.apply(target, receiver, [ + function ( + this: unknown, + ...initArgs: Parameters + ) { + runInstrumentation(() => + wrapMCPProtocol(initArgs[0].server, mcpHandlerChannels), + ); + return Reflect.apply(initializeServer, this, initArgs); + }, + ...options, + ]); + }, + ), + mcpHandlerChannels.connect.intercept((target, receiver, args) => { + runInstrumentation(() => + wrapMCPTransport( + args[0], + receiver as MCPProtocol, + mcpHandlerChannels, + ), + ); + return Reflect.apply(target, receiver, args); + }), + mcpHandlerChannels.message.intercept( + (target, receiver, args, { transport }) => + traceMCPMessage( + () => Reflect.apply(target, receiver, args), + transport, + args[0], + this.config, + INSTRUMENTATION_NAMES.MCP_HANDLER, + ), + ), + mcpHandlerChannels.send.intercept( + (target, receiver, args, { transport }) => + traceMCPSend( + () => Reflect.apply(target, receiver, args), + transport, + args[0], + args[1], + this.config, + INSTRUMENTATION_NAMES.MCP_HANDLER, + ), + ), + mcpHandlerChannels.close.intercept( + (target, receiver, args, { transport }) => + traceMCPClose(() => Reflect.apply(target, receiver, args), transport), + ), + mcpHandlerChannels.requestHandler.intercept( + (target, receiver, args, { protocol }) => + traceMCPHandler( + () => Reflect.apply(target, receiver, args), + protocol, + args[0], + args[1], + ), + ), + mcpHandlerChannels.protocolError.intercept((target, receiver, args) => + traceMCPProtocolError( + () => Reflect.apply(target, receiver, args), + args[0], + ), + ), + mcpHandlerChannels.notificationHandler.intercept( + (target, receiver, args, { protocol }) => + traceMCPNotificationHandler( + () => Reflect.apply(target, receiver, args), + protocol, + ), + ), + mcpHandlerChannels.httpRequest.intercept( + (target, receiver, args, { transport }) => + traceMCPHttpRequest( + () => Reflect.apply(target, receiver, args), + transport, + args[0], + ), + ), + ); + } + + protected onDisable(): void { + this.unsubscribers = unsubscribeAll(this.unsubscribers); + } +} diff --git a/js/src/instrumentation/plugins/mcp-interceptors.ts b/js/src/instrumentation/plugins/mcp-interceptors.ts index 13445cb17..a0d45daa8 100644 --- a/js/src/instrumentation/plugins/mcp-interceptors.ts +++ b/js/src/instrumentation/plugins/mcp-interceptors.ts @@ -24,7 +24,7 @@ export const mcpServerChannels = defineInterceptor( "@modelcontextprotocol/server", protocolChannels, ); -export const mcpChannels = defineInterceptor("mcp", { +export const mcpChannelDefinitions = { connect: channel<[MCPTransport, ...unknown[]], unknown>({ channelName: "connect", }), @@ -57,7 +57,9 @@ export const mcpChannels = defineInterceptor("mcp", { unknown, { protocol: MCPProtocol } >({ channelName: "notificationHandler" }), -}); +}; +export const mcpChannels = defineInterceptor("mcp", mcpChannelDefinitions); +type MCPInvocationChannels = typeof mcpChannels; const wrapped = Symbol.for("braintrust.mcp.transport-wrapper"); function wrapMethod( @@ -78,13 +80,14 @@ function wrapMethod( export function wrapMCPTransport( transport: MCPTransport, protocol: MCPProtocol, + channels: MCPInvocationChannels = mcpChannels, ): void { wrapMethod( transport, "handleRequest", (original) => function (this: unknown, ...args) { - return mcpChannels.httpRequest.invoke( + return channels.httpRequest.invoke( original, this, args as [unknown, ...unknown[]], @@ -103,15 +106,17 @@ export function wrapMCPTransport( protocol.fallbackRequestHandler = wrapMCPRequestHandler( protocol.fallbackRequestHandler, protocol, + channels, ); } if (protocol.fallbackNotificationHandler) { protocol.fallbackNotificationHandler = wrapMCPNotificationHandler( protocol.fallbackNotificationHandler, protocol, + channels, ); } - return mcpChannels.message.invoke( + return channels.message.invoke( original, this, args as [unknown, ...unknown[]], @@ -124,7 +129,7 @@ export function wrapMCPTransport( "onclose", (original) => function (this: unknown, ...args) { - return mcpChannels.close.invoke(original, this, args, { transport }); + return channels.close.invoke(original, this, args, { transport }); }, ); }; @@ -144,7 +149,7 @@ export function wrapMCPTransport( "send", (original) => function (this: unknown, ...args) { - return mcpChannels.send.invoke( + return channels.send.invoke( original, this, args as [unknown, ...unknown[]], @@ -158,6 +163,7 @@ export function wrapMCPTransport( export function wrapMCPRequestHandler( handler: MCPRequestHandler, protocol: MCPProtocol, + channels: MCPInvocationChannels = mcpChannels, ): MCPRequestHandler { if (Reflect.get(handler, wrapped) === protocol) return handler; const replacement: MCPRequestHandler = function ( @@ -165,7 +171,7 @@ export function wrapMCPRequestHandler( request, extra, ) { - return mcpChannels.requestHandler.invoke(handler, this, [request, extra], { + return channels.requestHandler.invoke(handler, this, [request, extra], { protocol, }); }; @@ -173,13 +179,16 @@ export function wrapMCPRequestHandler( return replacement; } -export function wrapMCPProtocol(protocol: MCPProtocol): void { +export function wrapMCPProtocol( + protocol: MCPProtocol, + channels: MCPInvocationChannels = mcpChannels, +): void { wrapMethod( protocol, "connect", (original) => function (this: unknown, ...args) { - return mcpChannels.connect.invoke( + return channels.connect.invoke( original, this, args as [MCPTransport, ...unknown[]], @@ -197,6 +206,7 @@ export function wrapMCPProtocol(protocol: MCPProtocol): void { args[index] = wrapMCPRequestHandler( args[index] as MCPRequestHandler, protocol, + channels, ); return Reflect.apply(original, this, args); }, @@ -211,7 +221,7 @@ export function wrapMCPProtocol(protocol: MCPProtocol): void { for (const [method, handler] of protocol._notificationHandlers ?? []) { protocol._notificationHandlers!.set( method, - wrapMCPNotificationHandler(handler, protocol), + wrapMCPNotificationHandler(handler, protocol, channels), ); } return result; @@ -222,34 +232,36 @@ export function wrapMCPProtocol(protocol: MCPProtocol): void { "_onerror", (original) => function (this: unknown, error) { - return mcpChannels.protocolError.invoke(original, this, [error], {}); + return channels.protocolError.invoke(original, this, [error], {}); }, ); for (const [method, handler] of protocol._requestHandlers ?? []) { protocol._requestHandlers!.set( method, - wrapMCPRequestHandler(handler, protocol), + wrapMCPRequestHandler(handler, protocol, channels), ); } for (const [method, handler] of protocol._notificationHandlers ?? []) { protocol._notificationHandlers!.set( method, - wrapMCPNotificationHandler(handler, protocol), + wrapMCPNotificationHandler(handler, protocol, channels), ); } - if (protocol.transport) wrapMCPTransport(protocol.transport, protocol); + if (protocol.transport) + wrapMCPTransport(protocol.transport, protocol, channels); } function wrapMCPNotificationHandler( handler: MCPNotificationHandler, protocol: MCPProtocol, + channels: MCPInvocationChannels = mcpChannels, ): MCPNotificationHandler { if (Reflect.get(handler, wrapped) === protocol) return handler; const replacement: MCPNotificationHandler = function ( this: unknown, ...args ) { - return mcpChannels.notificationHandler.invoke(handler, this, args, { + return channels.notificationHandler.invoke(handler, this, args, { protocol, }); }; diff --git a/js/src/instrumentation/plugins/mcp-plugin.test.ts b/js/src/instrumentation/plugins/mcp-plugin.test.ts index f46d760a0..08b511e64 100644 --- a/js/src/instrumentation/plugins/mcp-plugin.test.ts +++ b/js/src/instrumentation/plugins/mcp-plugin.test.ts @@ -446,6 +446,36 @@ describe("MCPPlugin", () => { expect(span.metadata["jsonrpc.request.id"]).toBe("0"); }); + it.each([false, true])( + "observes send settlement when the transport closes during send (reject=%s)", + async (reject) => { + const protocol = createProtocol(); + const transport = protocol.transport!; + const failure = new Error("send failed"); + transport.send = () => { + queueMicrotask(() => + mcpChannels.close.invoke(() => {}, transport, [], { transport }), + ); + return reject ? Promise.reject(failure) : Promise.resolve(); + }; + const result = handleRequest( + async () => ({ content: [] }), + { + method: "tools/call", + params: { name: "close-on-send" }, + }, + {}, + protocol, + ); + if (reject) await expect(result).rejects.toBe(failure); + else await result; + const [span] = await drainSpans(); + expect(span.metrics.end).toEqual(expect.any(Number)); + if (reject) expect(span.error).toContain("send failed"); + else expect(span.error).toBeUndefined(); + }, + ); + it("propagates response-send failures and ends the span", async () => { const protocol = createProtocol(); const failure = new Error("send failed"); diff --git a/js/src/instrumentation/plugins/mcp-tracing.ts b/js/src/instrumentation/plugins/mcp-tracing.ts index ff9527976..23cd87a69 100644 --- a/js/src/instrumentation/plugins/mcp-tracing.ts +++ b/js/src/instrumentation/plugins/mcp-tracing.ts @@ -12,6 +12,7 @@ import { import { INSTRUMENTATION_NAMES, withSpanInstrumentationName, + type SpanInstrumentationName, } from "../../span-origin"; import type { MCPTransport, @@ -42,6 +43,7 @@ type PendingRequest = { method: string; ended: boolean; handlerStarted?: boolean; + responseInFlight?: boolean; initialClient?: Record; failure?: unknown; }; @@ -79,6 +81,7 @@ function canCacheTransportIdentity(transport: MCPTransport): boolean { ![ "StreamableHTTPServerTransport", "WebStandardStreamableHTTPServerTransport", + "PerRequestHTTPServerTransport", "SSEServerTransport", ].includes(transport.constructor.name) ); @@ -292,6 +295,7 @@ export function traceMCPMessage( transport: MCPTransport, message: unknown, config: MCPIntegrationConfig = {}, + instrumentationName: SpanInstrumentationName = INSTRUMENTATION_NAMES.MCP, ): T { const request = runInstrumentation(() => { if ( @@ -371,6 +375,7 @@ export function traceMCPMessage( ...([ "StreamableHTTPServerTransport", "WebStandardStreamableHTTPServerTransport", + "PerRequestHTTPServerTransport", "SSEServerTransport", ].includes(transportName) ? { "network.protocol.name": "http" } @@ -435,7 +440,7 @@ export function traceMCPMessage( : {}), }, }, - INSTRUMENTATION_NAMES.MCP, + instrumentationName, ); const span = withCurrent(NOOP_SPAN, () => startSpan(spanArgs)); const state: PendingRequest = { @@ -493,6 +498,7 @@ export function traceMCPSend( message: unknown, options?: unknown, config: MCPIntegrationConfig = {}, + instrumentationName: SpanInstrumentationName = INSTRUMENTATION_NAMES.MCP, ): T { if ( isObject(message) && @@ -505,6 +511,7 @@ export function traceMCPSend( message, options, config, + instrumentationName, ); } let initialization: @@ -586,6 +593,7 @@ export function traceMCPSend( return state; }); if (!request) return call(); + request.responseInFlight = true; try { return observeResult( call(), @@ -624,6 +632,7 @@ function traceMCPOutgoingNotification( message: Record, options: unknown, config: MCPIntegrationConfig, + instrumentationName: SpanInstrumentationName, ): T { if (isAutoInstrumentationSuppressed()) return call(); const span = runInstrumentation(() => { @@ -671,7 +680,7 @@ function traceMCPOutgoingNotification( : {}), }, }, - INSTRUMENTATION_NAMES.MCP, + instrumentationName, ), ), ); @@ -774,10 +783,14 @@ export function traceMCPNotificationHandler( export function traceMCPClose(call: () => T, transport: MCPTransport): T { for (const request of connections.get(transport)?.values() ?? []) { - finish( - request, - new Error("MCP connection closed before the response completed"), - ); + // Per-request HTTP transports close as part of sending the terminal result, + // before the send promise settles. Let that promise determine the outcome. + if (!request.responseInFlight) { + finish( + request, + new Error("MCP connection closed before the response completed"), + ); + } } connections.delete(transport); protocolVersions.delete(transport); diff --git a/js/src/span-origin.ts b/js/src/span-origin.ts index 19998e780..aeb7e0676 100644 --- a/js/src/span-origin.ts +++ b/js/src/span-origin.ts @@ -33,6 +33,7 @@ export const INSTRUMENTATION_NAMES = { LIVEKIT: "livekit", MASTRA: "mastra", MCP: "mcp", + MCP_HANDLER: "mcp-handler", MISTRAL: "mistral", LANGGRAPH_SDK: "langgraph-sdk", OLLAMA: "ollama", diff --git a/js/src/vendor-sdk-types/mcp-handler.ts b/js/src/vendor-sdk-types/mcp-handler.ts new file mode 100644 index 000000000..a4958d0fc --- /dev/null +++ b/js/src/vendor-sdk-types/mcp-handler.ts @@ -0,0 +1,12 @@ +import type { MCPProtocol } from "./mcp"; + +/** Relevant public API shared by mcp-handler v1 and v2. */ +export type MCPHandlerInitializer = ( + server: { server: MCPProtocol }, + ...args: unknown[] +) => unknown; + +export type MCPHandlerCreator = ( + initializeServer: MCPHandlerInitializer, + ...options: unknown[] +) => (request: Request) => Promise; diff --git a/js/src/wrappers/mcp-handler.ts b/js/src/wrappers/mcp-handler.ts new file mode 100644 index 000000000..252879aaf --- /dev/null +++ b/js/src/wrappers/mcp-handler.ts @@ -0,0 +1,46 @@ +import { debugLogger } from "../debug-logger"; +import { mcpHandlerChannels } from "../instrumentation/plugins/mcp-handler-interceptors"; +import type { MCPHandlerCreator } from "../vendor-sdk-types/mcp-handler"; + +const wrappers = new WeakMap(); + +/** + * Wrap the `createMcpHandler` factory from `mcp-handler` to trace incoming MCP + * requests with the same conventions as `wrapMCPServer`. + * Configure this integration with `integrations.mcpHandler`. + * + * @example + * ```ts + * import { createMcpHandler } from "mcp-handler"; + * import { wrapMCPHandler } from "braintrust"; + * + * const handler = wrapMCPHandler(createMcpHandler)((server) => { + * server.registerTool("hello", {}, async () => ({ + * content: [{ type: "text", text: "Hello!" }], + * })); + * }); + * ``` + */ +export function wrapMCPHandler(createMcpHandler: T): T { + if (typeof createMcpHandler !== "function") { + debugLogger.notice.warn("Unsupported MCP handler factory. Not wrapping."); + return createMcpHandler; + } + const factory = createMcpHandler as MCPHandlerCreator; + const cached = wrappers.get(factory); + if (cached) return cached as T; + + const wrapped = new Proxy(factory, { + apply(target, receiver, args: Parameters) { + return mcpHandlerChannels.createMcpHandler.invoke( + target, + receiver, + args, + {}, + ); + }, + }); + wrappers.set(factory, wrapped); + wrappers.set(wrapped, wrapped); + return wrapped as T; +} diff --git a/js/src/wrappers/mcp.test.ts b/js/src/wrappers/mcp.test.ts index b95be4942..15109ca03 100644 --- a/js/src/wrappers/mcp.test.ts +++ b/js/src/wrappers/mcp.test.ts @@ -18,6 +18,8 @@ import type { MCPRequest, } from "../vendor-sdk-types/mcp"; import { wrapMCPServer } from "./mcp"; +import { wrapMCPHandler } from "./mcp-handler"; +import { registry } from "../instrumentation/registry"; try { configureNode(); @@ -135,4 +137,88 @@ describe("MCP server wrapper", () => { expect(wrapMCPServer(value)).toBe(value); expect(warn).toHaveBeenCalledWith("Unsupported MCP server. Not wrapping."); }); + + it("wraps servers before initialization and preserves factory and callback semantics", async () => { + const protocol = new FakeProtocol(); + const server = { server: protocol }; + const receiver = {}; + const initReceiver = {}; + const options = {}; + const config = {}; + const ready = Promise.resolve(); + const handler = async () => new Response(); + const initialize = vi.fn(function ( + this: unknown, + value: typeof server, + extra: unknown, + ) { + expect(this).toBe(initReceiver); + expect(value).toBe(server); + expect(extra).toBe("extra"); + value.server.setRequestHandler("tools/call", async () => ({ + content: [], + })); + return ready; + }); + const factory = vi.fn(function ( + this: unknown, + init: typeof initialize, + receivedOptions: unknown, + receivedConfig: unknown, + ) { + expect(this).toBe(receiver); + expect(receivedOptions).toBe(options); + expect(receivedConfig).toBe(config); + expect(init.call(initReceiver, server, "extra")).toBe(ready); + return handler; + }); + const wrapped = wrapMCPHandler(factory); + expect(wrapMCPHandler(factory)).toBe(wrapped); + expect(wrapMCPHandler(wrapped)).toBe(wrapped); + expect(wrapped.call(receiver, initialize, options, config)).toBe(handler); + expect(factory).toHaveBeenCalledTimes(1); + expect(initialize).toHaveBeenCalledTimes(1); + + await protocol.connect({ start() {}, send() {} }); + await protocol.handle("tools/call", { name: "from-handler" }); + expect(await spanNames()).toEqual(["tools/call from-handler"]); + }); + + it("preserves initializer failures without retrying", () => { + const error = new Error("initialization failed"); + const initialize = vi.fn((_server: { server: FakeProtocol }) => { + throw error; + }); + const factory = (init: typeof initialize) => + init({ server: new FakeProtocol() }); + expect(() => wrapMCPHandler(factory)(initialize)).toThrow(error); + expect(initialize).toHaveBeenCalledTimes(1); + }); + + it("passes factory arguments through unchanged when instrumentation is disabled", () => { + registry.disable(); + try { + const initialize = () => {}; + const handler = () => {}; + const factory = vi.fn(() => handler); + const options = {}; + expect( + Reflect.apply(wrapMCPHandler(factory), null, [initialize, options]), + ).toBe(handler); + expect(factory).toHaveBeenCalledWith(initialize, options); + } finally { + registry.enable(); + } + }); + + it("returns unsupported MCP handler factories unchanged", () => { + const warn = vi + .spyOn(debugLogger.notice, "warn") + .mockImplementation(() => true); + const value = {}; + expect(wrapMCPHandler(value)).toBe(value); + expect(warn).toHaveBeenCalledWith( + "Unsupported MCP handler factory. Not wrapping.", + ); + }); });