# Testing MCP Servers: A Comprehensive Guide
## Overview
This document outlines the approach to testing Model Context Protocol (MCP) servers, using our Etherscan server implementation as an example.
## Testing Architecture
### Core Components
1. **Test Client**: Uses `@modelcontextprotocol/sdk/client` to simulate real client interactions
2. **Transport Layer**: Uses `StdioClientTransport` for communication between client and server
3. **Test Cases**: Structured test cases that verify each endpoint's functionality
### Basic Test Setup
```typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { CallToolRequestSchema, CallToolResultSchema } from "@modelcontextprotocol/sdk/types.js";
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
async function main() {
// Set up the server path
const serverPath = path.join(__dirname, 'index.js');
// Initialize transport layer
const transport = new StdioClientTransport({
command: 'node',
args: [serverPath]
});
// Create test client
const client = new Client(
{
name: "test-client",
version: "1.0.0",
},
{
capabilities: {},
}
);
// Connect client to transport
await client.connect(transport);
// Run tests...
}
```
## Test Case Structure
Test cases should be structured as an array of objects, each containing:
1. Name of the tool/endpoint to test
2. Parameters to test with
Example:
```typescript
const testCases = [
{
name: "get-contract-abi",
params: {
address: "0x6b175474e89094c44da98b954eedeac495271d0f"
}
},
// More test cases...
];
```
## Running Tests
Tests are executed in a loop, making requests to the server and verifying responses:
```typescript
for (const test of testCases) {
console.log(`\nTesting ${test.name}...`);
try {
const result = await client.request(
{
method: "tools/call",
params: {
name: test.name,
arguments: test.params
}
},
CallToolResultSchema
);
console.log("Success:", result.content[0].text);
} catch (error) {
console.error("Error:", error);
}
}
```
## Best Practices
1. **Test Data**: Use well-known, stable examples for test data
- Example: Using the DAI contract (0x6b175474e89094c44da98b954eedeac495271d0f) for contract-related tests
2. **Error Handling**: Always include try-catch blocks to handle and log errors properly
3. **Environment Variables**: Use environment variables for sensitive data
- Example: API keys should be in .env files
4. **Test Case Independence**: Each test case should be independent and not rely on the state of other tests
5. **Validation**: Verify both successful responses and error cases
- Check response format
- Verify error handling
- Validate data types and content
## Example Test Cases
Here are some example test cases for different types of endpoints:
### Contract Information
```typescript
{
name: "get-contract-abi",
params: {
address: "0x6b175474e89094c44da98b954eedeac495271d0f"
}
}
```
### Blockchain Data
```typescript
{
name: "get-block",
params: {
blockNumber: 17000000
}
}
```
### Paginated Data
```typescript
{
name: "get-verified-contracts",
params: {
page: 1,
offset: 10,
sortBy: "timestamp"
}
}
```
## Running the Tests
1. **Build the Project**:
```bash
npm run build
```
2. **Run Tests**:
```bash
npm run test
```
## Debugging Tips
1. Use console.log for debugging server responses
2. Check the transport layer for communication issues
3. Verify environment variables are properly loaded
4. Monitor API rate limits when testing external services
## Common Issues and Solutions
1. **Transport Connection Issues**
- Verify server path is correct
- Check server is built before running tests
2. **Schema Validation Errors**
- Ensure parameters match the schema defined in the server
- Check data types match expected types
3. **API Rate Limiting**
- Implement delays between tests
- Use test data that minimizes API calls
- Consider mocking external services for heavy testing
## Future Improvements
1. Add automated test assertions
2. Implement test coverage reporting
3. Add integration tests for complex workflows
4. Create mock services for external APIs
5. Add performance testing capabilities
## Conclusion
Testing MCP servers requires a combination of:
- Proper client-server communication setup
- Well-structured test cases
- Robust error handling
- Careful consideration of external dependencies
This approach ensures reliable testing while maintaining readability and maintainability.