mcp-server-tester-sse-http-stdio
Version:
MCP Server Tester with SSE support - Test MCP servers using HTTP, SSE, and STDIO transports
268 lines (267 loc) • 13.8 kB
JavaScript
/**
* Simplified Prompts tests leveraging SDK error detection
* Focuses on business logic validation while SDK handles protocol compliance
*/
import { DiagnosticTest } from '../DiagnosticTest.js';
import { SdkErrorDetector } from '../SdkErrorDetector.js';
import { TEST_SEVERITY, ISSUE_TYPE, } from '../types.js';
import { ErrorCode } from '@modelcontextprotocol/sdk/types.js';
export class PromptsCapabilityTest extends DiagnosticTest {
name = 'Server Features: Prompts - Capability Declaration';
description = 'Verify server declares prompts capability using SDK validation';
category = 'server-features';
feature = 'prompts';
severity = TEST_SEVERITY.CRITICAL;
requiredCapability = 'prompts';
mcpSpecSection = 'Server Features - Prompts';
async execute(client, config) {
// Use SDK to validate prompts capability - it handles all protocol validation
const sdkTests = [
{
name: 'Prompts Capability Declaration',
description: 'Validate prompts capability through SDK',
category: 'server-features',
feature: 'prompts',
severity: TEST_SEVERITY.CRITICAL,
operation: async (client) => await client.sdk.listPrompts(),
expectsError: false,
},
];
try {
const sdkResults = await SdkErrorDetector.executeTests(client, config, sdkTests);
const diagnosticResults = SdkErrorDetector.convertTodiagnosticResults(sdkResults);
const failedTests = diagnosticResults.filter(r => r.status === 'failed');
if (failedTests.length === 0) {
const result = await client.sdk.listPrompts();
const promptCount = result.prompts?.length || 0;
return this.createResult(true, `Prompts capability validated via SDK (${promptCount} prompts found)`, { promptCount, sdkValidated: true });
}
else {
return this.createEnhancedResult({
success: false,
message: 'SDK detected prompts capability issues',
details: { failedTests: failedTests.length, results: diagnosticResults },
issueType: failedTests[0].issueType || ISSUE_TYPE.CRITICAL_FAILURE,
expected: 'Valid prompts capability declaration',
actual: 'SDK validation failed',
fixInstructions: failedTests.flatMap(t => t.recommendations || []),
specLinks: ['https://spec.modelcontextprotocol.io/specification/server/prompts/'],
});
}
}
catch (error) {
return this.createResult(false, 'Prompts capability validation failed', { error: error instanceof Error ? error.message : String(error) }, ['Check server prompts capability implementation', 'Verify server connection']);
}
}
}
export class PromptSchemaValidationTest extends DiagnosticTest {
name = 'Server Features: Prompts - Schema Validation';
description = 'Validate prompt schema compliance and quality (business logic)';
category = 'server-features';
feature = 'prompts';
severity = TEST_SEVERITY.WARNING;
requiredCapability = 'prompts';
mcpSpecSection = 'Server Features - Prompts';
async execute(client, _config) {
try {
// SDK handles the listPrompts call validation, we focus on business logic
const result = await client.sdk.listPrompts();
const prompts = result.prompts || [];
if (prompts.length === 0) {
return this.createResult(false, 'No prompts available for schema validation', {
promptCount: 0,
});
}
const issues = [];
const warnings = [];
const validations = [];
// Business logic validation (not protocol validation)
prompts.forEach((prompt, index) => {
// Check for required business fields
if (!prompt.name || prompt.name.trim().length === 0) {
issues.push(`Prompt ${index}: Empty name`);
}
else if (prompt.name.length > 100) {
warnings.push(`Prompt ${index} (${prompt.name}): Name very long (${prompt.name.length} chars)`);
}
else {
validations.push(`Prompt ${index} (${prompt.name}): Valid name`);
}
if (!prompt.description || prompt.description.trim().length === 0) {
warnings.push(`Prompt ${index} (${prompt.name}): Missing description`);
}
else if (prompt.description.length < 10) {
warnings.push(`Prompt ${index} (${prompt.name}): Very short description`);
}
else if (prompt.description.length > 500) {
warnings.push(`Prompt ${index} (${prompt.name}): Very long description (${prompt.description.length} chars)`);
}
else {
validations.push(`Prompt ${index} (${prompt.name}): Good description`);
}
// Check for argument schema quality
if (prompt.arguments) {
if (Array.isArray(prompt.arguments) && prompt.arguments.length > 0) {
const argCount = prompt.arguments.length;
validations.push(`Prompt ${index} (${prompt.name}): Has ${argCount} arguments`);
// Check argument quality
prompt.arguments.forEach((arg, argIndex) => {
if (!arg.name || !arg.description) {
warnings.push(`Prompt ${index} (${prompt.name}): Argument ${argIndex} missing name or description`);
}
});
}
else {
validations.push(`Prompt ${index} (${prompt.name}): No arguments required`);
}
}
});
// Check for duplicate names (business logic)
const names = prompts.map(p => p.name);
const duplicates = names.filter((name, index) => names.indexOf(name) !== index);
if (duplicates.length > 0) {
issues.push(`Duplicate prompt names: ${duplicates.join(', ')}`);
}
const hasIssues = issues.length > 0;
const message = hasIssues
? `Prompt schema validation found ${issues.length} issues, ${warnings.length} warnings`
: warnings.length > 0
? `Prompt schemas valid with ${warnings.length} improvement suggestions`
: `All ${prompts.length} prompt schemas are well-formed`;
return this.createResult(!hasIssues, message, {
promptCount: prompts.length,
issues,
warnings,
validations: validations.slice(0, 10), // Limit output
}, hasIssues || warnings.length > 0
? [...issues.map(i => `Fix: ${i}`), ...warnings.map(w => `Consider: ${w}`)]
: undefined);
}
catch (error) {
return this.createResult(false, 'Prompt schema validation failed', { error: error instanceof Error ? error.message : String(error) }, ['Check prompts/list implementation', 'Verify prompt schema format']);
}
}
}
export class PromptRetrievalTest extends DiagnosticTest {
name = 'Server Features: Prompts - Retrieval (prompts/get)';
description = 'Test prompt retrieval using SDK error detection';
category = 'server-features';
feature = 'prompts';
severity = TEST_SEVERITY.WARNING;
requiredCapability = 'prompts';
async execute(client, config) {
try {
// Get available prompts via SDK
const promptsResult = await client.sdk.listPrompts();
const prompts = promptsResult.prompts || [];
if (prompts.length === 0) {
return this.createResult(false, 'No prompts available for retrieval test', {
promptCount: 0,
});
}
// Test retrieval with SDK error detection
const sdkTests = [
{
name: 'Invalid Prompt Name Error Handling',
description: 'Test error handling for non-existent prompts',
category: 'server-features',
feature: 'prompts',
severity: TEST_SEVERITY.WARNING,
operation: async (client) => {
return await client.sdk.getPrompt({
name: 'definitely_non_existent_prompt_xyz_12345',
arguments: {},
});
},
expectsError: true,
expectedErrorCode: ErrorCode.InvalidParams,
},
];
const sdkResults = await SdkErrorDetector.executeTests(client, config, sdkTests);
const diagnosticResults = SdkErrorDetector.convertTodiagnosticResults(sdkResults);
const failedTests = diagnosticResults.filter(r => r.status === 'failed');
const passedTests = diagnosticResults.filter(r => r.status === 'passed');
if (failedTests.length === 0) {
return this.createResult(true, `Prompt retrieval error handling validated via SDK`, {
availablePrompts: prompts.length,
passedTests: passedTests.length,
sdkValidated: true,
});
}
else {
return this.createEnhancedResult({
success: false,
message: `SDK detected ${failedTests.length} prompt retrieval issues`,
details: {
availablePrompts: prompts.length,
failedTests: failedTests.length,
passedTests: passedTests.length,
results: diagnosticResults,
},
issueType: failedTests[0].issueType || ISSUE_TYPE.SPEC_WARNING,
expected: 'Proper prompt retrieval error handling',
actual: `${failedTests.length} retrieval issues detected by SDK`,
fixInstructions: failedTests.flatMap(t => t.recommendations || []),
specLinks: ['https://spec.modelcontextprotocol.io/specification/server/prompts/'],
});
}
}
catch (error) {
return this.createResult(false, 'Prompt retrieval test failed', { error: error instanceof Error ? error.message : String(error) }, ['Check prompts/get implementation', 'Verify prompt retrieval logic']);
}
}
}
export class PromptArgumentValidationTest extends DiagnosticTest {
name = 'Server Features: Prompts - Argument Validation';
description = 'Check for proper argument handling (business logic)';
category = 'server-features';
feature = 'prompts';
severity = TEST_SEVERITY.INFO;
requiredCapability = 'prompts';
async execute(client, _config) {
try {
// SDK handles the protocol validation, we check business logic
const result = await client.sdk.listPrompts();
const prompts = result.prompts || [];
if (prompts.length === 0) {
return this.createResult(false, 'No prompts available for argument validation checking', {
promptCount: 0,
});
}
const suggestions = [];
let argumentCount = 0;
let wellDocumentedCount = 0;
prompts.forEach((prompt) => {
// Check for argument usage (business logic, not spec requirements)
if (prompt.arguments && Array.isArray(prompt.arguments)) {
argumentCount += prompt.arguments.length;
const wellDocumented = prompt.arguments.every(arg => arg.name && arg.description && arg.name.length > 0 && arg.description.length > 5);
if (wellDocumented) {
wellDocumentedCount++;
}
else {
suggestions.push(`Prompt '${prompt.name}': Consider improving argument documentation`);
}
}
else if (prompt.name.toLowerCase().includes('arg') ||
prompt.description?.toLowerCase().includes('parameter')) {
suggestions.push(`Prompt '${prompt.name}': Might benefit from structured arguments`);
}
});
const message = suggestions.length === 0
? `All ${prompts.length} prompts have proper argument handling`
: `Found ${suggestions.length} argument handling improvement opportunities`;
return this.createResult(true, // This is informational, not a failure
message, {
promptCount: prompts.length,
argumentCount,
wellDocumentedCount,
suggestionCount: suggestions.length,
suggestions: suggestions.slice(0, 5), // Limit output
}, suggestions.length > 0 ? suggestions.slice(0, 3) : undefined);
}
catch (error) {
return this.createResult(false, 'Prompt argument validation check failed', { error: error instanceof Error ? error.message : String(error) }, ['Check prompts/list implementation']);
}
}
}