Skip to content

feat(mcp): add structured metadata field to MCP tool responses #51

@prosdev

Description

@prosdev

🎯 Problem

Currently, MCP tool responses embed operational metadata (like token counts) within the markdown content string:

{
  "action": "search",
  "content": "## Results...\n\n🪙 ~31 tokens"
}

Issues with current approach:

  • Token counts require string parsing to extract
  • No separation between user-facing content and operational metadata
  • Difficult to aggregate metrics across calls
  • Not machine-readable for optimization tracking
  • Cannot easily add new metadata without polluting content

🎯 Goal

Token counts are critical metadata for tracking optimization and adoption pain points. We need a structured way to expose this data to enable:

  • Performance monitoring and optimization tracking
  • Token reduction measurement
  • Client-side analytics and dashboards
  • Data quality and freshness indicators
  • Informed decision-making about search strategies

💡 Proposed Solution

Add a structured metadata field to all MCP tool responses with three tiers of implementation:

Tier 1: Initial Implementation (Core Metrics)

interface MCPMetadata {
  // Cost tracking
  tokens: number;                    // Approximate token count (documented)
  
  // Performance
  duration_ms: number;               // Response time for optimization
  timestamp: string;                 // ISO 8601 timestamp
  
  // Data freshness
  cached: boolean;                   // Was response from cache?
  
  // Data quality
  results_total?: number;            // Total matches found
  results_returned?: number;         // Results in this response
  results_truncated?: boolean;       // Were results cut off?
}

Example response:

{
  "action": "search",
  "query": "logger integration",
  "format": "compact",
  "content": "## GitHub Search Results\n**Query:** \"logger integration\"\n**Found:** 1 results\n- 🔵 × #6: Implement CLI and Integration Examples [18%]",
  "metadata": {
    "tokens": 31,
    "duration_ms": 142,
    "timestamp": "2025-11-25T10:30:00Z",
    "cached": false,
    "results_total": 1,
    "results_returned": 1,
    "results_truncated": false
  }
}

Tier 2: Near-Term Enhancements (Quality Indicators)

interface MCPMetadata {
  // ... Tier 1 fields ...
  
  // Search quality
  confidence?: number;               // 0-1 relevance score for semantic search
  threshold?: number;                // Search threshold applied
  
  // Index information
  index_size?: number;               // Total items in searchable corpus
  index_last_updated?: string;       // When index was last refreshed
  
  // Issues & warnings
  warnings?: Array<{
    code: string;                    // e.g., "INDEX_STALE"
    message: string;                 // Human-readable message
    severity: "info" | "warning" | "error";
  }>;
}

Example with warnings:

{
  "metadata": {
    "tokens": 31,
    "duration_ms": 142,
    "cached": true,
    "index_last_updated": "2025-11-23T08:00:00Z",
    "warnings": [
      {
        "code": "INDEX_STALE",
        "message": "Index is 2 days old, run 'dev-agent update'",
        "severity": "warning"
      }
    ]
  }
}

Tier 3: Future Enhancements (Debugging & Advanced)

interface MCPMetadata {
  // ... Tier 1 & 2 fields ...
  
  // Debugging
  request_id?: string;               // For log correlation
  tool_version?: string;             // MCP server version
  adapter?: string;                  // Which adapter handled request
  
  // Resource usage
  tokens_input?: number;             // Tokens in query
  tokens_output?: number;            // Tokens in response
  api_calls_made?: number;           // External API calls
  rate_limit_remaining?: number;     // GitHub/external API limits
  
  // Tool-specific context
  tool_context?: Record<string, unknown>;  // Flexible tool-specific data
}

📋 Implementation Tasks

Phase 1: Core Infrastructure (Tier 1)

1.1 Type Definitions

  • Define MCPMetadata interface in packages/mcp-server/src/server/types.ts
  • Update MCPResponse interfaces to include metadata field
  • Create utility types for metadata building

1.2 Update Formatters

Change all formatters to return structured data:

  • packages/mcp-server/src/formatters/search.ts
  • packages/mcp-server/src/formatters/github.ts
  • packages/mcp-server/src/formatters/plan.ts
  • packages/mcp-server/src/formatters/status.ts
  • packages/mcp-server/src/formatters/explore.ts

Return format:

return {
  content: formattedString,
  tokens: calculatedTokens  // Extract from current string format
};

1.3 Update Adapters

All adapters construct metadata:

  • github-adapter.ts - Add timing, result counts
  • search-adapter.ts - Add timing, result counts, confidence
  • plan-adapter.ts - Add timing, tasks generated
  • status-adapter.ts - Add timing, index info
  • explore-adapter.ts - Add timing, files analyzed

1.4 Remove Token Display from Content

  • Remove 🪙 ~XX tokens suffix from all formatter outputs
  • Clean up content formatting

