{
  "openapi": "3.1.0",
  "info": {
    "title": "XenolithGraph MCP Tools",
    "version": "1.0.0",
    "description": "Function-calling catalog mirroring every MCP tool exposed by `@xenolithengine/graph-mcp-server`. Use this if your agent framework reads OpenAPI but not MCP directly.",
    "contact": {
      "url": "https://github.com/XenolithEngine/xenolith-graph"
    },
    "license": {
      "name": "MIT",
      "url": "https://opensource.org/licenses/MIT"
    }
  },
  "servers": [
    {
      "url": "ws://127.0.0.1:7777",
      "description": "Local MCP WebSocket bridge."
    }
  ],
  "paths": {
    "/tools/list_node_types": {
      "post": {
        "operationId": "list_node_types",
        "summary": "List every node type registered in the editor with its pins (direction/label/type)",
        "description": "List every node type registered in the editor with its pins (direction/label/type). ALWAYS call this before add_node and connect_pins so you can use the real type names and pin labels — otherwise pins will not match.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/get_graph": {
      "post": {
        "operationId": "get_graph",
        "summary": "Return the current graph as xenolith",
        "description": "Return the current graph as xenolith.v1 JSON (nodes, edges, comments). Read-only snapshot.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/add_node": {
      "post": {
        "operationId": "add_node",
        "summary": "Insert a node of the given type",
        "description": "Insert a node of the given type. Coordinates are OPTIONAL: if omitted the editor drops it just to the right of the existing graph (or at the origin if empty). Prefer adding all nodes without coordinates, then calling auto_layout once to tidy the whole picture — the LLM has no idea about node sizes/spacing, so manual coords almost always overlap.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "description": "Node type as listed by list_node_types (e.g. \"Source\", \"Filter\", \"Output\")."
                  },
                  "x": {
                    "type": "number",
                    "description": "Optional world-space X. Omit unless the user explicitly asked for a position."
                  },
                  "y": {
                    "type": "number",
                    "description": "Optional world-space Y. Omit unless the user explicitly asked for a position."
                  },
                  "state": {
                    "type": "object",
                    "additionalProperties": {},
                    "description": "Optional initial state map (widget values etc)."
                  }
                },
                "required": [
                  "type"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/connect_pins": {
      "post": {
        "operationId": "connect_pins",
        "summary": "Connect an output pin (from) to a compatible input pin (to)",
        "description": "Connect an output pin (from) to a compatible input pin (to). Pin types must match (float→float, object→object). The `pin` field accepts the pin LABEL (\"Output\", \"In\") as returned by list_node_types, OR a numeric index (\"0\", \"1\"), OR the literal \"in\"/\"out\" for simple single-pin nodes — pick whatever is easiest. Never invent uuids. On error the response lists the available pins so you can retry.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "from": {
                    "type": "object",
                    "properties": {
                      "node": {
                        "type": "string"
                      },
                      "pin": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "number"
                          }
                        ],
                        "description": "Pin label, index, or \"out\"."
                      }
                    },
                    "required": [
                      "node",
                      "pin"
                    ],
                    "additionalProperties": false
                  },
                  "to": {
                    "type": "object",
                    "properties": {
                      "node": {
                        "type": "string"
                      },
                      "pin": {
                        "anyOf": [
                          {
                            "type": "string"
                          },
                          {
                            "type": "number"
                          }
                        ],
                        "description": "Pin label, index, or \"in\"."
                      }
                    },
                    "required": [
                      "node",
                      "pin"
                    ],
                    "additionalProperties": false
                  }
                },
                "required": [
                  "from",
                  "to"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/fit_view": {
      "post": {
        "operationId": "fit_view",
        "summary": "Frame the whole graph (or a specific node subset) in the viewport with padding",
        "description": "Frame the whole graph (or a specific node subset) in the viewport with padding. No return value.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "nodeIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Optional subset to frame; default = entire graph."
                  },
                  "padding": {
                    "type": "number"
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/set_widget_value": {
      "post": {
        "operationId": "set_widget_value",
        "summary": "Set the value of a widget on a node (slider/number/toggle/combo/text/color/custom)",
        "description": "Set the value of a widget on a node (slider/number/toggle/combo/text/color/custom). Value type must match the widget type (number for slider/number, boolean for toggle, string for combo/text/color, arbitrary JSON for custom). Undoable.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "nodeId": {
                    "type": "string"
                  },
                  "widget": {
                    "type": "string",
                    "description": "Widget id or key as listed in list_node_types pin/widget arrays."
                  },
                  "value": {}
                },
                "required": [
                  "nodeId",
                  "widget"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/remove_node": {
      "post": {
        "operationId": "remove_node",
        "summary": "Delete a node by id",
        "description": "Delete a node by id. Incident edges are removed automatically. Undoable.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "nodeId": {
                    "type": "string"
                  }
                },
                "required": [
                  "nodeId"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/disconnect_edge": {
      "post": {
        "operationId": "disconnect_edge",
        "summary": "Remove an edge by id (as returned by connect_pins or get_graph)",
        "description": "Remove an edge by id (as returned by connect_pins or get_graph).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "edgeId": {
                    "type": "string"
                  }
                },
                "required": [
                  "edgeId"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/create_macro": {
      "post": {
        "operationId": "create_macro",
        "summary": "Wrap a set of nodes into a collapsed Macro (group)",
        "description": "Wrap a set of nodes into a collapsed Macro (group). External edges touching the selection become proxy pins on the macro, so the macro behaves like a single node from the outside. Returns the macro id.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "nodeIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1
                  },
                  "title": {
                    "type": "string",
                    "description": "Display title; defaults to \"Macro\"."
                  }
                },
                "required": [
                  "nodeIds"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/expand_macro": {
      "post": {
        "operationId": "expand_macro",
        "summary": "Open a collapsed macro inline — members become visible again",
        "description": "Open a collapsed macro inline — members become visible again. Camera animates to fit the group automatically.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "macroId": {
                    "type": "string"
                  }
                },
                "required": [
                  "macroId"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/collapse_macro": {
      "post": {
        "operationId": "collapse_macro",
        "summary": "Re-collapse an expanded macro back into a single node with proxy pins",
        "description": "Re-collapse an expanded macro back into a single node with proxy pins.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "macroId": {
                    "type": "string"
                  }
                },
                "required": [
                  "macroId"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/auto_layout": {
      "post": {
        "operationId": "auto_layout",
        "summary": "Tidy the entire graph: re-position every node using a layered left-to-right layout based on edge topology (sources on the left, sinks on the right)",
        "description": "Tidy the entire graph: re-position every node using a layered left-to-right layout based on edge topology (sources on the left, sinks on the right). Call this AFTER adding nodes/edges so the result looks like a hand-arranged graph instead of overlapping boxes. Safe to call multiple times.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "direction": {
                    "type": "string",
                    "enum": [
                      "LR",
                      "TB"
                    ],
                    "description": "LR = left-to-right (default, good for pipelines), TB = top-to-bottom (good for tall trees)."
                  },
                  "spacing": {
                    "type": "number",
                    "description": "Pixels of padding between nodes (default 80)."
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/set_category_palette": {
      "post": {
        "operationId": "set_category_palette",
        "summary": "Recolour every node by category",
        "description": "Recolour every node by category. Pass a map of category id → CSS colour. Categories come from node schemas — the values returned by list_node_types under `category` (e.g. \"logic\", \"data\", \"macro\", \"utility\", or whatever the host registered). Each entry can be (a) a plain \"#RRGGBB\" / rgba(...) / CSS-named colour for a solid pill, or (b) an object `{start, end}` for a two-stop gradient header. Pass an empty object to reset to the theme defaults.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "palette": {
                    "type": "object",
                    "additionalProperties": {},
                    "description": "Map of category → colour. Examples: {\"logic\":\"#5b8def\",\"data\":\"#fcb400\"} for solid, or {\"logic\":{\"start\":\"#5b8def\",\"end\":\"#1e40af\"}} for a gradient."
                  }
                },
                "required": [
                  "palette"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/set_theme": {
      "post": {
        "operationId": "set_theme",
        "summary": "Override a subset of theme tokens (colours, gradients, geometry) at runtime",
        "description": "Override a subset of theme tokens (colours, gradients, geometry) at runtime. Pass a partial token object — anything you do not specify keeps its current value. The shape mirrors the editor's theme tokens; the most common subset is `color.accent`, `color.surface.*`, `color.text.*`, `geometry.node.radius`. Use this to make AI-driven recolours visible without uploading a whole new theme.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "tokens": {
                    "type": "object",
                    "additionalProperties": {},
                    "description": "Partial theme tokens to merge. Example: {\"color\":{\"accent\":\"#fcb400\",\"surface\":{\"canvas\":\"#0e0e0e\"}}}."
                  }
                },
                "required": [
                  "tokens"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/register_node_schema": {
      "post": {
        "operationId": "register_node_schema",
        "summary": "Define a brand-new node type the user can then instantiate via add_node",
        "description": "Define a brand-new node type the user can then instantiate via add_node. The schema must include `type` (unique identifier), `title`, optional `category`, and a `pins` array (each pin has `kind`, `direction`, `type`, optional `label`). Optional `widgets` array describes inline controls — for `combo` widgets you MUST pass a `values` array; `slider`/`number` accept `min`/`max`/`step`. If list_node_types already shows a type with the name you want, prefer add_node on the existing type rather than registering a variant with a different name.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "description": "Stable unique id (e.g. \"CSVRead\", \"InvoiceParser\"). Must not clash with existing types."
                  },
                  "title": {
                    "type": "string",
                    "description": "Display name shown in the node header (defaults to `type`)."
                  },
                  "category": {
                    "type": "string",
                    "description": "Category id — used PURELY as a colour tag for the pill (matches set_category_palette keys: \"logic\" / \"data\" / \"macro\" / \"utility\" / whatever is registered). Note: category \"macro\" is just a colour, NOT the special expandable Macro node type — these are unrelated."
                  },
                  "pins": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "kind": {
                          "type": "string",
                          "enum": [
                            "data",
                            "exec"
                          ],
                          "default": "data"
                        },
                        "direction": {
                          "type": "string",
                          "enum": [
                            "in",
                            "out"
                          ]
                        },
                        "type": {
                          "type": "string",
                          "description": "Pin data type — typically \"any\" / \"number\" / \"string\" / \"image\" / a custom domain type."
                        },
                        "label": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "direction",
                        "type"
                      ],
                      "additionalProperties": false
                    },
                    "minItems": 1
                  },
                  "widgets": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "key": {
                          "type": "string"
                        },
                        "type": {
                          "type": "string",
                          "enum": [
                            "number",
                            "slider",
                            "toggle",
                            "combo",
                            "text",
                            "color",
                            "button"
                          ]
                        },
                        "label": {
                          "type": "string"
                        },
                        "values": {
                          "type": "array",
                          "items": {
                            "anyOf": [
                              {
                                "type": "string"
                              },
                              {
                                "type": "object",
                                "properties": {
                                  "label": {
                                    "type": "string"
                                  },
                                  "value": {
                                    "type": "string"
                                  }
                                },
                                "required": [
                                  "label",
                                  "value"
                                ],
                                "additionalProperties": false
                              }
                            ]
                          },
                          "description": "REQUIRED for combo: list of options. Strings [\"gpt-4o\", \"gpt-4o-mini\"] or labelled {label, value} entries."
                        },
                        "min": {
                          "type": "number"
                        },
                        "max": {
                          "type": "number"
                        },
                        "step": {
                          "type": "number"
                        }
                      },
                      "required": [
                        "id",
                        "type"
                      ],
                      "additionalProperties": false
                    }
                  }
                },
                "required": [
                  "type",
                  "pins"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/select_nodes": {
      "post": {
        "operationId": "select_nodes",
        "summary": "Set the editor selection to exactly the listed node ids",
        "description": "Set the editor selection to exactly the listed node ids. Use to draw the user's attention to nodes the AI just changed, or to scope a follow-up operation visually.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "nodeIds": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "minItems": 1
                  }
                },
                "required": [
                  "nodeIds"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/clear_selection": {
      "post": {
        "operationId": "clear_selection",
        "summary": "Deselect every node",
        "description": "Deselect every node.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/dive_into_template": {
      "post": {
        "operationId": "dive_into_template",
        "summary": "Open a template-instance node to edit its inner subgraph",
        "description": "Open a template-instance node to edit its inner subgraph. Pass the instance node id (as listed by get_graph). The breadcrumb in the editor updates so the user can see where they are.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "instanceId": {
                    "type": "string"
                  }
                },
                "required": [
                  "instanceId"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/dive_out": {
      "post": {
        "operationId": "dive_out",
        "summary": "Pop out of the current subgraph back to its parent",
        "description": "Pop out of the current subgraph back to its parent. Optional `toDepth` jumps multiple levels at once (0 = back to Root).",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "toDepth": {
                    "type": "integer",
                    "minimum": 0
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/find_nodes": {
      "post": {
        "operationId": "find_nodes",
        "summary": "Search the current graph for nodes matching a predicate",
        "description": "Search the current graph for nodes matching a predicate. Returns ids + types + labels. Combine fields with AND. Use this instead of pulling the whole graph when you only need a subset.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "type": {
                    "type": "string",
                    "description": "Match by node type (exact)."
                  },
                  "category": {
                    "type": "string",
                    "description": "Match by category."
                  },
                  "titleContains": {
                    "type": "string",
                    "description": "Substring match against the node title (case-insensitive)."
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/describe_node": {
      "post": {
        "operationId": "describe_node",
        "summary": "Return a detailed snapshot of one node: type, title, category, position, all pins (with live connections), and all widgets (with current values)",
        "description": "Return a detailed snapshot of one node: type, title, category, position, all pins (with live connections), and all widgets (with current values). Use before reasoning about a node — get_graph returns a thinner shape.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "nodeId": {
                    "type": "string"
                  }
                },
                "required": [
                  "nodeId"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/screenshot": {
      "post": {
        "operationId": "screenshot",
        "summary": "Render the entire graph (NOT just the viewport — every node, padded) to a base64-encoded PNG/JPEG",
        "description": "Render the entire graph (NOT just the viewport — every node, padded) to a base64-encoded PNG/JPEG. Use to verify what the user is seeing after a series of edits. The response is large — only call when you need to \"see\" the result.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "format": {
                    "type": "string",
                    "enum": [
                      "png",
                      "jpeg"
                    ],
                    "description": "Default png."
                  },
                  "scale": {
                    "type": "number",
                    "description": "1 = native, 2 = retina (default)."
                  },
                  "padding": {
                    "type": "number",
                    "description": "World-space padding around the bounds (default 48)."
                  }
                },
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/node_screenshot": {
      "post": {
        "operationId": "node_screenshot",
        "summary": "Render a single node's current view (with its widget values, status ring, the lot) to a base64-encoded image",
        "description": "Render a single node's current view (with its widget values, status ring, the lot) to a base64-encoded image. Much smaller than full `screenshot`. Use when you want to inspect or show ONE node — e.g. \"did my set_widget_value land?\".",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "nodeId": {
                    "type": "string"
                  },
                  "format": {
                    "type": "string",
                    "enum": [
                      "png",
                      "jpeg"
                    ]
                  },
                  "scale": {
                    "type": "number",
                    "description": "Default 2 (retina)."
                  }
                },
                "required": [
                  "nodeId"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/list_recipes": {
      "post": {
        "operationId": "list_recipes",
        "summary": "Return the named subgraph templates the editor knows about",
        "description": "Return the named subgraph templates the editor knows about. Each entry has `id`, `title`, `description`, `category`, and `requires` (the schema types the recipe assumes exist). Use this BEFORE instantiate_recipe so you know which recipes are available and what types you may need to register_node_schema first.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {},
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    },
    "/tools/instantiate_recipe": {
      "post": {
        "operationId": "instantiate_recipe",
        "summary": "Drop a named recipe into the current graph in one call",
        "description": "Drop a named recipe into the current graph in one call. Creates every node + every edge, returns the map of recipe-local id → real node id (so you can describe_node / connect_pins to additional nodes after). If the recipe needs schemas that aren't registered, the call fails with a list of missing types — register them with register_node_schema and retry. Nodes are stacked at the origin; call auto_layout afterwards to tidy.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "id": {
                    "type": "string",
                    "description": "Recipe id from list_recipes (e.g. \"linear-float\", \"branching-pipeline\")."
                  },
                  "x": {
                    "type": "number",
                    "description": "Optional origin X (world space). Defaults to 0."
                  },
                  "y": {
                    "type": "number",
                    "description": "Optional origin Y. Defaults to 0."
                  }
                },
                "required": [
                  "id"
                ],
                "additionalProperties": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Tool result (shape depends on the tool)."
          }
        },
        "tags": [
          "mcp"
        ]
      }
    }
  }
}