Skip to content

Conversation

@dgenio
Copy link

@dgenio dgenio commented Nov 28, 2025

Summary

This PR introduces a pluggable instrumentation interface with a token-based API for the MCP Python SDK, enabling OpenTelemetry and other observability integrations as requested in #421.

Key Innovation: Token-Based API

The instrumentation interface uses a token-based approach that solves a critical design problem:

class Instrumenter(Protocol):
    def on_request_start(...) -> Any:  # Returns a token
        """Start instrumentation, return state token (e.g., OTel span)"""
    
    def on_request_end(token: Any, ...) -> None:  # Receives the token
        """End instrumentation using the token"""
    
    def on_error(token: Any, ...) -> None:  # Receives the token
        """Handle errors using the token"""

This design enables instrumenters to maintain state (like OpenTelemetry spans) without external storage or side-channels, addressing feedback from the community on API design best practices.

Changes

Core Interface

  • Defined Instrumenter protocol with token-based hooks
  • Created NoOpInstrumenter as default implementation with minimal overhead
  • Token can be any value (span object, dict, etc.)

Integration

  • Added instrumenter parameter to ServerSession and ClientSession constructors
  • Wired instrumentation into Server._handle_request() to track:
    • Request start/end with duration tracking
    • Success/failure status
    • Error occurrences with token propagation
  • Added request_id to logging extra fields for correlation

OpenTelemetry Example

  • NEW: Complete OpenTelemetryInstrumenter implementation in examples/opentelemetry_instrumentation.py
  • Demonstrates span management using tokens
  • Includes setup code and runnable example
  • Shows proper error recording and status codes

Testing

  • Comprehensive tests verifying:
    • Token flow from start → end/error
    • Hooks are invoked for successful and failed requests
    • request_id is consistent across lifecycle
    • Metadata is passed correctly
    • Default no-op behavior works

Documentation

  • Added docs/instrumentation.md with:
    • Token-based API explanation with "Why Tokens?" section
    • Complete OpenTelemetry integration guide
    • Usage examples for server and client
    • Custom metrics example
    • Best practices and migration guide

Benefits

  1. No External Storage: Instrumenters don't need spans = {} dictionaries
  2. OpenTelemetry Compatible: Spans can be returned and passed directly
  3. Thread-Safe: Each request gets its own token
  4. Automatic Cleanup: Tokens are garbage collected
  5. Flexible: Token can be any value

Follow-up Work

  • Package OpenTelemetry instrumenter as installable extra (pip install mcp[opentelemetry])
  • Additional built-in instrumenters (Prometheus, StatsD, Datadog)
  • Distributed tracing via params._meta.traceparent propagation
  • Client-side instrumentation (server-side is complete)

Fixes #421

Add a pluggable instrumentation interface for monitoring MCP request/response lifecycle. This lays groundwork for OpenTelemetry and other observability integrations.

Changes:
- Define Instrumenter protocol with on_request_start, on_request_end, and on_error hooks
- Add NoOpInstrumenter as default implementation with minimal overhead
- Wire instrumenter into ServerSession and ClientSession constructors
- Add instrumentation calls in Server._handle_request for server-side monitoring
- Add request_id to log records via extra field for correlation
- Add comprehensive tests for instrumentation protocol
- Add documentation with examples and best practices

Addresses modelcontextprotocol#421
This change addresses feedback on the instrumentation interface design. The updated API now uses a token-based approach where on_request_start() returns a token that is passed to on_request_end() and on_error(). This enables instrumenters to maintain state (like OpenTelemetry spans) without external storage or side-channels.

Changes:

- Updated Instrumenter protocol to return token from on_request_start()

- Modified on_request_end() and on_error() to accept token as first parameter

- Updated server.py to capture and pass instrumentation tokens

- Updated all tests to match new API

- Added complete OpenTelemetry example implementation

- Updated documentation with token-based examples

Fixes modelcontextprotocol#421
@dgenio
Copy link
Author

dgenio commented Nov 28, 2025

Updated the instrumentation interface to use a token-based API based on community feedback. This enables proper OpenTelemetry integration without external storage. See updated PR description for details.

…ection

Pytest was trying to collect TestInstrumenter as a test class because it starts with 'Test', but it's actually a helper class with an __init__ constructor. Renaming to MockInstrumenter resolves the PytestCollectionWarning.
Added full type hints to MockInstrumenter class to resolve pyright type checking errors. This ensures the test helper class properly implements the Instrumenter protocol with correct types.
@dgenio dgenio marked this pull request as ready for review November 28, 2025 14:50
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.

Adding Opentelemetry to MCP SDK

1 participant