mcp-server-tester-sse-http-stdio
Version:
MCP Server Tester with SSE support - Test MCP servers using HTTP, SSE, and STDIO transports
272 lines (271 loc) • 13.9 kB
JavaScript
/**
* Simplified Resources 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 ResourcesCapabilityTest extends DiagnosticTest {
name = 'Server Features: Resources - Capability Declaration';
description = 'Verify server declares resources capability using SDK validation';
category = 'server-features';
feature = 'resources';
severity = TEST_SEVERITY.CRITICAL;
requiredCapability = 'resources';
mcpSpecSection = 'Server Features - Resources';
async execute(client, config) {
// Use SDK to validate resources capability - it handles all protocol validation
const sdkTests = [
{
name: 'Resources Capability Declaration',
description: 'Validate resources capability through SDK',
category: 'server-features',
feature: 'resources',
severity: TEST_SEVERITY.CRITICAL,
operation: async (client) => await client.sdk.listResources(),
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.listResources();
const resourceCount = result.resources?.length || 0;
return this.createResult(true, `Resources capability validated via SDK (${resourceCount} resources found)`, { resourceCount, sdkValidated: true });
}
else {
return this.createEnhancedResult({
success: false,
message: 'SDK detected resources capability issues',
details: { failedTests: failedTests.length, results: diagnosticResults },
issueType: failedTests[0].issueType || ISSUE_TYPE.CRITICAL_FAILURE,
expected: 'Valid resources capability declaration',
actual: 'SDK validation failed',
fixInstructions: failedTests.flatMap(t => t.recommendations || []),
specLinks: ['https://spec.modelcontextprotocol.io/specification/server/resources/'],
});
}
}
catch (error) {
return this.createResult(false, 'Resources capability validation failed', { error: error instanceof Error ? error.message : String(error) }, ['Check server resources capability implementation', 'Verify server connection']);
}
}
}
export class ResourceSchemaValidationTest extends DiagnosticTest {
name = 'Server Features: Resources - Schema Validation';
description = 'Validate resource schema compliance and quality (business logic)';
category = 'server-features';
feature = 'resources';
severity = TEST_SEVERITY.WARNING;
requiredCapability = 'resources';
mcpSpecSection = 'Server Features - Resources';
async execute(client, _config) {
try {
// SDK handles the listResources call validation, we focus on business logic
const result = await client.sdk.listResources();
const resources = result.resources || [];
if (resources.length === 0) {
return this.createResult(false, 'No resources available for schema validation', {
resourceCount: 0,
});
}
const issues = [];
const warnings = [];
const validations = [];
// Business logic validation (not protocol validation)
resources.forEach((resource, index) => {
// Check for required business fields
if (!resource.uri || resource.uri.trim().length === 0) {
issues.push(`Resource ${index}: Empty URI`);
}
else {
// Basic URI format validation
try {
new URL(resource.uri);
validations.push(`Resource ${index}: Valid URI format`);
}
catch {
// Not a full URL, might be a valid URI scheme
if (resource.uri.includes('://') || resource.uri.startsWith('/')) {
validations.push(`Resource ${index}: Acceptable URI format`);
}
else {
warnings.push(`Resource ${index}: URI format unclear: ${resource.uri}`);
}
}
}
if (!resource.name || resource.name.trim().length === 0) {
warnings.push(`Resource ${index}: Missing name`);
}
else if (resource.name.length > 100) {
warnings.push(`Resource ${index} (${resource.name}): Name very long (${resource.name.length} chars)`);
}
else {
validations.push(`Resource ${index} (${resource.name}): Valid name`);
}
if (resource.description) {
if (resource.description.length < 10) {
warnings.push(`Resource ${index} (${resource.name}): Very short description`);
}
else if (resource.description.length > 500) {
warnings.push(`Resource ${index} (${resource.name}): Very long description (${resource.description.length} chars)`);
}
else {
validations.push(`Resource ${index} (${resource.name}): Good description`);
}
}
// Check MIME type if provided
if (resource.mimeType) {
if (resource.mimeType.includes('/')) {
validations.push(`Resource ${index} (${resource.name}): Has MIME type`);
}
else {
warnings.push(`Resource ${index} (${resource.name}): Invalid MIME type format`);
}
}
});
// Check for duplicate URIs (business logic)
const uris = resources.map(r => r.uri);
const duplicates = uris.filter((uri, index) => uris.indexOf(uri) !== index);
if (duplicates.length > 0) {
issues.push(`Duplicate resource URIs: ${duplicates.join(', ')}`);
}
const hasIssues = issues.length > 0;
const message = hasIssues
? `Resource schema validation found ${issues.length} issues, ${warnings.length} warnings`
: warnings.length > 0
? `Resource schemas valid with ${warnings.length} improvement suggestions`
: `All ${resources.length} resource schemas are well-formed`;
return this.createResult(!hasIssues, message, {
resourceCount: resources.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, 'Resource schema validation failed', { error: error instanceof Error ? error.message : String(error) }, ['Check resources/list implementation', 'Verify resource schema format']);
}
}
}
export class ResourceReadingTest extends DiagnosticTest {
name = 'Server Features: Resources - Reading (resources/read)';
description = 'Test resource reading using SDK error detection';
category = 'server-features';
feature = 'resources';
severity = TEST_SEVERITY.WARNING;
requiredCapability = 'resources';
async execute(client, config) {
try {
// Get available resources via SDK
const resourcesResult = await client.sdk.listResources();
const resources = resourcesResult.resources || [];
if (resources.length === 0) {
return this.createResult(false, 'No resources available for reading test', {
resourceCount: 0,
});
}
// Test reading with SDK error detection
const sdkTests = [
{
name: 'Invalid Resource URI Error Handling',
description: 'Test error handling for non-existent resources',
category: 'server-features',
feature: 'resources',
severity: TEST_SEVERITY.WARNING,
operation: async (client) => {
return await client.sdk.readResource({
uri: 'nonexistent://definitely-not-found/resource',
});
},
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, `Resource reading error handling validated via SDK`, {
availableResources: resources.length,
passedTests: passedTests.length,
sdkValidated: true,
});
}
else {
return this.createEnhancedResult({
success: false,
message: `SDK detected ${failedTests.length} resource reading issues`,
details: {
availableResources: resources.length,
failedTests: failedTests.length,
passedTests: passedTests.length,
results: diagnosticResults,
},
issueType: failedTests[0].issueType || ISSUE_TYPE.SPEC_WARNING,
expected: 'Proper resource reading error handling',
actual: `${failedTests.length} reading issues detected by SDK`,
fixInstructions: failedTests.flatMap(t => t.recommendations || []),
specLinks: ['https://spec.modelcontextprotocol.io/specification/server/resources/'],
});
}
}
catch (error) {
return this.createResult(false, 'Resource reading test failed', { error: error instanceof Error ? error.message : String(error) }, ['Check resources/read implementation', 'Verify resource reading logic']);
}
}
}
export class ResourceMimeTypeTest extends DiagnosticTest {
name = 'Server Features: Resources - MIME Type Handling';
description = 'Check for proper MIME type usage (business logic)';
category = 'server-features';
feature = 'resources';
severity = TEST_SEVERITY.INFO;
requiredCapability = 'resources';
async execute(client, _config) {
try {
// SDK handles the protocol validation, we check business logic
const result = await client.sdk.listResources();
const resources = result.resources || [];
if (resources.length === 0) {
return this.createResult(false, 'No resources available for MIME type checking', {
resourceCount: 0,
});
}
const suggestions = [];
let mimeTypeCount = 0;
resources.forEach((resource) => {
// Check for MIME type usage (business logic, not spec requirements)
if (resource.mimeType) {
mimeTypeCount++;
// Validate common MIME type patterns
if (!resource.mimeType.includes('/')) {
suggestions.push(`Resource '${resource.uri}': Invalid MIME type format '${resource.mimeType}'`);
}
}
else {
suggestions.push(`Resource '${resource.uri}': Consider adding MIME type for better client handling`);
}
});
const message = suggestions.length === 0
? `All ${resources.length} resources have proper MIME type handling`
: `Found ${suggestions.length} MIME type improvement opportunities`;
return this.createResult(true, // This is informational, not a failure
message, {
resourceCount: resources.length,
mimeTypeCount,
suggestionCount: suggestions.length,
suggestions: suggestions.slice(0, 5), // Limit output
}, suggestions.length > 0 ? suggestions.slice(0, 3) : undefined);
}
catch (error) {
return this.createResult(false, 'Resource MIME type check failed', { error: error instanceof Error ? error.message : String(error) }, ['Check resources/list implementation']);
}
}
}