The slow way to test an MCP server is to connect it to Claude, start a session, and see what breaks. The problem: by the time Claude tells you the schema is wrong, you've already spent context on the setup. The schema error is in the first tool call and you don't find out until you try to use it.
You can test most MCP server behavior before any client is involved.
MCP servers expose a tools/list method that returns all available tools with their schemas. You can call this directly over stdio without a client by sending a JSON-RPC request:
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' | node dist/index.js
This gives you the raw schema your server is advertising. Read it carefully. The names, descriptions, and parameter types in this output are exactly what Claude sees when deciding how to call your tools. If a parameter name is ambiguous or a description is missing, Claude will guess wrong.
Common things to catch at this stage: required parameters that are listed as optional, parameter names that don't match the actual logic, missing descriptions on parameters that need context.
Once the listing looks right, call a tool directly:
echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"your_tool","arguments":{"param":"value"}}}' | node dist/index.js
Test the happy path first — valid input, expected output. Then test the cases that should fail: missing required parameters, invalid types, values outside expected ranges. Does your server return a proper error object or does it crash? Does the error message tell Claude what was wrong, or just that something failed?
The error format matters. Claude reads error text and tries to correct its next call based on it. "Invalid parameter: 'path' must be an absolute path, got './relative'" is useful. "Error: validation failed" is not.
If your MCP server makes external API calls or file system operations, test what happens when those fail. Unplug the network. Point it at a file that doesn't exist. Pass in credentials that will be rejected.
What you want: a clean error with a message Claude can act on. What you don't want: an unhandled promise rejection that dumps a stack trace to stdout, which breaks the JSON-RPC framing and corrupts the session.
The stdio transport for MCP servers uses stdout for all communication. Anything that writes to stdout that isn't valid JSON-RPC breaks the connection. Stack traces, console.log debug output, and error dumps all go to stderr instead — but unhandled exceptions can write to stdout before you catch them.
Rather than running raw echo commands, a small test file pays for itself quickly:
// test-server.ts
import { spawn } from 'child_process';
const server = spawn('node', ['dist/index.js'], {
stdio: ['pipe', 'pipe', 'inherit']
});
function call(req: object) {
return new Promise((resolve) => {
server.stdout.once('data', (d) => resolve(JSON.parse(d.toString())));
server.stdin.write(JSON.stringify(req) + '\n');
});
}
async function run() {
// List tools
const tools = await call({ jsonrpc: '2.0', id: 1, method: 'tools/list', params: {} });
console.log('Tools:', JSON.stringify(tools, null, 2));
// Call a tool with valid input
const result = await call({
jsonrpc: '2.0', id: 2, method: 'tools/call',
params: { name: 'your_tool', arguments: { param: 'test_value' } }
});
console.log('Result:', JSON.stringify(result, null, 2));
// Call with invalid input — should get structured error
const err = await call({
jsonrpc: '2.0', id: 3, method: 'tools/call',
params: { name: 'your_tool', arguments: {} } // missing required param
});
console.log('Error case:', JSON.stringify(err, null, 2));
server.kill();
}
run();
Run this before every significant change. The schema and error paths should stay stable across refactors — if they change, you want to know before connecting to Claude.
How Claude actually uses your tools. A schema can be technically valid and still confusing in practice — Claude might consistently misunderstand a parameter or call the wrong tool first. You find this out in a real session. But catching schema errors, crashes, and broken error formats before that session means you spend session context on actual work, not debugging the server itself.
The MCP Server Starter Kit includes a TypeScript server template with the error handling, stdout discipline, and schema patterns already in place — plus four complete working example servers you can read alongside this approach. $49 at Payhip.