/**
* Protocol Helpers
*
* Utilities for validating MCP protocol compliance and common test patterns.
*/
import { JsonRpcResponse, JsonRpcMessage } from './mcp-test-client';
/**
* MCP Error Codes
*/
export enum MCPErrorCode {
InvalidRequest = -32600,
MethodNotFound = -32601,
InvalidParams = -32602,
InternalError = -32603,
// MCP-specific error codes
Cancelled = -32800,
ResourceNotFound = -32801,
ResourceUnavailable = -32802,
}
/**
* Validate JSON-RPC 2.0 message structure
*/
export function isValidJsonRpcMessage(message: any): message is JsonRpcMessage {
return (
typeof message === 'object' &&
message !== null &&
message.jsonrpc === '2.0' &&
(
// Request
('id' in message && typeof message.method === 'string') ||
// Response
('id' in message && ('result' in message || 'error' in message)) ||
// Notification
(!('id' in message) && typeof message.method === 'string')
)
);
}
/**
* Validate JSON-RPC response structure
*/
export function isValidJsonRpcResponse(message: any): message is JsonRpcResponse {
return (
isValidJsonRpcMessage(message) &&
'id' in message &&
('result' in message || 'error' in message)
);
}
/**
* Validate MCP initialize response
*/
export function validateInitializeResponse(response: any): void {
expect(response).toHaveProperty('protocolVersion');
expect(response).toHaveProperty('capabilities');
expect(response).toHaveProperty('serverInfo');
// Validate server info
expect(response.serverInfo).toHaveProperty('name');
expect(response.serverInfo).toHaveProperty('version');
// Validate capabilities
expect(response.capabilities).toBeInstanceOf(Object);
}
/**
* Validate tools/list response
*/
export function validateToolsListResponse(response: any): void {
expect(response).toHaveProperty('tools');
expect(Array.isArray(response.tools)).toBe(true);
response.tools.forEach((tool: any) => {
expect(tool).toHaveProperty('name');
expect(tool).toHaveProperty('description');
expect(tool).toHaveProperty('inputSchema');
expect(typeof tool.name).toBe('string');
expect(typeof tool.description).toBe('string');
expect(typeof tool.inputSchema).toBe('object');
});
}
/**
* Validate prompts/list response
*/
export function validatePromptsListResponse(response: any): void {
expect(response).toHaveProperty('prompts');
expect(Array.isArray(response.prompts)).toBe(true);
response.prompts.forEach((prompt: any) => {
expect(prompt).toHaveProperty('name');
expect(prompt).toHaveProperty('description');
expect(typeof prompt.name).toBe('string');
expect(typeof prompt.description).toBe('string');
if (prompt.arguments) {
expect(Array.isArray(prompt.arguments)).toBe(true);
}
});
}
/**
* Validate tools/call response
*/
export function validateToolCallResponse(response: any): void {
expect(response).toHaveProperty('content');
expect(Array.isArray(response.content)).toBe(true);
response.content.forEach((item: any) => {
expect(item).toHaveProperty('type');
expect(typeof item.type).toBe('string');
});
}
/**
* Validate prompts/get response
*/
export function validatePromptGetResponse(response: any): void {
expect(response).toHaveProperty('messages');
expect(Array.isArray(response.messages)).toBe(true);
response.messages.forEach((message: any) => {
expect(message).toHaveProperty('role');
expect(message).toHaveProperty('content');
expect(['user', 'assistant', 'system']).toContain(message.role);
});
}
/**
* Validate JSON-RPC error response
*/
export function validateErrorResponse(response: any, expectedCode?: number): void {
expect(response).toHaveProperty('error');
expect(response.error).toHaveProperty('code');
expect(response.error).toHaveProperty('message');
expect(typeof response.error.code).toBe('number');
expect(typeof response.error.message).toBe('string');
if (expectedCode !== undefined) {
expect(response.error.code).toBe(expectedCode);
}
}
/**
* Create tool call arguments for ask-one-question
*/
export function createAskOneQuestionArgs(question: string): any {
return {
question: question.trim()
};
}
/**
* Create tool call arguments for ask-multiple-choice
*/
export function createAskMultipleChoiceArgs(questions: Array<{ text: string; options: string[] }>): any {
return {
questions: questions.map(q => ({
text: q.text.trim(),
options: q.options.map(opt => opt.trim())
}))
};
}
/**
* Create tool call arguments for challenge-hypothesis
*/
export function createChallengeHypothesisArgs(title: string, description: string, hypotheses: string[]): any {
return {
title: title.trim(),
description: description.trim(),
hypotheses: hypotheses.map(h => h.trim())
};
}
/**
* Create tool call arguments for choose-next
*/
export function createChooseNextArgs(title: string, description: string, options: Array<{
id: string;
title: string;
description: string;
icon?: string;
}>): any {
return {
title: title.trim(),
description: description.trim(),
options: options.map(opt => ({
id: opt.id.trim(),
title: opt.title.trim(),
description: opt.description.trim(),
icon: opt.icon || '📋'
}))
};
}
/**
* Create human-decision prompt arguments
*/
export function createHumanDecisionPromptArgs(context: string, options?: string, urgency?: string, domain?: string): any {
const args: any = {
context: context.trim()
};
if (options) args.options = options.trim();
if (urgency) args.urgency = urgency;
if (domain) args.domain = domain.trim();
return args;
}
/**
* Create expert-consultation prompt arguments
*/
export function createExpertConsultationPromptArgs(
topic: string,
expertiseArea: string,
specificQuestion?: string,
backgroundInfo?: string,
expectedOutcome?: string
): any {
const args: any = {
topic: topic.trim(),
expertise_area: expertiseArea.trim()
};
if (specificQuestion) args.specific_question = specificQuestion.trim();
if (backgroundInfo) args.background_info = backgroundInfo.trim();
if (expectedOutcome) args.expected_outcome = expectedOutcome.trim();
return args;
}
/**
* Create creative-brainstorm prompt arguments
*/
export function createCreativeBrainstormPromptArgs(
challenge: string,
goal?: string,
constraints?: string,
targetAudience?: string,
inspirationSources?: string,
ideaCount?: number
): any {
const args: any = {
challenge: challenge.trim()
};
if (goal) args.goal = goal.trim();
if (constraints) args.constraints = constraints.trim();
if (targetAudience) args.target_audience = targetAudience.trim();
if (inspirationSources) args.inspiration_sources = inspirationSources.trim();
if (ideaCount) args.idea_count = ideaCount;
return args;
}
/**
* Create suggest-follow-up-questions prompt arguments
*/
export function createSuggestFollowUpPromptArgs(
originalQuestion: string,
response: string,
goal?: string,
responseType?: string
): any {
const args: any = {
originalQuestion: originalQuestion.trim(),
response: response.trim()
};
if (goal) args.goal = goal.trim();
if (responseType) args.responseType = responseType;
return args;
}
/**
* Wait for a condition with timeout
*/
export async function waitFor(
condition: () => boolean | Promise<boolean>,
timeoutMs = 5000,
intervalMs = 100
): Promise<void> {
const startTime = Date.now();
while (Date.now() - startTime < timeoutMs) {
if (await condition()) {
return;
}
await new Promise(resolve => setTimeout(resolve, intervalMs));
}
throw new Error(`Timeout waiting for condition after ${timeoutMs}ms`);
}
/**
* Sleep for specified milliseconds
*/
export function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));
}