aimock: one local port that answers as every AI provider your tests call
Mock everything your AI app talks to — LLM APIs, MCP, A2A, AG-UI, vector DBs, search. One package, one port, zero dependencies.
At a glance
- What is it?
- CopilotKit's aimock is an MIT-licensed TypeScript mock server that stands in for LLM APIs, MCP, A2A, AG-UI, vector databases and search behind a single port. The record and replay tape is the part worth evaluating, and the base-URL ordering trap is the part worth reading first.
- Who is it for?
- Adopt aimock if your test suite already speaks HTTP to provider SDKs and you want deterministic answers without keys or network calls, especially if you plan to record a few real sessions and replay them. Do not adopt it if you need to verify that a real provider still behaves the way your fixtures assume, or if your app builds its provider client before environment variables are set.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 16, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The problem aimock targets: provider calls inside unit tests
An AI application's test suite touches more than its own code. A chat feature calls an LLM endpoint, a retrieval step calls a vector database, a tool-using agent calls an MCP server, and a frontend test needs a stream of AG-UI events. Each of those is a network dependency with a key, a rate limit and a bill. The README frames the goal plainly: point your SDK at one local port and every provider, protocol and service answers deterministically.
The intended user is a TypeScript engineer who already writes tests against provider SDKs and wants those tests to run offline. The package is published as @copilotkit/aimock and licensed MIT. The README states it has zero dependencies, which matters if you are adding it to a project that is sensitive about its dependency tree. It is not a general HTTP mock. It answers the specific shapes that AI SDKs expect, which is why the suite is split into LLMock, MCPMock, A2AMock, AGUIMock and VectorMock rather than one generic handler.
LLMock, MCPMock, A2AMock, AGUIMock, VectorMock: one port, five surfaces
The architecture is a set of mocks that can run together behind a single port. The README's suite table gives each one a scope. LLMock covers provider chat and completion surfaces: OpenAI Chat, Responses and Realtime in both GA and Beta shim forms, Claude, Gemini REST and Live and Interactions and embedContent, Bedrock, Azure, Vertex AI, Ollama chat and embeddings, Cohere chat and embed, OpenRouter chat and router failover, and ElevenLabs TTS. MCPMock answers MCP tools, resources and prompts with session management. A2AMock speaks the agent-to-agent protocol with SSE streaming. AGUIMock emits AG-UI event streams for frontend testing. VectorMock exposes Pinecone, Qdrant and ChromaDB compatible endpoints. A separate Services group covers Tavily search, Cohere rerank, OpenAI moderation and ElevenLabs TTS.
The README also lists multimedia endpoints: image generation for DALL-E and Imagen, /v1/images/edits, text-to-speech, /v1/audio/transcriptions, /v1/audio/translations, video generation, OpenRouter video generation at /api/v1/videos with an async job lifecycle, and Google Veo video generation. The claim of full streaming support appears next to the provider list. The practical consequence is that a single process can satisfy a test that starts with an LLM call, retrieves from a vector store, invokes an MCP tool and then pushes AG-UI events to a frontend, without any of those four steps leaving localhost.
The class name is worth noting. The README says the class is still named LLMock for back-compat after the v1.7.0 package rename from @copilotkit/llmock to @copilotkit/aimock. If you find LLMock in older code or examples, it is the same entry point, not a second library.
Starting a mock in code and the base-URL ordering constraint
The quick start is short. Install with npm install @copilotkit/aimock, then construct the mock. The README example passes port 0, which lets the operating system choose a free port, registers a single response with mock.onMessage("hello", { content: "Hi there!" }), calls await mock.start(), and reads the resulting address from mock.url. Teardown is await mock.stop().
The part that deserves attention is the comment around the environment variables. The README sets process.env.OPENAI_BASE_URL to `${mock.url}/v1` and process.env.OPENAI_API_KEY to the literal string "mock", and it warns that this must happen before importing or constructing the provider client. Many SDKs cache the base URL at construction time. If the client is built first, it will talk to the real API, and the README calls the outcome surprise bills. The API key still has to be set to some value because the SDK requires one even when the base URL is mocked.
For a whole suite rather than a single test, the CLI takes a config file: npx @copilotkit/aimock --config aimock.json. The README does not reproduce the schema of aimock.json in the material available here, so the keys inside that file cannot be confirmed from what is shown. Treat the programmatic API as the documented path and the config file as the path to read up on before committing to it.
Record and replay, and what the fixtures actually preserve
The feature with the most detail in the README is record and replay. The described flow is to proxy real APIs, save the traffic as fixtures, and replay those fixtures deterministically afterwards. The recording is timing-aware: fixtures capture per-frame arrival timestamps, and replay uses the recorded timings to approximate timing based on recorded time to first token and inter-frame cadence. The README is explicit about the limit here. Replay chunk count may differ from the recording, and what is preserved is TTFT and average pace, not per-token fidelity. A --replay-speed multiplier is configurable.
Token accounting is captured too. Recording saves the final usage frame of a streaming completion, so a replayed fixture serves real prompt_tokens and completion_tokens instead of a length estimate. For OpenRouter, the provider-reported usage.cost and its cost_details and *_tokens_details breakdowns are captured as well, which the README says lets an app that bills from real provider cost exercise its wallet path end to end from a tape. That is a narrower and more useful claim than generic determinism: the fixture is not just a canned response, it carries the numbers your billing code reads.
Multi-turn is handled through matching rather than a fixed script. The README lists turnIndex, hasToolResult, toolCallId, toolResultContains for gating on the tool-result payload, sequenceIndex, systemMessage for gating on host-supplied agent context, and custom predicates. Those selectors are how a recorded trace with tool rounds is replayed against a live conversation, because the mock has to decide which recorded turn answers the current request.
The failure mode that costs money, and the cases aimock cannot cover
The base-URL ordering issue is not a footnote. It is the one documented way this tool fails silently and expensively. If any part of your setup imports a provider client at module load time, or if a test helper constructs the client in a shared fixture that runs before the mock starts, the client keeps the real base URL and the test goes to the network. The README's own warning is the strongest statement in the repository, and it implies the failure is invisible until the bill arrives. There is no stated runtime check that the client is pointed at mock.url.
The second limitation is inherent to mocking. A replayed fixture proves your code handles the recorded response. It does not prove the provider still returns that shape. The README mentions a Drift Tests workflow in the repository's badges, but the material here does not describe what that workflow checks, so its coverage cannot be judged from this page. If your risk is provider API change rather than test speed, a mock is the wrong instrument, because the mock is the thing that will not notice the change.
The third limitation is scope. The suite table lists 13 providers across 15 API surfaces, and the prose lists a different set including multimedia endpoints. Those two lists do not line up one to one in the material provided, so before adopting, check that the specific endpoint your app calls is in the table rather than only in the feature bullets. A provider named in prose but absent from the surface list is a gap you would discover during test writing.
How aimock differs from WireMock and from VCR-style HTTP recorders
WireMock is the general-purpose comparison. It is a mature HTTP stub server with request matching, fault injection and a JSON mapping format, and it does not know what an SSE completion frame or an AG-UI event looks like. With WireMock you would author the provider's response shape yourself, including the streaming frame format, the usage frame, and the MCP session handshake. aimock ships those shapes. The trade-off runs the other way too: WireMock's matching language and its stub management are broader than what the README describes for aimock, and WireMock has no opinion about which provider you are faking. If your tests need to simulate malformed chunked responses or connection resets at the transport level, a general HTTP stub is the more direct tool.
The closer comparison is a VCR-style recorder such as nock's recording mode or Polly.js. Those intercept HTTP at the client library level and replay cassettes, and they are agnostic about the payload. The difference is where the knowledge lives. A VCR cassette is a byte-for-byte transcript, so a change in your request body can break matching unless you configure a matcher. aimock's fixtures carry semantic selectors instead: turnIndex, hasToolResult, toolCallId, toolResultContains, sequenceIndex and systemMessage. That lets the same tape answer a conversation whose exact bytes differ, at the cost of depending on aimock's matching semantics rather than your own. The timing-aware replay and the captured usage and cost frames are also aimock-specific; a plain cassette replays as fast as the disk allows and carries whatever the provider sent, which may or may not include the usage frame your code reads.
Maintenance, upgrade cost and what the MIT licence leaves you to do
The release cadence visible in the material is roughly one minor version every two to three weeks: v1.38.0 on 2026-08-04, v1.39.0 on 2026-08-19, v1.40.0 on 2026-09-09, with the last push to main on 2026-09-10. That is frequent enough that pinning a version in CI is the reasonable default, and it also means the provider surface list is a moving target. A mock server tracks other people's APIs, so its minor releases are where new endpoints and protocol revisions land.
The upgrade cost is concentrated in fixtures. If you record tapes and later upgrade aimock, the fixture format is the thing that has to keep parsing, and the README does not state a fixture compatibility guarantee. The safe posture is to re-record rather than assume old tapes survive a version jump, and to keep the recording step scripted so re-recording is cheap.
The licence is MIT, which permits commercial use, modification and redistribution provided the copyright notice and permission notice are retained. That is a statement about the licence text, not legal advice. Because aimock is a test dependency, the more practical question is whether it should be a devDependency in your manifest. The README's own quick start installs it with npm install without a --save-dev flag, but nothing in the material suggests it belongs in a production bundle.
Editorial conclusion
Adopt aimock if your test suite already speaks HTTP to provider SDKs and you want deterministic answers without keys or network calls, especially if you plan to record a few real sessions and replay them. Do not adopt it if you need to verify that a real provider still behaves the way your fixtures assume, or if your app builds its provider client before environment variables are set. Verify first that the client is constructed after OPENAI_BASE_URL points at mock.url, that port 0 is acceptable to whatever reads mock.url, and that the provider surface you depend on appears in the 13 providers across 15 API surfaces list rather than only in the prose.
Community notes