Mockwave is an open-source, multi-protocol mock server. Define rules and simulations in JSON, manage them through the browser UI, or let an AI assistant do it — Mockwave responds to HTTP, GraphQL, SOAP, and gRPC requests with weighted traffic splitting, dynamic JavaScript responses, real-time metrics, and a built-in MCP server for Claude Code integration.
- Multi-protocol — HTTP REST, GraphQL, SOAP, gRPC (reflection-free, descriptor-based)
- Traffic splitting — weighted buckets per rule (e.g., 90% mock / 10% forward to real service)
- Dynamic scripting — per-simulation JavaScript (goja) for computed responses
- Real-time admin UI — browser dashboard at
localhost:9090for rule/simulation CRUD, live metrics, and unmatched request capture - Multiple store backends — JSON file, DynamoDB, MongoDB, Azure Cosmos DB (MongoDB API)
- Hot reload — update rules without restarting via the admin API
- Import/Export — export rules + simulations to a runnable JSON config from the admin UI/API; two-phase import with conflict override (remote stores; with the JSON store the config file already plays this role)
- Chaos testing with profiles — inject faults (latency, errors, resets, truncation, throttling) at the boundary; profile chaos testing with realistic scenarios (degraded services, network partitions, cascading failures) without host agents or root access
- Matched request capture — opt-in capture of requests that matched a rule, retrievable via
GET /api/matched/{rule_id}(paginated; filter by method/path/status/header) andGET /api/matched/{rule_id}/{id}(full request + bodies). Global TTL with native expiry on DynamoDB/Mongo/Cosmos. Enables regressive e2e: assert both the mock's response and the exact request your system sent. - Outgoing event capture (AWS) — intercept the app's SNS / SQS / EventBridge publishes on the mock port, capture them for assertion, and either return a valid mock response or forward to the real broker (re-signed with your credentials, relaying the broker's real id). Point your AWS SDK client at Mockwave via endpoint override (
--protocols http,aws --event-capture). Seedocs/event-capture.md. Event rules and captures persist on DynamoDB/MongoDB/Cosmos like matched capture. - AI integration (MCP) —
mockwave mcpexposes a Model Context Protocol server so Claude Code can create rules, manage simulations, and auto-generate mocks from any OpenAPI 2.0/3.0 spec - Embeddable library — public
store.DataStore,observability.Logger/Tracer/MetricsRecorderinterfaces; bring your own backends
brew tap lfdubiela/mockwave
brew trust lfdubiela/mockwave
brew install mockwave # or: brew upgrade mockwave (if already installed)
# Verify installation
mockwave versionNote:
brew trust lfdubiela/mockwaveis only needed if Homebrew prompts to trust the tap. Skip it if no prompt appears.
# Create a minimal config
cat > config.json <<'EOF'
{
"rules": [
{
"id": "hello",
"name": "Hello World",
"match": { "method": "GET", "path": "/hello" },
"buckets": [{ "weight": 100, "action": "simulate", "simulation_id": "hello-sim" }]
}
],
"simulations": [
{
"id": "hello-sim",
"protocol": "http",
"response": { "status": 200, "body": { "message": "Hello from Mockwave!" } }
}
]
}
EOF
# Start on default ports (mock :8080, admin :9090)
mockwave start -f config.json
# Custom ports
mockwave start -f config.json --port 3000 --admin-port 3001# Test it
curl http://localhost:8080/hello
# {"message":"Hello from Mockwave!"}
# Open the admin UI
open http://localhost:9090# Upgrade
brew upgrade mockwaveWith Mockwave running, add to ~/.claude/mcp.json (create if it doesn't exist):
{
"mcpServers": {
"mockwave-local": {
"command": "mockwave",
"args": ["mcp", "--admin-url", "http://localhost:9090"]
}
}
}Then ask Claude Code to do the work:
"Generate mocks from https://petstore3.swagger.io/api/v3/openapi.json"
"Create a mock for POST /checkout that returns 201 with an order ID"
"What requests are hitting mockwave but not matching any rule?"
# Download the latest release binary (replace OS/ARCH as needed)
curl -Lo mockwave https://github.com/lfdubiela/mockwave/releases/download/v0.2.0/mockwave-linux-amd64
chmod +x mockwave
# Start the server
./mockwave start -f config.jsondocker run -p 8080:8080 -p 9090:9090 \
-v $(pwd)/config.json:/config.json \
ghcr.io/lfdubiela/mockwave:v0.1.0 \
start -f /config.jsonmockwave [command]
Commands:
start Start the mock server
validate Validate a config file without starting the server
version Print version
mcp Start MCP server for AI assistant integration (Claude Code, etc.)
Flags (start):
-f, --config string Path to JSON config file (required for --store=json)
--port int Mock server port (default 8080)
--admin-port int Admin UI/API port (default 9090)
--protocols string Comma-separated: http,graphql,soap,grpc,aws (default "http")
--grpc-port int gRPC server port (default 50051)
--grpc-proto string Path to compiled .pb descriptor for gRPC proto conversion
# Store backend
--store string Storage backend: json|dynamodb|mongo|cosmos (default "json")
--reload-interval duration Version-poll reload interval for remote stores (default 15s)
# DynamoDB
--dynamo-rules-table string DynamoDB table for rules (default "mockwave-rules")
--dynamo-sims-table string DynamoDB table for simulations (default "mockwave-simulations")
--dynamo-faults-table string DynamoDB table for fault profiles (default "mockwave-fault-profiles")
--dynamo-scenarios-table string DynamoDB table for scenarios (default "mockwave-scenarios")
--dynamo-region string AWS region (default "us-east-1")
--dynamo-endpoint string Custom endpoint, e.g. http://localhost:8000
# MongoDB
--mongo-uri string MongoDB connection URI (default "mongodb://localhost:27017")
--mongo-db string MongoDB database name (default "mockwave")
# Cosmos DB
--cosmos-uri string Cosmos DB connection string (MongoDB API)
--cosmos-db string Cosmos DB database name (default "mockwave")
# Matched request capture
--matched-capture Enable matched request capture (default false)
--matched-ttl int Capture TTL in seconds (default 3600)
--matched-buffer-size int In-memory ring buffer capacity (default 10000)
--matched-sync-interval int Write-behind sync interval in seconds (default 30)
# AWS event capture (use with --protocols aws)
--event-capture Enable AWS event interception + capture (default false)
--event-ttl int Capture TTL in seconds (default 3600)
--event-buffer-size int In-memory buffer capacity (default 10000)
--event-sync-interval int Write-behind sync interval in seconds (default 30)
# HTTP + GraphQL on the same port
mockwave start -f config.json --protocols http,graphql
# All protocols
mockwave start -f config.json --protocols http,graphql,soap,grpc --grpc-proto service.pb
# DynamoDB backend (uses default AWS credential chain)
mockwave start --store dynamodb --dynamo-region eu-west-1
# Local DynamoDB (e.g. DynamoDB Local)
mockwave start --store dynamodb --dynamo-endpoint http://localhost:8000
# MongoDB backend
mockwave start --store mongo --mongo-uri mongodb://user:pass@host:27017/mydb
# Validate a config file
mockwave validate config.jsonThe JSON config file has two top-level arrays: rules and simulations.
{
"rules": [ ...Rule... ],
"simulations": [ ...Simulation... ]
}{
"id": "string (required, unique)",
"name": "string (display label)",
"match": {
"protocol": "http | graphql | soap | grpc",
"method": "GET | POST | PUT | DELETE | PATCH | ...",
"path": "/users/* (glob supported)",
"headers": { "X-Tenant": "acme" },
"query": { "version": "2" },
"body": { "$.type": "order" }
},
"buckets": [
{
"weight": 100,
"action": "simulate | forward",
"simulation_id": "sim-id (required when action=simulate)"
}
],
"forward_url": "https://real-api.example.com (required when any bucket has action=forward)"
}Path globs: * matches a single segment, ** matches any number of segments.
/users/*matches/users/123but not/users/123/orders/api/**matches any path under/api/
Traffic splitting: weights are relative. [{weight:90,…}, {weight:10,…}] routes 90% to the first bucket and 10% to the second. Weights do not need to sum to 100.
Forwarding: set action: "forward" and provide forward_url. The request is proxied to forward_url + original_path with original headers and body.
{
"id": "string (required, unique)",
"protocol": "http | graphql | soap | grpc",
"response": {
"status": 200,
"headers": { "Content-Type": "application/json" },
"body": { "any": "JSON value" },
"delay_ms": 150
},
"script": "// optional JS — return value overrides response.body\nreturn { computed: request.path };",
"soap_envelope": "<soap:Envelope>...</soap:Envelope>",
"grpc_message": "{ \"userId\": \"123\" }",
"grpc_status": 0
}Enabled by default. Routes by method + path. Response body supports Go template variables:
{
"id": "user-get",
"protocol": "http",
"response": {
"status": 200,
"body": { "id": "{{.PathParam \"id\"}}", "name": "Alice" }
}
}Enable with --protocols http,graphql. Mockwave parses the operationName from the request body and matches against match.path (treated as operation name prefix/glob).
{
"id": "gql-user",
"match": { "protocol": "graphql", "path": "GetUser" },
"buckets": [{ "weight": 100, "action": "simulate", "simulation_id": "gql-user-sim" }]
}Enable with --protocols http,soap. Mockwave reads the SOAP action from the SOAPAction header and routes accordingly. Set soap_envelope in the simulation to return a raw XML envelope.
{
"id": "soap-create-order",
"match": { "protocol": "soap", "path": "CreateOrder" },
"buckets": [{ "weight": 100, "action": "simulate", "simulation_id": "create-order-sim" }]
}{
"id": "create-order-sim",
"protocol": "soap",
"soap_envelope": "<soap:Envelope xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"><soap:Body><CreateOrderResponse><orderId>42</orderId></CreateOrderResponse></soap:Body></soap:Envelope>"
}Enable with --protocols http,grpc. Requires a compiled protobuf descriptor:
# Compile your .proto to a descriptor
protoc --descriptor_set_out=service.pb --include_imports service.proto
# Start with the descriptor
mockwave start -f config.json --protocols http,grpc --grpc-proto service.pbSet grpc_message (JSON representation of the proto response) and grpc_status (gRPC status code, 0 = OK) in the simulation:
{
"id": "get-user-sim",
"protocol": "grpc",
"grpc_message": "{ \"userId\": \"abc\", \"name\": \"Alice\" }",
"grpc_status": 0
}| Backend | Flag | Notes |
|---|---|---|
| JSON file | --store json -f config.json |
Default. File is read on start; hot-reloaded via admin API. |
| DynamoDB | --store dynamodb |
Uses AWS default credential chain. Tables must exist with PK id (String). |
| MongoDB | --store mongo |
Tested with MongoDB 6+. |
| Cosmos DB | --store cosmos |
Uses MongoDB wire protocol. ssl=true and retryWrites=false applied automatically. |
Create two tables (substitute region/table names as needed):
aws dynamodb create-table --table-name mockwave-rules \
--attribute-definitions AttributeName=id,AttributeType=S \
--key-schema AttributeName=id,KeyType=HASH \
--billing-mode PAY_PER_REQUEST
aws dynamodb create-table --table-name mockwave-simulations \
--attribute-definitions AttributeName=id,AttributeType=S \
--key-schema AttributeName=id,KeyType=HASH \
--billing-mode PAY_PER_REQUESTOpen http://localhost:9090 in a browser. The UI is served from the admin port and requires no external dependencies.
| Tab | What it does |
|---|---|
| Rules | List, create, edit, and delete rules |
| Simulations | List, add (JSON editor), and delete simulations |
| Metrics | Live request counters and per-rule hit rates (SSE, updates every second) |
| Unmatched | Requests that matched no rule — click "Create Rule" to pre-fill the rule form |
All endpoints are on the admin port (default :9090).
| Method | Path | Description |
|---|---|---|
GET |
/api/rules |
List all rules |
POST |
/api/rules |
Create a rule |
GET |
/api/rules/:id |
Get a rule |
PUT |
/api/rules/:id |
Update a rule |
DELETE |
/api/rules/:id |
Delete a rule |
GET |
/api/simulations |
List all simulations |
POST |
/api/simulations |
Create a simulation |
GET |
/api/simulations/:id |
Get a simulation |
PUT |
/api/simulations/:id |
Update a simulation |
DELETE |
/api/simulations/:id |
Delete a simulation |
GET |
/api/openapi.json |
OpenAPI 3.0 spec (JSON) |
GET |
/api/metrics |
Current metrics snapshot (JSON) |
GET |
/api/metrics/stream |
SSE stream — one event per second |
GET |
/api/unmatched |
List captured unmatched requests |
DELETE |
/api/unmatched |
Clear unmatched request buffer |
POST |
/api/reload |
Trigger hot-reload from store |
GET |
/api/health |
{"status":"ok"} |
{
"at": "2026-05-24T12:00:00Z",
"total_requests": 1042,
"misses": 13,
"rules": [
{
"rule_id": "hello",
"rule_name": "Hello World",
"hits": 1029,
"p95_ms": 42.3
}
]
}mockwave mcp exposes a Model Context Protocol server that lets Claude Code (and other MCP-compatible AI assistants) create, inspect, and delete rules and simulations on any Mockwave instance — local or remote.
Claude Code
│ stdio (stdin/stdout)
▼
mockwave mcp (local process, spawned by Claude Code)
│ HTTP
▼
Mockwave Admin API (:9090) — localhost OR remote (EKS, sandbox, etc.)
mockwave mcp runs locally and bridges MCP tool calls to HTTP requests against --admin-url. The admin URL can point anywhere — a local dev instance or a shared sandbox running in the cloud.
Ensure mockwave is on your $PATH (e.g. via brew install mockwave), then add to ~/.claude/mcp.json:
{
"mcpServers": {
"mockwave-local": {
"command": "mockwave",
"args": ["mcp", "--admin-url", "http://localhost:9090"]
}
}
}Multiple instances are supported — Claude Code namespaces the tools automatically:
{
"mcpServers": {
"mockwave-local": {
"command": "mockwave",
"args": ["mcp", "--admin-url", "http://localhost:9090"]
},
"mockwave-sandbox": {
"command": "mockwave",
"args": ["mcp", "--admin-url", "https://mockwave.sandbox.example.com"]
}
}
}Security: The Mockwave admin API has no authentication. When pointing
--admin-urlat a remote instance, ensure the admin port is protected by a firewall or reverse proxy.
Rules
| Tool | Description |
|---|---|
list_rules |
List all rules |
get_rule |
Get a rule by ID |
create_rule |
Create a new rule |
update_rule |
Replace a rule |
delete_rule |
Delete a rule |
Simulations
| Tool | Description |
|---|---|
list_simulations |
List all simulations |
get_simulation |
Get a simulation by ID |
create_simulation |
Create a new simulation |
update_simulation |
Replace a simulation |
delete_simulation |
Delete a simulation |
generate_from_openapi |
Auto-generate rules + simulations from an OpenAPI 2.0/3.0 spec (URL or file) |
Event Rules
| Tool | Description |
|---|---|
list_event_rules |
List all event rules |
get_event_rule |
Get an event rule by ID |
create_event_rule |
Create a new event rule |
update_event_rule |
Replace an existing event rule |
delete_event_rule |
Delete an event rule |
Event Captures
| Tool | Description |
|---|---|
list_event_captures |
List captured events for a rule (supports body/attr/query filters) |
get_event_capture |
Get the full detail of a single captured event (including bodies) |
clear_event_captures |
Delete all captured events for a rule |
Matched (HTTP) Requests
| Tool | Description |
|---|---|
list_matched |
List matched HTTP requests for a rule (supports body/query filters) |
get_matched |
Get the full detail of a single matched HTTP request (including bodies) |
clear_matched |
Delete all matched HTTP requests for a rule |
Fault Profiles (Chaos)
| Tool | Description |
|---|---|
list_faults |
List all fault profiles |
get_fault |
Get a fault profile by ID |
create_fault |
Create a new fault profile (types: jitter/error/hang/reset/halfResponse/slowBody/retryStorm) |
update_fault |
Replace an existing fault profile |
delete_fault |
Delete a fault profile |
Chaos Control
| Tool | Description |
|---|---|
halt_chaos |
Pause all active chaos faults immediately (kill-switch) |
resume_chaos |
Resume chaos fault injection after a halt |
get_chaos_status |
Get current chaos state: halted flag and active-scenario name |
Scenarios
| Tool | Description |
|---|---|
list_scenarios |
List all chaos scenarios |
get_scenario |
Get a chaos scenario by ID |
create_scenario |
Create a new chaos scenario |
update_scenario |
Replace an existing chaos scenario |
delete_scenario |
Delete a chaos scenario |
start_scenario |
Start a chaos scenario (applies fault phases to targeted rules) |
stop_scenario |
Stop a running chaos scenario (restores normal rule behavior) |
Config Import/Export
| Tool | Description |
|---|---|
export_config |
Export the full Mockwave config (rules, simulations, fault profiles, scenarios) as JSON |
import_config |
Bulk-import a config; use preview:true for a dry-run conflict report |
Dev / Debugging
| Tool | Description |
|---|---|
eval_script |
Evaluate a JS simulation script against a sample request to test computed responses |
get_metrics_history |
Get the rolling metrics history (recent traffic time-series) |
Observability
| Tool | Description |
|---|---|
get_metrics |
Current metrics snapshot |
list_unmatched |
List requests that matched no rule |
clear_unmatched |
Clear the unmatched buffer |
reload |
Trigger hot-reload from store |
health |
Check admin API reachability |
Create a rule from a natural language description:
You: "Create a mock for GET /orders that returns 200 with an empty orders array"
Claude calls create_simulation → POST /api/simulations
Claude calls create_rule → POST /api/rules
Claude: "Done. GET http://localhost:8080/orders now returns {"orders":[]}"
Generate mocks from an existing OpenAPI spec:
You: "Generate mocks from https://petstore3.swagger.io/api/v3/openapi.json"
Claude calls generate_from_openapi with the URL
Claude: "Created 18 rules and 18 simulations covering all Petstore endpoints.
GET /pet/{petId} → 200 {"id":1,"name":"doggie","status":"available"}
POST /pet → 201 {"id":1,"name":"doggie"}
DELETE /pet/{petId} → 200
..."
Inspect what's being hit and fix gaps:
You: "What requests are hitting mockwave but not matching any rule?"
Claude calls list_unmatched
Claude: "3 unmatched requests found:
POST /api/v2/checkout ← no rule
GET /api/v2/cart/99 ← no rule
Want me to create mocks for these?"
Dynamic script mock via MCP:
You: "Create a mock for GET /users/:id that echoes the ID back in the response"
Claude calls create_simulation with script:
const id = request.path.split('/').pop();
return { body: { id: id, name: "User " + id } };
Claude calls create_rule → POST /api/rules
Claude: "Done. GET /users/42 now returns {"id":"42","name":"User 42"}"
Intercept and forward SNS publishes to real AWS:
You: "Intercept SNS publishes to the orders topic and forward them to real AWS"
Claude calls create_event_rule with match.service=sns, match.target=*:orders,
and a forward block pointing at the real SNS endpoint with static credentials.
Claude: "Event rule created. SNS publishes to *:orders will be captured and
re-signed to real AWS. Check captures with list_event_captures."
Query event captures by correlation ID:
You: "What did my app publish with correlation_id abc-123?"
Claude calls list_event_captures with body filter $.correlation_id:abc-123
Claude: "Found 2 captures for rule 'order-placed-sns':
01J8X... — SNS Publish to arn:...:order-placed at 10:00:01Z
01J8Y... — SNS Publish to arn:...:order-placed at 10:00:05Z
Want me to fetch the full message body for either one?"
Create and run a chaos scenario:
You: "Create a chaos scenario that injects 500 errors on /orders for 30 seconds, then start it"
Claude calls create_fault with type=error, status=500, targeting the /orders rule.
Claude calls create_scenario with a single phase using that fault, duration=30s.
Claude calls start_scenario.
Claude: "Scenario started. /orders will return 500 for the next 30 seconds.
Call get_chaos_status to monitor, or halt_chaos to stop immediately."
Inspect matched HTTP requests sent to a mock:
You: "Show me the requests my service sent to the payments mock"
Claude calls list_matched for the payments rule
Claude: "Last 5 matched requests to rule 'payments':
POST /v1/charges body: {"amount":4999,"currency":"usd"} at 10:01Z
POST /v1/charges body: {"amount":1200,"currency":"usd"} at 10:02Z
..."
Drop this in your project's CLAUDE.md to make Claude aware of Mockwave automatically:
## Mocking
Mockwave is running at http://localhost:8080 (admin: http://localhost:9090).
Use the `mockwave-local` MCP tools to create or update mocks instead of hardcoding responses.
When a test hits an unmocked endpoint, call `list_unmatched` and create the missing rule.Set "script" on any simulation to run JavaScript (via goja) on every matched request. The script must return an object with at least a body key (and optionally status, headers, delay_ms) to override the response.
{
"id": "dynamic-user",
"protocol": "http",
"response": { "status": 200 },
"script": "const id = request.path.split('/').pop(); return { body: { id: id, ts: Date.now() } };"
}The return object can override any part of the response:
return {
status: 201,
headers: { "X-Custom": "value" },
body: { id: request.path.split('/').pop(), ts: Date.now() },
delay_ms: 100
};request.path is the raw path string. Use standard JS string methods to extract segments:
// GET /users/42 → id = "42"
const id = request.path.split('/').pop();
return { body: { id: id } };// GET /org/acme/users/42 → segments = ["org","acme","users","42"]
const parts = request.path.split('/').filter(Boolean);
const org = parts[1]; // "acme"
const id = parts[3]; // "42"
return { body: { org: org, userId: id } };// named segment via regex: /users/:id/orders/:orderId
const match = request.path.match(/\/users\/([^/]+)\/orders\/([^/]+)/);
const userId = match ? match[1] : null;
const orderId = match ? match[2] : null;
return { body: { userId: userId, orderId: orderId } };// numeric ID anywhere in path
const match = request.path.match(/\/(\d+)/);
const id = match ? parseInt(match[1], 10) : null;
return {
status: id ? 200 : 404,
body: id ? { id: id } : { error: "not found" }
};// reflect full path + method back (useful for debugging)
return {
body: {
method: request.method,
path: request.path,
parts: request.path.split('/').filter(Boolean)
}
};// bearer token presence check
const auth = request.headers["authorization"] || "";
const token = auth.replace("Bearer ", "");
return {
status: token ? 200 : 401,
body: token ? { token: token } : { error: "unauthorized" }
};// tenant routing via custom header
const tenant = request.headers["x-tenant-id"] || "default";
const id = request.path.split('/').pop();
return { body: { tenant: tenant, id: id, source: "mock" } };// echo all headers back (debugging)
return { body: { headers: request.headers } };// body is already parsed when Content-Type is application/json
const name = request.body && request.body.name;
return { body: { message: "Hello, " + (name || "stranger") } };// validate required fields, return 422 if missing
const b = request.body || {};
if (!b.email || !b.name) {
return { status: 422, body: { error: "email and name are required" } };
}
return { status: 201, body: { id: Math.floor(Math.random() * 10000), email: b.email } };// POST /accounts/:accountId/transfers
const accountId = request.path.split('/').filter(Boolean)[1];
const requestId = request.headers["x-request-id"] || "none";
const amount = request.body && request.body.amount;
return {
status: 202,
headers: { "x-request-id": requestId },
body: {
transferId: "txn-" + Date.now(),
from: accountId,
amount: amount,
status: "pending"
}
};Available in the script context:
| Variable | Type | Description |
|---|---|---|
request.method |
string | HTTP method ("GET", "POST", …) |
request.path |
string | Full path string (e.g. "/orders/12345") |
request.headers |
object | Request headers (lowercase keys) |
request.body |
object|null | Parsed JSON body, or null |
response.status |
number | Current response status (modifiable) |
response.body |
object | Current response body (modifiable) |
Mockwave doubles as an API-level chaos tool: instead of breaking real infrastructure (Gremlin-style host agents), it injects failures at the boundary your client sees — mock responses and forwarded upstream traffic. No agent, no root; runs locally and in CI.
Define a fault profile (a reusable set of failure modes), attach it to a rule
bucket via fault_profile_id, and tune the blast radius with the bucket weight
and each fault's probability. Seven fault types are available:
| Type | Effect |
|---|---|
jitter |
latency: base_delay_ms + random [0, jitter_ms) |
error |
short-circuit with a custom HTTP status_code / body / headers |
hang |
blackhole: block max_ms then close with no response |
reset |
TCP RST — client sees a connection reset (dead upstream) |
halfResponse |
write fraction of the body then cut the connection |
slowBody |
bandwidth throttle to bytes_per_sec (modifier) |
retryStorm |
fail the first fail_first requests per key, then recover |
jitter/slowBody are modifiers (combine with anything); the rest are terminal
(first one to fire wins). All types work on REST/GraphQL/SOAP; gRPC is not yet
supported. A global kill switch suppresses everything instantly, and
scenarios chain profiles into timed phases for drills.
Quick example — 30% of /orders traffic gets a 503:
curl -X POST localhost:9090/api/faults -d '{
"id":"flaky-orders","name":"Flaky orders","enabled":true,
"faults":[{"type":"error","probability":0.3,"params":{"status_code":503,"body":"{\"error\":\"boom\"}"}}]}'
# then attach "fault_profile_id":"flaky-orders" to the /orders rule's bucket→ Full Chaos Testing guide: every fault type, UI/CLI/API walkthroughs, scenarios, API reference
All on the admin port (default :9090). Full details and schemas in the
chaos guide.
| Method | Path | Purpose |
|---|---|---|
GET POST |
/api/faults |
list / create fault profiles |
GET PUT DELETE |
/api/faults/{id} |
get / update / delete a profile (409 if referenced) |
POST |
/api/chaos/halt · /api/chaos/resume |
kill switch off / on |
GET |
/api/chaos/status |
{halted, active_scenario} |
GET POST |
/api/scenarios |
list / create scenarios |
GET PUT DELETE |
/api/scenarios/{id} |
get / update / delete a scenario |
POST |
/api/scenarios/{id}/start · /stop |
start (202, 409 if one runs) / stop |
Equivalent CLI: mockwave fault …, mockwave chaos halt|resume|status,
mockwave scenario list|start|stop (all accept --admin-url).
Fault profiles and scenarios persist on every backend (JSON file, DynamoDB,
MongoDB, Cosmos). DynamoDB needs two extra tables (mockwave-fault-profiles,
mockwave-scenarios, PK id); Mongo/Cosmos collections auto-create. See the
guide for details.
Mockwave exposes public Go interfaces for storage and observability. Implement any of them and pass to server.New:
| Interface | Controls |
|---|---|
store.DataStore |
Where rules and simulations are stored |
observability.Logger |
Structured logging (zerolog, zap, …) |
observability.Tracer |
Distributed tracing (OpenTelemetry, …) |
observability.MetricsRecorder |
Request metrics (Prometheus, …) |
All fields in server.Config default to Noop implementations when nil — safe to omit anything you don't need.
→ Extension guide: DataStore + observability interfaces, full examples
# Run with a local config file
docker run -p 8080:8080 -p 9090:9090 \
-v $(pwd)/config.json:/config.json \
ghcr.io/lfdubiela/mockwave:v0.1.0 \
start -f /config.json
# All protocols
docker run -p 8080:8080 -p 9090:9090 -p 50051:50051 \
-v $(pwd)/config.json:/config.json \
-v $(pwd)/service.pb:/service.pb \
ghcr.io/lfdubiela/mockwave:v0.1.0 \
start -f /config.json --protocols http,graphql,soap,grpc --grpc-proto /service.pb
# DynamoDB backend (IAM role or env vars)
docker run -p 8080:8080 -p 9090:9090 \
-e AWS_ACCESS_KEY_ID=... \
-e AWS_SECRET_ACCESS_KEY=... \
-e AWS_REGION=us-east-1 \
ghcr.io/lfdubiela/mockwave:v0.1.0 \
start --store dynamodbdocker build -t mockwave:local .
docker run -p 8080:8080 -p 9090:9090 \
-v $(pwd)/config.json:/config.json \
mockwave:local start -f /config.jsonRequirements: Go 1.21+
git clone https://github.com/lfdubiela/mockwave.git
cd mockwave
# Build binary
make build # outputs ./mockwave
# Run tests
make test
# Check coverage (must be ≥80%)
make coveragePlanned-but-not-yet-built work — including scope deliberately deferred from shipped features — is tracked in docs/roadmap.md.
Contributions are welcome. Please open an issue before submitting a large PR.
- Fork the repo
- Create a feature branch (
git checkout -b feat/my-feature) - Write tests first (TDD)
- Ensure
make testandmake coveragepass - Submit a pull request
Mockwave is released under the MIT License. Free to use, modify, and distribute — commercially or otherwise — with attribution.