Skip to content

Feature/client level identifiers #666

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

monahk
Copy link

@monahk monahk commented Jun 20, 2025

Summary

Implements client-level request identifiers feature that enables passing contextual metadata (trace IDs, tenant context, user info) through MCP tool calls to downstream APIs. This addresses the lack of standardized observability and multi-tenancy support in the current MCP protocol.

Motivation and Context

Currently, there's no standard way to pass request-scoped metadata like trace IDs, user context, or tenant information from MCP clients through servers to downstream APIs. This creates gaps in:

  • Distributed tracing: Cannot correlate requests across MCP boundaries
  • Multi-tenancy: No way to pass tenant/user context for proper data isolation
  • Audit compliance: Cannot maintain audit trails through MCP servers
  • Request correlation: Difficult to track requests across multiple services

This implementation provides a clean, backward-compatible solution by allowing clients to configure identifiers that automatically flow through as HTTP headers to downstream services.

Related to community discussion: modelcontextprotocol/modelcontextprotocol#801

How Has This Been Tested?

Comprehensive test suite with 11 scenarios and 100% pass rate:

Core Functionality Tests

  • Client-level identifiers only (trace-id, tenant-id forwarded correctly)
  • Request-level identifiers only (request-id, user-id forwarded correctly)
  • Identifier merging (4 identifiers: client + request combined successfully)
  • Conflict resolution (request trace-id correctly overrode client value)

Edge Case & Compatibility Tests

  • Empty identifier objects (no headers sent when empty)
  • Backward compatibility (existing code works unchanged, no identifiers = no headers)
  • Long values (100-character strings handled appropriately)
  • Special characters ([email protected], numbers, dashes, underscores)
  • Various naming patterns (kebab-case, snake_case transformations)

Security Validation Tests

  • Unsafe key rejection (spaces, symbols, control characters filtered out)
  • Control character filtering in values (\x00, \x1F, \x7F properly rejected)
  • ASCII-only validation (prevents HTTP header injection attacks)
  • Identifier count limits (25+ identifiers truncated to configurable limit of 20)
  • Value length limits (300+ character values rejected per 256-char limit)

Header Format Tests

  • Proper X-MCP- prefix application
  • Case transformation validation (kebab-case → Pascal-Case)
  • Deterministic behavior across JavaScript engines

End-to-End Validation

  • Real HTTP server receiving and validating forwarded headers
  • Complete request-response cycle testing with security validation
  • Multi-layer validation: client merging → server truncation → security filtering

Test Results: 11 test scenarios executed, 11/11 validation tests passed, all security measures working correctly.

Breaking Changes

