Docs Scripted Responses

Scripted Responses

Most mocks return a static body or a template with Faker/JsonPath placeholders. When a
response needs real logic — a calculation, a branch on the request, state that changes
between calls, or deliberate flakiness for resilience testing — use a scripted response.

A scripted response is JavaScript that runs every time the mock is hit. It receives the
incoming request and fills an outgoing response. It is just another response variant,
alongside Body, Empty, and File Template.

Scripted responses are available for HTTP, gRPC, GraphQL, SOAP, MCP, Kafka,
RabbitMQ, NATS, and Socket mocks. For the non-HTTP protocols the request is adapted to the same
request object and the produced response is mapped back to that protocol’s
reply (see Other protocols below).

Create a scripted response in the UI

  1. Open the Constructor and define the route and method as usual.
  2. Under Response Body Type, click Script.
  3. Write your script in the editor. Autocomplete (Ctrl+Space, or type request.) suggests
    everything available.
  4. Type an example request body in Try it and press Run example to see the response
    the script produces, plus any console.log output — without saving.
  5. Save the mock.

The request object (input)

Read-only. The incoming request, broken into parts:

Field Description
request.method HTTP method, e.g. "POST"
request.path Request path the mock matched, e.g. "/api/orders/42"
request.route The mock’s route pattern, e.g. "/api/orders/:id"
request.url Full URL
request.host Request host
request.remoteAddr Client address
request.query Query parameters (first value), e.g. request.query.page
request.queryAll Query parameters as arrays (all values), e.g. request.queryAll.tag
request.queryString Raw query string, e.g. "page=2&tag=a"
request.params Path parameters from the route pattern, e.g. request.params.id for /api/orders/:id
request.headers Headers, first value, lower-cased keys, e.g. request.headers["content-type"]
request.headersAll Headers as arrays (all values), lower-cased keys
request.header(name) Case-insensitive single header lookup
request.cookies Cookies by name
request.body Raw request body as a string
request.json() Parses the body as JSON and returns the object
request.protocol The protocol the mock answers, e.g. "http", "grpc", "kafka"
request.attrs Protocol-specific fields for non-HTTP mocks (e.g. the gRPC service/method, the Kafka topic, the MCP tool name). The common fields above always win on a key clash.

Every field of the incoming request is passed to the script — nothing is dropped.

The response object (output)

Mutable. Its final state becomes the response Mockarty sends:

Field / method Description
response.status(201) Set the HTTP status code (default 200). response.status = 201 also works.
response.json(obj) Set the body to JSON and Content-Type: application/json
response.text(s) Set a plain-text body
response.body = "..." Set the body directly
response.setHeader(name, value) Add a response header (alias: response.header(name, value))
response.delay(200) Delay the response by N milliseconds. response.delay = 200 also works.

The method forms are chainable, so you can write response.status(201).setHeader("X-Run", "1").json({ ok: true }).

You can also return a value as a shortcut: if you don’t set response.body, a returned
object becomes the JSON body.

Form values seed the response. The Status Code and Response Headers you set on the mock’s
form are pre-loaded as the script’s starting response — so a script that never touches the
status still returns the form’s status, and the form headers are already present. Anything the
script sets wins: response.status(503) overrides a form status of 201. This also drives the
Run example preview, so the dry run shows exactly what the live mock would return.

The mock object (metadata)

Read-only information about the mock that is currently responding:

Field Description
mock.id The mock’s identifier
mock.namespace The workspace the mock belongs to
mock.route The mock’s route, e.g. /api/orders/:id
mock.protocol http, grpc, graphql, soap, mcp, kafka, rabbitmq, nats, or socket
mock.chainId The chain this mock is part of (empty if none) — use it to key store.chain for a clean per-flow scope
// Tag the response with where it came from.
response.setHeader("X-Mock-Id", mock.id);
response.json({ servedBy: mock.id, chain: mock.chainId });

Stores and environment

Scripts read and write the same Mock, Chain, and Global stores as the rest of Mockarty,
so a script can keep state across requests:

// store.mock   — scratch for this single request
// store.chain  — shared across mocks in the same chain (an order flow)
// store.global — shared across the whole workspace
const visits = (store.global.get("visits") || 0) + 1;
store.global.set("visits", visits);
response.json({ visits: visits });

Each scope offers get(key), set(key, value), has(key), delete(key), and keys() —
you can both read and write. Chain and Global writes persist through the shared cache, so a
value set by one mock is visible to the next mock in the chain (and across nodes). Mock scope
is scratch for the current request.

env.get("KEY") reads workspace environment values.

Secrets are read-only — a script can read a secret to use it (e.g. inject an API key into
an outbound call) but never write one:

const apiKey = store.secrets.get("vault", "api_key");
if (store.secrets.has("vault", "api_key")) { /* ... */ }

Secrets are scoped to the mock’s workspace and are masked in the “Run example” preview (you
see a placeholder, never the real value). store.secrets is served by the main Mockarty
node; a mock exported to a standalone generated server runs without the secrets store, so
read any secret you need into a header or the body on the main node instead of relying on it
downstream.

The mk helpers

Helper Example
mk.faker.* A focused set of generators: name, firstName, lastName, email, username, phone, uuid, word, sentence, paragraph, url, ipv4, ipv6, date, timestamp, boolean, macAddress, currency, creditCard, domainName. (The larger $.fake.* catalogue documented in the Faker reference is for template responses, not scripts.)
mk.uuid() Random UUID
mk.randomInt(min, max) Random integer
mk.crypto.* mk.crypto.sha256(s), mk.crypto.hmac("sha256", key, msg)
mk.base64.* mk.base64.encode(s), mk.base64.decode(s)
mk.jsonpath(obj, path) Extract a value by JsonPath
mk.http.send(opts) Outbound HTTP request — off by default (see below)

Examples

Compute from the request:

const data = request.json();
response.json({
  orderId: request.params.id,
  total: data.amount * 1.1,
  currency: "USD"
});

Branch on a header:

if (request.header("authorization")) {
  response.json({ user: "alice@example.com" });
} else {
  response.status = 401;
  response.json({ error: "unauthorized" });
}

Stateful counter (chain store):

const n = (store.chain.get("hits") || 0) + 1;
store.chain.set("hits", n);
response.json({ count: n });

Toxicity: faults and latency

Scripts are the simplest way to make a mock behave like an unreliable upstream — useful for
resilience and chaos testing:

// Fail 10% of requests, add 200ms latency to the rest
if (Math.random() < 0.1) {
  response.status = 503;
  response.json({ error: "service unavailable" });
  return;
}
response.delay = 200;
response.json({ ok: true });

Outbound calls

By default a script cannot reach the network — mk.http.send is disabled. Enable Allow
outbound calls
on the script (or scriptAllowNet in the API) only when the response must
fetch from an external system. Outbound targets are validated to prevent requests to
private, loopback, or cloud-metadata addresses.

// requires "Allow outbound calls" to be enabled
const upstream = mk.http.send({ method: "GET", url: env.get("UPSTREAM_URL") });
response.status = upstream.status;
response.body = upstream.body;

For heavier side effects (publishing to a queue, notifying another service after responding),
prefer Webhooks & Callbacks, which run asynchronously after the
response is sent.

Limits

Scripts run with a tight time budget (50 ms by default; configurable per mock up to 1
second). A script that exceeds it is stopped and the mock returns 500. Use the budget for
fast logic; offload slow work to callbacks.

A script’s source is limited to 256 KB. When the script sets an HTTP status outside the
valid 100–999 range, the mock falls back to 200.

Validation and errors

The editor checks your script as you type: a green Valid badge means it compiles, and a
red badge shows the first syntax error with its line number. A mock with a script that does
not compile is rejected when you save it, with a message pointing at the line — so a broken
script never ships silently.

At run time, if the script throws (or its body is invalid JSON you read with
request.json()), the mock responds with a clear JSON error instead of a blank 500:

{ "error": "mock script error", "detail": "TypeError: ... at line 3" }

request.json() returns null when the request has no body, so the common guard
const body = request.json() || {} works without a special case.

Create via API

A scripted response is the script field on the response:

curl -X POST "http://localhost:5770/api/v1/mocks?namespace=sandbox" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "calc",
    "http": { "route": "/api/calc/:op", "httpMethod": "POST" },
    "response": {
      "script": {
        "code": "const d=request.json(); response.json({op:request.params.op, out: request.params.op===\"double\"? d.v*2 : d.v/2});",
        "allowNet": false
      }
    }
  }'

Test a script against an example request without saving:

curl -X POST "http://localhost:5770/api/v1/mocks/script/preview?namespace=sandbox" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "script": "response.json({ hi: request.json().name });", "request": { "body": "{\"name\":\"world\"}" } }'

Other protocols

Scripted responses also work for gRPC, GraphQL, SOAP, MCP, Kafka, RabbitMQ,
NATS, and Socket mocks. The request and response objects adapt to each protocol — the
protocol’s fields appear directly on request (and the full set on
request.attrs):

Protocol On request Build the response with
gRPC request.service, request.method, request.message (also request.json()) response.json(obj) — the reply message; response.status = gRPC code
GraphQL request.operation, request.field, request.variables, request.queryText response.json({ data: {...} })
SOAP request.action, request.method, request.service, request.bodyData response.text("<...>") — the XML body
MCP request.tool, request.arguments (also request.json()) response.json({ content: [{ type: "text", text: "..." }] })
Kafka request.topic, request.value (also request.json()) response.json(obj) — the reply payload
RabbitMQ request.queue, request.value (also request.json()) response.json(obj)
NATS request.subject, request.reply, request.queueGroup, request.message (also request.json()) response.json(obj) — the reply payload
Socket request.serverName, request.event, request.message (also request.json()) response.json(obj)

Headers work everywhere. request.headers and request.header(name) behave
the same as for HTTP across protocols:

  • gRPC — call metadata is exposed as headers, so request.header("authorization")
    reads a metadata entry (the raw map is also at request.metadata). request.route
    is the canonical service/method.
  • GraphQL, SOAP, MCP — these arrive over HTTP, so the client’s request headers are
    available via request.header(name) / request.headers, and request.path /
    request.route hold the matched endpoint path.
  • Kafka, RabbitMQ, NATS — message headers are exposed via request.headers.

Stores, env, secrets, mk.*, and the budget all work the same way as for HTTP.

// gRPC mock script
const u = request.message;            // the request message
response.json({ id: u.id, name: "User " + u.id });