Skip to content

lfdubiela/mockwave

Repository files navigation

Mockwave

License: MIT Go

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.


Features

  • 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:9090 for 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) and GET /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). See docs/event-capture.md. Event rules and captures persist on DynamoDB/MongoDB/Cosmos like matched capture.
  • AI integration (MCP)mockwave mcp exposes 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/MetricsRecorder interfaces; bring your own backends

Quick Start

Homebrew (macOS / Linux)

brew tap lfdubiela/mockwave
brew trust lfdubiela/mockwave
brew install mockwave   # or: brew upgrade mockwave (if already installed)

# Verify installation
mockwave version

Note: brew trust lfdubiela/mockwave is 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 mockwave

MCP (Claude Code integration)

With 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?"

Binary

# 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.json

Docker

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

CLI Reference

mockwave [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)

Examples

# 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.json

Config File Format

The JSON config file has two top-level arrays: rules and simulations.

{
  "rules": [ ...Rule... ],
  "simulations": [ ...Simulation... ]
}

Rule

{
  "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/123 but 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.

Simulation

{
  "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
}

Protocols

HTTP REST

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" }
  }
}

GraphQL

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" }]
}

SOAP

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>"
}

gRPC

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.pb

Set 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
}

Store Backends

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.

DynamoDB Setup

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_REQUEST

Admin UI

Open 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

Admin REST API

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"}

Metrics snapshot shape

{
  "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
    }
  ]
}

AI Integration (MCP)

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.

How it works

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.

Setup

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-url at a remote instance, ensure the admin port is protected by a firewall or reverse proxy.

Available tools

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

Examples

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
         ..."

Tip — add to CLAUDE.md

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.

JavaScript Scripting

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
};

Extracting path parameters

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)
  }
};

Using request headers

// 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 } };

Using request body

// 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 } };

Combining path + headers + body

// 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)

Chaos Testing

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

Admin API summary

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.


Extending Mockwave

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


Docker

# 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 dynamodb

Building locally

docker build -t mockwave:local .
docker run -p 8080:8080 -p 9090:9090 \
  -v $(pwd)/config.json:/config.json \
  mockwave:local start -f /config.json

Building from Source

Requirements: 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 coverage

Roadmap

Planned-but-not-yet-built work — including scope deliberately deferred from shipped features — is tracked in docs/roadmap.md.


Contributing

Contributions are welcome. Please open an issue before submitting a large PR.

  1. Fork the repo
  2. Create a feature branch (git checkout -b feat/my-feature)
  3. Write tests first (TDD)
  4. Ensure make test and make coverage pass
  5. Submit a pull request

License

Mockwave is released under the MIT License. Free to use, modify, and distribute — commercially or otherwise — with attribution.

About

HTTP mock server with Goja scripting, MCP integration for AI-assisted rule generation, and a real-time admin UI. Local dev or deployed.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages