Skip to content

feat(mcp): trace client operations and propagate context - #1025

Merged
Abhijeet Prasad (AbhiPrasad) merged 6 commits into
mainfrom
abhi/mcp-client-tracing-ae460b73
Oct 30, 2026
Merged

Abhijeet Prasad (AbhiPrasad) merged 6 commits into
mainfrom
abhi/mcp-client-tracing-ae460b73

Conversation

@AbhiPrasad

@AbhiPrasad Abhijeet Prasad (AbhiPrasad) commented Oct 29, 2026 •

Copy link
Copy Markdown
Member

🤖 This text was AI-generated.

Stacked on #1024, which adds MCP server instrumentation. This is PR 2 of the MCP instrumentation work.

Problem

Server instrumentation records MCP execution, but the caller has no span for the round trip. Without automatic context propagation, the agent and server appear in separate traces:

agent                         # caller trace

tools/call add             # server trace
└─ nested-work

Solution

Add wrap_mcp_client(client_or_session, capture_content=True) to trace outgoing requests and propagate W3C trace context and Braintrust baggage through params._meta.

from braintrust.integrations.mcp import wrap_mcp_client

client = wrap_mcp_client(client)

Each side works independently. With both client and server wrapped, nested work joins the caller's trace:

agent
└─ tools/call add (client)
   └─ tools/call add (server)
      └─ nested-work

Supports official mcp>=1.28.1,<3 clients and sessions, standalone fastmcp>=2.14.7,<5 clients, and corresponding fastmcp-slim releases. MCP dependencies remain optional.

  • Client spans share server names, types, and payload shaping. metadata["mcp.span.kind"] = "client" identifies the round trip.
  • Content capture is enabled by default. Pass capture_content=False to omit arguments and results; discovery inventories are always omitted.
  • Propagation preserves the original request and existing baggage. Concurrent requests keep their own parentage.
  • Errors, timeouts, cancellation, and disconnects finalize spans without changing application behavior. Returned tool errors retain their results; cancellation alone does not mark the span as failed.

Wrap before connecting to include negotiation, or wrap an active client/session. Repeated wrapping updates settings without adding spans, and FastMCP reconnects inherit those settings.

Framework tool spans can parent the MCP round trip. When opentelemetry-instrumentation-mcp is enabled, its instrumentor owns client spans, propagation, and capture settings; Braintrust adds no duplicate client span.

Integration tests use real clients and servers in memory, over localhost HTTP, and over stdio subprocesses. They assert span payloads, parentage, and received trace metadata directly, without cassettes or external LLM calls.

Scope and next steps

One-way notifications, session lifetimes, and detached task APIs do not create client operation spans. Automatic client setup and documentation follow in PR 3. MCPIntegration.setup() continues to enable server tracing independently.

Refs #1023

@AbhiPrasad
Abhijeet Prasad (AbhiPrasad) force-pushed the abhi/mcp-client-tracing-ae460b73 branch from 08f5717 to 12477ab Compare October 29, 2026 19:14
@AbhiPrasad
Abhijeet Prasad (AbhiPrasad) force-pushed the abhi/mcp-client-tracing-ae460b73 branch 2 times, most recently from 21b913d to 3c53e63 Compare October 29, 2026 20:44
@AbhiPrasad
Abhijeet Prasad (AbhiPrasad) marked this pull request as ready for review October 29, 2026 20:48
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-30T15:08:35.142396Z 00ba0eb New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@AbhiPrasad
Abhijeet Prasad (AbhiPrasad) force-pushed the abhi/mcp-client-tracing-ae460b73 branch from 3c53e63 to dd14cdc Compare October 29, 2026 21:13
Base automatically changed from abhi/add-mcp-instrumentation-b534c1c8 to main October 30, 2026 15:05
> 🤖 **This text was AI-generated.**

The server integration no longer provides ask_llm or cassette setup. Client
coverage still depended on both and stored version-specific protocol snapshots.

Use the real add tool for connected HTTP and subprocess tests:

```text
agent -> MCP client -> MCP server -> nested-work
```

Remove client cassettes and stdio recording hooks. Assert propagated trace
metadata on the actual server context, preserving baggage, content opt-out,
client-only operation, and connected parentage checks without external LLM calls.

Refs #1023
Abhijeet Prasad (AbhiPrasad) added a commit that referenced this pull request Oct 30, 2026
> 🤖 **This text was AI-generated.**

`auto_instrument()` previously skipped MCP, requiring separate setup:

```python
client = wrap_mcp_client(client)
server = wrap_mcp_server(server)
```

Enable both sides automatically, with independent controls and content
capture enabled by default. Applications can opt out of payload capture:

```python
auto_instrument(mcp_client=True, mcp_server=True, mcp_capture_content=False)
```

Repeated setup updates defaults without adding duplicate spans. Explicit
wrappers retain their per-object settings, and MCP dependencies stay optional.

Builds on [#1025](#1025)
for MCP 1.x/2.x and FastMCP 2.x–4.x.

Refs #1023
@AbhiPrasad
Abhijeet Prasad (AbhiPrasad) force-pushed the abhi/mcp-client-tracing-ae460b73 branch from dd14cdc to 00ba0eb Compare October 30, 2026 15:05
@AbhiPrasad
Abhijeet Prasad (AbhiPrasad) merged commit 7a3a6c4 into main Oct 30, 2026
98 checks passed
@AbhiPrasad
Abhijeet Prasad (AbhiPrasad) deleted the abhi/mcp-client-tracing-ae460b73 branch October 30, 2026 15:43
Abhijeet Prasad (AbhiPrasad) added a commit that referenced this pull request Oct 30, 2026
> 🤖 **This text was AI-generated.**

`auto_instrument()` previously skipped MCP, requiring separate setup:

```python
client = wrap_mcp_client(client)
server = wrap_mcp_server(server)
```

Enable both sides automatically, with independent controls and content
capture enabled by default. Applications can opt out of payload capture:

```python
auto_instrument(mcp_client=True, mcp_server=True, mcp_capture_content=False)
```

Repeated setup updates defaults without adding duplicate spans. Explicit
wrappers retain their per-object settings, and MCP dependencies stay optional.

Builds on [#1025](#1025)
for MCP 1.x/2.x and FastMCP 2.x–4.x.

Refs #1023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant