JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 834
  • Score
    100M100P100Q104203F
  • License Apache-2.0

In-memory MVA lifecycle emulator for MCP Fusion. Runs the full pipeline (Zod Input → Middlewares → Handler → Egress Firewall) without network transport. Returns structured MvaTestResult objects — zero coupling to Jest/Vitest.

Package Exports

  • @mcpfusion/testing

Readme

@mcpfusion/testing

MCP Server Testing Framework — MCP Fusion — A framework for testing MCP servers in-memory
Full MVA pipeline · Egress Firewall audit · PII redaction checks · Middleware guards · Vitest · Jest · Mocha

npm License Node Zero Dependencies MCP MCP Fusion


MCP Server Testing Framework — MCP Fusion, the Model Context Protocol framework for building production MCP servers. In-memory MVA lifecycle emulator — runs the full execution pipeline without network transport. Zero runtime dependencies. Runner agnostic (Vitest, Jest, Mocha, node:test).

Why

Every MCP server today is tested with HTTP mocks, raw JSON.stringify assertions, and string matching. That's like testing a REST API by reading TCP packets.

MCP Fusion applications have five auditable layers (Zod Validation → Middleware Chain → Handler → Presenter Egress Firewall → System Rules). The MCPFusionTester lets you assert each layer independently, in-memory, without starting a server.

┌─────────────────────────────────────────────────────────┐
│                    MCPFusionTester                          │
│                                                         │
│  ┌──────────┐   ┌────────────┐   ┌─────────┐           │
│  │   Zod    │──▶│ Middleware  │──▶│ Handler │           │
│  │  Input   │   │   Chain    │   │         │           │
│  └──────────┘   └────────────┘   └────┬────┘           │
│                                       │                 │
│                                  ┌────▼────┐            │
│                                  │Presenter│            │
│                                  │ (Egress │            │
│                                  │Firewall)│            │
│                                  └────┬────┘            │
│                                       │                 │
│  ┌────────────────────────────────────▼──────────────┐  │
│  │              MvaTestResult                        │  │
│  │  ┌──────┐ ┌───────────┐ ┌────────┐ ┌───────────┐ │  │
│  │  │ data │ │systemRules│ │uiBlocks│ │rawResponse│ │  │
│  │  └──────┘ └───────────┘ └────────┘ └───────────┘ │  │
│  └───────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────┘

What You Can Audit

MVA Layer What MCPFusionTester asserts SOC2 Relevance
Egress Firewall Hidden fields (passwordHash, tenantId) are physically absent from result.data Data leak prevention
OOM Guard Zod rejects take: 10000 before it reaches the handler Memory exhaustion protection
System Rules result.systemRules contains the expected domain rules Deterministic LLM governance
UI Blocks SSR blocks (echarts, summaries) are correctly generated Agent response quality
Middleware Auth guards block unauthorized calls, isError is true Access control verification
Agent Limit Collections are truncated at cognitive guardrail bounds Context window protection
HATEOAS suggestActions produces correct next-step affordances Agent navigation safety

Quick Start

import { describe, it, expect } from 'vitest';
import { createMCPFusionTester } from '@mcpfusion/testing';
import { registry } from './server/registry.js';

const tester = createMCPFusionTester(registry, {
    contextFactory: () => ({
        prisma: mockPrisma,
        tenantId: 't_enterprise_42',
        role: 'ADMIN',
    }),
});

describe('User MVA Audit', () => {
    it('Egress Firewall strips sensitive fields', async () => {
        const result = await tester.callAction('db_user', 'find_many', { take: 10 });

        expect(result.data[0]).not.toHaveProperty('passwordHash');
        expect(result.data[0]).not.toHaveProperty('tenantId');
        expect(result.data[0].email).toBe('ceo@acme.com');
    });

    it('System rules are injected by Presenter', async () => {
        const result = await tester.callAction('db_user', 'find_many', { take: 5 });

        expect(result.systemRules).toContain(
            'Data originates from the database via Prisma ORM.'
        );
    });

    it('OOM Guard rejects unbounded queries', async () => {
        const result = await tester.callAction('db_user', 'find_many', { take: 99999 });
        expect(result.isError).toBe(true);
    });
});

API Reference

