Testing
Test your MCP server's handlers as plain functions, run integration tests via server.fetch, use MCP Inspector for manual testing, and smoke-test with curl.
The framework has no test runner, no test helpers, and no special requirements for CI. Handlers are plain functions — test them with whatever tooling you already use.
Unit testing a handler
A handler is a function that takes arguments and returns a value. Call it directly in your tests:
import { describe, it, expect } from "vitest";
import { calculate } from "./index.js";
describe("calculate", () => {
it("adds two numbers", async () => {
const result = await calculate({ operation: "add", a: 3, b: 4 });
expect(result).toEqual({ operation: "add", a: 3, b: 4, result: 7 });
});
it("throws on division by zero", async () => {
await expect(calculate({ operation: "divide", a: 10, b: 0 })).rejects.toThrow("Cannot divide");
});
});Pass null for ask and undefined for ctx when your handler does not use them. If your handler guards against a null ask, test both the null path and the non-null path:
it("returns fallback when ask is null", async () => {
const result = await smartAnswer({ question: "What is 2+2?" }, null);
expect(result).toContain("LLM sampling is not available");
});
it("calls ask when available", async () => {
const mockAsk = async () => "4";
const result = await smartAnswer({ question: "What is 2+2?" }, mockAsk);
expect(result).toContain("4");
});Integration testing via server.fetch
server.fetch is a standard fetch-compatible handler. Send a JSON-RPC 2.0 request and assert the response:
import { describe, it, expect } from "vitest";
import server from "./index.js";
describe("server integration", () => {
it("calls the greet tool", async () => {
const request = new Request("http://localhost/", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
id: 1,
method: "tools/call",
params: {
name: "greet",
arguments: { name: "Alice" },
},
}),
});
const response = await server.fetch(request);
const body = await response.json();
expect(response.status).toBe(200);
expect(body.result.content[0].text).toContain("Alice");
});
});This exercises the full request path — routing, argument validation, and serialization — without a running HTTP server.
MCP Inspector
MCP Inspector is a browser-based tool for manual testing during development. Start your dev server, then open Inspector and connect to http://localhost:3000 (or whatever port you chose).
Inspector lets you:
- Browse your server's tools, resources, and prompts.
- Invoke any tool with custom arguments and see the raw response.
- Inspect the JSON-RPC messages flowing between client and server.
It is the fastest way to verify that a new tool works before writing automated tests.
curl smoke test
The dev server handles JSON-RPC 2.0 over HTTP POST. You can call any tool from the command line:
curl -s http://localhost:3000 \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"greet","arguments":{"name":"World"}}}' \
| jq .Expected output:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [{ "type": "text", "text": "Hello, World!" }],
"isError": false
}
}Replace greet and {"name":"World"} with your tool name and arguments.
CI
Run your unit and integration tests in CI the same way you run them locally. The framework has no external dependencies, no network requirements, and no environment variables required for tests to pass. A minimal GitHub Actions step looks like:
- name: Test
run: npm testIf your tests call external APIs or use ctx.userId, mock those values in the test setup rather than requiring a live environment.
Next steps
- Logging — inspect log output from handlers during tests
- Framework API Reference — full type definitions to use in test assertions
See something wrong? Report it or suggest an improvement — your feedback helps make these docs better.