Skip to main content
@frontmcp/testing is the official testing library for FrontMCP, providing a complete toolkit for end-to-end testing of MCP servers. Test your tools, resources, prompts, authentication, plugins, and the full MCP protocol.

Fixture-Based

Playwright-inspired fixtures inject ready-to-use test clients

Full Protocol

Test tools, resources, prompts, and raw JSON-RPC

Offline Testing

Mock HTTP calls for fully offline test suites

Installation

npm install -D @frontmcp/testing
@frontmcp/testing requires @frontmcp/sdk as a peer dependency and uses Jest as the test runner.

Quick Start

1. Configure Jest

Create or update your jest.e2e.config.ts:
jest.e2e.config.ts
import type { Config } from 'jest';

const config: Config = {
  displayName: 'e2e',
  preset: '@frontmcp/testing/jest-preset',
  testMatch: ['**/*.e2e.ts'],
  testTimeout: 30000,
};

export default config;

2. Write Your First Test

server.e2e.ts
import { test, expect } from '@frontmcp/testing';

// Point to your FrontMCP server entry file
test.use({
  server: './src/main.ts',
  port: 3003,
});

test('server exposes tools', async ({ mcp }) => {
  const tools = await mcp.tools.list();
  expect(tools).toContainTool('my-tool');
});

test('tool execution works', async ({ mcp }) => {
  const result = await mcp.tools.call('my-tool', { input: 'test' });
  expect(result).toBeSuccessful();
  expect(result.json()).toHaveProperty('success', true);
});

3. Run Tests

# Using jest directly
npx jest --config jest.e2e.config.ts

# Using nx
nx e2e my-app
The library automatically:
  • Starts your FrontMCP server on the specified port
  • Connects an MCP client using Streamable-HTTP transport
  • Runs your tests with injected fixtures
  • Cleans up after all tests complete

Key Features

Fixture System

Tests receive pre-configured fixtures via dependency injection:
FixtureDescription
mcpAuto-connected MCP client for making requests
serverServer control (restart, logs, create additional clients)
authToken factory for authentication testing
test('example with all fixtures', async ({ mcp, server, auth }) => {
  // mcp: ready-to-use MCP client
  const tools = await mcp.tools.list();

  // auth: create test tokens
  const token = await auth.createToken({ sub: 'user-123', scopes: ['read'] });

  // server: create additional clients
  const client2 = await server.createClient({ token });
});

Custom Jest Matchers

MCP-specific matchers for cleaner assertions:
// Tool matchers
expect(tools).toContainTool('create-note');

// Result matchers
expect(result).toBeSuccessful();
expect(result).toBeError(-32602);

// Content matchers
expect(result).toHaveTextContent();
expect(content).toHaveMimeType('application/json');

// Resource matchers
expect(resources).toContainResource('notes://all');
expect(templates).toContainResourceTemplate('notes://note/{id}');

HTTP Mocking

Mock external HTTP calls made by your tools for offline testing:
import { httpMock } from '@frontmcp/testing';

test('tool with external API', async ({ mcp }) => {
  const interceptor = httpMock.interceptor();

  interceptor.get('https://api.example.com/users', {
    body: [{ id: 1, name: 'John' }],
  });

  const result = await mcp.tools.call('fetch-users', {});
  expect(result).toBeSuccessful();

  interceptor.restore();
});

MCP Request Interception

Mock or modify MCP protocol requests:
test('mock tool response', async ({ mcp }) => {
  mcp.mock.tool('expensive-tool', { cached: true });

  const result = await mcp.tools.call('expensive-tool', {});
  expect(result.json()).toEqual({ cached: true });
});

Test Configuration

Configure tests using test.use():
test.use({
  // Server configuration
  server: './src/main.ts',    // Entry file path
  port: 3003,                  // Port (default: auto-select)
  startupTimeout: 30000,       // Startup timeout in ms

  // Transport
  transport: 'streamable-http', // 'sse' | 'streamable-http'

  // Authentication
  auth: {
    mode: 'public',            // 'public' | 'orchestrated'
    type: 'local',             // 'local' | 'remote'
  },

  // Debugging
  logLevel: 'debug',           // 'debug' | 'info' | 'warn' | 'error'
  env: { API_KEY: 'test' },    // Environment variables
});
Use port: 0 to automatically select an available port. This prevents conflicts when running multiple test suites in parallel.

Demo E2E reference suites

The repository ships scenario-focused Nx projects under apps/e2e/**. Each server mirrors a real deployment surface (auth modes, caching, CodeCall, OpenAPI ingestion, notifications) and already includes Jest suites wired to @frontmcp/testing.
Source layout: server code lives in apps/e2e/<project>/src, with matching tests in apps/e2e/<project>/e2e. Copy a project into your workspace or point TestServer at it during CI runs.
  • demo-e2e-public – minimal Notes + Tasks server that enforces public auth defaults and anonymous scopes.
  • demo-e2e-codecall – CRM sample that groups eight CRUD tools behind the CodeCall plugin and the new crm.store.ts.
  • demo-e2e-openapi – OpenAPI adapter smoke tests with built-in HTTP mocking examples.
  • demo-e2e-cache – Cache plugin playground for validating cache hits/misses plus execution tracking resources.
apps/e2e/demo-e2e-public/e2e/public-auth.e2e.test.ts
test.use({
  server: 'apps/e2e/demo-e2e-public/src/main.ts',
  publicMode: true,
});

test.describe('Public Auth Mode E2E', () => {
  test('lists all tools without auth', async ({ mcp }) => {
    const tools = await mcp.tools.list();
    expect(tools).toContainTool('create-note');
  });
});
Pair apps/e2e/demo-e2e-openapi/e2e/openapi.e2e.test.ts with httpMock.interceptor() to keep tests offline: stub Beeceptor endpoints, allow passthrough for the OpenAPI spec, then call interceptor.restore() in finally.

Next Steps