# Raptor Studio (pypi · raptor-studio-mcp)

Build, validate and simulate reliability block diagrams (RAM analysis) from an AI assistant.

- Trust score: 65/100 (medium)
- Change this week: −11
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-11-05

## Components

- pypi · `raptor-studio-mcp`: 65/100 (this document), [markdown](https://verifymcp.io/servers/openpowershift-raptor-studio/raptor-studio-mcp.md), [page](https://verifymcp.io/servers/openpowershift-raptor-studio/raptor-studio-mcp)

## Channel facts

- Registry: `pypi`
- Package: `raptor-studio-mcp`
- Version: `0.9.0`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-11-05.

- **Supply Chain Security**: 50/100
  - Malware scan not yet available for this package.
  - No known CVEs affecting this package version or its production dependencies.
  - Runs setuptools.build_meta at install time, a recognised build step with no custom scripting around it.
  - 0 of 23 dependencies flagged as unhealthy.
- **Provenance & Transparency**: 87/100
  - Source repository is publicly reachable at the declared URL.
  - Cryptographically verified build provenance (signed, bound to OpenPowerShift/RaptorStudio).
  - License check failed: the license (LicenseRef-BAPL-1.0 AND Apache-2.0) isn't a recognized OSI-approved license.
  - Actively maintained (last published 2 days ago).
  - Publishes a security disclosure policy (SECURITY.md).
- **Schema Quality & AI Usability**: 70/100
  - 100% of prompts and resources have a non-trivial description (not blank, and not just the item's name).
  - AI-judged instruction clarity (good).
  - Context-footprint check failed: tool/resource definitions use about 15816 tokens (~225/item across 70 items; 67 tools + 3 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 27/100
  - Stability observed for 8 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 72/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 2% of tool parameters carry a description.
  - Structured output schemas are declared (78% of tools); any adoption earns full credit.
- **Tool Safety**: 100/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - All 5 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation.
  - An AI judge read all 69 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a current MCP spec version (2026-07-28).

## Install

### How do I install the Raptor Studio MCP server?

Raptor Studio runs locally as a PyPI package, launched with uvx raptor-studio-mcp. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add openpowershift-raptor-studio -- uvx raptor-studio-mcp
```

### Cursor

```json
{
  "mcpServers": {
    "openpowershift-raptor-studio": {
      "command": "uvx",
      "args": [
        "raptor-studio-mcp"
      ]
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "openpowershift-raptor-studio": {
      "command": "uvx",
      "args": [
        "raptor-studio-mcp"
      ]
    }
  }
}
```

### Codex

```bash
codex mcp add openpowershift-raptor-studio -- uvx raptor-studio-mcp
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "openpowershift-raptor-studio": {
      "type": "local",
      "command": [
        "uvx",
        "raptor-studio-mcp"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add openpowershift-raptor-studio --command uvx --arg raptor-studio-mcp
```

### Hermes

```yaml
mcp_servers:
  openpowershift-raptor-studio:
    command: "uvx"
    args: ["raptor-studio-mcp"]
```

### Netclaw

```json
{
  "McpServers": {
    "openpowershift-raptor-studio": {
      "Transport": "stdio",
      "Command": "uvx",
      "Arguments": [
        "raptor-studio-mcp"
      ]
    }
  }
}
```

### Vellum

```bash
assistant mcp add openpowershift-raptor-studio -t stdio -c uvx -a raptor-studio-mcp
```

### Other

```json
{
  "mcpServers": {
    "openpowershift-raptor-studio": {
      "command": "uvx",
      "args": [
        "raptor-studio-mcp"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-11-05 (score 65, −14)

- [security regression] Malware scan: pass → unverified

### 2026-11-03 (score 79, +16)

- [security improvement] Malware scan: unverified → pass

### 2026-11-02 (score 63, −15)

- [security regression] Malware scan: pass → unverified
- [functional regression] Schema quality: 203 → 225
- [functional] Package version: 0.8.0 → 0.9.0

### 2026-11-01 (score 78, +1)

No change was recorded against any check on this day. Stability & Change Management went from 10 to 13. That category is still filling its 30-day observation window: 3 days of observed history at the previous scan, 4 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-10-30 (score 77, +1)

No change was recorded against any check on this day. Stability & Change Management went from 3 to 7. That category is still filling its 30-day observation window: 1 days of observed history at the previous scan, 2 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-10-28 (score 76)

First indexed and scored.

## MCP tools (67)

### `ping` (~20 tokens)

Health-check: returns 'pong' if the server is running.

Output parameters:

- `result` (string)

### `list_distributions` (~106 tokens)

List every distribution supported by the RAPTOR engine.

Returns structuredContent.distributions: each entry has the integer
code used in .rbd/.rbds, a human name, the number of parameters
expected, the parameter names in order, valid ranges, and an example
mean/stdev computed at unit parameters (the engine exposes numeric
stats, not symbolic formulas).

Use this before setting fail_dist / repair_dist to supply the right
number of parameters in the right order.

Output parameters:

- `distributions` (array)

### `list_models` (~123 tokens)

List open models and (optionally) bundled example models.

Returns structuredContent.models — rows of
{model_id, name, blocks, nodes, last_modified, source} — and
structuredContent.examples (names) when include_examples=true.
Use this to find model_id values for the other tools. Long workspaces
are paged: `limit` (max 200) and `offset`; `total` and `next_offset`
describe the rest.

Input parameters:

- `include_examples` (boolean)
- `limit` (integer)
- `offset` (integer)

Output parameters:

- `examples` (array)
- `models` (array)
- `next_offset` (integer|null)
- `total` (integer)

### `open_model` (~120 tokens)

Copy a model file into the workspace and return its handle.

The original file is never mutated.  Accepts a .rbds or .rbd path
\**relative to the workspace** (or an `--allow-read` directory), or a
bundled example name like 'MotorDemo'.  Absolute paths are refused,
as are paths outside those roots, so the MCP boundary cannot read
arbitrary files.  Returns {model_id, summary}.
Allowed under --read-only (it never changes the source file).

Input parameters:

- `path` (string, required)

Output parameters:

- `model_id` (string)
- `summary` (string)

### `create_model` (~120 tokens)

Create a new model.

template: 'blank' (default), 'series' (n blocks in series),
'parallel' (n in parallel), or 'k_of_n' (k-of-n voting of n blocks).
Non-blank templates are auto-laid-out on the shared layered grid, so
the diagram renders cleanly without a separate `auto_layout` call.
Returns {model_id, summary}.

Input parameters:

- `k` (integer)
- `n` (integer)
- `name` (string, required)
- `template` (string)

Output parameters:

- `model_id` (string)
- `summary` (string)

### `save_model_as` (~174 tokens)

Save a model to a file path.

The path must be inside the workspace or an --allow-write directory.
format: 'rbds' (default) or 'rbd'.  If `path` has **no extension** the
format's own extension is appended (so `"out"` -> `out.rbds`).  An
explicit extension must match `format`; a mismatch or a path into the
managed models/runs/renders trees is refused.  A .rbd export runs
validate() first and refuses on ERROR. The layout is saved as it is;
\`layout_warning` says when Raptor 7 would draw elements overlapping
(call `auto_layout` first to space them out).

Input parameters:

- `format` (string)
- `model_id` (string, required)
- `path` (string, required)

Output parameters:

- `bytes` (integer)
- `dropped` (array)
- `format` (string)
- `layout_warning` (null)
- `path` (string)

### `close_model` (~162 tokens)

Close one or more models.  Set delete=true to also remove their files.

\`model_id` accepts a single id or a list, for cleaning up several
scratch/sweep models in one call. Each id is closed independently and
only the ids you pass are affected -- other open models are never
touched, whatever their names. One bad id doesn't abort the rest; the
failures are reported alongside the successes.

\`delete=true` removes that model's own workspace directory (its
\`model.rbds`, backups and meta) and is **irreversible** -- the undo
history goes with it. It does not delete exported files (use
\`delete_file`) or runs.

Input parameters:

- `delete` (boolean)
- `model_id` (required)

Output parameters:

- `closed` (array)
- `failed` (object)

### `delete_file` (~86 tokens)

Delete a file previously exported with `save_model_as` (or any
other file inside the workspace or an --allow-write directory).

Does not touch an open model's own files -- use `close_model(...,
delete=true)` for those. Refuses a path outside the workspace/
allow-write directories, same containment `save_model_as` enforces.

Input parameters:

- `path` (string, required)

Output parameters:

- `bytes` (integer)
- `deleted` (string)

### `build_model` (~1502 tokens)

Build a complete model from a friendly .rbds spec document.

Give the spec inline as `spec`, or, for a large one, as `spec_path`: a
JSON file **relative to the workspace** (or an `--allow-read`
directory). Exactly one of the two. To open a finished `.rbds` or
\`.rbd` file instead, use `open_model`, which takes the same kind of path.

\`build_model` always creates a NEW model. `model_id` is accepted only
to reject it with a clear message: it used to be ignored silently, so
a caller that passed one believed it was appending to that model.
Use `add_elements`/`add_block` to edit an existing model.

\`spec` is a JSON object with `blocks`, `nodes`, `events`, `hierarchies`,
\`links`, `system`, `experiment`, `rates`, `spare_pools`, `resources`,
\`triggers`, `phases` and `annotations`, using the friendly snake_case
fields from docs/RBDS_FORMAT.md (e.g. a block's `fail_dist`/`fail_params`;
distributions may be given by name or code).  Ids
are assigned where omitted, links are wired, counts synced.  This is
the main tool for creating a whole model in one call. A complete
\`.rbds` document goes in as `spec={"rbds_text": "<the file's text>"}`.

\`experiment` takes the .rbds run block itself (`trials`,
\`termination_type`, `term_condition`, `analyses`, `recording`, ...), so
the run settings no longer need a second `configure_system` call.

RAPTOR Studio's input rules apply as they do to every edit: a spec with a link
RAPTOR Studio will not let you draw (Start linked to two blocks, say -- use
Start -> a Node -> the blocks) or a value its dialogs refuse is rejected
with RAPTOR Studio's own message.

\`spare_pools`/`resources`/`triggers`/`phases` are lists of dicts built
with the same row constructors the `configure_*` tools use.  Friendly
fields per entry:
\- spare_pools: name, stock, new_spares, arrive_every, eso, emer_time,
  slo, level, quantity, slo_time, rso
\- resources: name, num_in, spare_cost, fixed_per_use, per_time_unit,
  phases
\- triggers: name, init_used, dist, params, repeats…

Input parameters:

- `auto_layout` (boolean)
- `model_id`
- `name` (string, required)
- `spec`
- `spec_path`
- `validate_model` (boolean)

Output parameters:

- `model_id` (string)
- `summary` (string)
- `validation` (object)

### `describe_model` (~82 tokens)

Describe the model structure.

detail='summary' gives counts plus a topology sentence; detail='full'
lists every element in one response (use get_table for paged input
tables).  `level` filters to a hierarchy level (0 = whole model).

Input parameters:

- `detail` (string)
- `level` (integer)
- `model_id` (string, required)

Output parameters:

- `block_names` (array)
- `blocks` (integer)
- `event_names` (array)
- `events` (integer)
- `hierarchies` (array)
- `link_count` (integer)
- `links` (array|integer)
- `model_id` (string)
- `name` (string)
- `node_names` (array)
- `nodes` (integer)
- `nodes_detail` (array)
- `system` (object)

### `add_block` (~216 tokens)

Add a block to the model.

\`fail_dist`/`repair_dist` take a distribution name ("Weibull") or a
code from `list_distributions`.
Numeric parameters accept expression strings with a `#` comment for
traceability, e.g. "0.00015 * 20000 / 8760  # failure rate".  `after`
wires the new block after an existing element name/id, which must not
already have its one link out. A block takes at most one incoming and
one outgoing link (RAPTOR Studio) -- to feed it from/to more than one other
element, route through a Node (`add_node` + `connect`; see `connect`).
Returns {block_id, name, warnings}.

Input parameters:

- `after`
- `fail_dist`
- `fail_params`
- `model_id` (string, required)
- `name` (string, required)
- `parent` (integer)
- `repair_dist`
- `repair_params`

Output parameters:

- `block_id` (string)
- `name` (string)
- `warnings` (array)

### `add_node` (~195 tokens)

Add a node to the model.

type: 'start', 'regular', 'end', 'in' or 'out'.

k: K-out-of-N threshold.  A node's n comes from its *incoming* links,
so set k only after the voting blocks are wired INTO the node.  `-1`
(the default) is RAPTOR's "K-of-N never set" sentinel and is treated
as 1-of-N -- it does **not** mean "all incoming paths required"; use
\`k = n` for an all-required node.  It is stored verbatim as `-1`, so
\`get_element` reports `k=-1`; the coercion to 1 happens when the model
runs.

Input parameters:

- `k` (integer)
- `model_id` (string, required)
- `name` (string)
- `parent` (integer)
- `type` (string)

Output parameters:

- `name` (string)
- `node_id` (string)

### `add_event` (~49 tokens)

Add an event to the model.  `prob` is its probability (0-1).

Input parameters:

- `model_id` (string, required)
- `name` (string, required)
- `prob` (number)

Output parameters:

- `event_id` (string)
- `name` (string)

### `connect` (~240 tokens)

Connect two elements with a link.

\`from_element`/`to_element` are either ["Block", id] / ["Node", id]
or a bare element name like "PumpA".

\`replace=True` removes any link already leaving the source and any
link already entering the target first, so a wrong connection is
corrected in one call. Without it, RAPTOR Studio's own limit (one link
in/out per element) refuses the second connection.

RAPTOR Studio's link rules apply, and a link it will not let you draw is
refused with its own message, leaving the model unchanged: a block,
event or hierarchy takes at most one link in and one out; a Start or In
marker one link out and none in; an End or Out marker one link in and
none out. Fan-in/fan-out (two blocks feeding one element, or Start
feeding two) goes through a Node: `add_node` first, then connect each
element to it.

Input parameters:

- `from_element`
- `model_id` (string, required)
- `replace` (boolean)
- `to_element`

Output parameters:

- `link_id` (string)

### `delete_link` (~84 tokens)

Delete one link by id, leaving both elements in place.

This is the safe alternative to deleting an element just to break one
connection (which removed every other link on that element too). Find
link ids in `describe_model(detail='full')` or `get_table('inputs.links')`.

Input parameters:

- `link_id` (string, required)
- `model_id` (string, required)

### `move_element` (~119 tokens)

Move a block, node, event or hierarchy to another hierarchy level.

\`parent=0` moves it back to the top level; otherwise pass a hierarchy
id. `update_element` refuses `parent` because it is structural, and
the only alternative used to be deleting and recreating the element
(losing its links, sparing and PM settings). A hierarchy cannot be
moved into itself or one of its own descendants.

Input parameters:

- `model_id` (string, required)
- `parent` (integer, required)
- `selector` (string, required)

### `add_hierarchy` (~268 tokens)

Create an empty hierarchy -- a sub-model boundary -- with its In and
Out markers already wired up correctly.

This is the fiddly part to do by hand: each marker's own `parent` must
be the new hierarchy's id (not the enclosing level) and both markers'
\`n` must match their real link counts, or the engine crashes on load
with an unhelpful KeyError. Building one through `build_model` means
declaring `in_id`/`out_id`, the two marker nodes with their `parent`
set to the hierarchy's own id, and their link counts -- three
non-obvious constraints.

The hierarchy starts empty and unwired. Add contents with
\`add_block(parent=<hier_id>)` (read the id from
\`describe_model(detail='full')`, or from the return value here), then
\`connect` the In marker to the first element and the last element to
the Out marker. To build a hierarchy *around* elements you already
have, use `collapse_selection` instead -- it does all the rewiring.

Returns {hier_id, name, in_id, out_id}.

Input parameters:

- `model_id` (string, required)
- `name` (string, required)
- `parent` (integer)

Output parameters:

- `hier_id` (string)
- `in_id` (integer)
- `name` (string)
- `out_id` (integer)
- `parent` (integer)

### `collapse_selection` (~266 tokens)

Move existing blocks/nodes/events into a new hierarchy, rewiring the
boundary.

The API counterpart of the GUI's *Collapse Selection*. `selectors` names
the elements (by name, or `Block:N` / `Node:N` / `Event:N`) that should
become the new hierarchy's contents. Links wholly inside travel down a
level untouched; links that crossed the boundary are rewritten to
address the hierarchy, and each is split so the sub-model's own graph
stays complete (the outside half keeps its id, an inside half is added
off the In/Out marker).

Start/End/In/Out nodes and existing hierarchies are refused -- a
hierarchy cannot contain the model's own terminators, and nesting one
hierarchy inside another needs the boundary wired by hand.

This is the one-call way to wrap a branch you have already built, and
the way to reproduce the book's "three ovals" figures. Read
\`describe_model(detail='full')` afterwards to see the new level.

Returns {hier_id, name, in_id, out_id, moved}.

Input parameters:

- `model_id` (string, required)
- `name`
- `parent` (integer)
- `selectors` (array, required)

Output parameters:

- `hier_id` (string)
- `in_id` (integer)
- `moved` (array)
- `name` (string)
- `out_id` (integer)
- `parent` (integer)

### `get_element` (~276 tokens)

Read one block, node, event, hierarchy or link back in full.

\`selector` is an element name or 'Block:N' / 'Node:N' / 'Event:N' /
'Hier:N' / 'Link:N' (links have no name; use their id).  This is the
read counterpart of `update_element`: every field uses the friendly name
\`update_element` accepts.

A block returns its connections (upstream/downstream), `failure` and
\`repair` (distribution name, named parameters, mean/stdev), any
\`logistics_delay` before/after repair, `sparing`, `maintenance`,
\`dependency` and `phasing`.  `dependency` says what the block follows
(a block, node, event, hierarchy, its own node, or the whole system),
the % split of what happens when that goes down (nothing / idle / pm /
failure) and a plain-English `meaning`.  detail='full' adds costs,
growth/decay and standby stress.

A distribution's mean/stdev is null (with a note) when it has no finite
value for those parameters, e.g. a Pearson 5 with shape <= 2.

Input parameters:

- `detail` (string)
- `model_id` (string, required)
- `selector` (string, required)

Output parameters:

- `dependency` (object)
- `div_flow` (integer)
- `downstream` (array)
- `failure` (object)
- `full_flow` (integer)
- `id` (integer)
- `k` (integer)
- `kind` (string)
- `logistics_delay` (object)
- `maintenance` (object)
- `n` (integer)
- `name` (string)
- `node_type` (string)
- `parent` (integer)
- `phasing` (boolean)
- `position` (object)
- `repair` (object)
- `sparing` (object)
- `upstream` (array)

### `diff_models` (~391 tokens)

Say what is different between two models (their inputs, not their results).

This compares *the models themselves*: elements added, removed, renamed or
changed (distributions, sparing, maintenance, costs...), connections added
or removed, hierarchy moves, k-of-n changes, system settings, rates, spare
pools, resources, triggers and phases. It runs nothing. `compare_models`
is the other one: it runs models and compares their *results*.

\`model_a` / `model_b` are workspace model ids (the reference and the one
changed); open files with `open_model` first, this tool does not load
paths. Elements are paired by name, then by id (`match`: "auto"; "name" or
"id" to use only one; "id" is right for a model against an earlier copy of
itself).

\`ignore` lists categories to leave out. The default (omit it) is
["layout", "derived"]: screen positions and columns the file reader
computes. Pass [] to show everything. Categories: layout, derived,
identity, failure, repair, streams, sparing, maintenance, logistics,
dependency, cost, phasing, capacity, structure, system, project, other.

detail="summary" returns the counts, the added/removed/renamed lists and
at most 50 field changes (`truncated` says whether more exist;
\`total_changes` counts them); detail="full" returns every change. Each
change has `field`, `label`, `category` and the values `a` and `b`
(distributions are described in words, with raw columns in `a_raw` /
\`b_raw`).

Input parameters:

- `detail` (string)
- `ignore`
- `match` (string)
- `model_a` (string, required)
- `model_b` (string, required)

Output parameters:

- `a` (object)
- `b` (object)
- `elements` (object)
- `identical` (boolean)
- `ignored` (array)
- `links` (object)
- `phases` (object)
- `project` (array)
- `rates` (object)
- `resources` (object)
- `schema` (string)
- `spare_pools` (object)
- `structure` (array)
- `summary` (object)
- `system` (array)
- `total_changes` (integer)
- `triggers` (object)
- `truncated` (boolean)
- `warnings` (array)

### `update_element` (~1165 tokens)

Update fields on an element.

\`selector` is an element name, 'Block:N' / 'Node:N' / 'Event:N', a link
('Link:N', which carries capacity: `nom_flow`, `max_flow`, `cap_pri`),
or an auxiliary row by name ('Pool:RackSpares', 'Resource:Crew',
'Trigger:Storm', 'Phase:Summer' -- the bare name works too).
\`patch` accepts any friendly .rbds field (e.g. `fail_params`,
\`pm_freq`, pool `stock`) or a raw column name, including indexed forms
like `repair_params[0]`.  `fail_dist`/`repair_dist` take a distribution
name ("Weibull") or a code.  To make a block dependent on another element
use `dep_type` (RBDBlock/RBDNode/RBDEvent/RBDHier), `dep_num` (the
target's id; -1 = own node, -2 = whole system) and the effect split
\`dep_nothing`/`dep_idle`/`dep_pm`/`dep_fail` (percent); read it back
with `get_element`.

Numeric fields may be numbers OR expression strings such as
"50.0 + @Rate * 1.4  # basis"; expressions are resolved to a float and
their derivation is kept in the model's `formulas`.  Returns
{changed, warnings, element} where `element` is a compact
{id, name, kind} summary; pass full=true for every field of the element.

A value RAPTOR Studio's dialogs refuse (a PM stagger at or above the PM
frequency, a cost below 0, a name with a space, ...) is refused with
RAPTOR Studio's own message and the model is left unchanged. A problem the
model already had (a file written elsewhere) does not block other edits.

Input parameters:

- `full` (boolean)
- `model_id` (string, required)
- `patch` (object, required): Field -> value. Friendly names by element kind (the .rbd file's column names and indexed forms such as repair_params[0] also work) -- Block: add_done, amount_exhausted, amt_exhausted, arrive, arrive_…
- `selector` (string, required)

Output parameters:

- `changed` (object)
- `element` (object)
- `warnings` (array)

### `update_elements` (~164 tokens)

Update many elements with one save.

Same `selector` and `patch` semantics as `update_element`, but the whole
list is applied inside one workspace batch: the model is written once at
the end instead of once per element. On a large model this is the
difference between seconds and tens of minutes (one `update_element` on
a ~10k-element model costs ~10 s).

The batch is atomic: if any item is refused, the model is restored to
its state before the call and nothing is written.

Returns {updated, results: [{selector, id, name, kind, changed}]}.

Input parameters:

- `model_id` (string, required)
- `updates` (array, required): List of {"selector": str, "patch": {...}} objects

### `connect_many` (~102 tokens)

Create many links with one save.

Endpoints are the same as `connect` (a name, or ["Block", id] /
["Node", id] / ["Hier", id]). Applied as one atomic batch; the model is
written once. Returns {connected, links: [{link_id, from, to}]}.

Input parameters:

- `links` (array, required): List of {"from": endpoint, "to": endpoint} objects
- `model_id` (string, required)

### `add_elements` (~181 tokens)

Add many blocks, nodes or events with one save.

Each item is `kind` plus the same fields the single add tool takes:

\- block: `name`, optional `fail_dist`, `fail_params`, `repair_dist`,
  \`repair_params`, `parent`, `after`
\- node: `name`, `type` (start/regular/end/in/out), `parent`, `k`
\- event: `name`, `prob`, `parent`

Items are created in list order, so a later item may use `after` to wire
itself to an earlier one. Atomic: a refused item restores the model.
Returns {added, elements: [{kind, id, name}]}.

Input parameters:

- `elements` (array, required): List of {"kind": "block"|"node"|"event", ...fields}
- `model_id` (string, required)

### `delete_elements` (~83 tokens)

Delete many elements with one save.

\`reconnect` behaves as for `delete_element`. Applied as one atomic
batch; a refused selector restores the model.
Returns {deleted: [{selector, kind}], links_removed}.

Input parameters:

- `model_id` (string, required)
- `reconnect` (boolean)
- `selectors` (array, required): Element names or Kind:id selectors to delete

### `auto_layout` (~447 tokens)

Re-arrange a model's diagram onto the shared layered grid.

Runs the same engine the GUI's Auto Layout button and .rbd save
normalization use: longest-path columns from the Start node(s)
(left = upstream), rows spread within each column, so no connector
runs through a block.  Positions are written back to the model file,
so a subsequent `render_diagram` reflects them.

Scope the pass with one of:

\- `hierarchy`: a hierarchy name or id — only that level's elements
  are repositioned; every other level keeps its coordinates.
\- `selectors`: names / `Kind:id` of the elements to move. The layout
  is still computed for the whole level (a subset laid out in
  isolation does not line up with its neighbours), but only the named
  elements adopt the new positions.

With neither, every level is laid out. `build_model` already lays out
by default (`auto_layout=True`).

The collision-refining step has a time budget (30 s in all): a dense
model can otherwise take minutes. If it runs out, the layered layout is
kept with whatever refinement was done, and `refine_cut_short` says how
many levels were left less than fully refined (0 when none).

Annotations placed next to an element (`adjacent_to`, or an arrow's
\`from_`/`to`) move with it; ones placed by coordinates stay where they
are in the model. For a whole-model layout the gaps between stacked
elements are first stretched so notes anchored `above`/`below` fit
(the elements either side move apart equally; nothing moves closer),
and a note that would still land on an element or another note is
nudged to the nearest clear spot; one a user dragged away keeps its
offset. Nothing else ever re-lays out a model: saving and exporting
keep the positions as they are.
Returns {moved, model_id, scope, elements_moved, annotations_moved,
refine_cut_short}.

Input parameters:

- `hierarchy`
- `model_id` (string, required)
- `selectors`

Output parameters:

- `annotations_moved` (integer)
- `elements_moved` (integer)
- `model_id` (string)
- `moved` (boolean)
- `refine_cut_short` (integer)
- `scope` (string)

### `delete_element` (~57 tokens)

Delete an element from the model.

reconnect=true removes its links and rewires adjacent connections.
Returns {removed, links_removed}.

Input parameters:

- `model_id` (string, required)
- `reconnect` (boolean)
- `selector` (string, required)

Output parameters:

- `deleted` (string)
- `id` (string)
- `kind` (string)
- `links_removed` (integer)

### `undo_last_edit` (~97 tokens)

Step one edit back (multi-step: call again to go further back).

Every edit tool backs up the prior state first (up to 20 deep), so
repeated calls walk further back rather than repeating the same
restore. Each undo can itself be undone with `redo_last_edit`, until
a new edit is made -- which clears the redo history, same as any
other editor.

Input parameters:

- `model_id` (string, required)

Output parameters:

- `restored_version` (integer)
- `summary` (string)

### `redo_last_edit` (~56 tokens)

Step one edit forward again after `undo_last_edit` (multi-step).

Fails with a clear error once there's nothing left to redo, or once
a new edit has cleared the redo history.

Input parameters:

- `model_id` (string, required)

Output parameters:

- `restored_version` (integer)
- `summary` (string)

### `configure_sparing` (~200 tokens)

Configure sparing for a block.

mode: 'infinite' (default), 'custom', or 'pool'. `spare_pool` is
required for mode='pool' and must name an existing pool (create one
with `manage_spare_pools`).
stock: initial spare count; restock_every: restock interval (hours);
emergency: emergency shipping time (hours); slo: stock level order.
With mode='pool' the POOL's stock and restock interval govern; the
block's own `stock` only applies to mode='custom'.
Returns {applied, warnings}.

Input parameters:

- `emergency`
- `level`
- `mode` (string)
- `model_id` (string, required)
- `quantity`
- `restock_every`
- `selector` (string, required)
- `slo`
- `slo_time`
- `spare_pool`
- `stock`

Output parameters:

- `applied` (boolean)
- `element` (object)
- `warnings` (array)

### `configure_maintenance` (~206 tokens)

Configure preventive maintenance for a block.

pm_freq: maintenance interval (hours).  pm_duration_dist/pm_params:
the PM duration distribution.  trigger_name: a trigger element (for
triggered PM).

defer_flags: bool-valued PM Options, matching the block Properties
dialog exactly (unknown keys raise an error):
\- `spare`: needs a spare on hand to conduct PM.
\- `refresh`: after PM completes, refresh the block like new.
\- `mis_defer`: defer PM until a non-mission phase occurs (only
  meaningful when the model has phases).
\- `req_defer`: defer PM until a spare or resources are available.
\- `fail_reset`: a failure resets the time to next PM activity.

Input parameters:

- `defer_flags`
- `model_id` (string, required)
- `pm_duration_dist`
- `pm_freq`
- `pm_params`
- `selector` (string, required)
- `trigger_name`

Output parameters:

- `applied` (boolean)
- `element` (object)

### `configure_phases` (~329 tokens)

Configure mission phases.

phases: [{name, duration_dist, params, mission}].

\`mode='replace'` (default) makes `phases` the whole list;
\`mode='add'` appends them to the phases already there (cells for the
new phase are filled with Raptor's default); `mode='remove'` deletes
the phases named in `remove` and shifts the rest down.

\`unassign` names blocks/events/nodes whose phasing is cleared (flag
off, per-phase cells dropped) -- previously there was no MCP route
for this.

assignments: {element: [per-phase state]}, one list entry per phase
(a short list is an error, not padded).  A block/event state is
\`"<code>: <value>"` -- code `A` (Active), `C` (Cut) or `L` (Linked),
value the stress multiplier, e.g. `"A: 1.000"`, `"C: 0.500"`; the
colon and value are required even for Cut (a bare word like "Cut"
crashes the engine's phase parser instead of erroring here). A node
state is a bare token: `"C"`, `"L"`, or an integer k-of-n threshold
for that phase.  Phase rows are always built through rbdlib.make_phase_row.

Input parameters:

- `assignments`
- `mode` (string)
- `model_id` (string, required)
- `phases`
- `remove`
- `unassign`

Output parameters:

- `assigned` (integer)
- `phases` (integer)
- `removed` (integer)
- `unassigned` (integer)

### `configure_system` (~170 tokens)

Configure system-level simulation settings.

run_length: simulation duration (hours unless `units` says otherwise).
termination: 'TimeSim' or 'CycleSim'.  display_unit overrides the
display unit without changing stored values.  capacity=true enables
capacity analysis; `flow_generated` sets its demand (the flow the
system is asked to deliver).  Returns {applied, previous}.

Input parameters:

- `capacity`
- `cost`
- `display_unit`
- `flow_generated`
- `gy_threshold`
- `model_id` (string, required)
- `num_runs`
- `run_length`
- `start_stats`
- `system_streams`
- `termination`
- `units`
- `weak_link`
- `yr_threshold`

Output parameters:

- `applied` (array)
- `ok` (boolean)
- `previous` (object)

### `manage_rates` (~129 tokens)

Manage the model's named Rates library.

action: 'list', 'add', 'update' or 'delete'.
\`expr` is an expression like "0.00015 * 20000 / 8760  # failure rate".
Rates can then be referenced in any numeric field as `@RateName * k`,
which keeps the derivation in the model's `formulas`.
Returns {rates} for 'list', else the affected entry.

Input parameters:

- `action` (string)
- `description`
- `expr`
- `model_id` (string, required)
- `name`

Output parameters:

- `description` (string)
- `expr` (string)
- `name` (string)
- `rates` (array)
- `value` (number)

### `configure_block_defaults` (~243 tokens)

Set the distributions `add_block` uses when you don't name any.

This is the MCP equivalent of the GUI's *Block Defaults* button: set
your house values once and every block you place picks them up, instead
of restating four numbers on every `add_block` call.

\- `action='list'` — show the current defaults (`{}` means "use each
  model's own template-row defaults").
\- `action='set'` — store the fields you supply; omitted fields are
  left as they are.  `fail_dist`/`repair_dist` are distribution codes
  from `list_distributions`.
\- `action='clear'` — forget them and fall back to model defaults.

Precedence at placement time: the arguments you pass to `add_block`,
then these defaults, then the model's own block template row, then the
\`rbdlib` canonical values.  Defaults are per-workspace and persist
across server restarts.

Input parameters:

- `action` (string)
- `fail_dist`
- `fail_params`
- `repair_dist`
- `repair_params`

Output parameters:

- `block_defaults` (object)

### `add_annotation` (~632 tokens)

Add a drawing annotation (text box, rect, ellipse or arrow).

Place it **next to an element or another annotation** with
\`adjacent_to` (a block/node/event/hierarchy name or `Kind:id`, or
\`Annotation:<id>` / an annotation's label) plus `location`
(`right`/`left`/`above`/`below`/`center`, default `above`) and `gap`:
it is anchored,
so it moves with the target when the target is dragged or
\`auto_layout` runs. `center` puts the annotation over the target and
ignores `gap`. Or place it **by coordinates** with `x`/`y` (+ `w`/`h`):
in scene pixels (the default, what the GUI stores) or, with
\`units="model"`, in the model's own coordinates (the numbers element
\`x`/`y` use). `w`/`h` are scene pixels (a block is 108 x 46). It
inherits the target's hierarchy; set `parent` to draw it inside
another level.

An `arrow` connects two things by name: give `from_` and `to` (an
element or an `Annotation:<id>`), or the four coordinates
\`x1`/`y1`/`x2`/`y2`. Each end is independent: name just one (say
\`from_`) and give the other end as coordinates (`x2`/`y2`) to leave it
free. An attached end follows its target and stops at the target's
edge, not its centre.

\`style` carries the cosmetic fields: `border_colour`, `border_width`
(0 = none), `line_style` (`solid`/`dash`/`dot`/`dash-dot`/
\`dash-dot-dot`), `fill_colour` (null = transparent), `text_colour`,
\`font_family`, `font_size`, `font_bold`, `font_italic`, `z`.
Returns {annotation_id, annotation, summary}.

For an arrow the parameter is spelled `from_` (a Python keyword
conflict); the stored `anchor` object reads back as `from`.  In a
\`build_model` spec the same `from_` spelling applies.
Annotations exist only in `.rbds`; exporting with
\`save_model_as(format="rbd")` drops them.

Input parameters:

- `adjacent_to`
- `from_`
- `gap` (number)
- `h`
- `kind` (string)
- `location` (string)
- `model_id` (string, required)
- `parent`
- `style`
- `text` (string)
- `to`
- `units` (string)
- `w`
- `x`
- `x1`
- `x2`
- `y`
- `y1`
- `y2`

Output parameters:

- `annotation` (object)
- `annotation_id` (integer)
- `summary` (object)

### `list_annotations` (~96 tokens)

List a model's diagram annotations.

\`parent` filters to one hierarchy level (the `parent` field, 0 =
home).  Returns {annotations, count}; each annotation gives
annotation_id, kind, parent, text, geometry and, for name-anchored
ones, its `anchor` (target/location/gap, or from/to for an arrow).

Input parameters:

- `model_id` (string, required)
- `parent`

Output parameters:

- `annotations` (array)
- `count` (integer)

### `update_annotation` (~204 tokens)

Patch one annotation.

Accepts any style field, `text`, explicit geometry (`x`/`y`/`w`/`h`/
\`x1`/`y1`/`x2`/`y2`, in scene pixels or with `"units": "model"`), or an
anchor via `adjacent_to`/`location`/`gap` (text/rect/ellipse) or
\`from_`/`to` (arrow). Re-anchoring to another annotation uses
\`adjacent_to="Annotation:<id>"` (or its unique label). Moving an
anchored annotation by coordinates removes its anchor, as dragging it
in the GUI does; `"anchor": null` removes it and leaves the annotation
where it is.  Returns {annotation_id, annotation, summary}.

Input parameters:

- `annotation_id` (integer, required)
- `model_id` (string, required)
- `patch` (object, required)

Output parameters:

- `annotation` (object)
- `annotation_id` (integer)
- `summary` (object)

### `delete_annotation` (~37 tokens)

Delete one annotation by id.  Returns {deleted, kind}.

Input parameters:

- `annotation_id` (integer, required)
- `model_id` (string, required)

Output parameters:

- `deleted` (integer)
- `kind` (string)

### `search_docs` (~91 tokens)

Search the RAPTOR tutorial docs for passages matching a query.

Multi-word queries match passages containing **all** terms (so
"capacity analysis system capacity" finds the capacity passages, not
nothing).  Returns {results}, each {chapter, file, anchor, text,
score}.  Use `read_doc` to read a whole document.

Input parameters:

- `limit` (integer)
- `query` (string, required)

Output parameters:

- `results` (array)

### `read_doc` (~168 tokens)

Read a documentation source as text (paginated).

\`name` is 'rbds' (the `.rbds` format guide), 'rbds-schema' (its JSON
Schema; open_model, build_model and validate_model check files against
it), 'dependency' (the
dependency tutorial chapter), 'mcp-guide' (this MCP
chapter), a tutorial chapter stem like 'ch11-capacity', a chapter
title fragment, or a path under `docs/` or `new_docs/`.  `offset`/
\`limit` page the lines (limit <= 400).  Returns {name, file,
total_lines, offset, next_offset, text}.

Input parameters:

- `limit` (integer)
- `name` (string, required)
- `offset` (integer)

Output parameters:

- `file` (string)
- `name` (string)
- `next_offset` (integer|null)
- `offset` (integer)
- `text` (string)
- `total_lines` (integer)

### `workspace_info` (~84 tokens)

Return the server workspace root and read/write rules.

Learn where models/runs/renders live and which directories
\`save_model_as` may write to (the workspace plus any `--allow-write`
dirs) and `open_model` may read from (the workspace, examples and any
\`--allow-read` dirs).  Relative save paths resolve against the
workspace root.

Output parameters:

- `allowed_read` (array)
- `allowed_write` (array)
- `block_defaults` (object)
- `max_bytes` (integer)
- `max_models` (integer)
- `max_total_models` (integer)
- `retain_days` (integer)
- `root` (string)

### `manage_spare_pools` (~313 tokens)

Create, list, edit or delete a spare pool.

action='list' returns every pool. `action='add'` needs `name`;
\`action='update'` patches only the fields you pass; `action='delete'`
refuses while a block still has Pooled sparing that names the pool.

These are the pools `configure_sparing(mode="pool")` draws from, with
the stock and lead-time quantities spares planning needs. Field names
match the `inputs.spare_pools` table and the .rbds `spare_pools`
entries: `stock`, `new_spares`, `arrive_every` (restock interval, h),
\`eso`, `emer_time` (emergency shipping, h), `slo`, `level`,
\`quantity`, `slo_time`, `rso`, `spare_cost`, `emer_ship_cost`.

The pool's own `stock` is what governs a Pooled block; the block's
\`stock` field is only used by Custom sparing.

Input parameters:

- `action` (string)
- `arrive_every`
- `emer_ship_cost`
- `emer_time`
- `eso`
- `level`
- `model_id` (string, required)
- `name`
- `new_spares`
- `quantity`
- `rso`
- `slo`
- `slo_time`
- `spare_cost`
- `stock`

### `manage_resources` (~150 tokens)

Create, list, edit or delete a resource pool (shared crew/equipment).

Resources are what a block's `resource_name` points at and what
\`configure_sparing(mode="resource")` consumes. `action='delete'`
refuses while a block still names the resource. Fields match the
\`inputs.resources` table: `num_in`, `spare_cost`, `fixed_per_use`,
\`per_time_unit`, `phases`.

Input parameters:

- `action` (string)
- `fixed_per_use`
- `model_id` (string, required)
- `name`
- `num_in`
- `per_time_unit`
- `phases`
- `spare_cost`

### `manage_triggers` (~120 tokens)

Create, list, edit or delete a trigger (for triggered PM).

\`dist` takes a name ("Normal") or a code; `params` its parameters.
A block's PM can only name a trigger that exists, so add it here
first. Deleting is refused while a block's PM still names it.

Input parameters:

- `action` (string)
- `dist`
- `init_used`
- `model_id` (string, required)
- `name`
- `params`: Up to three distribution parameters
- `repeats`

### `validate_model` (~94 tokens)

Validate a model for structural and parameter errors.

Returns {errors, warnings, groups, runnable}. `groups` collapses the
same rule firing on many elements into one row (rule, count, example
elements), which is what makes a 700-error model readable; `errors`
and `warnings` keep the verbatim per-element list. Always call this
before a run.

Input parameters:

- `model_id` (string, required)

Output parameters:

- `errors` (array)
- `groups` (array)
- `runnable` (boolean)
- `warnings` (array)

### `estimate_runtime` (~260 tokens)

Estimate wall-clock time for a simulation before running it.

Times a real pilot and extrapolates.  `workers` is taken into account
with a contention-aware speed-up: parallel replications on a large
model scale at roughly ``w / (1 + 0.25 (w - 1))``, not ``w`` (8 workers
measured 2.8x on an 8-core box).  Models of 1,500+ elements are piloted
at an 8,760 h horizon and the fit scaled, so the call always returns.

\`importance` overrides the model experiment's importance setting for the
estimate. When importance is on, `seconds_estimate` is the TOTAL time
until the whole operation finishes: the baseline batch plus one batch
per block per variant kind. `baseline_seconds` and
\`importance_seconds` show the split.

Returns {seconds_estimate, baseline_seconds, importance_seconds,
total_batches, importance_batches, importance_blocks, range,
per_run_ms, pilot_runs, estimate_seconds, cached, advice}.

Input parameters:

- `importance`
- `model_id` (string, required)
- `num_runs` (integer)
- `run_length`
- `workers`

Output parameters:

- `advice` (string)
- `baseline_seconds` (number)
- `cached` (boolean)
- `estimate_seconds` (number)
- `importance_batches` (integer)
- `importance_blocks` (integer)
- `importance_mode` (string)
- `importance_seconds` (number)
- `per_run_ms` (number)
- `pilot_runs` (integer)
- `range` (object)
- `seconds_estimate` (number)
- `setup_s` (number)
- `total_batches` (integer)

### `simulate` (~684 tokens)

Run a simulation and return structured results.

\`hierarchy` (a name or id) runs just that hierarchy on its own, its input
always available -- useful for checking part of a model. This is a port
extension, not a Raptor feature: the result carries an `isolation` object
(hazards at the boundary, the treatment applied to each) and must not be
read as a whole-model figure. `treatments` is `{deps: up|system|
refuse, pools: full|share|refuse, streams: keep|separate, items: {...}}`;
call `extract_hierarchy` with `report_only` first to see the hazards.

Long runs send MCP progress notifications about once a second and can
be cancelled by the client (the engine stops between replications).

Returns {run_id, model_id, model_hash, runs, requested_runs, truncated,
wall_seconds, summary, warnings, caveats}; `summary` has each metric
with min/mean/max/stdev/sem. With capacity analysis on, `summary` adds
Units_Produced, Mean_Flow, Mean_Capacity, Capacity_Weighted_Availability
(Mean_Flow / FlowGenerated: the delivered fraction, where Availability
only means some flow got through) and Lost_Flow.  `caveats` is the model-validation subset
of `warnings` (surfaced separately so a client can show them first).
The Markdown body is a table with units in the headers.

The model is **validated first**; structural errors are returned with
the issue list instead of running.  The run is estimated first and
refused with a clear message if the estimate exceeds `timeout_s`,
unless `confirm_long_run=true`.  `workers > 1` is faster but not
bit-identical to a sequential batch.  Leave `workers` out and a large
model (500+ blocks, events and nodes) run 20+ times uses several cores
(half the machine's, at most 8); the result reports the `workers` used
and `workers_auto`. Pass `workers=1` for the sequential numbers. This is
a server default only: the desktop app and the command line are unchanged.

\`seed` (optional, non-negative) re-rolls the batch reproducibly;
leaving it unset keeps the engine's fixed…

Input parameters:

- `collect_events` (boolean)
- `confirm_long_run` (boolean)
- `date`
- `hierarchy`
- `importance`
- `model_id` (string, required)
- `num_runs`
- `run_length`
- `seed`
- `timeout_s` (number)
- `treatments`
- `workers`

Output parameters:

- `caveats` (array)
- `display_unit` (string)
- `estimate_seconds` (number)
- `estimated_wall_seconds` (null|number)
- `isolation` (object)
- `model_hash` (string)
- `model_id` (string)
- `requested_runs` (integer)
- `run_id` (string)
- `runs` (integer)
- `summary` (object)
- `total_seconds` (number)
- `truncated` (boolean)
- `units` (string)
- `wall_seconds` (number)
- `warnings` (array)
- `workers` (integer)
- `workers_auto` (boolean)

### `start_run` (~287 tokens)

Start a simulation in the background and return a job id immediately.

Same arguments as `simulate`, minus the hierarchy/isolation and
importance options. Use this for batches that outlive one tool call (a
100-replication, 20-year run is tens of minutes): the engine runs on a
daemon thread, `run_status` reports progress, `cancel_run` stops it
between replications, and the completed run is an ordinary workspace run
(`run_id`) whose results `get_run_results`, `get_table` and `get_report`
can read.

The model is validated and the batch size checked *before* the job
starts, so a structural error is reported here rather than asynchronously.

\`workers` defaults as it does for `simulate`: a large model run 20+ times
uses several cores unless you pass `workers=1`.

Returns {job_id, state, phase, setup, requested_runs, ...}. `run_status`
shows `phase` (`setting_up` while a big model is being prepared, then
\`simulating`) and `setup` progress, so a long quiet start is not a hang.

Input parameters:

- `collect_events` (boolean)
- `model_id` (string, required)
- `num_runs`
- `run_length`
- `seed`
- `timeout_s` (number)
- `workers`

### `run_status` (~207 tokens)

Progress of a job started with `start_run`.

Returns {job_id, model_id, state, phase, setup, completed_runs,
requested_runs, percent, elapsed_s, wall_seconds, run_id, summary,
error}. `state` is queued, running, done, cancelled or failed. `phase`
adds what a running job is doing: `setting_up` while a large model is
prepared (`setup` then has `{stage, done, total, percent}`; it can take
minutes before the first replication, and `completed_runs` stays 0), then
\`simulating`. Small models skip straight to `simulating` and `setup` is
null. `run_id` is set once the
batch has produced a run, and `summary` then carries the mean of each
metric. Jobs are in-process only -- after a server restart the run is
still in the workspace, the job id is not.

Input parameters:

- `job_id` (string, required)

### `cancel_run` (~89 tokens)

Ask a background job to stop (checked between replications, and
while a large model is still being set up).

The replications already completed are kept and become a run, exactly
as when `simulate` is cut short: the result is marked truncated. Returns
the job snapshot; poll `run_status` until `state` is `cancelled`.

Input parameters:

- `job_id` (string, required)

### `get_run_results` (~118 tokens)

One completed run as a single assessable bundle.

Reads what the run wrote: the metric means and per-replication figures,
the capacity summary, and the weakest elements (`weak_link`). Use it to
judge a run without paging through `get_report`, and `get_table` /
\`export_tables` (with the same `run_id`) when you need a whole table or
a workbook.

\`top` caps the weak-link list (1..100).

Input parameters:

- `run_id` (string, required)
- `top` (integer)

### `get_report` (~156 tokens)

Get a paginated report section from a completed run.

section: 'final_results', 'end_of_run', 'events', 'capacity', 'cost',
'weak_link', 'importance' (only when the run asked for it) or
'availability_plot'.  `run_number` selects one
replication where the section is per-run.  format: 'markdown'
(default) or 'raw'.  Returns {lines, total, next_offset, markdown};
\`lines` never exceeds `limit` (≤200).

Input parameters:

- `format` (string)
- `limit` (integer)
- `offset` (integer)
- `run_id` (string, required)
- `run_number`
- `section` (string)

Output parameters:

- `lines` (array)
- `next_offset` (integer|null)
- `section` (string)
- `total` (integer)

### `single_point_failures` (~189 tokens)

Find single-point failures in the model.

A single point failure is an element whose failure alone takes the
system down, found as Raptor does (by failing each element in Failure
Effects View, so dependencies and cold standby count). Returns
{spofs, count, total, offset, next_offset, elapsed_seconds, cached};
each spof is {kind, id, name, crit} plus `phase` for a phased model.

\`limit` pages the response (default 100, use 0 for all); the full row
set is still computed and cached by model content, so later pages and
repeated calls are instant. `workers > 1` parallelises the candidate
tests on an unphased model.

Input parameters:

- `limit` (integer)
- `model_id` (string, required)
- `offset` (integer)
- `workers` (integer)

Output parameters:

- `cached` (boolean)
- `count` (integer)
- `elapsed_seconds` (number)
- `next_offset` (integer|null)
- `offset` (integer)
- `spofs` (array)
- `total` (integer)

### `failure_effects` (~241 tokens)

Run failure-effects analysis (Raptor workbook Chapter 10).

Fails and/or repairs the listed blocks or events and reports the resulting
system status and the Failure Effects text (failed, induced, idle and PM
components, down and degraded nodes). A phased model starts in phase 1;
\`phase` moves it to another phase first. Returns {system_status, affected,
log, effects, criticality, phase}, plus, for a model with capacity
analysis, `capacity`: {flow_generated, delivered, lost,
delivered_fraction, system_capacity, nodes: [{id, name, flow,
capacity}]} -- the flow the state delivers, as capacity analysis would
record it (Raptor 7's view shows no flow).

Each call is an **independent scenario starting from the all-good
state**: it does not accumulate failures across calls.  To test
several simultaneous failures, name them all in one `fail` list; a
\`repair` can only undo a failure named in the same call.

Input parameters:

- `fail`
- `model_id` (string, required)
- `phase`
- `repair`

Output parameters:

- `affected` (array)
- `capacity` (object)
- `criticality` (number)
- `effects` (object)
- `log` (array)
- `phase` (integer|null)
- `system_status` (string)

### `failure_effects_batch` (~254 tokens)

Run many failure-effects scenarios in one call, on one model set-up.

\`scenarios` is a list of `{"fail": [...], "repair": [...], "phase": N}`
(every key optional), up to 2000. Each scenario is independent and starts
from the all-good state, exactly as a separate `failure_effects` call
would; the model is only set up once, so hundreds of combinations on a
large model take seconds, not one set-up each.

Returns {model_id, count, failed, scenarios}. `scenarios[i]` has the
scenario's `index`, `fail` and `repair`, then either its result
({system_status, affected, effects, criticality, phase}, `capacity` for
a model with capacity analysis, plus the `log` text when `include_log`
is true) or `{"error": ...}` when that scenario
could not be applied (an unknown or already-failed element, a bad
phase). A bad scenario does not stop the others. `failed` counts them.

Input parameters:

- `include_log` (boolean)
- `model_id` (string, required)
- `scenarios` (array, required)

Output parameters:

- `count` (integer)
- `failed` (integer)
- `model_id` (string)
- `scenarios` (array)

### `compare_models` (~146 tokens)

Compare 2–5 models or runs side by side.

Returns per-metric values (`metrics`, in `models`/`labels` order),
deltas against the first entry, and which entry is best per metric,
plus a Markdown comparison table. `labels` gives each entry's model
name (a run's cached `model_name`), parallel to `models` -- `deltas`
keys and `best` values use the label, not the raw id, unless two
entries share a name, in which case that label gets its id appended
to stay unique.

Input parameters:

- `metrics`
- `models` (array, required)
- `num_runs` (integer)

Output parameters:

- `best` (object)
- `deltas` (object)
- `labels` (array)
- `markdown` (string)
- `metrics` (object)
- `models_or_runs` (array)

### `sweep_parameter` (~229 tokens)

Sweep a parameter across values and measure the impact.

Works on copies — the original model is untouched.  `field` may be a
friendly field or an indexed form like `repair_params[0]`.  Each row
carries the `run_id` and the `runs` count that produced it.  Returns
{rows, monotonic, all_rows_identical, warnings} plus a Markdown table;
\`all_rows_identical` is true when every row's metrics are bit-identical
(which cannot be told apart from "the value was never applied"), and
\`warnings` says so.  `monotonic` looks at the first swept metric and
treats a flat or single-row series as monotonic; `all_rows_identical`
needs at least two rows.  Concurrent sweeps are serialised, so two in
one batch both succeed.

Input parameters:

- `field` (string, required)
- `metrics`
- `model_id` (string, required)
- `num_runs` (integer)
- `selector` (string, required)
- `values` (array, required)

Output parameters:

- `all_rows_identical` (boolean)
- `field` (string)
- `markdown` (string)
- `monotonic` (boolean)
- `rows` (array)
- `selector` (string)
- `warnings` (array)

### `get_timeseries` (~126 tokens)

Get time-series data from a completed run, for the client to plot.

series: 'availability', 'capacity' or 'cost'.  runs: 'mean' (default),
'all', or a specific run number.  points: max samples after decimation
(default 100).

Returns {t, values, runs_included, decimated_from, envelope?} plus a
short Markdown preview.  No image is ever returned.

Input parameters:

- `points` (integer)
- `run_id` (string, required)
- `runs` (string)
- `series` (string)

Output parameters:

- `decimated_from` (integer)
- `envelope` (null|object)
- `runs_included` (integer)
- `series` (string)
- `t` (array)
- `values` (array|object)

### `extract_hierarchy` (~180 tokens)

Build a standalone model from one hierarchy (a port extension), or just
list what crosses its boundary.

The hierarchy's In node becomes Start and its Out node End, so its input is
always available. Hazards at the boundary -- elements following something
outside, spare and resource pools shared with outside blocks, random streams
shared with outside elements -- each get a documented treatment; see
\`simulate`'s `treatments`. With `report_only` nothing is created and the
hazards (with the treatment each would get) are returned. Otherwise the
extract is registered as a new model: returns {model_id, name, isolation}.
The source model is not changed.

Input parameters:

- `hierarchy` (required)
- `model_id` (string, required)
- `name`
- `report_only` (boolean)
- `treatments`

Output parameters:

- `count` (integer)
- `hazards` (array)
- `isolation` (object)
- `model_id` (string)
- `name` (string)

### `describe_metrics` (~266 tokens)

List the result metrics, and optionally show one run's values.

Without `run_id`, returns the name and units of every metric RAPTOR
reports: availability, reliability, conditional reliability,
dependability, MTBDE/MDT, MTBM/MTBMs/MTBMu, MMT/MMTs/MMTu, system and
component failures, and green/yellow/red percentages, plus, for a run
with capacity analysis, units produced, mean flow and capacity,
capacity-weighted availability (mean flow / flow generated) and lost
flow. **Definitions and
equations are not repeated here** -- call `read_doc('formulas')` for
them; that page is the single source. With `run_id`, also returns that
run's sample count, min/mean/max, standard deviation and standard
error. `model_id` is optional and only checks that the run belongs to
that model.

These are the engine's existing outputs; no analytical or alternative
solution method is applied. Use `get_report` or `get_table` for the
underlying data. Censored MTBDE/MTBM values are lower bounds and are
marked with `>` in the Markdown view.

Input parameters:

- `model_id`
- `run_id`

Output parameters:

- `isolation` (object)
- `metrics` (array)
- `model_hash` (string)
- `model_id` (string)
- `notes` (array)
- `run_id` (string)
- `runs` (integer)
- `units` (string)

### `analytical_analysis` (~385 tokens)

Solve the static subset of a model exactly.

\**Reliability mode** is non-repairable: `R(t)`, density, hazard, MTTF,
BX life, conditional reliability and exact importance. **Availability
mode** is steady state for independent components with perfect repair:
\`A_i = MTTF_i / (MTTF_i + MTTR_i)` and exact availability importance.

The solver supports series, parallel, mixed, k-of-n, bridge networks and
hierarchies. It refuses PM, spares, resources, dependencies, standby,
phases and non-Bernoulli events; use `simulate` for those. `at` defaults
to the model's time-terminated run length. `tau` adds the conditional
reliability of a further mission of that length after `at`.
\`ignore_repairs` (same as `ignore=["repairs"]`) gives the first-failure
reliability of a repairable model.

The default solver limits are 5,000 blocks/events and 1,000,000 BDD
nodes. `max_variables` and `max_bdd_nodes` override them for a large but
tractable model. Raising a limit does not guarantee a solution: the BDD
can still exceed it, and the solver refuses rather than approximate.

A long solve sends MCP progress notifications about once a second while
the BDD and importance measures are built.

Input parameters:

- `at`
- `bx`
- `equation` (boolean)
- `ignore`
- `ignore_repairs` (boolean)
- `importance` (boolean)
- `max_bdd_nodes`
- `max_variables`
- `mode` (string)
- `model_id` (string, required)
- `phase`
- `standby`
- `tau`
- `times`

Output parameters:

- `applicable` (boolean)
- `assumptions` (array)
- `availability` (number|null)
- `bdd_nodes` (integer)
- `bx` (object)
- `components` (array)
- `equation` (string|null)
- `hazard` (number|null)
- `importance` (array)
- `mode` (string)
- `model` (string)
- `mttf` (number|null)
- `pdf` (number|null)
- `reliability` (number|null)
- `schema` (string)
- `scope` (object)
- `time` (number|null)
- `unavailability` (number|null)
- `units` (string)
- `warnings` (array)

### `list_tables` (~198 tokens)

List the tables that can be read or exported.

Two groups: **Inputs** (the model's data: blocks, nodes, events,
hierarchies, links, spare pools, resources, triggers, phases, system
settings) and **Results** (a run's report sections, per-run figures and
capacity samples). Pass `run_id` (from `simulate`) to include a run's
result tables; without it only the inputs are listed and the result
tables are shown as unavailable.

Returns {tables: [{key, title, group, rows, available, reason,
default}]}. `key` is what `get_table` and `export_tables` take, e.g.
'inputs.blocks' or 'results.summary'. `default` is false for the long
ones (event log, end-of-run report, interim cost, all block columns).

Input parameters:

- `model_id` (string, required)
- `run_id`

Output parameters:

- `model_id` (string)
- `run_id` (null|string)
- `tables` (array)

### `get_table` (~162 tokens)

Read one table: its column headers and a page of rows.

\`table` is a key from `list_tables` (e.g. 'inputs.blocks',
'results.summary'; result tables need `run_id`). Rows are returned as
lists in header order, as the model file holds them (text such as
"12.500000") or as numbers for computed tables. `limit` is at most 200;
use `offset` and `next_offset` to page. Returns {key, title, group,
headers, rows, total_rows, next_offset, notes}.

Input parameters:

- `limit` (integer)
- `model_id` (string, required)
- `offset` (integer)
- `run_id`
- `table` (string, required)

Output parameters:

- `display_unit` (string)
- `group` (string)
- `headers` (array)
- `key` (string)
- `next_offset` (integer|null)
- `notes` (array)
- `rows` (array)
- `title` (string)
- `total_rows` (integer)
- `units` (string)

### `export_tables` (~231 tokens)

Write tables to files: CSV, text, or one spreadsheet.

\`tables`: keys from `list_tables`; omit for the default set (all input
tables except the all-columns block table, plus every result table
except the long ones). Result tables need `run_id`.

\`formats` (default ['xlsx']): 'csv' one .csv per table; 'txt' one
fixed-width .txt per table; 'txt_report' ONE combined text report;
'xlsx' ONE workbook with a Run Info tab and a tab per table, real
numeric cells (the End-of-Run and interim cost reports go beside it as
.txt files). Any combination.

\`folder` is inside the workspace (relative paths resolve against its
root); default `Tables/<model>` (or `Tables/<model>_<run_id>`).
Existing files of the same names are replaced.

Returns {folder, tables, files: [path], resource_links}.

Input parameters:

- `folder`
- `formats`
- `model_id` (string, required)
- `run_id`
- `tables`

### `render_diagram_svg` (~302 tokens)

Draw one level of the diagram as SVG text, without Qt.

For checking layout, connections and annotations when `render_diagram`
(a PNG of the GUI canvas) is unavailable, or when SVG text is easier to
read than an image. It draws blocks, nodes (S/E, HI/HO, k/n), events,
hierarchy ovals, links with arrowheads and annotations at the GUI's
scene positions; it does not measure fonts (long names are shortened,
the full name is in each element's `<title>`) or show state colours.

\`level` is 0 for the top level or a **hierarchy id** (not a depth).
\`zoom_to` frames the named elements on that level, padded by `margin`
scene units; `highlight` outlines them. A selector is a name, a bare id
or `"Kind:id"`.

Returns the SVG in the text result (up to 200 KB; larger drawings are
only written to the file) and structured {svg_path, width, height,
elements_drawn, elements_in_view, links_drawn, annotations_drawn,
highlighted, inline}. The file is written to renders/ in the workspace.

Input parameters:

- `highlight`
- `include_annotations` (boolean)
- `level` (integer)
- `margin` (number)
- `model_id` (string, required)
- `theme` (string)
- `zoom_to`

Output parameters:

- `annotations_drawn` (integer)
- `elements_drawn` (integer)
- `elements_in_view` (integer)
- `height` (integer)
- `highlighted` (array)
- `inline` (boolean)
- `level` (integer)
- `links_drawn` (integer)
- `renderer` (string)
- `resource_link` (string)
- `svg_path` (string)
- `width` (integer)
- `zoom_to` (array)

### `render_diagram` (~451 tokens)

Render the model diagram as a PNG image.

\`level` is a **hierarchy id**, not a depth index: 0 renders the whole
model (home level); to render inside a hierarchy pass its id, e.g. the
hierarchy `id=900` is `level=900` (read hierarchy ids from
\`describe_model`).  Passing `1`/`2` is a common mistake and reports
the available ids.

\`zoom_to` frames a **close-up** on one or more named/id'd elements
(block/node/event/hierarchy) **within the current `level`** (it does
not change which level is shown), padded by `margin` scene units — the
way to render a large model legibly in sections.  A selector is a
name, a bare id, or `"Kind:id"` (e.g. `"Block:3"`); a selector that
matches nothing, or a bare id matching two element kinds, is an error.
The structured result carries `px_per_unit`, `effective_label_px` and
\`legible` (a heuristic: `effective_label_px >= 7`), so a caller can
tell whether the text is readable and zoom in further if not.

Returns image content plus structured {width, height, elements_drawn,
elements_in_view, px_per_unit, effective_label_px, legible, png_path,
resource_link}.  `highlight` outlines named elements (e.g. the single
points of failure).  `theme` is 'light' or 'dark'.
\`include_annotations` draws the model's text/rect/ellipse/arrow
annotations (default true).

This is the only RAPTOR tool that returns a picture: results are always
Markdown/data.  The PNG is capped at 1600 px on the long edge and is
also written to renders/ in the workspace.

Input parameters:

- `highlight`
- `include_annotations` (boolean)
- `level` (integer)
- `margin` (number)
- `max_px` (integer)
- `model_id` (string, required)
- `theme` (string)
- `zoom_to`

## Diagnostics

Captured diagnostic sections: Provenance, Install scripts, Dependencies. The full working is on the page: https://verifymcp.io/servers/openpowershift-raptor-studio/raptor-studio-mcp#diagnostics

## Score history

- 2026-11-05: 65
- 2026-11-04: 79
- 2026-11-03: 79
- 2026-11-02: 63
- 2026-11-01: 78
- 2026-10-31: 77
- 2026-10-30: 77
- 2026-10-29: 76
- 2026-10-28: 76

## Common questions

### What is the Raptor Studio MCP server?

Raptor Studio is an MCP server listed in the public MCP registry as io.github.OpenPowerShift/raptor-studio. Build, validate and simulate reliability block diagrams (RAM analysis) from an AI assistant. This page covers its PyPI package (raptor-studio-mcp).

### Is the Raptor Studio MCP server safe to use?

Raptor Studio scores 65 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 5 November 2026. Its build provenance is signed and verified. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the Raptor Studio MCP server expose?

Raptor Studio exposes 67 tools: ping, list_distributions, list_models, open_model, create_model, and 62 more. Their descriptions and schemas cost roughly 15,378 tokens of context every time the server is loaded.

### Is the Raptor Studio MCP server still maintained?

Raptor Studio is still listed as active in the MCP registry. We last reached this channel on 5 November 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

### What licence is the Raptor Studio MCP server under?

Raptor Studio declares the LicenseRef-BAPL-1.0 AND Apache-2.0 licence, which is not on the OSI-approved list. Read the terms before using it at work, and note this covers the source only, not the cost of any service it calls.

### What did VerifyMCP test on the Raptor Studio MCP server?

VerifyMCP ran 15 checks on Raptor Studio, covering Known CVEs, Install scripts, Dependency health, Source repository and 11 more. One item could not be completed, a gap on our side rather than a finding about Raptor Studio: Malware scan. Endpoint Security and Transport & Reachability do not apply to a PyPI package, so they were not tested. The score does not measure everything, and the scoring documentation lists what it leaves out.

## Links

- PyPI project: https://pypi.org/project/raptor-studio-mcp/
- Socket report: https://socket.dev/pypi/package/raptor-studio-mcp
- Repository: https://github.com/openpowershift/RaptorStudio
- Website: https://github.com/openpowershift/RaptorStudio/blob/main/docs/MCP.md
- Changelog RSS feed: https://verifymcp.io/servers/openpowershift-raptor-studio/raptor-studio-mcp.xml
- Changelog JSON feed: https://verifymcp.io/servers/openpowershift-raptor-studio/raptor-studio-mcp/changelog.json
- Trust report (JSON): https://verifymcp.io/servers/openpowershift-raptor-studio/raptor-studio-mcp.json
- HTML version of this page: https://verifymcp.io/servers/openpowershift-raptor-studio/raptor-studio-mcp
