Manual testing with the Inspector is great for development, but production servers need automated tests that run in CI/CD.
// test/server.test.ts
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { describe, it, expect, beforeAll, afterAll } from "vitest";
let client: Client;
beforeAll(async () => {
const transport = new StdioClientTransport({
command: "tsx",
args: ["src/index.ts"],
env: { NOTES_DIR: "./test/fixtures/notes" }
});
client = new Client({ name: "test-runner", version: "1.0.0" });
await client.connect(transport);
});
afterAll(async () => {
await client.close();
});
describe("Tool: search_notes", () => {
it("finds notes matching a keyword", async () => {
const result = await client.callTool("search_notes", {
query: "meeting",
maxResults: 5
});
expect(result.isError).toBeFalsy();
expect(result.content[0].type).toBe("text");
expect(result.content[0].text).toContain("meeting");
});
it("returns empty message for no matches", async () => {
const result = await client.callTool("search_notes", {
query: "xyznonexistent123"
});
expect(result.isError).toBeFalsy();
expect(result.content[0].text).toContain("No notes found");
});
it("handles missing arguments gracefully", async () => {
try {
await client.callTool("search_notes", {});
} catch (e: any) {
expect(e.message).toBeDefined();
}
});
});
describe("Capabilities", () => {
it("exposes expected tools", async () => {
const { tools } = await client.listTools();
const toolNames = tools.map(t => t.name);
expect(toolNames).toContain("search_notes");
expect(toolNames).toContain("create_note");
});
it("exposes resources", async () => {
const { resources } = await client.listResources();
expect(resources.length).toBeGreaterThan(0);
});
it("exposes prompts", async () => {
const { prompts } = await client.listPrompts();
expect(prompts.length).toBeGreaterThan(0);
});
});
| Test Type | What It Validates | Speed | When to Run |
|---|---|---|---|
| Unit Tests | Tool handler functions in isolation | Fast (~1s) | Every commit |
| Integration Tests | Full client → server round-trip | Medium (~5s) | Every PR |
| Protocol Tests | JSON-RPC message format compliance | Medium (~3s) | Every PR |
| Smoke Tests | Server starts and responds to init | Fast (~2s) | Every deploy |