Testing MCP Tools
Bring TDD discipline to MCP servers: unit-test handlers, protocol-test the surface, and contract-test against a real host.
The route
4 steps to Done
- 01
Separate logic from protocol glue
Testability is an architecture property - create it first.
Preview prompt + verify gate ▾ Hide ▴
Refactor for testability. Split each tool into: a pure(ish) handler function (typed inputs in, result or typed error out - no protocol objects) and a thin MCP registration wrapper (schema binding, content conversion, error mapping). The handler must be importable and callable in tests without any transport. Verify by writing one smoke unit test per handler calling it directly. This refactor should not change observable behavior - re-run the existing manual checks after.
- ✓ No protocol types inside handlers
- ✓ One smoke test per handler passes
- ✓ Behavior unchanged post-refactor
- 02
Write the handler unit suite
Cheap, fast, and where most bugs die.
Preview prompt + verify gate ▾ Hide ▴
Build the unit suite per handler: HAPPY PATHS - representative valid inputs asserting exact outputs; VALIDATION - every rejection rule triggered (bad enum, out-of-range, missing required) asserting the error names the field and the allowed values; ERROR CONVERSION - force internal failures (missing file, dependency down via injected fakes) and assert they surface as typed tool errors, not exceptions; BOUNDARIES - values exactly at limits pass. Target: every branch in each handler covered. Run the suite and fix what it finds - first runs on previously-untested handlers usually find real bugs.
- ✓ All rejection rules triggered by tests
- ✓ Internal failures asserted as typed errors
- ✓ Boundary values covered
- 03
Add protocol and contract layers
Test what the host actually sees.
Preview prompt + verify gate ▾ Hide ▴
Build the outer layers. PROTOCOL TESTS: start the real server in-process or as a subprocess; run initialization, list tools, invoke each tool with valid and invalid arguments, and assert on the wire-level responses (result content, error shape) - this catches serialization and registration bugs units cannot. CONTRACT TESTS: snapshot the full tool surface (names, descriptions, schemas) to golden JSON files; the test fails on any diff, and updating goldens requires a deliberate commit - this is your defense against accidental breaking changes to downstream hosts. Wire both into the test command alongside units.
- ✓ Protocol tests run the real transport
- ✓ Invalid-args tested at the wire level
- ✓ Golden diff fails the suite
- 04
Wire CI and the regression ritual
The suite is only real if it blocks bad merges.
Preview prompt + verify gate ▾ Hide ▴
Operationalize. Add the full suite (unit + protocol + contract) to CI on every push and PR, blocking merge on failure; keep total runtime fast enough that nobody is tempted to skip it (parallelize protocol tests if needed). Institute the regression ritual: every bug found in host integration gets a failing test reproducing it BEFORE the fix, committed with the fix. Backfill regression tests for the last 3 known bugs now. Finish by breaking something on purpose (rename a tool) and confirming CI catches it via the contract layer.
- ✓ Suite blocks merges
- ✓ 3 past bugs have regression tests
- ✓ Deliberate rename caught by contract test
Research-backed
Sources behind this flow
Tier 5 · mcp-tooling
mcp-agentic-framework
An MCP-based communication framework enabling multiple AI agents to collaborate through asynchronous messaging, built with TDD and functional programming principles - a clean, tested reference for MCP server design.
Tier 5 · mcp-tooling
mcp-use
A popular open library for connecting any LLM to any MCP server and building custom agents with tool access - the fastest path from zero to a working MCP-tool-using agent, with support for multi-server clients and restricted tool sets.