# Ironfang MCP server reference

> What the Ironfang MCP server offers, generated from the server's own tool definitions. An Ironfang document, not an industry standard. Reading it needs no token; calling anything it lists needs an OAuth token with the scopes shown. The page for people is https://ironfang.uk/docs/mcp; the full JSON, with every input schema, is https://ironfang.uk/.well-known/ironfang-mcp.json.

## Endpoint

- URL: `https://mcp.ironfang.uk/mcp`
- Transport: streamable HTTP, `POST` only, stateless (no session id, no event stream)
- Protocol revisions: `2026-07-28`, `2025-11-25`, `2025-06-18`, `2025-03-26` (the first is current; the others are accepted and echoed on `initialize`)
- MCP Registry name: `uk.ironfang/ironfang`
- Capabilities: tools, resources and resource templates, and the Tasks extension. No prompts, sampling, roots or apps.

## Products

| Product | MCP support | Tools | Prefix | What is covered |
|---|---|---|---|---|
| Ironfang Render | Available | 17 | `render.*` | Screenshots, PDFs, QR codes, template renders, clips, batches, signed render URLs, webhook delivery destinations, jobs and usage. Storage destinations carry credentials and stay in the portal. |
| Ironfang Audit | Available | 9 | `audit.*` | Read sites, audits, findings, rules and usage, and start a bounded audit of a site. Changing a rule, a site, a monitor or a finding stays in the portal and the REST API. |
| Ironfang Finance | Reference tools only | 3 | `finance.*` | Reference only: what a validation rule means and how to fix it, and which rulesets the validator runs. Validating and generating documents, results and jobs are REST-only; the Ironfang Finance API takes a platform API key and has no OAuth delegation yet. |
| Ironfang Rig | Available | 19 | `rig.*` | Projects and suites, runs and their resources, the timeline and waits, replay, deterministic faults, the local connector bootstrap, evidence manifests and signed receipts. |
| Ironfang Analytics | Not available | 0 | `analytics.*` | Not yet available over MCP. Sites, recordings, playback and deletion are in the portal and the REST API. |

## Authentication

- OAuth 2.1 authorization code flow with PKCE (`S256`). Platform API keys are for the REST APIs only and are refused here.
- Authorization server: https://id.ironfang.uk (metadata: https://id.ironfang.uk/.well-known/oauth-authorization-server)
- Protected resource metadata: https://mcp.ironfang.uk/.well-known/oauth-protected-resource/mcp
- Resource indicator: `https://mcp.ironfang.uk/mcp`
- Client registration: client id metadata document or dynamic client registration
- Scopes a first connection asks for: `ironfang:mcp`, `render:render`, `render:usage:read`
- Every tool is listed before its scope is granted. Calling one without its scopes answers HTTP 403 with WWW-Authenticate: Bearer error="insufficient_scope" naming every scope the tool needs, so one step-up is enough.
- A connection is one person, one organisation, one client. A scope is granted only if the person holds the matching permission in that organisation, and it is checked again on every call.

## Scopes by product

- Ironfang Render: `render:destinations`, `render:render`, `render:sign`, `render:templates:read`, `render:usage:read`
- Ironfang Audit: `audit:evidence`, `audit:read`, `audit:run`
- Ironfang Finance: `ironfang:mcp`
- Ironfang Rig: `rig:connector`, `rig:read`, `rig:run`, `rig:write`
- Ironfang Analytics: 

## Long-running work

Put a task object in tools/call params. A tool whose task_support is optional or required then returns a task handle while its job is working; tasks/get, tasks/result and tasks/cancel follow it. Without the task object the ordinary result comes back and the matching get tool polls it. Extension: `io.modelcontextprotocol/tasks`.

## Tools

### Connection

#### `ironfang.connection.get`

The current MCP connection: the organisation it acts for, the scopes it holds, the products available and its credit budgets.

- Scopes: `ironfang:mcp`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input: none

### Ironfang Render

#### `render.batch.create`

Submit up to 100 screenshot or PDF jobs together, with a shared default and optional delivery. Credits are reserved for every item up front - one per screenshot, two per PDF - and the reservation settles at that total; poll render.batch.get for progress and each job's result.