createMCPFusionTester(registry, options)

Factory function — creates a MCPFusionTester instance.

function createMCPFusionTester<TContext>(
    registry: ToolRegistry<TContext>,
    options: TesterOptions<TContext>,
): MCPFusionTester<TContext>;
Parameter Type Description
registry ToolRegistry<TContext> Your application's tool registry — the same one wired to the MCP server
options TesterOptions<TContext> Configuration object

TesterOptions<TContext>

interface TesterOptions<TContext> {
    contextFactory: () => TContext | Promise<TContext>;
}

tester.callAction(toolName, actionName, args?, overrideContext?)

Executes a single tool action through the full MVA pipeline and returns a decomposed result.

async callAction<TArgs>(
    toolName: string,
    actionName: string,
    args?: TArgs,
    overrideContext?: Partial<TContext>,
): Promise<MvaTestResult>;
Parameter Type Required Description
toolName string The registered tool name (e.g. 'db_user', 'analytics')
actionName string The action discriminator (e.g. 'find_many', 'create')
args object Arguments for the action — omit the action discriminator
overrideContext Partial<TContext> Per-test context overrides. Shallow-merged with contextFactory() output

MvaTestResult<TData>

Decomposed MVA response — each field maps to a specific pipeline layer.

Field Type Source Description
data TData Presenter Zod schema Validated data after the Egress Firewall. Hidden fields are physically absent.
systemRules string[] Presenter .systemRules() JIT domain rules injected by the Presenter. Empty array if no Presenter.
uiBlocks unknown[] Presenter .uiBlocks() SSR UI blocks (charts, summaries, markdown). Empty array if no Presenter.
isError boolean Pipeline true if Zod rejected, middleware blocked, or handler returned error().
rawResponse unknown Pipeline The raw MCP ToolResponse for protocol-level inspection.

Cookbook

Egress Firewall Audit

it('strips PII from response', async () => {
    const result = await tester.callAction('db_user', 'find_many', { take: 5 });

    const users = result.data as Array<Record<string, unknown>>;
    for (const user of users) {
        expect(user).not.toHaveProperty('passwordHash');
        expect(user).not.toHaveProperty('tenantId');
    }
});

Middleware Guards (RBAC)

it('blocks GUEST role', async () => {
    const result = await tester.callAction(
        'db_user', 'find_many', { take: 5 },
        { role: 'GUEST' },
    );
    expect(result.isError).toBe(true);
});

it('allows ADMIN role', async () => {
    const result = await tester.callAction(
        'db_user', 'find_many', { take: 5 },
        { role: 'ADMIN' },
    );
    expect(result.isError).toBe(false);
});

Agent Limit (Cognitive Guardrail)

it('truncates at agentLimit', async () => {
    const result = await tester.callAction('analytics', 'list', { limit: 100 });
    expect((result.data as any[]).length).toBe(20);
});

Protocol-Level Inspection

it('raw response follows MCP shape', async () => {
    const result = await tester.callAction('db_user', 'find_many', { take: 1 });
    const raw = result.rawResponse as { content: Array<{ type: string; text: string }> };

    expect(raw.content).toBeInstanceOf(Array);
    expect(raw.content[0].type).toBe('text');
});

How It Works

The MCPFusionTester runs the real execution pipeline — the exact same code path as your production MCP server:

ToolRegistry.routeCall()
  → Concurrency Semaphore
    → Discriminator Parsing
      → Zod Input Validation
        → Compiled Middleware Chain
          → Handler Execution
            → PostProcessor (Presenter auto-application)
              → Egress Guard

The key insight: ResponseBuilder.build() attaches structured MVA metadata via a global Symbol (MVA_META_SYMBOL). Symbols are ignored by JSON.stringify, so the MCP transport never sees them — but the MCPFusionTester reads them in RAM.

No XML regex. No string parsing. Zero coupling to response formatting.

Installation

npm install @mcpfusion/testing

Peer Dependencies

Package Version
@mcpfusion/core ^2.0.0
zod ^3.25.1 || ^4.0.0

Requirements

  • Node.js ≥ 18.0.0
  • TypeScript 5.7+
  • MCP Fusion ≥ 2.0.0 (peer dependency)

License

Apache-2.0