create-mcp-eval
Generate eval tests for MCP servers using @mcpjam/sdk.
Read this file first. It carries the two things you need before writing any code — what to ask the user, and the rules the generated tests must follow — and routes you to the rest only when you actually need it.
Reference map
Load a reference when you reach the step that needs it, not before.
| You are about to… |
Read |
Scaffold package.json, tsconfig.json, .env.example, .gitignore |
references/project-setup.md |
Call MCPClientManager, HostRunner, PromptResult, EvalTest, EvalSuite, validators, or the MCPJam reporter |
references/sdk-api.md |
| Choose a shape — config block, toggled suites, shared reporter, parameterized agents, save modes, multi-turn, validator coverage |
references/patterns.md |
| Write the file out |
references/template.md |
| Debug a test that runs but behaves oddly |
references/common-mistakes.md |
| Turn an MCPJam Agent Brief into tests |
references/agent-brief.md |
1. Context Gathering
Before generating any code, collect the following from the user:
| Question |
Options |
Default |
| Connection type |
stdio (local binary) or http (SSE/Streamable HTTP URL) |
http |
| Test framework |
jest, vitest, or none (SDK-only) |
(detect from repo; fall back to vitest) |
| LLM provider |
See Supported Providers table below. Format: provider/model |
(must ask user) |
| Save results to MCPJam |
none, auto (saves when MCPJAM_API_KEY is set), or reporter (shared EvalRunReporter). Use an MCPJam API key (sk_…) from Settings → API keys; optionally set MCPJAM_PROJECT_ID to file results under a specific project (defaults to the org’s Default project). |
(must ask user) |
| Tool list |
Ask user to paste their tool names or an Agent Brief (references/agent-brief.md) |
— |
If the user provides an Agent Brief (markdown with ## Tools table), parse it to auto-populate tool names, descriptions, parameters, and suggested eval scenarios. See references/agent-brief.md.
Provider Selection (REQUIRED)
You MUST ask the developer which LLM provider they want before generating any code. Do not default to any provider.
Supported Providers:
| Provider |
Model format |
Env var |
Example model |
openai |
openai/<model> |
OPENAI_API_KEY |
openai/gpt-4o-mini |
anthropic |
anthropic/<model> |
ANTHROPIC_API_KEY |
anthropic/claude-sonnet-4-20250514 |
google |
google/<model> |
GOOGLE_API_KEY |
google/gemini-2.0-flash |
mistral |
mistral/<model> |
MISTRAL_API_KEY |
mistral/mistral-small-latest |
deepseek |
deepseek/<model> |
DEEPSEEK_API_KEY |
deepseek/deepseek-chat |
xai |
xai/<model> |
XAI_API_KEY |
xai/grok-2 |
openrouter |
openrouter/<model> |
OPENROUTER_API_KEY |
openrouter/openai/gpt-4o-mini |
azure |
azure/<deployment> |
AZURE_API_KEY |
azure/gpt-4o |
ollama |
ollama/<model> |
(none, local) |
ollama/llama3 |
| Custom |
<name>/<model> |
(configurable) |
litellm/gpt-4 |
Once the user selects a provider, use the corresponding env var name and model format in all generated code:
{LLM_ENV_VAR} — e.g., OPENAI_API_KEY
{LLM_MODEL} — e.g., openai/gpt-4o-mini
{LLM_KEY_EXAMPLE} — e.g., sk-...
Test Runner Selection
Before generating tests, check what the codebase already uses:
package.json scripts and devDependencies for jest or vitest
- Config files:
jest.config.*, vitest.config.*, vite.config.*
Then:
- If Jest is present, use Jest (and
ts-jest if TypeScript).
- If Vitest is present, use Vitest.
- If neither is present, default to Vitest.
- If the developer prefers no test framework, the
@mcpjam/sdk classes (EvalTest, EvalSuite) can run standalone — call .run() directly and check results in a plain script without Jest/Vitest.
In all cases, use @mcpjam/sdk for the eval harness (HostRunner, EvalTest, EvalSuite, validators).
5. Generation Guidelines
Follow these rules when generating eval test files:
Deterministic suite first — always include a deterministic test section using HostRunner.mock() that validates the test structure itself without requiring LLM calls or server connections.
One EvalTest per tool — create a separate EvalTest for each tool you want to evaluate. Each test should prompt the runner with a natural-language request and assert the correct tool was selected.
Single-shot LLM tests are non-deterministic — a single runner.run() may not select the expected tool every time. For single-shot tests, prefer saving results to MCPJam without hard-asserting (expect(...).toBe(true)). Use EvalTest with iterations >= 3 and assert on accuracy() for reliable pass/fail gates. Reserve hard asserts for high-confidence cases (negative tests, multi-turn with clear context).
Write unambiguous prompts for similar tools — when a server has tools with overlapping descriptions (e.g., create_view vs export_to_excalidraw), prompts must reference the tool's unique action. Mention specific verbs, targets, or outcomes. Bad: "Share my diagram". Good: "Export and upload my diagram to excalidraw.com so I can open it in a browser".
Multi-turn for related tools — when tools logically chain together (e.g., get_user then list_workspaces), create a multi-turn test using { context: previousResult }.
Negative test — always include at least one test that verifies the runner does NOT call tools when given an irrelevant prompt (e.g., "What is the capital of France?"). Use matchNoToolCalls().
Reasonable defaults:
iterations: 5 for EvalTest runs
timeoutMs: 60_000 for LLM tests
maxSteps: 8 for HostRunner
retries: 1 for flaky network tolerance
concurrency: 5 (default, no need to set explicitly)
Timeout on test cases — set explicit timeouts on it() blocks: 90_000 for single-turn, 120_000 for multi-turn and suite tests.
Always await — every runner.run(), test.run(), suite.run(), reporter.record*(), and reporter.finalize() is async. Never forget await.
One reporter per file — create the reporter at module level to save results to MCPJam, and finalize in afterAll. Never create multiple reporters in the same file.
Use describe.skip for missing credentials — wrap LLM tests in conditional describe blocks so CI runs cleanly without secrets.
Match the repo's test runner — check package.json and config files for an existing test framework before generating. Only default to Vitest if the repo has no test runner. If the user prefers no framework at all, use @mcpjam/sdk classes (EvalTest.run(), EvalSuite.run()) standalone in a plain script.
Log key metrics — add console.log statements for accuracy, tool calls, and latency so CI output is informative.
1---2name: create-mcp-eval3description: Generate comprehensive eval tests for any MCP server using @mcpjam/sdk. Supports Jest and Vitest with deterministic and LLM-driven test patterns.4---56# create-mcp-eval78Generate eval tests for MCP servers using **@mcpjam/sdk**.910Read this file first. It carries the two things you need before writing any code — what to ask the user, and the rules the generated tests must follow — and routes you to the rest only when you actually need it.1112## Reference map1314Load a reference when you reach the step that needs it, not before.1516| You are about to… | Read |17|---|---|18| Scaffold `package.json`, `tsconfig.json`, `.env.example`, `.gitignore` | `references/project-setup.md` |19| Call `MCPClientManager`, `HostRunner`, `PromptResult`, `EvalTest`, `EvalSuite`, validators, or the MCPJam reporter | `references/sdk-api.md` |20| Choose a shape — config block, toggled suites, shared reporter, parameterized agents, save modes, multi-turn, validator coverage | `references/patterns.md` |21| Write the file out | `references/template.md` |22| Debug a test that runs but behaves oddly | `references/common-mistakes.md` |23| Turn an MCPJam **Agent Brief** into tests | `references/agent-brief.md` |2425## 1. Context Gathering2627Before generating any code, collect the following from the user:2829| Question | Options | Default |30|----------|---------|---------|31| **Connection type** | `stdio` (local binary) or `http` (SSE/Streamable HTTP URL) | `http` |32| **Test framework** | `jest`, `vitest`, or `none` (SDK-only) | _(detect from repo; fall back to `vitest`)_ |33| **LLM provider** | See Supported Providers table below. Format: `provider/model` | _(must ask user)_ |34| **Save results to MCPJam** | `none`, `auto` (saves when MCPJAM_API_KEY is set), or `reporter` (shared EvalRunReporter). Use an MCPJam API key (`sk_…`) from **Settings → API keys**; optionally set `MCPJAM_PROJECT_ID` to file results under a specific project (defaults to the org’s Default project). | _(must ask user)_ |35| **Tool list** | Ask user to paste their tool names or an **Agent Brief** (`references/agent-brief.md`) | — |3637If the user provides an **Agent Brief** (markdown with `## Tools` table), parse it to auto-populate tool names, descriptions, parameters, and suggested eval scenarios. See `references/agent-brief.md`.3839### Provider Selection (REQUIRED)4041You MUST ask the developer which LLM provider they want before generating any code. Do not default to any provider.4243**Supported Providers:**4445| Provider | Model format | Env var | Example model |46|----------|-------------|---------|---------------|47| `openai` | `openai/<model>` | `OPENAI_API_KEY` | `openai/gpt-4o-mini` |48| `anthropic` | `anthropic/<model>` | `ANTHROPIC_API_KEY` | `anthropic/claude-sonnet-4-20250514` |49| `google` | `google/<model>` | `GOOGLE_API_KEY` | `google/gemini-2.0-flash` |50| `mistral` | `mistral/<model>` | `MISTRAL_API_KEY` | `mistral/mistral-small-latest` |51| `deepseek` | `deepseek/<model>` | `DEEPSEEK_API_KEY` | `deepseek/deepseek-chat` |52| `xai` | `xai/<model>` | `XAI_API_KEY` | `xai/grok-2` |53| `openrouter` | `openrouter/<model>` | `OPENROUTER_API_KEY` | `openrouter/openai/gpt-4o-mini` |54| `azure` | `azure/<deployment>` | `AZURE_API_KEY` | `azure/gpt-4o` |55| `ollama` | `ollama/<model>` | _(none, local)_ | `ollama/llama3` |56| Custom | `<name>/<model>` | _(configurable)_ | `litellm/gpt-4` |5758Once the user selects a provider, use the corresponding env var name and model format in all generated code:59- `{LLM_ENV_VAR}` — e.g., `OPENAI_API_KEY`60- `{LLM_MODEL}` — e.g., `openai/gpt-4o-mini`61- `{LLM_KEY_EXAMPLE}` — e.g., `sk-...`6263### Test Runner Selection6465Before generating tests, check what the codebase already uses:6667- `package.json` scripts and devDependencies for `jest` or `vitest`68- Config files: `jest.config.*`, `vitest.config.*`, `vite.config.*`6970Then:71- If Jest is present, use Jest (and `ts-jest` if TypeScript).72- If Vitest is present, use Vitest.73- If neither is present, default to Vitest.74- If the developer prefers **no test framework**, the `@mcpjam/sdk` classes (`EvalTest`, `EvalSuite`) can run standalone — call `.run()` directly and check results in a plain script without Jest/Vitest.7576In all cases, use `@mcpjam/sdk` for the eval harness (`HostRunner`, `EvalTest`, `EvalSuite`, validators).7778---7980## 5. Generation Guidelines8182Follow these rules when generating eval test files:83841. **Deterministic suite first** — always include a deterministic test section using `HostRunner.mock()` that validates the test structure itself without requiring LLM calls or server connections.85862. **One EvalTest per tool** — create a separate `EvalTest` for each tool you want to evaluate. Each test should prompt the runner with a natural-language request and assert the correct tool was selected.87883. **Single-shot LLM tests are non-deterministic** — a single `runner.run()` may not select the expected tool every time. For single-shot tests, prefer saving results to MCPJam without hard-asserting (`expect(...).toBe(true)`). Use `EvalTest` with `iterations >= 3` and assert on `accuracy()` for reliable pass/fail gates. Reserve hard asserts for high-confidence cases (negative tests, multi-turn with clear context).89904. **Write unambiguous prompts for similar tools** — when a server has tools with overlapping descriptions (e.g., `create_view` vs `export_to_excalidraw`), prompts must reference the tool's *unique* action. Mention specific verbs, targets, or outcomes. Bad: "Share my diagram". Good: "Export and upload my diagram to excalidraw.com so I can open it in a browser".91925. **Multi-turn for related tools** — when tools logically chain together (e.g., `get_user` then `list_workspaces`), create a multi-turn test using `{ context: previousResult }`.93946. **Negative test** — always include at least one test that verifies the runner does NOT call tools when given an irrelevant prompt (e.g., "What is the capital of France?"). Use `matchNoToolCalls()`.95967. **Reasonable defaults**:97 - `iterations: 5` for EvalTest runs98 - `timeoutMs: 60_000` for LLM tests99 - `maxSteps: 8` for HostRunner100 - `retries: 1` for flaky network tolerance101 - `concurrency: 5` (default, no need to set explicitly)1021038. **Timeout on test cases** — set explicit timeouts on `it()` blocks: `90_000` for single-turn, `120_000` for multi-turn and suite tests.1041059. **Always `await`** — every `runner.run()`, `test.run()`, `suite.run()`, `reporter.record*()`, and `reporter.finalize()` is async. Never forget `await`.10610710. **One reporter per file** — create the reporter at module level to save results to MCPJam, and finalize in `afterAll`. Never create multiple reporters in the same file.10810911. **Use `describe.skip` for missing credentials** — wrap LLM tests in conditional describe blocks so CI runs cleanly without secrets.11011112. **Match the repo's test runner** — check `package.json` and config files for an existing test framework before generating. Only default to Vitest if the repo has no test runner. If the user prefers no framework at all, use `@mcpjam/sdk` classes (`EvalTest.run()`, `EvalSuite.run()`) standalone in a plain script.11211313. **Log key metrics** — add `console.log` statements for accuracy, tool calls, and latency so CI output is informative.