Skip to main content
Glama

Context Pods

by conorluddy
validator.tsโ€ข9.71 kB
/** * MCP Protocol validator */ import { logger } from '@context-pods/core'; import { z } from 'zod'; import type { MCPValidationResult, TestResult } from '../types.js'; import { TestStatus } from '../types.js'; import { MCPMessageSchema, InitializeRequestSchema, InitializeResponseSchema, ErrorResponseSchema, JsonRpcRequestSchema, JsonRpcResponseSchema, } from './schemas.js'; /** * MCP Protocol Validator class */ export class MCPProtocolValidator { private errors: string[] = []; private warnings: string[] = []; /** * Validate a complete MCP server implementation */ async validateServer(serverPath: string): Promise<MCPValidationResult> { logger.info(`Validating MCP server at: ${serverPath}`); const result: MCPValidationResult = { valid: true, protocol: { handshake: await this.validateHandshake(serverPath), messageFormat: await this.validateMessageFormat(serverPath), jsonRpc: await this.validateJsonRpc(serverPath), }, errors: [], warnings: [], }; // Aggregate results const protocolTests = Object.values(result.protocol); result.valid = protocolTests.every((test) => test.status === TestStatus.PASSED); result.errors = this.errors; result.warnings = this.warnings; return result; } /** * Validate MCP handshake protocol */ private async validateHandshake(_serverPath: string): Promise<TestResult> { await Promise.resolve(); // Make function actually async const startTime = Date.now(); try { // Test initialize request/response const initRequest = { jsonrpc: '2.0', method: 'initialize', params: { protocolVersion: '1.0', capabilities: { tools: true, resources: true, prompts: true, }, clientInfo: { name: 'test-client', version: '1.0.0', }, }, id: 1, }; // Validate request format const requestValidation = InitializeRequestSchema.safeParse(initRequest); if (!requestValidation.success) { throw new Error(`Invalid initialize request: ${String(requestValidation.error?.message)}`); } // Simulate response validation const mockResponse = { jsonrpc: '2.0', result: { protocolVersion: '1.0', capabilities: { tools: { listTools: true, callTool: true }, resources: { listResources: true, readResource: true }, prompts: { listPrompts: true, getPrompt: true }, }, serverInfo: { name: 'test-server', version: '1.0.0', }, }, id: 1, }; const responseValidation = InitializeResponseSchema.safeParse(mockResponse); if (!responseValidation.success) { throw new Error(`Invalid initialize response: ${responseValidation.error.message}`); } return { name: 'MCP Handshake Protocol', status: 'passed' as TestStatus, duration: Date.now() - startTime, }; } catch (error) { this.errors.push(`Handshake validation failed: ${String(error)}`); return { name: 'MCP Handshake Protocol', status: 'failed' as TestStatus, duration: Date.now() - startTime, error: error instanceof Error ? error.message : String(error), }; } } /** * Validate message format compliance */ private async validateMessageFormat(_serverPath: string): Promise<TestResult> { await Promise.resolve(); // Make function actually async const startTime = Date.now(); try { // Test various message formats const testMessages = [ { jsonrpc: '2.0', method: 'tools/list', id: 1, }, { jsonrpc: '2.0', method: 'resources/list', id: 2, }, { jsonrpc: '2.0', method: 'prompts/list', id: 3, }, ]; for (const message of testMessages) { const validation = MCPMessageSchema.safeParse(message); if (!validation.success) { throw new Error(`Invalid message format: ${String(validation.error?.message)}`); } } return { name: 'MCP Message Format', status: 'passed' as TestStatus, duration: Date.now() - startTime, }; } catch (error) { this.errors.push(`Message format validation failed: ${String(error)}`); return { name: 'MCP Message Format', status: 'failed' as TestStatus, duration: Date.now() - startTime, error: error instanceof Error ? error.message : String(error), }; } } /** * Validate JSON-RPC 2.0 compliance */ private async validateJsonRpc(_serverPath: string): Promise<TestResult> { await Promise.resolve(); // Make function actually async const startTime = Date.now(); try { // Test JSON-RPC request format const validRequest = { jsonrpc: '2.0', method: 'test', params: { foo: 'bar' }, id: 1, }; const requestValidation = JsonRpcRequestSchema.safeParse(validRequest); if (!requestValidation.success) { throw new Error(`Invalid JSON-RPC request: ${String(requestValidation.error?.message)}`); } // Test JSON-RPC response format const validResponse = { jsonrpc: '2.0', result: { data: 'test' }, id: 1, }; const responseValidation = JsonRpcResponseSchema.safeParse(validResponse); if (!responseValidation.success) { throw new Error(`Invalid JSON-RPC response: ${String(responseValidation.error?.message)}`); } // Test error response const errorResponse = { jsonrpc: '2.0', error: { code: -32601, message: 'Method not found', }, id: 1, }; const errorValidation = ErrorResponseSchema.safeParse(errorResponse); if (!errorValidation.success) { throw new Error(`Invalid error response: ${errorValidation.error.message}`); } return { name: 'JSON-RPC 2.0 Compliance', status: 'passed' as TestStatus, duration: Date.now() - startTime, }; } catch (error) { this.errors.push(`JSON-RPC validation failed: ${String(error)}`); return { name: 'JSON-RPC 2.0 Compliance', status: 'failed' as TestStatus, duration: Date.now() - startTime, error: error instanceof Error ? error.message : String(error), }; } } /** * Validate a single MCP message */ validateMessage(message: unknown): { valid: boolean; type?: string; errors?: string[]; } { try { const result = MCPMessageSchema.safeParse(message); if (!result.success) { return { valid: false, errors: result.error.issues.map((e) => e.message), }; } // Determine message type const msg = result.data as Record<string, unknown>; let type = 'unknown'; if (msg.method) { type = `request:${msg.method as string}`; } else if (msg.result) { type = 'response'; } else if (msg.error) { type = 'error'; } return { valid: true, type, }; } catch (error) { return { valid: false, errors: [error instanceof Error ? error.message : String(error)], }; } } /** * Validate tool declaration */ validateToolDeclaration(tool: unknown): boolean { const schema = z.object({ name: z.string().min(1), description: z.string().optional(), inputSchema: z.object({ type: z.literal('object'), properties: z.record(z.string(), z.unknown()).optional(), required: z.array(z.string()).optional(), additionalProperties: z.boolean().optional(), }), }); const result = schema.safeParse(tool); if (!result.success) { this.warnings.push(`Invalid tool declaration: ${result.error.message}`); return false; } return true; } /** * Validate resource declaration */ validateResourceDeclaration(resource: unknown): boolean { const schema = z.object({ uri: z.string().min(1), name: z.string().min(1), description: z.string().optional(), mimeType: z.string().optional(), }); const result = schema.safeParse(resource); if (!result.success) { this.warnings.push(`Invalid resource declaration: ${result.error.message}`); return false; } // Validate URI format const uri = (resource as Record<string, unknown>).uri as string; if (!uri?.includes('://')) { this.warnings.push(`Resource URI should include protocol: ${String(uri)}`); } return true; } /** * Validate prompt declaration */ validatePromptDeclaration(prompt: unknown): boolean { const schema = z.object({ name: z.string().min(1), description: z.string().optional(), arguments: z .array( z.object({ name: z.string().min(1), description: z.string().optional(), required: z.boolean().optional(), }), ) .optional(), }); const result = schema.safeParse(prompt); if (!result.success) { this.warnings.push(`Invalid prompt declaration: ${result.error.message}`); return false; } return true; } /** * Clear accumulated errors and warnings */ clearValidation(): void { this.errors = []; this.warnings = []; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/conorluddy/ContextPods'

If you have feedback or need assistance with the MCP directory API, please join our Discord server