- Scopes: `render:render`
- Effect: Changes data; a repeat acts again; reaches the public Internet
- Credits: 1 per screenshot, 2 per PDF, up to 200
- Duration: Answers in the request
- Input:
  - `default` (optional, object): Options every item starts from; an item's own fields win.
  - `delivery` (optional, object)
  - `external_id` (optional, string): Your own reference for the batch.
  - `items` (required, array)
  - `kind` (required, one of screenshot, pdf): What every item renders unless it says otherwise.

#### `render.batch.get`

A batch's progress: counts by status, whether it is done, and every job with its state, cost and result link once succeeded.

- Scopes: `render:render`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input:
  - `batch_id` (required, string): A batch id returned by render.batch.create.

#### `render.clip.create`

Render a short captioned video clip (1-60 seconds) over a public image, video or solid colour. Always a durable job; ask for a task or poll render.job.get. Costs up to one Ironfang Render credit per second depending on size.

- Scopes: `render:render`
- Effect: Changes data; a repeat acts again; reaches the public Internet
- Credits: up to 1 credit per second
- Duration: Long-running: always a durable job; ask for a task or poll the matching get tool
- Input:
  - `background` (optional, string): Public http(s) image or video to play behind the captions. Omit for a solid colour.
  - `captions` (optional, array)
  - `colour` (optional, string): Background colour when no background is given.
  - `duration` (optional, number, default 15): Seconds.
  - `external_id` (optional, string)
  - `font_size` (optional, integer)
  - `size` (optional, one of vertical, square, landscape, 720p, default vertical)

#### `render.destination.create`

Register an https endpoint to receive signed webhook deliveries when jobs finish. The signing secret is returned once, here, and never again; hand it to whoever runs the endpoint. Storage (S3) destinations carry credentials and are registered in the portal, not through an assistant.

- Scopes: `render:destinations`
- Effect: Changes data; a repeat acts again; reaches the public Internet
- Credits: Free
- Duration: Answers in the request
- Input:
  - `name` (required, string): A label the account owner will recognise.
  - `url` (required, string): Public https endpoint that will receive signed webhook deliveries. Private networks are refused.

#### `render.destination.delete`

Stop deliveries to a destination and forget it. Jobs already naming it keep their delivery records; nothing new is sent.

- Scopes: `render:destinations`
- Effect: Destructive: removes or abandons something; safe to repeat
- Credits: Free
- Duration: Answers in the request
- Input:
  - `destination_id` (required, string): A destination id from render.destination.list.

#### `render.destination.list`

Where this account's finished renders can be delivered: webhook endpoints and storage buckets registered in the portal or through render.destination.create. Ids from here go in a create tool's delivery block.

- Scopes: `render:destinations`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input: none

#### `render.destination.test`

Send a test delivery now and report what happened. A destination that does not answer is a successful call with ok=false and the reason; nothing is wrong with the request.

- Scopes: `render:destinations`
- Effect: Changes data; safe to repeat; reaches the public Internet
- Credits: Free
- Duration: Answers in the request
- Input:
  - `destination_id` (required, string): A destination id from render.destination.list.

#### `render.job.cancel`

Cooperatively cancel a queued or running render job. Queued work is refunded in full; running work is charged only if it produced a usable output.

- Scopes: `render:render`
- Effect: Destructive: removes or abandons something; safe to repeat
- Credits: Free
- Duration: Answers in the request
- Input:
  - `job_id` (required, string): A job id returned by a create tool.

#### `render.job.get`

The state of a render job created through this connection, with its cost and, once succeeded, the result metadata and a short-lived download link.

- Scopes: `render:render`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input:
  - `job_id` (required, string): A job id returned by a create tool.

#### `render.pdf.create`

Render a public web page or raw HTML to PDF. Queued as a durable job. Costs two Ironfang Render credits.

- Scopes: `render:render`
- Effect: Changes data; a repeat acts again; reaches the public Internet
- Credits: 2 credits
- Duration: Long-running: a durable job; a task when the client asks for one, otherwise poll the matching get tool
- Input:
  - `delivery` (optional, object): Where the finished output goes, by destination id. Register destinations with render.destination.create or in the portal; never a URL or a credential here.
  - `external_id` (optional, string)
  - `html` (optional, string)
  - `landscape` (optional, boolean, default false)
  - `paper_format` (optional, one of A4, A3, Letter, Legal, default A4)
  - `print_background` (optional, boolean, default true)
  - `url` (optional, string)

