# 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"