1.5 Testing

  • Update all adapter tests to expect metadata field
  • Add tests for metadata structure validation
  • Verify token counts match previous display
  • Add integration tests for timing accuracy

Phase 2: Quality Enhancements (Tier 2)

  • Add confidence scoring to semantic search results
  • Track and expose index freshness
  • Implement warning system for stale data
  • Add threshold information to search metadata

Phase 3: Advanced Features (Tier 3)

  • Add request ID generation and tracking
  • Implement detailed token breakdowns
  • Add rate limit tracking for external APIs
  • Create tool-specific context fields

Documentation

  • Document that token counts are approximate
  • Update README with new response format and examples
  • Add migration guide for breaking change
  • Document all metadata fields with examples per tool
  • Update CURSOR_SETUP.md with metadata usage examples

🔧 Tool-Specific Examples

dev_search

{
  "action": "search",
  "metadata": {
    "tokens": 45,
    "duration_ms": 230,
    "cached": false,
    "results_total": 12,
    "results_returned": 10,
    "confidence": 0.82,
    "threshold": 0.7,
    "index_size": 1250
  }
}

dev_gh

{
  "action": "context",
  "metadata": {
    "tokens": 113,
    "duration_ms": 85,
    "cached": true,
    "index_last_updated": "2025-11-25T08:00:00Z",
    "tool_context": {
      "github_repo": "lytics/dev-agent",
      "issue_number": 6,
      "has_comments": true
    }
  }
}

dev_plan

{
  "action": "plan",
  "metadata": {
    "tokens": 450,
    "duration_ms": 1200,
    "cached": false,
    "tool_context": {
      "issue_number": 29,
      "related_files_analyzed": 15,
      "tasks_generated": 8,
      "detail_level": "detailed",
      "used_explorer": true
    }
  }
}

dev_explore

{
  "action": "pattern",
  "metadata": {
    "tokens": 280,
    "duration_ms": 450,
    "cached": false,
    "results_total": 24,
    "results_returned": 10,
    "confidence": 0.75,
    "tool_context": {
      "action": "pattern",
      "patterns_found": 12,
      "files_analyzed": 45
    }
  }
}

dev_status

{
  "action": "status",
  "metadata": {
    "tokens": 156,
    "duration_ms": 45,
    "cached": true,
    "index_size": 1250,
    "index_last_updated": "2025-11-25T08:00:00Z",
    "warnings": [
      {
        "code": "INDEX_STALE",
        "message": "Index is 2 days old",
        "severity": "warning"
      }
    ]
  }
}

✅ Benefits

  1. Machine Readable: No string parsing needed
  2. Type Safe: Structured data with proper TypeScript types
  3. Extensible: Three-tier approach allows gradual enhancement
  4. Standard Pattern: Follows industry best practices (GitHub, Stripe, GraphQL)
  5. Optimization Tracking: Easy to build analytics and monitoring
  6. Client Flexibility: Clients choose how/if to display metadata
  7. Data Quality: Users understand freshness and completeness
  8. Performance Insights: Track and optimize slow operations

🚨 Breaking Change Notice

This is a breaking change to the MCP response format.

Impact:

  • Clients parsing token counts from content string need updates
  • Response structure changes (adds metadata field)

Migration Path:

  1. Read from metadata.tokens instead of parsing content
  2. Use metadata.results_* fields for result information
  3. Content will be cleaner without token/result suffixes
  4. Clients can optionally display additional metadata

Timeline:

  • Phase 1 (Tier 1): Next minor version (breaking)
  • Phase 2 (Tier 2): Non-breaking additions to metadata
  • Phase 3 (Tier 3): Non-breaking additions to metadata

📊 Success Metrics

After Phase 1 implementation:

  • ✅ Track token reduction across tool improvements
  • ✅ Identify which tools/queries are most expensive
  • ✅ Build dashboards showing token usage trends
  • ✅ Make data-driven decisions about optimization priorities
  • ✅ Monitor response time performance
  • ✅ Track cache hit rates

After Phase 2:

  • ✅ Surface data freshness issues proactively
  • ✅ Help users understand search quality
  • ✅ Reduce support burden with better warnings

After Phase 3:

  • ✅ Full observability for debugging
  • ✅ Rate limit awareness and management
  • ✅ Detailed cost attribution

🎨 Design Decisions

Why metadata vs top-level fields?

  • Extensible for future fields without API changes
  • Semantic clarity - metadata about the response
  • Industry standard pattern
  • Groups related operational data

Remove tokens from content?

Yes - cleaner separation, no duplication, client control

Token precision?

Whole numbers documented as approximate - simpler and sufficient

Tiered approach?

Yes - ship incrementally, validate each tier, avoid over-engineering

Tool-specific vs uniform?

Hybrid - common base fields + optional tool_context for specific data

🔗 Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions