@agent-vcr/core
v0.1.0
Published
Record, replay, and diff MCP (Model Context Protocol) interactions — like VCR for AI agents
Downloads
39
Maintainers
Readme
Agent VCR - TypeScript/Node.js
Record, replay, and diff MCP interactions — like VCR for AI agents.
First-class TypeScript implementation for the MCP ecosystem. 72 tests cover format, matcher, replayer, recorder, and diff. npm-ready as @agent-vcr/core. Same functionality as the Python version with full cross-language compatibility — record with Python, replay with TypeScript (or the other way around). For implementation details and contributor notes, see docs/typescript-implementation.md.
Installation
npm install @agent-vcr/coreFor testing framework integrations:
# Jest
npm install --save-dev @agent-vcr/core
# Vitest
npm install --save-dev @agent-vcr/coreQuick Start
Recording MCP Interactions (CLI)
# Record stdio transport
npx agent-vcr record \
--transport stdio \
--server-command "node my-mcp-server.js" \
-o my-recording.vcr
# Record SSE transport
npx agent-vcr record \
--transport sse \
--server-command "node my-mcp-server.js" \
--host 127.0.0.1 \
--port 3000 \
-o my-recording.vcrReplaying Recordings (CLI)
# Replay via stdio
npx agent-vcr replay -i my-recording.vcr --transport stdio
# Replay via SSE
npx agent-vcr replay -i my-recording.vcr --transport sse --port 3000Diffing Recordings (CLI)
npx agent-vcr diff \
--baseline v1-recording.vcr \
--current v2-recording.vcr \
--fail-on-breakingProgrammatic Usage
Recording
import { MCPRecorder } from "@agent-vcr/core";
const recorder = new MCPRecorder({
transport: "stdio",
command: "node my-mcp-server.js",
metadata: {
tags: { env: "test" },
},
});
await recorder.start();
// Let it record interactions...
// Press Ctrl+C or call stop()
const recording = await recorder.stop();
await recorder.save(recording, "recording.vcr");Replaying
import { MCPReplayer } from "@agent-vcr/core";
// Load from file
const replayer = await MCPReplayer.fromFile("recording.vcr");
// Handle a request
const request = {
jsonrpc: "2.0",
id: 1,
method: "tools/list",
};
const response = replayer.handleRequest(request);
console.log(response);
// Serve as a mock server
await replayer.serveStdio(); // or serveSSE(host, port)Error Injection
const replayer = await MCPReplayer.fromFile("recording.vcr");
// Inject an error for request id=3
replayer.setResponseOverride(3, {
jsonrpc: "2.0",
id: 3,
error: {
code: -32603,
message: "Internal server error",
},
});
// Test your error handling
const response = replayer.handleRequest({
jsonrpc: "2.0",
id: 3,
method: "tools/call",
params: { name: "calculator" },
});Diffing
import { MCPDiff } from "@agent-vcr/core";
const result = await MCPDiff.compareFiles("v1.vcr", "v2.vcr");
console.log(`Breaking changes: ${result.summary.breaking_count}`);
console.log(`Added methods: ${result.summary.added_count}`);
console.log(`Removed methods: ${result.summary.removed_count}`);
// Check for specific breaking changes
for (const change of result.breaking_changes) {
console.log(`⚠️ ${change.type}: ${change.method}`);
console.log(` ${change.details}`);
}Testing Framework Integration
Vitest
import { describe, it, expect } from "vitest";
import { useVCRCassette } from "@agent-vcr/core/vitest";
describe("My MCP client", () => {
it("should list tools correctly", async () => {
const { replayer, cassette } = await useVCRCassette({
name: "list-tools",
dir: "tests/fixtures/cassettes",
});
try {
const response = replayer.handleRequest({
jsonrpc: "2.0",
id: 1,
method: "tools/list",
});
expect(response?.result).toBeDefined();
expect(response?.result?.tools).toHaveLength(3);
} finally {
await cassette.eject();
}
});
});Jest
import { useVCRCassette } from "@agent-vcr/core/jest";
describe("My MCP client", () => {
it("should list tools correctly", async () => {
const { replayer, cassette } = await useVCRCassette({
name: "list-tools",
dir: "__fixtures__/cassettes",
});
try {
const response = replayer.handleRequest({
jsonrpc: "2.0",
id: 1,
method: "tools/list",
});
expect(response?.result).toBeDefined();
} finally {
await cassette.eject();
}
});
});Match Strategies
When replaying, you can choose how requests are matched to recorded interactions:
| Strategy | Description | Best For |
| -------------------- | ------------------------------------ | ------------------------ |
| exact | Full JSON equality | Strictest regression |
| method | Method name only | Broad acceptance tests |
| method_and_params | Method + params equality (default) | Standard testing |
| fuzzy | Method + params subset matching | Flexible testing |
| sequential | Returns in order, ignores content | Scripted replay |
const replayer = await MCPReplayer.fromFile("recording.vcr", "fuzzy");Cross-Language Compatibility
Recordings are stored as JSON and are fully compatible between Python and TypeScript implementations:
# Record with Python
python -m agent_vcr record --server-command "node server.js" -o recording.vcr
# Replay with TypeScript
npx agent-vcr replay -i recording.vcr
# Or vice versa!CLI Reference
agent-vcr record --help
agent-vcr replay --help
agent-vcr diff --help
agent-vcr inspect --help
agent-vcr convert --helpAPI Documentation
Full TypeScript API documentation is available in the source code with JSDoc comments. Key exports:
import {
// Core types
VCRRecording,
VCRInteraction,
JSONRPCRequest,
JSONRPCResponse,
// Main classes
MCPRecorder,
MCPReplayer,
MCPDiff,
// Transport
StdioTransport,
SSETransport,
// Matching
createMatcher,
MatchStrategy,
// Testing integrations
} from "@agent-vcr/core";Examples
See the /examples directory in the repository for complete examples:
- Basic recording and replay
- Error injection for resilience testing
- CI/CD integration patterns
- Multi-version compatibility testing
Contributing
See CONTRIBUTING.md in the repository root.
License
MIT — see LICENSE
