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):
// 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):
// 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
{
"experimental": {
"toolValidation": {
"supported": true
}
}
}Advanced Options
{
"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:
{
"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:
{
"valid": true,
"errors": [],
"warnings": ["Optional warning messages"],
"suggestions": ["Optional improvement suggestions"]
}Implementation Guide
For MCP Servers
1. Announce Capability in Initialize Response:
// 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:
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:
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:
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.toolValidationcapability - 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
experimentalto 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:
// 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:
// 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
{
"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
{
"experimental": {
"toolValidation": {
"supported": true,
"batchValidation": true, // Can validate multiple tools at once
"maxBatchSize": 10
}
}
}Validation Caching
{
"experimental": {
"toolValidation": {
"supported": true,
"caching": {
"enabled": true,
"ttl": 3600, // seconds
"invalidateOn": ["resource_change", "tool_update"]
}
}
}
}Adoption Strategy
For NCP
- ✅ Implement in internal MCPs (scheduler)
- ✅ Update orchestrator to check capability
- ✅ Document for MCP developers
- ✅ Provide examples and templates
For MCP Ecosystem
- Share proposal with MCP maintainers
- Get feedback from MCP developers
- Create reference implementations
- Submit to MCP specification (if successful)
For Scheduler
- ✅ Use
validatetool as fallback - ✅ Add capability check to ToolValidator
- ✅ Skip validation attempt if capability not announced
- ✅ Log when falling back to schema-only
Open Questions
Should validation be synchronous only?
- Pro: Simpler to implement
- Con: Some checks need async (network calls)
- Proposal: Support both, indicate via capability
Should we version the validation protocol?
- Pro: Allows evolution
- Con: More complexity
- Proposal: Add
version: "1.0"to capability
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:
- Clients can detect support instantly
- No wasted round-trips on unsupported MCPs
- Clear separation of concerns (capability vs validation result)
- Follows established MCP patterns
- 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:
- ✅ ToolValidator checks capabilities before attempting validation (lines 184-193 in tool-validator.ts)
- ✅ SchedulerMCP announces toolValidation capability (lines 32-39 in scheduler.ts)
- ✅ InternalMCPManager provides capability checking (hasCapability method with dot-notation support)
- ✅ Documentation complete for external MCP developers
How It Works:
- Scheduler announces
experimental.toolValidation.supported: trueduring 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"