BondGraph MCP server
The BondGraph Studio MCP server lives at https://xn--lda.se/api/mcp. It is a JSON-RPC 2.0 endpoint that speaks the MCP Streamable HTTP transport. Every POST must include both headers:
Content-Type: application/json
Accept: application/json, text/event-streamAll ten tools below take JSON arguments and return a single text content item whose text field is a JSON string. The envelope is always:
{
"jsonrpc": "2.0",
"id": <your id>,
"result": {
"content": [
{ "type": "text", "text": "<JSON string — see each tool below>" }
]
}
}Decode the content[0].text field with JSON.parse (or jq -r '.result.content[0].text' on the command line) to get the structured payload shown in the response examples below.
Handshake
Every MCP host runs initialize and tools/list first:
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize",
"params":{"protocolVersion":"2025-06-18",
"capabilities":{},
"clientInfo":{"name":"curl","version":"1"}}}'
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'A complete round trip
The exchange below is the full, literal wire traffic for one tool call — request headers, request body, response headers and response body — so you can reproduce it byte for byte.
Request
POST /api/mcp HTTP/1.1
Host: xn--lda.se
Content-Type: application/json
Accept: application/json, text/event-stream
{
"jsonrpc": "2.0",
"id": 7,
"method": "tools/call",
"params": {
"name": "validate_bgif",
"arguments": {
"bgif": { "bgifVersion": "1.0.0", "components": [], "systems": [] }
}
}
}Response
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Access-Control-Allow-Origin: *
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"content": [
{
"type": "text",
"text": "{\"valid\":true,\"modelId\":\"empty\",\"modelName\":\"Untitled\",\"elementCount\":0,\"bondCount\":0,\"subsystemCount\":0}"
}
]
}
}Decoded payload
{
"valid": true,
"modelId": "empty",
"modelName": "Untitled",
"elementCount": 0,
"bondCount": 0,
"subsystemCount": 0
}Batching and notifications
The endpoint accepts a JSON array of requests and answers with an array of the responses that have an id. Requests without an id are notifications: they are executed and produce no response object. If a whole batch consists of notifications the server replies 202 Accepted with an empty body.
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '[{"jsonrpc":"2.0","id":1,"method":"ping"},
{"jsonrpc":"2.0","id":2,"method":"tools/list"}]'Error codes
Two different failure channels exist, and a correct client checks both.
1. Protocol errors — error instead of result
The request never reached a tool. The response carries no result field at all:
{
"jsonrpc": "2.0",
"id": 7,
"error": {
"code": -32602,
"message": "Unknown tool: simulat"
}
}| Code | HTTP | Meaning | How to fix |
|---|---|---|---|
-32700 | 400 | Parse error — the body was not valid JSON. | Check quoting/escaping; send Content-Type: application/json. |
-32600 | 200 | Invalid Request — no method field. | Send a well-formed JSON-RPC 2.0 object. |
-32601 | 200 | Method not found — unsupported JSON-RPC method. | Only initialize, notifications/initialized, ping, tools/list and tools/call exist. |
-32602 | 200 | Invalid params — params.name is not a known tool. | Call tools/list and use an exact tool name. |
-32000 | 405 | Method not allowed — you used GET or DELETE. | All MCP traffic is POST. OPTIONS returns 204 for CORS preflight. |
2. Tool errors — result.isError
The tool ran and threw (bad BGIF, unreachable URL, a solver failure). This is HTTP 200 with a normal result, flagged with isError: true and a plain-text message rather than a JSON payload:
{
"jsonrpc": "2.0",
"id": 7,
"result": {
"content": [
{ "type": "text", "text": "bond references unknown element id \"R7\"" }
],
"isError": true
}
}A third, softer channel exists inside the payload itself: several tools (validate_bgif, validate_library, validate_scene, check_units) report expected, non-exceptional problems as {"valid": false, "error": "…"} or a warnings array in an otherwise successful result. Treat those as data, not failures.
body.error → protocol problem; body.result.isError → tool threw; otherwise JSON.parse(body.result.content[0].text) and inspect valid / warnings.Machine-readable schemas (type generation)
Every tool publishes a JSON Schema 2020-12 pair — request input and response output — in one self-contained bundle, together with the JSON-RPC envelope and the error-code table above:
curl -sS https://xn--lda.se/api/public/mcp-schema.jsonAdd ?tool=simulate to fetch a single tool (plus the shared $defs and error codes). Structured tools also return their outputSchema inline from tools/list, so MCP hosts that support structured output pick it up automatically.
{
"tools": {
"simulate": {
"name": "simulate",
"input": { "type": "object", "required": ["bgif"], "properties": { … } },
"output": { "type": "object", "required": ["status", "time", "variables", "meta"], … },
"resultEncoding": "json",
"resultPath": "result.content[0].text"
}
},
"errorCodes": [ { "code": -32602, "name": "InvalidParams", "http": 200, … } ],
"$defs": { "JsonRpcRequest": …, "JsonRpcResponse": …, "ToolResult": … }
}resultEncoding tells a generator how to decode result.content[0].text: "json" for structured payloads, "text" for raw documents such as render_svg’s SVG. Generate types with any standard tool:
# TypeScript
curl -sS https://xn--lda.se/api/public/mcp-schema.json?tool=simulate \
| npx json-schema-to-typescript > simulate.d.ts
# Python
curl -sS https://xn--lda.se/api/public/mcp-schema.json?tool=simulate \
| datamodel-codegen --input-file-type jsonschema --output simulate.pyMinimal working client
About thirty lines is enough to talk to every tool on this page — no MCP SDK required.
TypeScript / JavaScript (fetch)
const ENDPOINT = "https://xn--lda.se/api/mcp";
let nextId = 1;
async function rpc(method: string, params?: unknown) {
const res = await fetch(ENDPOINT, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json, text/event-stream",
},
body: JSON.stringify({ jsonrpc: "2.0", id: nextId++, method, params }),
});
if (!res.ok) throw new Error(`HTTP ${res.status} ${await res.text()}`);
const body = await res.json();
if (body.error) throw new Error(`${body.error.code}: ${body.error.message}`);
return body.result;
}
/** Call a tool and return its decoded JSON payload. */
async function callTool(name: string, args: Record<string, unknown> = {}) {
const result = await rpc("tools/call", { name, arguments: args });
const text = result.content?.[0]?.text ?? "";
if (result.isError) throw new Error(`${name} failed: ${text}`);
try {
return JSON.parse(text);
} catch {
return text; // render_svg and export_* return raw text, not JSON
}
}
// --- usage -------------------------------------------------------------
await rpc("initialize", {
protocolVersion: "2025-06-18",
capabilities: {},
clientInfo: { name: "my-client", version: "1.0.0" },
});
const { tools } = await rpc("tools/list");
console.log(tools.map((t: { name: string }) => t.name));
const { bgif } = await callTool("get_example", { name: "RLC Circuit" });
const run = await callTool("simulate", {
bgif,
config: { endTime: 5, stepSize: 0.01, solver: "bdf2" },
format: "compact",
});
console.log(run.meta.sampleCount, Object.keys(run.variables));Python (standard library only)
import json, itertools, urllib.request
ENDPOINT = "https://xn--lda.se/api/mcp"
_ids = itertools.count(1)
def rpc(method, params=None):
payload = {"jsonrpc": "2.0", "id": next(_ids), "method": method}
if params is not None:
payload["params"] = params
req = urllib.request.Request(
ENDPOINT,
data=json.dumps(payload).encode(),
headers={
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
},
)
with urllib.request.urlopen(req) as res:
body = json.load(res)
if "error" in body:
raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
return body["result"]
def call_tool(name, **args):
result = rpc("tools/call", {"name": name, "arguments": args})
text = result["content"][0]["text"]
if result.get("isError"):
raise RuntimeError(f"{name} failed: {text}")
try:
return json.loads(text)
except json.JSONDecodeError:
return text
rpc("initialize", {"protocolVersion": "2025-06-18", "capabilities": {},
"clientInfo": {"name": "py", "version": "1.0.0"}})
print([t["name"] for t in rpc("tools/list")["tools"]])
model = call_tool("get_example", name="RLC Circuit")["bgif"]
run = call_tool("simulate", bgif=model,
config={"endTime": 5, "stepSize": 0.01, "solver": "bdf2"},
format="compact")
print(run["meta"]["sampleCount"], list(run["variables"]))Existing MCP hosts
Claude Desktop, Cursor, Codex and other hosts only need the URL — the server is public and takes no authentication:
{
"mcpServers": {
"bondgraph": {
"type": "http",
"url": "https://xn--lda.se/api/mcp"
}
}
}Simulation tools
simulate
Run a BGIF v1 model with the same solver the editor uses and return time-series results.
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
bgif | object | yes | BGIF v1 document, either bare ({bgifVersion,components,systems}) or the {bgif,layout} wrapper saved by the editor. |
config | object | no | Optional overrides: startTime, endTime, stepSize, solver ("bdf1"|"bdf2"), tolerance. |
format | "full"|"compact" | no | compact drops empty variable arrays from the response. |
JSON-RPC template (copyable)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "simulate",
"arguments": {
"bgif": "<BGIF document or {bgif,layout} wrapper>",
"config": {
"endTime": 5,
"stepSize": 0.01,
"solver": "bdf2"
},
"format": "compact"
}
}
}Example request
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":10,"method":"tools/call",
"params":{"name":"simulate","arguments":{
"bgif": {"bgifVersion":"1.0.0","components":[…],"systems":[…]},
"config": {"endTime":5,"stepSize":0.01,"solver":"bdf2"},
"format": "compact"
}}}'Example response (parsed content[0].text)
{
"status": "ok",
"error": null,
"time": [0, 0.01, 0.02, …, 5.00],
"variables": {
"e_C1": [0, 0.0099, 0.0197, …],
"f_I1": [0, 0.0001, 0.0004, …]
},
"meta": {
"modelId": "rlc",
"modelName": "RLC Circuit",
"variableCount": 2,
"sampleCount": 501,
"config": {"startTime":0,"endTime":5,"stepSize":0.01,"solver":"bdf2"},
"durationMs": 38
}
}validate_bgif
Parse a BGIF document and report whether it is structurally valid. Cheap; use before simulating large models.
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
bgif | object | yes | BGIF v1 document or {bgif,layout} wrapper. |
JSON-RPC template (copyable)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "validate_bgif",
"arguments": {
"bgif": "<BGIF document or {bgif,layout} wrapper>"
}
}
}Example request
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":11,"method":"tools/call",
"params":{"name":"validate_bgif","arguments":{"bgif":{"bgifVersion":"1.0.0","components":[],"systems":[]}}}}'Example response — valid
{
"valid": true,
"modelId": "empty",
"modelName": "Untitled",
"elementCount": 0,
"bondCount": 0,
"subsystemCount": 0
}Example response — invalid
{
"valid": false,
"error": "bond references unknown element id \"R7\""
}list_examples
List every built-in example model with its description, element count, and default simulation config.
Inputs
None — pass "arguments": {}.
JSON-RPC template (copyable)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_examples",
"arguments": {}
}
}Example response
{
"examples": [
{
"name": "RLC Circuit",
"description": "Series RLC driven by a step voltage source.",
"elementCount": 5,
"defaultSimulation": {"startTime":0,"endTime":5,"stepSize":0.01,"solver":"bdf2"}
},
{
"name": "DC Motor",
"description": "Armature + rotor + load with gyrator coupling.",
"elementCount": 8,
"defaultSimulation": {"startTime":0,"endTime":2,"stepSize":0.001,"solver":"bdf2"}
}
/* … */
]
}get_example
Return the full BGIF wrapper for a named example. Feed the bgif field straight into simulate.
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Example name as returned by list_examples (case sensitive). |
JSON-RPC template (copyable)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_example",
"arguments": {
"name": "<example name>"
}
}
}Example request
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":12,"method":"tools/call",
"params":{"name":"get_example","arguments":{"name":"RLC Circuit"}}}'Example response
{
"name": "RLC Circuit",
"bgif": {
"bgif": {
"bgifVersion": "1.0.0",
"components": [ /* SubsystemDef objects */ ],
"systems": [ /* root system with elements + bonds */ ]
},
"layout": { /* canvas positions */ }
},
"defaultSimulation": {"startTime":0,"endTime":5,"stepSize":0.01,"solver":"bdf2"}
}render_svg
Render a BGIF model as an SVG image styled exactly like the editor canvas.
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
bgif | object | yes | BGIF v1 document or {bgif,layout} wrapper. |
theme | "dark"|"light" | no | Color theme; defaults to dark. |
width | number | no | Output width in px (optional; auto from layout). |
height | number | no | Output height in px (optional). |
padding | number | no | Padding around the model in px, default 60. |
showPortLabels | boolean | no | Show port id labels on subsystem ports. |
JSON-RPC template (copyable)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "render_svg",
"arguments": {
"bgif": "<BGIF document or {bgif,layout} wrapper>",
"theme": "dark",
"padding": 60,
"showPortLabels": false
}
}
}Example request
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":13,"method":"tools/call",
"params":{"name":"render_svg","arguments":{
"bgif": {"bgifVersion":"1.0.0","components":[…],"systems":[…]},
"theme": "light"
}}}'Example response
The content[0].text field is raw SVG markup (<svg …>…</svg>) — extract it with jq -r '.result.content[0].text' > model.svg.
The same renderer is also exposed as a plain HTTP image endpoint at GET /api/public/render.svg?bgif=<urlencoded>(or POST with a JSON body for large models). Useful for <img src> embedding in markdown or docs.
Diagnostics, editing, calibration & symbolic export
These tools complement simulate and target three workflows: understanding a model (analyze_causality, check_units), changing it from an agent loop without resending the whole document (the model-editing primitives), and turning it into something else — a parameter sweep, a calibrated parameter set, a small-signal state-space model, a frequency response, or compilable Modelica source.
analyze_causality
Sequential Causality Assignment (SCAP). Returns per-bond causality, the chosen state variables, any algebraic loops, derivative-causality elements, an estimated DAE index, and an overall well-posedness verdict. validate_bgif accepts analyze:true to embed a condensed summary inline.
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
bgif | object | yes | BGIF document or { bgif, layout } wrapper. |
Example request
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":30,"method":"tools/call",
"params":{"name":"analyze_causality","arguments":{"bgif":<BGIF>}}}'check_units
Dimensional / domain type-check. Infers a physical domain (electrical, mechanical-translational, mechanical-rotational, fluid, thermal, magnetic, chemical) for every element and bond from labels, optional params.domain overrides, and optional <key>_unit strings (e.g. resistance_unit:"ohm", capacitance_unit:"J/K"). Reports per-bond effort/flow units, bond-domain conflicts, GY misuse, and parameter-unit mismatches. Advisory by default; pass strict:true to promote findings to errors.
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
bgif | object | yes | BGIF document or wrapper. |
strict | boolean | no | When true, promote advisory findings to errors and flip ok to false. |
Example request
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":31,"method":"tools/call",
"params":{"name":"check_units","arguments":{"bgif":<BGIF>,"strict":false}}}'parameter_sweep
Cartesian product of parameter axes over the simulator. Each axis is values:[…] or range:{min,max,n,scale:"linear"|"log"}. reduce picks a per-output scalar (final | max | min | absmax | rms | mean) or "timeseries" returns full traces. Capped at maxRuns (default 200).
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
bgif | object | yes | BGIF document or wrapper. |
parameters | object[] | yes | Array of {elementId,key,values?|range?} axes. |
outputs | string[] | yes | Variable names to record (e.g. q_C1, e_1_R1). |
reduce | string | no | final | max | min | absmax | rms | mean | "timeseries". |
config | object | no | Partial SimulationConfig overrides. |
maxRuns | number | no | Safety cap (default 200). |
Example request
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":32,"method":"tools/call",
"params":{"name":"parameter_sweep","arguments":{"bgif":<BGIF>,
"parameters":[{"elementId":"r1","key":"resistance","range":{"min":50,"max":200,"n":4}}],
"outputs":["q_C1"],
"reduce":"final"}}}'fit_parameters
Derivative-free Nelder-Mead calibration of free parameters to a target {t, series:{name:[…]}}. Supports per-series weights and per-parameter bounds. Returns fitted values and RMS residual.
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
bgif | object | yes | BGIF document or wrapper. |
target | object | yes | { t:number[], series:{ varName:number[] } } — observed trajectory. |
parameters | object[] | yes | Free parameters: {elementId,key,initial?,min?,max?}. |
weights | object | no | Optional per-series weights. |
config | object | no | Simulation config used for each candidate. |
maxIterations | number | no | Optimizer cap (default 200). |
Example request
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":33,"method":"tools/call",
"params":{"name":"fit_parameters","arguments":{"bgif":<BGIF>,
"target":{"t":[0,0.1,0.2],"series":{"q_C1":[0,0.5,0.9]}},
"parameters":[{"elementId":"r1","key":"resistance","initial":100,"min":1,"max":1000}]}}}'linearize
Extract a small-signal state-space {A, B?, C?, D?} around an operating point. operatingPoint can be "auto" (settle the model via a short simulation), "initial", or a user-supplied {stateName:value} map. State variables are q_*/p_* (bond-graph displacements / momenta). Pass inputs:[{elementId,key}] (source parameters) for B/D and outputs:[varName] (any DAE variable) for C/D. Assumes DAE index ≤ 1 — use analyze_causality to check.
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
bgif | object | yes | BGIF document or wrapper. |
operatingPoint | "auto" | "initial" | object | no | Where to linearize. Defaults to initial conditions. |
inputs | object[] | no | Source parameters to use as inputs — produces B and D. |
outputs | string[] | no | Variable names to use as outputs — produces C and D. |
perturbation | number | no | Finite-difference step (default 1e-6). |
Example request
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":34,"method":"tools/call",
"params":{"name":"linearize","arguments":{"bgif":<BGIF>,
"operatingPoint":"auto",
"inputs":[{"elementId":"se1","key":"effort","label":"V_in"}],
"outputs":["q_C1"]}}}'analyze_frequency
Same inputs as linearize plus an optional frequencies field (rad/s — either a list or {start,stop,points,scale:"log"|"linear"}). Returns poles with damping ratio and natural frequency, a stable verdict, the spectral abscissa, and — when both inputs and outputs are supplied — Bode magnitude (dB) and phase (deg) at each frequency.
Example request
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":35,"method":"tools/call",
"params":{"name":"analyze_frequency","arguments":{"bgif":<BGIF>,
"operatingPoint":"auto",
"inputs":[{"elementId":"se1","key":"effort","label":"V_in"}],
"outputs":["q_C1"],
"frequencies":{"start":0.1,"stop":1000,"points":40,"scale":"log"}}}}'export_equations
Symbolic equation export. form:'dae' returns a structured DAE — states with initials, algebraic vars, parameters with units, equations tagged source / junction-equal / junction-balance / constitutive / state-derivative. form:'ode' best-effort reduces to an explicit ODE by forward-substituting algebraic definitions and linearly solving single-unknown junction balances (so RC / RL / RLC-series reduce to plain der(q)=…); any irreducible loops are surfaced in unreducedAlgebraic. form:'modelica' emits compilable Modelica source — parameter docstrings, unit="…" attributes, start= on non-zero states, equations grouped by element, sanitized identifiers, and dialect→Modelica rewrites for PI, step, dead, **, log2, cbrt, round, and t→time. Subsystems are flattened automatically and names use the same alias resolution as the editor and simulator.
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
bgif | object | yes | BGIF document or wrapper. |
form | "dae" | "ode" | "modelica" | no | Output shape. Default "dae". |
modelName | string | no | Override the Modelica model identifier (sanitized). |
Example request — Modelica
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":36,"method":"tools/call",
"params":{"name":"export_equations","arguments":{"bgif":<BGIF>,"form":"modelica","modelName":"MyModel"}}}'Example response (excerpt) — Modelica
model MyModel
// Auto-generated from BondGraph Studio.
// ----- Parameters -----
// R1
parameter Real R_R1(unit="ohm") = 100.0 "resistance of R1";
// C1
parameter Real C_C1 = 0.001 "capacitance of C1";
// V_in
parameter Real E_V_in = 10.0 "effort source of V_in";
// ----- State variables -----
Real q_C1 "generalized displacement of C1";
// ----- Algebraic variables (bond efforts and flows) -----
Real e_V_in_1; Real f_V_in_1;
Real e_1_R1; Real f_1_R1;
Real e_1_C1; Real f_1_C1;
equation
// V_in
e_V_in_1 = E_V_in;
// 1-junction 1
f_1_R1 = f_V_in_1;
f_1_C1 = f_V_in_1;
0 = (-e_V_in_1) + e_1_R1 + e_1_C1;
// R1
e_1_R1 = R_R1 * f_1_R1;
// C1
der(q_C1) = f_1_C1;
e_1_C1 = q_C1 / C_C1;
end MyModel;Model-editing primitives
Round-trip editing tools — each takes a BGIF wrapper and returns the updated wrapper, so an agent can mutate a model across many turns without resending the whole document. Use apply_edits when you want several changes to land atomically (it rolls back on first failure).
| Field | Type | Required | Description |
|---|---|---|---|
add_element | tool | no | Insert a primitive (R/C/I/Se/Sf/TF/GY/0/1) or subsystem instance. |
remove_element | tool | no | Delete an element and every bond touching it. |
set_parameter | tool | no | Change a single params field on an element (including subsystem instance overrides). |
rename_element | tool | no | Change an element's label — alias-resolved variable names follow. |
connect | tool | no | Add a bond between two (element, port) pairs with an optional causal stroke. |
disconnect | tool | no | Remove a bond by id. |
apply_edits | tool | no | Run an ordered list of the above as a transaction. |
Example request — apply_edits
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":37,"method":"tools/call",
"params":{"name":"apply_edits","arguments":{"bgif":<BGIF>,
"edits":[
{"op":"set_parameter","elementId":"r1","key":"resistance","value":220},
{"op":"rename_element","elementId":"c1","label":"C_out"}
]}}}'3D scene authoring tools
Geometry is a first-class artefact next to the bond graph: preview_scene and animate_scene render and play it back, while these four tools let an agent discover the primitive catalogue, author or repair a scene, check it, and write it out as URDF, MJCF or the native JSON scene format. Every one of them accepts the same geometry sources — a scene (URDF/MJCF XML or JSON), a bgif model with optional geometryHints, or a built-in example bundle.
list_scene_primitives
List the geometric primitives available to the scene editor with their shape, default size vector, size-field meaning and default colour.
Example request
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":60,"method":"tools/call",
"params":{"name":"list_scene_primitives","arguments":{}}}'validate_scene
Structurally validate a scene: duplicate body/joint names, missing or cyclic parents, zero joint axes, inverted limits, empty or zero-sized geometry, meshes with no file reference.
Example request
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":61,"method":"tools/call",
"params":{"name":"validate_scene","arguments":{"example":"single_joint_arm"}}}'edit_scene
Apply a batch of declarative edit operations and return the edited scene, per-operation results and fresh validation warnings. Omit every geometry source to build a scene from scratch.
Operations
| Field | Type | Required | Description |
|---|---|---|---|
set_scene_name | op | no | Rename the scene. |
add_body | op | no | Add a body: name?, parent?, primitive?. |
remove_body | op | no | Remove a body and re-parent its children. |
rename_body | op | no | Rename a body (references follow). |
update_body | op | no | Patch parent, pos, rpy, axis, jointName, jointType, limit, variable. |
set_joint_type | op | no | fixed | revolute | prismatic, with sensible default limits. |
add_geometry | op | no | Append a primitive to a body. |
remove_geometry | op | no | Drop geometry by index. |
update_geometry | op | no | Patch shape, size, pos, rpy, color, mesh. |
Example request
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":62,"method":"tools/call",
"params":{"name":"edit_scene","arguments":{"ops":[
{"op":"set_scene_name","name":"crane"},
{"op":"add_body","name":"base","primitive":"plate"},
{"op":"add_body","name":"arm","parent":"base","primitive":"rod"},
{"op":"set_joint_type","name":"arm","jointType":"revolute"},
{"op":"update_body","name":"arm","patch":{"axis":[0,1,0],"jointName":"shoulder"}}
]}}}'export_scene
Serialise a scene to URDF (ROS), MJCF (MuJoCo) or the native JSON scene format, returning { format, filename, content, warnings }.
Example request
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":63,"method":"tools/call",
"params":{"name":"export_scene","arguments":{"example":"single_joint_arm","format":"urdf"}}}'Gazebo export (SDFormat)
export_sdf emits an SDFormat model that Gazebo (gz-sim) loads directly, so Gazebo itself becomes the simulator and viewer. It reuses the mechanical mapping of export_physics_xml — a mechanical I becomes a link, the junction it shares motion with becomes the joint DOF, a C on that junction becomes the joint spring, R becomes damping, and Se/Sf mark the actuated DOFs — and the same geometryHints. What SDF adds over URDF:
- Real inertia tensors computed from the resolved primitive and its mass (box, sphere, cylinder, capsule), with the
comOffsetwritten as the inertial pose. For a rotational DOF theIvalue is an inertia in kg·m² and replaces the geometry-derived component about the joint axis. - Joint dynamics from R elements: the resistance becomes
<damping>, while explicitfriction,coulomborstictionparameters on the same R become<friction>(N or N·m). The compliance becomes<spring_stiffness>natively — no controller workaround needed. - Gravity worked out from the graph itself: a constant effort source that is named like a weight, carries a
gparameter, or supplies roughly m·g becomes the world gravity, and every link is marked<gravity>true</gravity>. A model with no such source gets gravity switched off, so Gazebo never adds a load the bond graph never simulated. The same numbers appear in the Physics panel of the geometry view. - Contact surfaces: every collision carries
<surface><friction>coefficients, plus an optional restitution for bouncing contacts.
export_sdf
Export the mechanical structure as an SDFormat model for Gazebo, with computed inertials, joint damping/friction/spring from R and C elements, and contact surface friction.
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
bgif | object | no | Required. BGIF v1 document. |
sdfVersion | string | no | SDFormat version on the root element (default "1.9"). |
fixedBase | boolean | no | Emit a fixed joint from Gazebo's world to the base link (default true). |
baseLinkName | string | no | Name of the anchor link (default "base_link"). |
surfaceFriction | number | [mu, mu2] | no | Contact friction coefficients (default 1). |
restitution | number | no | Contact bounciness 0..1; omitted when unset. |
selfCollide | boolean | no | Allow links of this model to collide (default false). |
rotationalMass | number | no | Mass assigned to rotational links, whose I value is an inertia (default 1 kg). |
gravity | number | [x,y,z] | no | World gravity. Omit to infer it from weight-like effort sources; 0 marks the links gravity-free. |
geometryHints | array | no | Same per-body pose/shape hints as export_physics_xml; they drive the inertia tensor too. |
defaultGeometry | object | no | Hint applied to every body before its specific hint. |
bodySize / autoLayout / autoLayoutSpacing | number / boolean / number | no | Placeholder size and automatic chain spacing for unhinted bodies. |
Response
| Field | Type | Required | Description |
|---|---|---|---|
xml | string | no | SDFormat document text, ready to drop into a Gazebo model directory. |
filename | string | no | Suggested file name, e.g. "pendulum.sdf". |
links | array | no | { name, elementLabel, mass, inertia{ixx,iyy,izz}, com, inertiaSource }. |
joints | array | no | { name, type, parent, child, axis, damping, friction, springStiffness }. |
bodies | array | no | The full mechanical mapping, identical to export_physics_xml. |
gravity | object | no | { vector, magnitude, source: option|inferred|none, enabled, note, bodies[] } — the gravity the model already assumes. |
mapping / warnings | string[] | no | Human-readable mapping log and non-fatal diagnostics. |
Example request
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":64,"method":"tools/call",
"params":{"name":"export_sdf","arguments":{"bgif":<BGIF>,
"surfaceFriction":[0.9,0.9],
"restitution":0.4,
"geometryHints":[{"target":"arm","shape":"cylinder","size":[0.04,0.6],
"axis":[0,1,0],"pos":[0,0,1.0]}]}}}'Runnable Gazebo package
export_gazebo_model wraps the SDF in everything gz-sim needs to run it: the model directory (model.sdf, model.config), a world with the inferred gravity, the chosen physics engine and step size, the standard gz-sim plugins and a joint-state publisher, an optional URDF of the same mechanism, a README, the launch commands, and a parity table pairing each joint with the variable the 3D view animates so the Gazebo run can be compared against the bond graph run. The same package is browsable and downloadable on the Gazebo page.
export_gazebo_model
Build a runnable gz-sim package — model.sdf, model.config, world file, optional URDF, README, launch commands and a physics parity table.
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
bgif | object | no | Required. BGIF v1 document. |
packageName | string | no | Model directory name (default derived from the model name). |
worldName | string | no | World name and world file stem (default "bondgraph_world"). |
physicsEngine | "dart" | "bullet-featherstone" | "tpe" | no | Physics engine the world requests (default "dart"). |
stepSize | number | no | Max physics step size in seconds (default 0.001). |
groundPlane | boolean | no | Include a ground plane and sun in the world (default true). |
includeUrdf | boolean | no | Also emit model.urdf for ROS tooling (default true). |
paused | boolean | no | Start gz-sim paused. |
…export_sdf options | — | no | Every export_sdf option (gravity, geometryHints, surfaceFriction, …) is accepted and forwarded. |
Response
| Field | Type | Required | Description |
|---|---|---|---|
files | array | no | { path, language, description, contents } for every file in the package. |
commands | array | no | { label, command, description } — gz sim, gz sdf validation and ROS 2 bridging. |
parity | array | no | Per joint: mass, damping, friction, spring, the 3D-view variable and the gz joint-state topic to compare against. |
gravity / sdf / urdf | object | no | The resolved gravity plus the full export_sdf (and URDF) result. |
checks / warnings | string[] | no | What to verify after launching, and non-fatal diagnostics. |
Example request
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":65,"method":"tools/call",
"params":{"name":"export_gazebo_model","arguments":{"bgif":<BGIF>,"packageName":"pendulum","physicsEngine":"dart","stepSize":0.0005}}}'Library discovery & validation tools
Component libraries are bundled in the BGLIB v1 wire format. These five tools let an agent browse the libraries that ship with BondGraph Studio, fetch one a user linked, sanity-check a candidate file, and assemble a fresh library from raw components.
list_libraries
List the curated component libraries shipped with BondGraph Studio (Buildings & HVAC, Power & Energy, Automotive, Hydraulics, Data Center) with a per-component summary.
Inputs
None — pass "arguments": {}.
JSON-RPC template (copyable)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "list_libraries",
"arguments": {}
}
}Example request
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":20,"method":"tools/call",
"params":{"name":"list_libraries","arguments":{}}}'Example response
{
"bglibVersion": "1.0.0",
"libraries": [
{
"name": "Buildings & HVAC",
"description": "Thermal zones, HVAC, building envelopes",
"componentCount": 12,
"components": [
{
"id": "thermal-zone",
"name": "Thermal Zone",
"group": "Buildings",
"portCount": 2,
"description": "Single-capacitance lumped zone with envelope loss."
},
/* … */
]
},
{
"name": "Power & Energy",
"description": "Generators, transmission, storage, loads",
"componentCount": 9,
"components": [ /* … */ ]
}
/* Automotive, Hydraulics, Data Center … */
]
}get_library
Return the full BGLIB v1 document for a shipped library, ready to drop into the editor's Import Library → File dialog.
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
name | string | yes | Library name from list_libraries (e.g. "Hydraulics"). |
JSON-RPC template (copyable)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_library",
"arguments": {
"name": "<library name>"
}
}
}Example request
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":21,"method":"tools/call",
"params":{"name":"get_library","arguments":{"name":"Hydraulics"}}}'Example response
{
"bglibVersion": "1.0.0",
"name": "Hydraulics",
"version": "1.0.0",
"author": "BondGraph Studio",
"description": "Pumps, valves, cylinders, accumulators",
"license": "MIT",
"bgif": {
"bgifVersion": "1.0.0",
"components": [ /* SubsystemDef objects in BGIF form */ ],
"systems": []
}
}validate_library
Parse a candidate BGLIB v1 document (or a legacy BGIF library) and report whether it is valid, the metadata, and the component count — or the exact parser error.
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
library | object | yes | A BGLIB v1 document, or a BGIF document / wrapper containing components. |
JSON-RPC template (copyable)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "validate_library",
"arguments": {
"library": "<BGLIB v1 document or BGIF wrapper>"
}
}
}Example request
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":22,"method":"tools/call",
"params":{"name":"validate_library","arguments":{
"library": {
"bglibVersion": "1.0.0",
"name": "Acme Pneumatics",
"version": "0.3.1",
"author": "Acme Engineering",
"bgif": {"bgifVersion":"1.0.0","components":[…],"systems":[]}
}
}}}'Example response — valid
{
"valid": true,
"legacy": false,
"meta": {
"name": "Acme Pneumatics",
"version": "0.3.1",
"author": "Acme Engineering",
"description": null,
"license": null,
"homepage": null
},
"componentCount": 7
}Example response — invalid
{
"valid": false,
"error": "BGLIB.bgif.components[2]: missing required field \"ports\""
}Example response — legacy BGIF accepted
{
"valid": true,
"legacy": true,
"meta": {
"name": "imported.bgif",
"version": "0.0.0",
"author": null,
"description": "Imported from legacy BGIF document",
"license": null,
"homepage": null
},
"componentCount": 4
}fetch_library
Download a BGLIB v1 (or legacy BGIF) document from an HTTPS URL — GitHub raw, gist, S3, a personal site — and return its parsed metadata and component summary.
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
url | string (https://) | yes | HTTPS URL pointing to a .bglib.json / .bgif / .json file. |
summary | boolean | no | If true (default) return only metadata + component summary; if false also include the rebuilt BGLIB document. |
JSON-RPC template (copyable)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "fetch_library",
"arguments": {
"url": "<https URL to .bglib.json or .bgif>",
"summary": true
}
}
}Example request
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":23,"method":"tools/call",
"params":{"name":"fetch_library","arguments":{"url":"https://example.com/pumps.bglib.json"}}}'Example response (summary)
{
"meta": {
"name": "Centrifugal Pumps",
"version": "1.2.0",
"author": "Jane Doe",
"description": "Single- and multi-stage centrifugal pumps.",
"license": "Apache-2.0",
"homepage": "https://example.com/pumps"
},
"legacy": false,
"componentCount": 5,
"components": [
{
"id": "centrifugal-pump-1stage",
"name": "Single-stage centrifugal pump",
"group": "Pumps",
"portCount": 2,
"description": "Polynomial head curve, parameterised by rated flow + head."
}
/* … */
]
}Example response (summary: false)
{
"meta": { /* same as above */ },
"legacy": false,
"componentCount": 5,
"components": [ /* same summary */ ],
"document": {
"bglibVersion": "1.0.0",
"name": "Centrifugal Pumps",
"version": "1.2.0",
"author": "Jane Doe",
"bgif": { "bgifVersion": "1.0.0", "components": [ /* full */ ], "systems": [] }
}
}Example response — fetch error
{
"jsonrpc": "2.0",
"id": 23,
"error": {
"code": -32000,
"message": "fetch_library: HTTP 404 from https://example.com/pumps.bglib.json"
}
}build_library
Wrap one or more BGIF subsystem components into a fresh BGLIB v1 document with the given metadata. Use when an agent has authored components and wants to give the user a single shareable file.
Inputs
| Field | Type | Required | Description |
|---|---|---|---|
meta.name | string | yes | Human-readable library name. |
meta.version | string | no | Semver string; defaults to 1.0.0. |
meta.author | string | no | Author / maintainer name. |
meta.description | string | no | One-line description. |
meta.license | string | no | SPDX identifier, e.g. MIT. |
meta.homepage | string (https://) | no | Project homepage. |
components | object[] | yes | Array of BGIF SubsystemDef components. |
JSON-RPC template (copyable)
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "build_library",
"arguments": {
"meta": {
"name": "<library name>",
"version": "1.0.0",
"author": "<author>",
"license": "MIT"
},
"components": [
"<SubsystemDef object 1>",
"<SubsystemDef object 2>"
]
}
}
}Example request
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":24,"method":"tools/call",
"params":{"name":"build_library","arguments":{
"meta": {
"name": "My Pumps",
"version": "0.1.0",
"author": "Me",
"license": "MIT"
},
"components": [ /* SubsystemDef objects */ ]
}}}'Example response
{
"bglibVersion": "1.0.0",
"name": "My Pumps",
"version": "0.1.0",
"author": "Me",
"license": "MIT",
"bgif": {
"bgifVersion": "1.0.0",
"components": [ /* echoed back, normalised */ ],
"systems": []
}
}Example BGLIB payload
Below is a complete, copyable BGLIB v1 document containing a single component — a Thermal Zone subsystem. You can paste this directly into the validate_library or build_library tools, or save it as a .bglib.json file and import it into the editor via Import Library → File.
{
"bglibVersion": "1.0.0",
"name": "Demo Building Library",
"version": "1.0.0",
"author": "BondGraph Studio",
"description": "A minimal example library with one thermal-zone component.",
"license": "MIT",
"bgif": {
"bgifVersion": "1.0.0",
"components": [
{
"id": "bld_thermal_zone",
"library": "Demo Building Library",
"group": "Zones",
"name": "Thermal Zone",
"description": "Single-air-node thermal zone with lumped thermal capacitance. Ports: envelope (left), gains (right), adjacent (bottom).",
"ports": [
{ "id": "env", "side": "left", "offset": 0.5 },
{ "id": "gain", "side": "right", "offset": 0.5 },
{ "id": "adj", "side": "bottom", "offset": 0.5 }
],
"svgIcon": "<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect x="15" y="20" width="70" height="60" rx="4" fill="none" stroke="oklch(0.78 0.13 200)" stroke-width="2"/><text x="50" y="50" text-anchor="middle" dominant-baseline="central" font-family="monospace" font-size="14" fill="oklch(0.78 0.13 200)" font-weight="600">T_z</text></svg>",
"innerElements": [
{
"id": "j",
"type": "0",
"label": "0",
"x": 200,
"y": 160,
"width": 50,
"height": 50,
"ports": [
{ "id": "p1", "side": "top", "offset": 0.5 },
{ "id": "p2", "side": "right", "offset": 0.5 },
{ "id": "p3", "side": "bottom", "offset": 0.5 },
{ "id": "p4", "side": "left", "offset": 0.5 }
],
"params": {}
},
{
"id": "Cz",
"type": "C",
"label": "C_zone",
"x": 200,
"y": 60,
"width": 80,
"height": 50,
"ports": [
{ "id": "p1", "side": "left", "offset": 0.5 },
{ "id": "p2", "side": "right", "offset": 0.5 },
{ "id": "p3", "side": "top", "offset": 0.5 },
{ "id": "p4", "side": "bottom", "offset": 0.5 }
],
"params": { "capacitance": 5000000, "q0": 90000000 }
}
],
"innerBonds": [
{
"id": "b1",
"fromElementId": "j",
"fromPortId": "p1",
"toElementId": "Cz",
"toPortId": "p4",
"causalStroke": "none"
}
],
"portMap": {
"env": { "elementId": "j", "portId": "p4" },
"gain": { "elementId": "j", "portId": "p2" },
"adj": { "elementId": "j", "portId": "p3" }
}
}
],
"systems": []
}
}