@mcp-layer/test-server
v1.0.2
Published
Feature-rich MCP server for integration testing and local development.
Maintainers
Readme
@mcp-layer/test-server
@mcp-layer/test-server is a feature-complete MCP server used for integration tests and local experiments. It mirrors the tool, resource, prompt, sampling, elicitation, notification, and MCP Apps features expected by the official SDK so clients can validate end-to-end behavior against a single, real server.
The rest of this workspace depends on a real MCP server to validate protocols. This server:
- Runs over stdio (and optional HTTP/SSE transports)
- Exercises all major MCP capabilities
- Exposes stable fixtures for tests and demos
Table of Contents
- Installation
- Quick start
- What it provides
- Structure
- Spec support matrix
- Usage in tests
- Testing
- Extending the server
- Runtime Error Reference
Installation
# npm
npm install @mcp-layer/test-server
# pnpm
pnpm add @mcp-layer/test-server
# yarn
yarn add @mcp-layer/test-serverRequirements: Node.js 20+ (or Deno/Bun with Node compatibility).
Quick start
Stdio transport
npx mcp-test-serverStreamable HTTP + SSE transport
npx mcp-test-server-http --port 3333This exposes:
- Streamable HTTP on
/mcp - SSE compatibility on
/sse(GET stream + POST messages with?sessionId=...)
SSE is kept for legacy MCP clients. Streamable HTTP clients should use only /mcp.
What it provides
Tools
echo-- returns text, structured outputadd-- arithmetic + structured outputannotated-- tool annotations +_metacoveragedashboard-- MCP Apps UI metadata via_meta.uifiles-- emitsresource_linkpayloadspresent-- mixed content types (text/markdown/image/audio/resource/resource_link)summaries-- sampling supportbooking-- elicitation supportroots-- roots capabilitynote-update-- mutable resourceslogs-- logging notificationsprogress-- progress notificationsrebalance-- debounced list updates
Resources
resource://manual-- markdown manualnote://{topic}/{detail}-- template-backed resources with completionsui://dashboard/app.html-- MCP Apps HTML UI
Prompts
welcome-- prompt arguments with completions
MCP Apps
dashboardtool advertises_meta.ui.resourceUriui://dashboard/app.htmlserves HTML and_meta.uisettings (csp,permissions)
Structure
src/tools/*-- tool implementationssrc/resources/*-- resource + template handlerssrc/prompts/*-- prompt definitionssrc/data/*-- shared manual text and fixturessrc/shared/*-- utilities and capability checks
Spec support matrix
| Feature | Status | Notes |
| --- | --- | --- |
| Tools (structured outputs, resource links) | yes | echo, add, files, summaries, booking, roots, logs, progress, rebalance. |
| Tool annotations + metadata | yes | annotated exposes annotations + _meta. |
| MCP Apps (UI resources) | yes | dashboard tool exposes _meta.ui.resourceUri, ui://dashboard/app.html serves HTML. |
| Mixed content outputs | yes | present emits text/markdown/image/audio/resource/resource_link. |
| Resources & dynamic templates | yes | resource://manual, note://{topic}/{detail} with completions. |
| Prompts & completion API | yes | welcome prompt + completion/complete. |
| Sampling (sampling/createMessage) | yes | summaries tool proxies sampling and validates responses. |
| Elicitation (elicitation/create) | yes | booking tool requests alternate booking info. |
| Roots (roots/list) | yes | roots tool lists file URIs from capable clients. |
| Resource subscriptions/updates | yes | resources/subscribe + note-update tool emit notifications/resources/updated. |
| Logging notifications | yes | logs tool emits notifications/message. |
| Progress notifications | yes | progress tool streams notifications/progress. |
| Debounced list notifications | yes | rebalance tool coalesces updates. |
| Alternate transports (Streamable HTTP / SSE) | yes | mcp-test-server-http bootstraps both. |
| Auth/OAuth proxying | no | Out of scope for this fixture. |
Usage in tests
Use it as the real server target for integration tests:
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
const transport = new StdioClientTransport({
command: process.execPath,
args: [require.resolve('@mcp-layer/test-server/src/bin.js')]
});
const client = new Client({ name: 'demo', version: '0.0.0' });
await client.connect(transport);Use this programmatic helper when you need a mounted localhost HTTP endpoint in tests:
import { startHttpServer } from '@mcp-layer/test-server/http';
const server = await startHttpServer({ port: 0 });
const url = `http://127.0.0.1:${server.port}`;
// streamable HTTP endpoint
const mcp = `${url}/mcp`;
// legacy SSE endpoint
const sse = `${url}/sse`;
await server.close();Testing
pnpm test --filter @mcp-layer/test-serverThe suite boots the real binary, exercises all tools/resources/prompts, verifies notifications, and asserts MCP Apps metadata so this fixture remains trustworthy.
Extending the server
When adding capabilities:
- Implement the feature in
src/*. - Add or update a test in
test/index.test.js. - Document it in this README.
Runtime Error Reference
This section is written for high-pressure debugging moments. These errors are intentionally emitted by the test server to exercise client and REST error-handling paths.
Protocol failure
Thrown from: registerCrash.crashTool
This error is intentionally thrown by the crash tool. It simulates an MCP tool that fails at protocol/runtime level so downstream layers can validate error mapping and HTTP problem responses.
Step-by-step resolution:
- Confirm you actually invoked the
crashtool (tools crash exec) and not a production tool. - If this appeared unexpectedly, verify test fixture/tool name selection.
- In integration tests, assert the expected failure shape (
isError/problem details) instead of treating it as an infra bug. - Use a non-crashing test tool when validating success paths.
await t.test('error mapping', async function errorCase() {
await assert.rejects(client.callTool({ name: 'crash', arguments: {} }));
});
await t.test('success path', async function successCase() {
const result = await client.callTool({ name: 'slow', arguments: {} });
assert.equal(result.isError, false);
});first failure
Thrown from: registerFlap.flapTool
This error is intentionally thrown on the first invocation of the flap tool. The second and later calls succeed, which is useful for circuit-breaker recovery tests.
Step-by-step resolution:
- Confirm this is the first call in the process lifetime; first-call failure is expected behavior.
- If you need deterministic success, warm up the tool once before the measured assertion.
- For resilience tests, assert both phases: failure first, success next.
- Reset server state between test cases when first-call semantics matter.
await assert.rejects(client.callTool({ name: 'flap', arguments: {} }));
const ok = await client.callTool({ name: 'flap', arguments: {} });
assert.equal(ok.isError, false);progress tool aborted
Thrown from: registerProgress.progressTool
This error is raised when the progress tool sees extra.signal.aborted before or during loop processing. It represents explicit cancellation of long-running MCP work.
Step-by-step resolution:
- Check client/request timeout and cancellation behavior for this call.
- Confirm you did not pass an already-aborted signal into the tool execution context.
- Increase timeout or delay cancellation if you expect completion.
- For cancellation tests, assert abort behavior explicitly instead of treating this as unexpected.
const controller = new AbortController();
const result = await client.callTool({
name: 'progress',
arguments: { steps: 3, delayMs: 20 },
signal: controller.signal
});
assert.equal(result.isError, false);progress tool aborted
Thrown from: registerProgress.progressTool.sleep
This error is raised when cancellation occurs while progress is sleeping between step notifications. It is the mid-flight cancellation branch of the same tool.
Step-by-step resolution:
- Correlate abort timing with
delayMsandstepsin the progress request. - If completion is required, avoid cancelling during the sleep interval.
- If cancellation is expected, assert this specific branch so tests remain intentional.
- Tune
delayMsin tests to make cancel timing deterministic.
const controller = new AbortController();
const pending = client.callTool({
name: 'progress',
arguments: { steps: 5, delayMs: 100 },
signal: controller.signal
});
setTimeout(function abortLater() {
controller.abort();
}, 30);
await assert.rejects(pending);Invalid --port value "{value}".
Thrown from: port
This happens when mcp-test-server-http is started with a non-integer or out-of-range --port argument. The HTTP fixture only accepts numeric TCP ports in the 0-65535 range.
Step-by-step resolution:
- Pass
--portas a base-10 integer (--port 3333). - Avoid host:port strings (
127.0.0.1:3333) and non-numeric values. - If you use environment variables, ensure the value is present and parseable.
- In tests, reserve an ephemeral port first and pass that numeric value.
npx mcp-test-server-http --port 3333License
MIT