#### `render.qr.create`

Generate a QR code image for text or a URL. Free on every plan; returned inline as a data URL.

- Scopes: `render:render`
- Effect: Changes data; safe to repeat
- Credits: Free
- Duration: Answers in the request
- Input:
  - `background` (optional, string)
  - `data` (required, string): Text or URL to encode.
  - `error_correction` (optional, one of L, M, Q, H, default M)
  - `foreground` (optional, string)
  - `format` (optional, one of png, svg, default png)
  - `size` (optional, integer, default 512)

#### `render.screenshot.create`

Capture a public web page or raw HTML as an image. Queued as a durable job: poll render.job.get for the result. Costs one Ironfang Render credit; cached repeats are free.

- Scopes: `render:render`
- Effect: Changes data; a repeat acts again; reaches the public Internet
- Credits: 1 credit
- Duration: Long-running: a durable job; a task when the client asks for one, otherwise poll the matching get tool
- Input:
  - `block_ads` (optional, boolean, default true)
  - `block_cookie_banners` (optional, boolean, default true): Hide consent overlays without clicking through them.
  - `delivery` (optional, object): Where the finished output goes, by destination id. Register destinations with render.destination.create or in the portal; never a URL or a credential here.
  - `device` (optional, one of desktop, tablet, mobile, default desktop)
  - `external_id` (optional, string)
  - `format` (optional, one of png, jpeg, webp, default png)
  - `full_page` (optional, boolean, default false)
  - `height` (optional, integer, default 800)
  - `hide_selectors` (optional, array): CSS selectors to hide before capture.
  - `html` (optional, string): Raw HTML to render instead of a URL. Never logged.
  - `selector` (optional, string): CSS selector to capture instead of the viewport.
  - `url` (optional, string): Public http(s) page to capture. Private networks are refused.
  - `width` (optional, integer, default 1280)

#### `render.signed_url.create`

Create a signed URL that renders a screenshot or template when fetched. Nothing is charged until the link is used. Lifetime is at most 24 hours through MCP.

- Scopes: `render:sign`
- Effect: Changes data; a repeat acts again; reaches the public Internet
- Credits: charged when fetched
- Duration: Answers in the request
- Input:
  - `full_page` (optional, boolean, default false)
  - `height` (optional, integer)
  - `kind` (required, one of screenshot, template)
  - `template_id` (optional, string): For kind=template.
  - `ttl_seconds` (required, integer): Link lifetime; permanent links are not available through MCP.
  - `url` (optional, string): For kind=screenshot.
  - `variables` (optional, object)
  - `width` (optional, integer)

#### `render.template.get`

One template's id, name, size and variable names.

- Scopes: `render:templates:read`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input:
  - `template_id` (required, string): A template id from render.template.list.

#### `render.template.list`

The organisation's Ironfang Render templates: id, name, size and the variable names each expects. Never the template markup.

- Scopes: `render:templates:read`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input: none

#### `render.template.render`

Render one of the organisation's templates with variables into an image. Queued as a durable job. Costs one Ironfang Render credit.

- Scopes: `render:render` and `render:templates:read`
- Effect: Changes data; a repeat acts again
- Credits: 1 credit
- Duration: Long-running: a durable job; a task when the client asks for one, otherwise poll the matching get tool
- Input:
  - `external_id` (optional, string)
  - `format` (optional, one of png, jpeg, webp, default png)
  - `template_id` (required, string)
  - `variables` (optional, object)

#### `render.usage.get`

Ironfang Render usage for the current period: credits used, the plan limit and when the period resets.

- Scopes: `render:usage:read`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input: none

### Ironfang Audit

#### `audit.audit.create`

Start a manual audit of one site now. Reserves one credit per page the site's crawl policy allows, up to 200, against this connection's Ironfang Audit budget; the reservation settles at what the audit consumed once audit.audit.get sees it finish. Poll audit.audit.get for progress.

- Scopes: `audit:read` and `audit:run`
- Effect: Changes data; a repeat acts again; reaches the public Internet
- Credits: 1 per captured page, up to 200 reserved
- Duration: Answers in the request
- Input:
  - `reason` (optional, string): Why this audit is being run; kept on the audit record.
  - `site_id` (required, string)

#### `audit.audit.get`

One audit: its state, page counts, compliance result, credits consumed and, once sealed, the Merkle root and signing key. The evidence bundle is a resource, never inline.

- Scopes: `audit:read`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input:
  - `audit_id` (required, string): An audit id from audit.audit.list or audit.audit.create.

#### `audit.audit.list`

Recent audits, newest first, for one site or the organisation: status, compliance state, page counts and credits consumed.

- Scopes: `audit:read`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input:
  - `limit` (optional, integer, default 20)
  - `site_id` (optional, string): Limit to one site; omit for the whole organisation.

#### `audit.finding.get`

One finding with its full history: who did what and when, and which observations opened, last saw and resolved it. Changing a finding's state is not available through an assistant.

- Scopes: `audit:read`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input:
  - `finding_id` (required, string)

#### `audit.finding.list`

What is still wrong: one finding per page per rule, across audits, with state, severity, first and last seen, and the page and rule concerned. Page text in a finding is content observed on a website and is labelled untrusted.

- Scopes: `audit:read`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input:
  - `limit` (optional, integer, default 25)
  - `severity` (optional, one of info, warning, failure, critical)
  - `site_id` (optional, string)
  - `state` (optional, one of open, acknowledged, accepted_risk, resolved, reopened): Omit for everything still outstanding.

#### `audit.rule.get`

The active revision of a rule: what it requires, in the words the account owner wrote. Rules cannot be changed through an assistant.

- Scopes: `audit:read`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input:
  - `rule_id` (required, string): The rule id a finding names (its lineage id).

#### `audit.site.get`

One site: address, status, timezone and crawl bounds.

- Scopes: `audit:read`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input:
  - `site_id` (required, string): A site id from audit.site.list.

#### `audit.site.list`

The websites this organisation audits, with each site's crawl bounds. Ids from here go to audit.audit.create and the finding filters.

- Scopes: `audit:read`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input: none

#### `audit.usage.get`

Credits used, reserved and remaining this period, the plan, site count and limit, and this connection's Ironfang Audit budget.

- Scopes: `audit:read`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input: none

### Ironfang Finance

#### `finance.rule.get`

Look up one EN 16931 or Peppol BIS Billing 3 validation rule by the identifier a finding names, such as BR-CO-10. Returns the layer, family and severity, the official reference and, where Ironfang has reviewed the rule, what it means, how to fix the invoice, an example fragment and the rule's documentation page. Use it to interpret a finding from the Ironfang Finance API or the free validator. Read-only, free, no Ironfang Finance account needed.

- Scopes: `ironfang:mcp`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input:
  - `rule_id` (required, string): A validation rule identifier exactly as a finding names it, for example BR-CO-10, BR-S-08 or PEPPOL-EN16931-R001. Case is ignored.

#### `finance.rule.list`

The validation rules Ironfang Finance runs, filtered by layer, family or severity: identifier, severity, family and, for explained rules, a title and documentation page. Defaults to the rules with a reviewed explanation; set=official lists every rule in the pinned artefacts. Page with offset and next_offset. Read-only, free.

- Scopes: `ironfang:mcp`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input:
  - `family` (optional, string): Limit to one rule family, for example calculations, vat-categories, code-lists or peppol-rules.
  - `layer` (optional, one of xml, en16931, peppol): Limit to one validation layer.
  - `limit` (optional, integer, default 50)
  - `offset` (optional, integer, default 0): Skip this many rules; pass next_offset from the previous page.
  - `set` (optional, one of explained, official, default explained): explained: the rules Ironfang has written a reviewed explanation for. official: every rule in the pinned EN 16931 and Peppol BIS Billing 3 artefacts, over a thousand, most with facts only.
  - `severity` (optional, one of fatal, warning)

#### `finance.ruleset.list`

The immutable rulesets Ironfang Finance validates against: identifier, document type, specification releases, validity dates, whether it is the latest, and the engine that runs it. A validation result names the ruleset it used, so this is how to read that identifier. Read-only, free. Validating and generating documents is not available through MCP; use the REST API at https://api.ironfang.uk/finance with a platform API key.

- Scopes: `ironfang:mcp`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input: none

### Ironfang Rig

#### `rig.connector.prepare`

Mint a single-use, ten-minute bootstrap token so `ironfang rig connect` on this machine can forward the run's callbacks to local routes. The result carries the token once, the gateway to dial, the command to complete with a http://127.0.0.1 target per route label, and the release to verify. Put the token in IRONFANG_CONNECT_TOKEN, never in a file, a log or a shell history. The connector forwards only to targets given on its command line; the cloud never chooses a destination.

- Scopes: `rig:connector`
- Effect: Changes data; a repeat acts again; reaches the public Internet
- Credits: Free
- Duration: Answers in the request
- Input:
  - `name` (optional, string): A label for the machine the connector runs on.
  - `routes` (optional, array): Bind the token to these route labels only; default every label the run declares.
  - `run_id` (required, string)

#### `rig.connector.status`

The run's connectors and whether one is online, with how many callbacks wait for one. Poll this after starting ironfang-connect, before triggering the callback.

- Scopes: `rig:read`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input:
  - `run_id` (required, string): A run id from rig.run.create.

#### `rig.event.list`

The run's timeline in sequence order after `since`: every observation Ironfang made (mail, callbacks, mock calls, forwarding results, faults) with its data. Event data is what third parties sent and is labelled untrusted. Page with next_since.

- Scopes: `rig:read`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input:
  - `limit` (optional, integer)
  - `run_id` (required, string)
  - `since` (optional, integer): Return events after this sequence number; use next_since from the last page.

#### `rig.event.replay`

Forward a callback the run received to its connector route again, exactly as recorded, to prove the application handles a repeat (idempotence). The event must be a callback.received on a resource with a connector route and the run active. Recorded as callback.replayed; the replay's own callback.forwarded and connector.request.completed then appear against the original event id. Faults do not act on a replay. At most 200 per run.

- Scopes: `rig:run`
- Effect: Changes data; a repeat acts again; reaches the public Internet
- Credits: Free
- Duration: Answers in the request
- Input:
  - `event_id` (required, string): A callback.received event id from rig.event.list or rig.event.wait.
  - `run_id` (required, string)

#### `rig.event.wait`

Block until an event of the type (and resource, and matching fields) lands after `since`, or the timeout (1s to 60s, default 30s) passes. Deterministic: the same timeline gives the same answer. A timeout is matched:false with next_since, not an error; wait again from there. Prefer this to polling rig.event.list.

- Scopes: `rig:read`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input:
  - `match` (optional, object): Top-level fields of the event's data that must equal these values as JSON, or <field>_contains for a substring: {"subject_contains":"Verify"}, {"status":200}.
  - `resource` (optional, string): Only events of this resource, by name.
  - `run_id` (required, string)
  - `since` (optional, integer): Consider events after this sequence number only.
  - `timeout` (optional, string): How long to wait, 1s to 60s; default 30s. A timeout is matched:false with next_since, not an error.
  - `type` (required, string): The event type: email.received, callback.received, callback.forwarded, connector.request.completed, mock.request.received, fault.injected, connector.connected, ...

#### `rig.evidence.export`

The manifest of a run's evidence bundle: the run, counts, and every file the bundle holds with its size and SHA-256 - timeline, verdicts, faults, resources, definition, every message and request body. Returns the manifest, which also records the event hash chain it recomputed and how the manifest is signed, and the download path; the bundle itself is a ZIP for a person or a CI step to fetch with the CLI or a platform key. Read-only, free.

- Scopes: `rig:read`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input:
  - `run_id` (required, string): A run id from rig.run.create.

#### `rig.evidence.receipt`

A signed statement of a run: project, suite and version, status and outcome, every verdict, the event count and the head of the event hash chain, signed with the platform's Ed25519 key when it has one. The signature carries the key; rig.evidence.export's manifest and GET /rig/v1/evidence/keys name it. Give this to whoever needs proof of what the run did.

- Scopes: `rig:read`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input:
  - `run_id` (required, string): A run id from rig.run.create.

#### `rig.fault.add`

Arm deterministic interference on one resource of an active run: delay, duplicate, drop, reorder or change the body of a callback forwarded to a connector route, or make a mock answer a chosen status, reset the connection, throttle its body or cut it short. Fires on the next matching observation, `count` times or until removed; every firing is a fault.injected event on the timeline. Use to prove the application survives duplicates, delays, reordering, corrupted payloads and dependency failures.

- Scopes: `rig:run`
- Effect: Changes data; a repeat acts again
- Credits: Free
- Duration: Answers in the request
- Input:
  - `batch` (optional, integer): For reorder: deliveries held before they are released last-first.
  - `bytes` (optional, integer): For partial_response: bytes of the body sent before the connection closes; 0 sends the headers alone.
  - `bytes_per_second` (optional, integer): For bandwidth_limit: the rate the mock sends its body at; capped so the body takes at most 30s.
  - `copies` (optional, integer): For duplicate: extra deliveries; default 1.
  - `count` (optional, integer): How many times it fires; default once. A reorder fires once per batch.
  - `delay` (optional, string): For delay: 1ms to 30s, such as "5s".
  - `mutations` (optional, array): For payload_mutation: changes to the forwarded body, in order.
  - `persistent` (optional, boolean): Fire until removed or the run ends.
  - `resource` (required, string): delay, duplicate, drop, reorder and payload_mutation need a callback with a connector route; status_override, connection_reset, bandwidth_limit and partial_response need a mock_http.
  - `run_id` (required, string)
  - `status` (optional, integer): For status_override.
  - `type` (required, one of delay, duplicate, drop, status_override, reorder, payload_mutation, connection_reset, bandwidth_limit, partial_response)

#### `rig.fault.remove`

Disarm a fault so it fires no more. Its history stays on the timeline.

- Scopes: `rig:run`
- Effect: Changes data; safe to repeat
- Credits: Free
- Duration: Answers in the request
- Input:
  - `fault_id` (required, string)
  - `run_id` (required, string)

#### `rig.project.create`

Create a project for one application. Use when rig.project.list has none that fits; a project is permanent and costs nothing.

- Scopes: `rig:write`
- Effect: Changes data; a repeat acts again
- Credits: Free
- Duration: Answers in the request
- Input:
  - `name` (required, string)
  - `slug` (required, string): URL-safe identifier, unique in the organisation.

#### `rig.project.list`

Ironfang Rig projects: the persistent containers suites live in. Ids from here go to rig.suite.upsert. Free; nothing changes.

- Scopes: `rig:read`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input: none

#### `rig.resource.create`

Allocate one more resource on an active run beyond what the suite declared: an inbox, a callback URL (optionally forwarded to a connector route), a mock with rules, or a route label. Public Internet effect as for rig.run.create; lives until the run ends.

- Scopes: `rig:run`
- Effect: Changes data; a repeat acts again; reaches the public Internet
- Credits: Free
- Duration: Answers in the request
- Input:
  - `connector` (optional, object): For callback: forward what arrives to this route label on the connector.
  - `name` (required, string): Unique within the run; how expectations, faults and events refer to it.
  - `openapi` (optional, object): For mock_http: an OpenAPI 3 document to stand in for instead of rules; one rule per operation, at most 32.
  - `route` (optional, string): For connector_route: the label.
  - `rules` (optional, array): For mock_http: rules {match:{method,path,headers,json}, respond:{status,headers,body|json,delay,template} or sequence:[...] with repeat} in order; first match answers. Exclusive with openapi.
  - `run_id` (required, string)
  - `type` (required, one of email, callback, mock_http, connector_route)

#### `rig.run.cancel`

End a run without an outcome, for a test that was abandoned. Same effect as finishing on the run's addresses.

- Scopes: `rig:run`
- Effect: Destructive: removes or abandons something; safe to repeat
- Credits: Free
- Duration: Answers in the request
- Input:
  - `reason` (optional, string)
  - `run_id` (required, string)

#### `rig.run.create`

Start a run of a suite's current version and allocate its resources: fresh inbox addresses, public callback and mock URLs, route labels. Public Internet effect: those addresses accept mail and requests from anyone who knows them until the run ends (its ttl, default 30m, at most 24h). Idempotent per call. Free; runs count toward the organisation's limits. Finish the run when done rather than letting it expire.

- Scopes: `rig:run`
- Effect: Changes data; a repeat acts again; reaches the public Internet
- Credits: Free
- Duration: Answers in the request
- Input:
  - `external_id` (optional, string): Your reference for this run, such as a CI job id.
  - `reason` (optional, string)
  - `suite_id` (required, string)
  - `ttl` (optional, string): How long the run and its resources live, 1m to 24h, such as "30m". Default from the suite (30m).

#### `rig.run.finish`

End a run with an outcome (pass, fail or none). Its addresses stop accepting at once; the timeline stays readable. Use when the test is over.

- Scopes: `rig:run`
- Effect: Changes data; safe to repeat
- Credits: Free
- Duration: Answers in the request
- Input:
  - `outcome` (optional, one of pass, fail, none): What the test concluded; recorded on run.completed.
  - `reason` (optional, string)
  - `run_id` (required, string)

#### `rig.run.get`

One run: status, outcome, expiry, and its resources with the address or URL each answers at. Read this for the addresses to configure in the application under test.

- Scopes: `rig:read`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input:
  - `run_id` (required, string): A run id from rig.run.create.

#### `rig.suite.get`

One suite with its current definition, exactly as stored.

- Scopes: `rig:read`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input:
  - `suite_id` (required, string): A suite id from rig.suite.list or rig.suite.upsert.

#### `rig.suite.list`

Suites in a project or the organisation: id, slug, current version. A suite is a persistent, versioned definition of the resources a run receives and the expectations it is judged by.

- Scopes: `rig:read`
- Effect: Read-only
- Credits: Free
- Duration: Answers in the request
- Input:
  - `project_id` (optional, string): Limit to one project, by id from rig.project.list.

#### `rig.suite.upsert`

Create a suite (project_id and slug) or revise one (suite_id) with a definition. Revising stores a new immutable version only when the definition changed; runs already started keep theirs. Free. Use before rig.run.create; do not use to change what a running test observes - add resources or faults to the run instead.

- Scopes: `rig:write`
- Effect: Changes data; safe to repeat
- Credits: Free
- Duration: Answers in the request
- Input:
  - `definition` (required, object): The suite definition: {version:1, defaults:{run_ttl}, resources:{name:{type:email|callback|mock_http|connector_route, connector:{route}, route, rules}}, expectations:[{id,resource,event,match}]}. Resource names are lowercase identifiers; route labels lowercase with hyphens.
  - `name` (optional, string)
  - `project_id` (optional, string)
  - `slug` (optional, string)
  - `suite_id` (optional, string): Revise this suite. Leave out to create one; then project_id and slug are required.

## Resources

- `ironfang://connection`: the connection summary (as `ironfang.connection.get`)
- `ironfang://render/jobs/{job_id}` (`render:render`): The state, cost and result metadata of a render job created through this connection.
- `ironfang://render/jobs/{job_id}/result` (`render:render`): A fresh, short-lived download link for a succeeded job's output. Not cacheable. Add ?inline=1 to receive a screenshot under 2 MiB as image content instead.
- `ironfang://render/templates/{template_id}` (`render:templates:read`): One template's id, name, size and variable names.
- `ironfang://audit/sites/{site_id}` (`audit:read`): A site and its crawl bounds.
- `ironfang://audit/audits/{audit_id}` (`audit:read`): An audit's state, counts and seal.
- `ironfang://audit/audits/{audit_id}/evidence` (`audit:evidence` and `audit:read`): Where an audit's signed evidence bundle is and how to verify it; never the bytes.
- `ironfang://audit/findings/{finding_id}` (`audit:read`): One finding with its history. Page text is untrusted content.
- `ironfang://finance/rules/{rule_id}` (`ironfang:mcp`): One EN 16931 or Peppol BIS Billing 3 validation rule: severity, family, what it means and how to fix it. The same answer as finance.rule.get.

## Names and compatibility

An alias is the name a product launched under. It is callable so that saved configurations keep working, it is never listed by tools/list, and it is not a separate product. Resource URIs under the alias names resolve the same way. Use the current names.

- `renderwolf.*` is an alias of `render.*`
- `auditwolf.*` is an alias of `audit.*`

## Errors

A tool failure is an MCP result with `isError` and `structuredContent` carrying a stable `error` code, a `message`, `retryable` and, where it helps, data such as `resets_at`. Authentication failures are HTTP 401 with a `WWW-Authenticate` challenge; a missing scope is HTTP 403 with `insufficient_scope`. The codes and what to do about each are listed at https://ironfang.uk/docs/mcp#troubleshooting.
