Relative — MCP Interpreter
An MCP server that lets an LLM (Claude Code,Claude Desktop, any MCP host) act as a third interpreter for theRelative service — alongsiderelative-interpreter-list and relative-interpreter-graph, which are browserUIs for a human. This one exposes a model as tools instead of buttons.
See DESIGN.md for why it is shaped the way it is.
Run
npm install
RELATIVE_SERVICE_URL=http://localhost:4000/api npm start
Needs a running relative service (see that repo's README.md).RELATIVE_SERVICE_URL defaults to http://localhost:4000/api.
Register it as a local stdio server. For Claude Code, in .mcp.json:
{
"mcpServers": {
"relative": {
"command": "npx",
"args": ["tsx", "/path/to/relative-interpreter-mcp/src/main.ts"],
"env": { "RELATIVE_SERVICE_URL": "http://localhost:4000/api" }
}
}
}
What it gives the model
Reads return a compact outline, not JSON — names as identifiers, links asarrows, submodels summarised to a line:
Model "Switch Circuit" · 4 nodes, 3 links, 1 view · rev 0
Scope: (root)
Nodes:
battery const boolean = true out out:boolean
switch var boolean = false in in:boolean out out:boolean
circuit fn python runtimes/python/examples/circuit.py [onUpdate] in powered:boolean,closed:boolean out lit:boolean
bulb var boolean = false in in:boolean out out:boolean
Links:
battery.out -> circuit.powered [power flow] battery powers circuit
switch.out -> circuit.closed [power flow] switch closes circuit
circuit.lit -> bulb.in [power flow] circuit lights bulb
Views:
power flow (3 links)
One edit_model tool takes many steps, applied as a single undoableoperation via the service's /batch endpoint, with later steps able toreference what earlier ones create:
{ "steps": [
{ "action": "addValueNode", "input": { "name": "Switch", "mutability": "variable",
"initialValue": { "type": "boolean", "value": false } } },
{ "action": "addValueNode", "input": { "name": "Bulb", "mutability": "variable",
"initialValue": { "type": "boolean", "value": false } } },
{ "action": "addView", "input": { "name": "Power" } },
{ "action": "addLinkWithEndpoints", "input": { "name": "sw-to-bulb",
"endpointA": "Switch.out", "endpointB": "Bulb.in",
"direction": "forward", "views": ["Power"] } }
] }
References are names, and bad ones are refused. The service's state layeris total, so a mutation aimed at something that does not exist silentlysucceeds; every reference is resolved and checked here first:
Step 1 (updateNode): No node "NoSuchNode" in this scope.
Available: Switch#4e64975c, Bulb#159d3b8a
Names are not unique in Relative, so an ambiguous one is an error offeringname#uuidprefix handles — the same handles reads print.
Staleness is reported, not assumed. The server consumes the service's SSEstream, pushes resources/updated to subscribed hosts, and prefixes any readwhose revision moved since the caller last saw it:
[changed outside this conversation since you last looked: rev 3 -> 4,
views 0->1. The view below is current.]
Tools
| Group | Tools |
|---|---|
| Navigation | list_models, select_model, create_model, import_model, delete_model, get_scope, enter_submodel, exit_submodel |
| Read | get_model, get_element, get_validation, get_script_source |
| Edit | edit_model, link_external_submodel |
| Execution & history | run_node, undo, redo, list_snapshots, save_snapshot, restore_snapshot |
Resources: relative://models, relative://models/{id} (subscribable).