How do you test whether agents actually use your MCP server correctly?
Test two layers. Protocol conformance is ordinary testing: connect a client, list the tools, and check every schema and error shape. Whether a model picks the right tool with the right arguments is a behavioral question, so run real prompts through real clients, give the tools a backend that actually executes and holds state, and assert on which tool was called and with what. Treat names, descriptions, and schemas as prompt text.
Last updated 2 August 2026
Layer one: does the server do what the protocol says
This half is a normal integration test and deserves no ceremony. Start the server, connect a client over the transport you actually ship (stdio, or streamable HTTP), and assert on the handshake, the tools/list response, and each tool's input schema. Then call every tool directly with valid arguments, with missing required arguments, and with arguments of the wrong type, and check that what comes back is a protocol-shaped error rather than a stack trace or a hang.
Cover the unglamorous cases while you are there: a tool that takes ten seconds, a tool that returns a very large result, concurrent calls, and a client that disconnects mid-call. These are the failures that make a server feel broken in a way no model-level test will surface.
If the server needs OAuth, test the failure paths explicitly. An expired token, a missing scope, and a token issued for a different resource should each produce a clear error, because the client on the other side has no way to guess.
Layer two: does a model choose the right tool
This is the question the title asks, and it is not answerable by calling the tools yourself. It is a behavioral property of the model, your tool descriptions, and the client's own system prompt together, and it has to be measured by running realistic requests end to end.
Build a set of user requests with a known correct tool sequence for each: which tool should be called, in what order, and with what arguments derived from the request. Include requests that should call nothing, requests that are ambiguous between two tools, and requests that need two tools chained. Then run them through an actual client and assert on the transcript.
Run it against more than one model, because tool selection is where models differ most, and the answer to "does the agent use it correctly" is genuinely different for each. Re-run when a model version changes: a provider updating a model under a stable alias is the most common cause of a tool that quietly stopped being called.
Your tool descriptions are prompt engineering
The name, the description, and the parameter descriptions are the only thing the model sees. Most "the agent uses my server wrong" bugs are description bugs, and they are cheap to fix once you can measure them.
The failure patterns repeat: two tools whose descriptions overlap, so the model picks either at random; a description that says what the function does internally rather than when to use it; an enum with no explanation of what the values mean; an optional parameter the model never supplies because nothing says why it would; and a surface with thirty flat tools where the model reliably picks the wrong one of a near-identical five. Iterate on the wording with the suite above as the measurement, and treat a description change like a code change.
Give the tools something real to act on
A behavioral test needs the tools to actually execute, because tool selection depends on results. If create_ticket returns a canned success no matter what, the model never has to handle "that project does not exist", and the multi-step behavior you wanted to test never happens.
So back the server with a real store during tests, seeded with invented data, reset between runs. Now a request can be checked at both ends: the right tools were called with the right arguments, and the state afterwards is what it should be, with one ticket created rather than three.
Assert on the transcript, and keep it in CI
The assertions worth writing are structural: this tool was called, these arguments were passed, this tool was not called, and the end state matches. Save a judged assertion for the genuinely fuzzy part, such as whether the final message explained what it did.
Then run it on every change to the server, the same way you would run any other suite, and compare against the previous run rather than against an absolute score. A change to a description is exactly the kind of edit that looks harmless in a diff and moves tool selection by twenty points.
Where Mirrors fits
MCP-style tool definitions are one of the sources Mirrors builds an environment from, alongside traces, tool code, OpenAPI specs, and docs. Point it at the tool surface you publish and it composes a backend that supports exactly what those tools need: a schema covering the entities they touch, fabricated seed data, and each tool bound to real code where you have it or to a synthesized implementation where you do not. That gives layer two somewhere to act, with state, without wiring your MCP server to anything real.
Each tool also carries an evidence grade and a fidelity score, which is a useful signal when the tool surface is the thing you are shipping: a tool built only from its declaration is marked as exactly that, rather than being presented as though it had been validated against real behavior.
The suite itself, the requests, the expected tool sequences, and the choice of models to run against, is yours. Mirrors does not evaluate whether a model picked the right tool, and nothing on this page should be read as saying it does.
Separately and less usefully for testing: Mirrors is itself reachable over MCP at https://api.runmirrors.com/mcp, so a client can drive builds and replays without the dashboard.
What this does not solve
A pass on your suite says a specific model, in a specific client, with a specific system prompt, used your server correctly. It does not generalize. Every client wraps tools in its own instructions, and a server that behaves well in one can behave badly in another, so the honest scope of any result is the matrix you actually ran.
Tool selection also moves under you. Providers update models, clients change their prompts, and neither event produces a diff in your repository. That makes this a suite to run on a schedule, not only on your own changes.
And a rebuilt backend tests the tools, not your infrastructure. Rate limits, authentication against the real identity provider, and the behavior of your production database under load are outside what any of this covers.
Read next
- What tool-call drift is →
- How do you test an AI agent that calls internal APIs with no staging environment? →
- The Mirrors MCP server, in the docs
- Model Context Protocol specification