None. This is fully backward compatible:

  • Existing clients work unchanged (no identifiers sent)
  • Existing servers ignore unknown identifiers field
  • Identifier forwarding is disabled by default for security
  • All new functionality is opt-in

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the [MCP Documentation](https://modelcontextprotocol.io)
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

Additional context

Key Design Decisions:

  1. Two-level identifier system: Client-level (applies to all calls) + request-level (per-call) with request taking precedence on conflicts

  2. Security-first approach:

    • Identifier forwarding disabled by default
    • Multi-layer validation (client merging + server truncation + security filtering)
    • ASCII-only values to prevent HTTP header injection
    • Key format validation (alphanumeric, hyphens, underscores only)
    • Configurable limits with deterministic truncation
  3. HTTP header transformation: Identifiers converted to X-MCP-{Key} headers with kebab-case to Pascal-Case transformation (e.g., trace-idX-MCP-Trace-Id)

  4. Comprehensive server-side configuration:

    • allowedKeys whitelist for restricting which identifiers can be forwarded
    • maxIdentifiers limit (default: 20) with deterministic alphabetical sorting
    • maxValueLength limit (default: 256) for preventing abuse
    • headerPrefix customization (default: X-MCP-)
  5. Helper utilities: Added EnhancedRequestHandlerExtra interface with applyIdentifiersToRequestOptions() to make it easy for tool implementations to forward identifiers

Security Implementation:

  • Input sanitization prevents header injection attacks
  • Control character filtering ensures HTTP compliance
  • Deterministic truncation via alphabetical sorting for cross-platform consistency
  • Performance-optimized validation order (cheapest checks first)

Implementation highlights:

  • Extends existing ClientOptions and CallToolRequest schemas
  • Zero impact on existing functionality
  • Comprehensive example and documentation with working test suite
  • Enterprise-ready with proper security considerations
  • Production-tested with 11 comprehensive security and functionality tests

Files added/modified:

  • Extended client and server interfaces with security validation
  • Added comprehensive security-focused example with test suite
  • Updated CHANGELOG with security considerations
  • Complete README with usage examples, security best practices, and test coverage documentation

Manah Khalil and others added 6 commits June 19, 2025 18:58
Implements the proposal from modelcontextprotocol/modelcontextprotocol#801
to enable passing contextual metadata through MCP tool calls, supporting
distributed tracing, multi-tenancy, and request correlation across the protocol boundary.

This implementation:

- Extends the protocol schema by adding an optional `identifiers` field to
  CallToolRequest for passing key-value string pairs
- Introduces client-level identifiers in ClientOptions that automatically
  apply to all tool calls made through a client instance
- Implements merging logic in Client.callTool() that combines client-level
  and request-level identifiers (with request taking precedence on conflicts)
- Adds server-side configuration via IdentifierForwardingConfig to control
  how identifiers are processed and forwarded
- Provides forwardIdentifiersAsHeaders() utility to convert identifiers into
  standard HTTP headers for downstream requests
- Implements EnhancedRequestHandlerExtra interface with helpers for tool
  implementations to access and forward identifiers

Security considerations:
- Identifier forwarding is disabled by default and must be explicitly enabled
- Server configuration includes options for filtering allowed keys, limiting
  the number of identifiers, and restricting value length

Documentation:
- Added comprehensive README with examples and best practices
- Added CHANGELOG entry
- Created working example demonstrating both client and request-level identifiers

This feature enables important cross-cutting concerns like request tracing,
multi-tenant isolation, and authorization context propagation without
modifying the core protocol or breaking backward compatibility.
Implements the proposal from modelcontextprotocol/modelcontextprotocol#801
to enable passing contextual metadata through MCP tool calls, supporting
distributed tracing, multi-tenancy, and request correlation across the protocol boundary.

Core Implementation:
- Extends CallToolRequest schema with optional `identifiers` field for key-value string pairs
- Adds client-level identifiers in ClientOptions that automatically apply to all tool calls
- Implements merging logic in Client.callTool() combining client and request identifiers
- Provides server-side IdentifierForwardingConfig for controlling identifier processing
- Converts identifiers to X-MCP-* HTTP headers with kebab-to-Pascal-Case transformation
- Adds EnhancedRequestHandlerExtra interface with helper methods for tool implementations

Comprehensive Testing:
✅ Client-level identifiers only (trace-id, tenant-id forwarded correctly)
✅ Request-level identifiers only (request-id, user-id forwarded correctly)
✅ Identifier merging (4 identifiers: client + request combined successfully)
✅ Conflict resolution (request trace-id correctly overrode client value)
✅ Empty identifiers (no headers sent when empty objects provided)
✅ Backward compatibility (existing code works unchanged, no identifiers = no headers)
✅ Edge cases (long values, special characters, various naming patterns handled)
✅ Header format validation (proper X-MCP- prefix, case transformation)
✅ End-to-end HTTP forwarding (7 test scenarios, 5/5 validation tests passed)

Security Features:
- Identifier forwarding disabled by default, must be explicitly enabled
- Server configuration supports allowedKeys filtering, size limits
- Input validation and sanitization of identifier values
- No sensitive data mixing (identifiers for tracking, not authentication)

Documentation:
- Comprehensive README with examples, test coverage, and best practices
- CHANGELOG entry documenting all new features
- Working example demonstrating both client and request-level identifiers
- Complete test suite with automated validation

This feature enables critical cross-cutting concerns like request tracing,
multi-tenant isolation, and context propagation without modifying the core
protocol or breaking backward compatibility. All tests pass with 100% success rate.
Implements a comprehensive identifier forwarding system for distributed tracing,
multi-tenancy, and request correlation across MCP tool calls.

## Client-Side Features
- Add optional `identifiers` field to `ClientOptions` for client-wide identifiers
- Add optional `identifiers` field to `CallToolRequest` schema for per-request identifiers
- Implement identifier merging logic with request-level precedence over client-level
- Only include identifiers field in requests when non-empty (clean protocol)

## Server-Side Features
- Add `IdentifierForwardingConfig` to `ServerOptions` (disabled by default for security)
- Implement `forwardIdentifiersAsHeaders()` method for HTTP header conversion
- Add comprehensive server-side validation and security filtering:
  - Key format validation (alphanumeric, hyphens, underscores only)
  - ASCII-only value validation for HTTP header safety
  - Configurable count limits with deterministic truncation
  - Configurable value length limits
  - Optional whitelist filtering via `allowedKeys`
- Extend `EnhancedRequestHandlerExtra` interface with identifiers and helper methods

## Security & Performance
- Identifier forwarding disabled by default for security
- Multi-layer validation (client merging + server truncation + security filtering)
- Optimized validation order (cheapest checks first)
- Deterministic behavior via alphabetical sorting for cross-platform consistency
- Comprehensive input sanitization prevents header injection attacks

## Developer Experience
- Zero breaking changes - fully backward compatible
- Rich TypeScript types with proper generics
- Helper method `applyIdentifiersToRequestOptions()` for easy HTTP forwarding
- Comprehensive test suite with 11 test scenarios covering security edge cases
- Detailed documentation with usage examples and security best practices

## Example Usage
```typescript
// Client with default identifiers
const client = new Client(serverInfo, {
  identifiers: { "trace-id": "abc-123", "tenant-id": "org-456" }
});

// Server with forwarding enabled
const server = new McpServer(serverInfo, {
  identifierForwarding: {
    enabled: true,
    allowedKeys: ["trace-id", "tenant-id"]
  }
});

// Tool implementation with header forwarding
mcpServer.registerTool("api_call", config, async (args, extra) => {
  const options = extra.applyIdentifiersToRequestOptions({
    headers: { "Content-Type": "application/json" }
  });
  return fetch("https://api.example.com", options);
});
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