Skip to content

feat: Add first-class middleware integration to Client/Protocol/Transport #80

@guyernest

Description

@guyernest

Summary

The middleware system (Middleware, AdvancedMiddleware, MiddlewareChain, EnhancedMiddlewareChain) is comprehensive and powerful, but there's no standard integration path to attach middleware to Client, Protocol, or Transport. Users must manually invoke middleware chains, which is error-prone and defeats the purpose of having middleware infrastructure.

Current State

What exists:

  • ✅ Comprehensive middleware traits (Middleware, AdvancedMiddleware)
  • ✅ Chain implementations (MiddlewareChain, EnhancedMiddlewareChain)
  • ✅ Built-in middleware (Logging, Auth, Retry, RateLimit, CircuitBreaker, Metrics, Compression)
  • ✅ Priority ordering, context propagation, conditional execution
  • ✅ Excellent documentation in pmcp-book/src/ch11-middleware.md

What's missing:

  • ❌ No ClientBuilder::with_middleware_chain(...) or similar API
  • ❌ No automatic middleware invocation on requests/responses
  • ❌ No integration examples showing middleware running on real client operations
  • ❌ Users must manually call chain.process_request() / chain.process_response()

Current workaround (manual integration):

// Manual middleware invocation - cumbersome and easy to forget
let mut client = Client::new(transport);
let mut middleware_chain = MiddlewareChain::new();
middleware_chain.add(Arc::new(LoggingMiddleware::default()));

// Must manually intercept before/after every operation
let mut request = create_request("tools/list", None);
middleware_chain.process_request(&mut request).await?;
let response = client.call_method(request).await?;
middleware_chain.process_response(&mut response).await?;

Proposed Solution

Add first-class middleware integration at one or more levels:

Option 1: Client-level Integration (Recommended)

use pmcp::{Client, ClientCapabilities, MiddlewareChain, LoggingMiddleware};
use std::sync::Arc;
use tracing::Level;

let mut chain = MiddlewareChain::new();
chain.add(Arc::new(LoggingMiddleware::new(Level::INFO)));

let client = Client::builder()
    .capabilities(ClientCapabilities::default())
    .with_middleware_chain(chain)  // ← NEW API
    .streamable_http_transport("http://localhost:8080")
    .await?
    .build()
    .await?;

// Middleware runs automatically on all operations
let tools = client.list_tools(None).await?;  // Logging middleware runs transparently

Option 2: Transport-level Integration

use pmcp::{StdioTransport, LoggingMiddleware};

let transport = StdioTransport::new()
    .with_middleware(Arc::new(LoggingMiddleware::default()));  // ← NEW API

let client = Client::new(transport);
// Middleware intercepts all transport send/receive operations

Option 3: Protocol-level Integration

use pmcp::shared::{Protocol, ProtocolOptions};

let protocol = Protocol::new(ProtocolOptions {
    middleware: Some(chain),  // ← NEW FIELD
    ..Default::default()
});

Implementation Approach

Client-level (Recommended Starting Point)

1. Update Client struct:

pub struct Client {
    transport: Box<dyn Transport>,
    middleware: Option<Arc<MiddlewareChain>>,  // NEW
    // ... existing fields
}

2. Add builder method:

impl ClientBuilder {
    pub fn with_middleware_chain(mut self, chain: MiddlewareChain) -> Self {
        self.middleware = Some(Arc::new(chain));
        self
    }
}

3. Intercept in request/response methods:

impl Client {
    pub async fn call_method(&mut self, mut request: JSONRPCRequest) -> Result<JSONRPCResponse> {
        // Apply middleware to request
        if let Some(ref middleware) = self.middleware {
            middleware.process_request(&mut request).await?;
        }

        // Send request
        let response = /* ... existing logic ... */;

        // Apply middleware to response
        if let Some(ref middleware) = self.middleware {
            middleware.process_response(&mut response).await?;
        }

        Ok(response)
    }
}

Enhanced Middleware Support

For EnhancedMiddlewareChain with context:

pub fn with_enhanced_middleware_chain(mut self, chain: EnhancedMiddlewareChain) -> Self {
    self.enhanced_middleware = Some(Arc::new(chain));
    self
}

// In request handling:
let context = MiddlewareContext::with_request_id(generate_request_id());
middleware.process_request_with_context(&mut request, &context).await?;

Comparison with TypeScript SDK

TypeScript SDK (~/Development/mcp/sdk/typescript-sdk):

  • HTTP-only: fetch wrapper middleware
  • Limited scope: Only HTTP transport layer

Rust SDK (current + proposed):

  • ✅ Protocol-first: Hooks at JSON-RPC level AND raw transport messages
  • ✅ Transport-agnostic: Works with stdio, WebSocket, HTTP, Streamable HTTP
  • ✅ Advanced patterns: Rate limiting, circuit breakers, metrics, compression
  • ✅ Priority ordering and conditional execution
  • ⚠️ Missing: First-class integration (this issue)

With this enhancement, Rust SDK would be superior in every aspect.

Benefits

  1. Zero-boilerplate usage: Set up once, runs automatically
  2. Reduced error surface: Can't forget to apply middleware
  3. Clear integration point: Obvious where/how to attach middleware
  4. Matches user expectations: Standard pattern from other middleware systems
  5. Makes powerful middleware system usable: Current system is powerful but disconnected

Example Use Case

Production deployment with observability:

use pmcp::shared::{
    EnhancedMiddlewareChain,
    MetricsMiddleware,
    LoggingMiddleware,
    RateLimitMiddleware,
    CircuitBreakerMiddleware,
};

let mut chain = EnhancedMiddlewareChain::new();
chain.add(Arc::new(MetricsMiddleware::new("my-service".to_string())));
chain.add(Arc::new(LoggingMiddleware::new(Level::INFO)));
chain.add(Arc::new(RateLimitMiddleware::new(100, 200, Duration::from_secs(1))));
chain.add(Arc::new(CircuitBreakerMiddleware::new(5, Duration::from_secs(60), Duration::from_secs(30))));

// One-line integration
let client = Client::builder()
    .with_enhanced_middleware_chain(chain)
    .streamable_http_transport("http://localhost:8080")
    .await?
    .build()
    .await?;

// All operations now have:
// - Automatic metrics collection
// - Request/response logging
// - Rate limiting protection
// - Circuit breaker fault tolerance
let tools = client.list_tools(None).await?;

Proposed Implementation Plan

  1. Phase 1: Client-level integration (highest impact, easiest to implement)

    • Add with_middleware_chain() to ClientBuilder
    • Add with_enhanced_middleware_chain() to ClientBuilder
    • Intercept in Client::call_method() and similar methods
    • Update examples/15_middleware.rs with real integration
  2. Phase 2: Transport-level integration (for advanced use cases)

    • Add with_middleware() to transport implementations
    • Intercept Transport::send() / Transport::receive()
  3. Phase 3: Protocol-level integration (if needed)

    • Add middleware field to ProtocolOptions
    • Intercept in Protocol request handling
  4. Documentation updates:

    • Update pmcp-book/src/ch11-middleware.md with integration examples
    • Add "Real-World Integration" section
    • Update examples/15_middleware.rs and examples/30_enhanced_middleware.rs

Files to Modify

  • src/client/mod.rs or src/client/builder.rs - Add builder methods
  • src/client/client.rs - Add middleware interception
  • src/shared/protocol.rs - Optional: Protocol-level integration
  • src/shared/transport.rs - Optional: Transport trait extension
  • examples/15_middleware.rs - Update with real integration
  • examples/30_enhanced_middleware.rs - Update with real integration
  • pmcp-book/src/ch11-middleware.md - Add integration documentation

Breaking Changes

None - This is purely additive. All existing code continues to work, and middleware remains optional.

References

  • Current middleware implementation: src/shared/middleware.rs
  • Middleware documentation: pmcp-book/src/ch11-middleware.md
  • Basic middleware example: examples/15_middleware.rs
  • Enhanced middleware example: examples/30_enhanced_middleware.rs
  • TypeScript SDK comparison: ~/Development/mcp/sdk/typescript-sdk

Priority: High
Complexity: Medium
Impact: High - Makes middleware system actually usable in production

This enhancement would complete the middleware story and make PMCP's middleware system best-in-class across all MCP SDKs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions