Skip to content

MCP Validation Capability

Overview

Instead of assuming a validate tool exists, MCPs should announce validation support via capabilities during initialization. This follows MCP protocol patterns and allows clients to detect support programmatically.

Problem with Tool-Based Approach

Current (suboptimal):

typescript
// Client has to try calling validate and handle errors
try {
  await mcp.call('validate', params);
} catch (error) {
  // Does this MCP support validation? Who knows!
  // Fall back to schema-only validation
}

Issues:

  • ❌ No way to know if MCP supports validation
  • ❌ Must attempt and handle failure
  • ❌ Wastes round-trip if not supported
  • ❌ Error handling is ambiguous (not supported vs validation failed)

Solution: Capabilities-Based Announcement

Proposed (better):

typescript
// Server announces during initialization
{
  "jsonrpc": "2.0",
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "tools": {},
      "experimental": {
        "toolValidation": {
          "supported": true,
          "method": "validate"  // Optional: custom validation tool name
        }
      }
    },
    "serverInfo": {
      "name": "my-mcp",
      "version": "1.0.0"
    }
  }
}

// Client checks capability first
if (mcp.capabilities.experimental?.toolValidation?.supported) {
  // MCP supports validation - use it!
  const result = await mcp.call('validate', params);
} else {
  // Fall back to schema-only validation
  validateAgainstSchema(params);
}

Benefits:

  • ✅ Client knows support before attempting call
  • ✅ No wasted round-trips
  • ✅ Clear separation: not supported vs validation failed
  • ✅ Follows MCP protocol patterns
  • ✅ Backward compatible (experimental capabilities)

Capability Schema

Basic Support

typescript
{
  "experimental": {
    "toolValidation": {
      "supported": true
    }
  }
}

Advanced Options

typescript
{
  "experimental": {
    "toolValidation": {
      "supported": true,
      "method": "validate",           // Default: "validate"
      "async": true,                  // Supports async validation
      "streaming": false,             // Supports streaming validation
      "contextAware": true,           // Can validate based on current context
      "cacheable": true               // Validation results can be cached
    }
  }
}

Validation Tool Specification

When toolValidation.supported = true, the MCP MUST implement a validation tool:

Tool Name: validate (or custom name specified in method)

Input Schema:

json
{
  "type": "object",
  "properties": {
    "tool": {
      "type": "string",
      "description": "Tool name to validate (without MCP prefix)"
    },
    "arguments": {
      "type": "object",
      "description": "Tool arguments to validate"
    }
  },
  "required": ["tool", "arguments"]
}

Output Format:

json
{
  "valid": true,
  "errors": [],
  "warnings": ["Optional warning messages"],
  "suggestions": ["Optional improvement suggestions"]
}

Implementation Guide

For MCP Servers

1. Announce Capability in Initialize Response:

typescript
// In your MCP server initialization
server.setRequestHandler('initialize', async (request) => {
  return {
    protocolVersion: '2024-11-05',
    capabilities: {
      tools: {},
      experimental: {
        toolValidation: {
          supported: true
        }
      }
    },
    serverInfo: {
      name: 'my-mcp',
      version: '1.0.0'
    }
  };
});

2. Implement Validate Tool:

typescript
server.setRequestHandler('tools/call', async (request) => {
  if (request.params.name === 'validate') {
    const { tool, arguments: args } = request.params.arguments;

    // Perform validation
    const errors = [];
    const warnings = [];

    if (tool === 'write_file') {
      if (!args.path) errors.push('Missing required parameter: path');
      if (args.path && !isWritable(args.path)) {
        errors.push(`Path not writable: ${args.path}`);
      }
      if (!args.content) warnings.push('Empty content will create empty file');
    }

    return {
      content: [{
        type: 'text',
        text: JSON.stringify({
          valid: errors.length === 0,
          errors,
          warnings
        })
      }]
    };
  }

  // Handle other tools...
});

For MCP Clients

1. Check Capability During Connection:

typescript
class MCPClient {
  private validationSupported: boolean = false;

  async connect() {
    const initResult = await this.initialize();

    // Check if server supports validation
    this.validationSupported =
      initResult.capabilities.experimental?.toolValidation?.supported === true;

    console.log(`Validation supported: ${this.validationSupported}`);
  }

  async validateTool(tool: string, args: any): Promise<ValidationResult> {
    if (!this.validationSupported) {
      // Fall back to schema-only validation
      return this.validateAgainstSchema(tool, args);
    }

    // Use MCP-native validation
    const result = await this.callTool('validate', {
      tool,
      arguments: args
    });

    return JSON.parse(result.content[0].text);
  }
}

2. Graceful Degradation:

typescript
async function scheduleJob(mcp: MCPClient, tool: string, args: any) {
  let validationResult;

  if (mcp.supportsValidation()) {
    // Use MCP-native validation (deep checks)
    validationResult = await mcp.validateTool(tool, args);
  } else {
    // Fall back to schema-only validation
    validationResult = await validateAgainstSchema(tool, args);
  }

  if (!validationResult.valid) {
    throw new Error(`Validation failed: ${validationResult.errors.join(', ')}`);
  }

  // Schedule the job...
}

Migration Path

Phase 1: Experimental (Current)

  • Use experimental.toolValidation capability
  • MCPs opt-in by announcing support
  • Clients detect and use if available
  • Gather feedback and iterate

Phase 2: Standardization (Future)

If widely adopted:

  • Propose to MCP protocol maintainers
  • Move from experimental to standard capability
  • Add to official MCP specification
  • Version: MCP 2025-XX-XX

Phase 3: Ecosystem Adoption

  • Major MCPs implement validation
  • Clients expect validation support
  • Becomes de facto standard

Reference Implementation

See NCP's internal Scheduler MCP for reference:

Server Side:

typescript
// src/internal-mcps/scheduler.ts
export class SchedulerMCP implements InternalMCP {
  name = 'scheduler';

  // Announce capability
  capabilities = {
    experimental: {
      toolValidation: {
        supported: true
      }
    }
  };

  // Implement validate tool
  async executeTool(name: string, params: any) {
    if (name === 'validate') {
      return this.handleValidate(params);
    }
    // ... other tools
  }

  private async handleValidate(params: any) {
    const { tool, arguments: args } = params;
    const errors = [];

    // Tool-specific validation logic
    if (tool === 'schedule') {
      if (!args.name) errors.push('Missing required parameter: name');
      if (!args.schedule) errors.push('Missing required parameter: schedule');
      // ... more validation
    }

    return {
      success: true,
      content: JSON.stringify({
        valid: errors.length === 0,
        errors,
        warnings: []
      })
    };
  }
}

Client Side:

typescript
// src/services/scheduler/tool-validator.ts
export class ToolValidator {
  async validateTool(tool: string, parameters: any): Promise<ValidationResult> {
    const [mcpName, toolName] = tool.split(':');

    // Check if MCP supports validation capability
    if (orchestrator.hasCapability(mcpName, 'experimental.toolValidation')) {
      // Use MCP-native validation
      const result = await orchestrator.executeTool(
        `${mcpName}:validate`,
        { tool: toolName, arguments: parameters }
      );

      return JSON.parse(result.content);
    }

    // Fall back to schema-only validation
    return this.validateAgainstSchema(tool, parameters);
  }
}

Benefits

For MCP Developers

  • ✅ Clear API contract
  • ✅ Discoverable via capabilities
  • ✅ Optional (experimental capabilities)
  • ✅ Backward compatible

For MCP Clients

  • ✅ No guessing if validation exists
  • ✅ Faster detection (no failed attempts)
  • ✅ Clear error handling
  • ✅ Graceful degradation

For End Users

  • ✅ Better error messages before execution
  • ✅ Fewer runtime failures
  • ✅ More reliable scheduled jobs
  • ✅ Improved developer experience

Comparison

Before (Tool-Only Approach)

Client: "Does MCP support validation?"
[Attempts to call validate tool]
[Gets error - but why? Not supported? Or validation failed?]
[Client has to guess]

After (Capability-Based Approach)

Client: "Does MCP support validation?"
[Checks capabilities.experimental.toolValidation]
[Knows immediately: supported or not]
[Makes informed decision]

Future Extensions

Validation Levels

typescript
{
  "experimental": {
    "toolValidation": {
      "supported": true,
      "levels": {
        "syntax": true,      // Basic syntax/type checking
        "semantic": true,    // Logical correctness
        "runtime": true,     // Runtime checks (file exists, etc.)
        "security": false    // Security policy checks
      }
    }
  }
}

Batch Validation

typescript
{
  "experimental": {
    "toolValidation": {
      "supported": true,
      "batchValidation": true,  // Can validate multiple tools at once
      "maxBatchSize": 10
    }
  }
}

Validation Caching

typescript
{
  "experimental": {
    "toolValidation": {
      "supported": true,
      "caching": {
        "enabled": true,
        "ttl": 3600,  // seconds
        "invalidateOn": ["resource_change", "tool_update"]
      }
    }
  }
}

Adoption Strategy

For NCP

  1. ✅ Implement in internal MCPs (scheduler)
  2. ✅ Update orchestrator to check capability
  3. ✅ Document for MCP developers
  4. ✅ Provide examples and templates

For MCP Ecosystem

  1. Share proposal with MCP maintainers
  2. Get feedback from MCP developers
  3. Create reference implementations
  4. Submit to MCP specification (if successful)

For Scheduler

  1. ✅ Use validate tool as fallback
  2. ✅ Add capability check to ToolValidator
  3. ✅ Skip validation attempt if capability not announced
  4. ✅ Log when falling back to schema-only

Open Questions

  1. Should validation be synchronous only?

    • Pro: Simpler to implement
    • Con: Some checks need async (network calls)
    • Proposal: Support both, indicate via capability
  2. Should we version the validation protocol?

    • Pro: Allows evolution
    • Con: More complexity
    • Proposal: Add version: "1.0" to capability
  3. Should validation results be cacheable?

    • Pro: Performance improvement
    • Con: May become stale
    • Proposal: Optional, controlled by MCP

Conclusion

Use capabilities, not just tools!

The MCP protocol provides experimental capabilities specifically for this use case. By announcing toolValidation support during initialization:

  1. Clients can detect support instantly
  2. No wasted round-trips on unsupported MCPs
  3. Clear separation of concerns (capability vs validation result)
  4. Follows established MCP patterns
  5. Paves path for standardization

Recommendation: Implement both the capability announcement AND the validate tool. The capability tells clients what to expect, and the tool provides the actual validation.


Status: ✅ Implemented in NCP v1.6.0 Implementation Complete:

  1. ✅ ToolValidator checks capabilities before attempting validation (lines 184-193 in tool-validator.ts)
  2. ✅ SchedulerMCP announces toolValidation capability (lines 32-39 in scheduler.ts)
  3. ✅ InternalMCPManager provides capability checking (hasCapability method with dot-notation support)
  4. ✅ Documentation complete for external MCP developers

How It Works:

  • Scheduler announces experimental.toolValidation.supported: true during initialization
  • ToolValidator checks capability via internalMCPManager.hasCapability() before calling validate tool
  • If capability not announced, falls back to schema-only validation
  • No wasted round-trips, clear separation of "not supported" vs "validation failed"

Released under the Elastic License 2.0.