{
  "server": "@xenolithengine/graph-mcp-server",
  "version": "0.0.0",
  "tools": [
    {
      "name": "list_node_types",
      "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.",
      "inputSchema": {
        "$ref": "#/definitions/list_node_types",
        "definitions": {
          "list_node_types": {
            "type": "object",
            "properties": {},
            "additionalProperties": false
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "get_graph",
      "description": "Return the current graph as xenolith.v1 JSON (nodes, edges, comments). Read-only snapshot.",
      "inputSchema": {
        "$ref": "#/definitions/get_graph",
        "definitions": {
          "get_graph": {
            "type": "object",
            "properties": {},
            "additionalProperties": false
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "add_node",
      "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.",
      "inputSchema": {
        "$ref": "#/definitions/add_node",
        "definitions": {
          "add_node": {
            "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
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "connect_pins",
      "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.",
      "inputSchema": {
        "$ref": "#/definitions/connect_pins",
        "definitions": {
          "connect_pins": {
            "type": "object",
            "properties": {
              "from": {
                "type": "object",
                "properties": {
                  "node": {
                    "type": "string"
                  },
                  "pin": {
                    "type": [
                      "string",
                      "number"
                    ],
                    "description": "Pin label, index, or \"out\"."
                  }
                },
                "required": [
                  "node",
                  "pin"
                ],
                "additionalProperties": false
              },
              "to": {
                "type": "object",
                "properties": {
                  "node": {
                    "type": "string"
                  },
                  "pin": {
                    "type": [
                      "string",
                      "number"
                    ],
                    "description": "Pin label, index, or \"in\"."
                  }
                },
                "required": [
                  "node",
                  "pin"
                ],
                "additionalProperties": false
              }
            },
            "required": [
              "from",
              "to"
            ],
            "additionalProperties": false
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "fit_view",
      "description": "Frame the whole graph (or a specific node subset) in the viewport with padding. No return value.",
      "inputSchema": {
        "$ref": "#/definitions/fit_view",
        "definitions": {
          "fit_view": {
            "type": "object",
            "properties": {
              "nodeIds": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "description": "Optional subset to frame; default = entire graph."
              },
              "padding": {
                "type": "number"
              }
            },
            "additionalProperties": false
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "set_widget_value",
      "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.",
      "inputSchema": {
        "$ref": "#/definitions/set_widget_value",
        "definitions": {
          "set_widget_value": {
            "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
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "remove_node",
      "description": "Delete a node by id. Incident edges are removed automatically. Undoable.",
      "inputSchema": {
        "$ref": "#/definitions/remove_node",
        "definitions": {
          "remove_node": {
            "type": "object",
            "properties": {
              "nodeId": {
                "type": "string"
              }
            },
            "required": [
              "nodeId"
            ],
            "additionalProperties": false
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "disconnect_edge",
      "description": "Remove an edge by id (as returned by connect_pins or get_graph).",
      "inputSchema": {
        "$ref": "#/definitions/disconnect_edge",
        "definitions": {
          "disconnect_edge": {
            "type": "object",
            "properties": {
              "edgeId": {
                "type": "string"
              }
            },
            "required": [
              "edgeId"
            ],
            "additionalProperties": false
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "create_macro",
      "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.",
      "inputSchema": {
        "$ref": "#/definitions/create_macro",
        "definitions": {
          "create_macro": {
            "type": "object",
            "properties": {
              "nodeIds": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "minItems": 1
              },
              "title": {
                "type": "string",
                "description": "Display title; defaults to \"Macro\"."
              }
            },
            "required": [
              "nodeIds"
            ],
            "additionalProperties": false
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "expand_macro",
      "description": "Open a collapsed macro inline — members become visible again. Camera animates to fit the group automatically.",
      "inputSchema": {
        "$ref": "#/definitions/expand_macro",
        "definitions": {
          "expand_macro": {
            "type": "object",
            "properties": {
              "macroId": {
                "type": "string"
              }
            },
            "required": [
              "macroId"
            ],
            "additionalProperties": false
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "collapse_macro",
      "description": "Re-collapse an expanded macro back into a single node with proxy pins.",
      "inputSchema": {
        "$ref": "#/definitions/collapse_macro",
        "definitions": {
          "collapse_macro": {
            "type": "object",
            "properties": {
              "macroId": {
                "type": "string"
              }
            },
            "required": [
              "macroId"
            ],
            "additionalProperties": false
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "auto_layout",
      "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.",
      "inputSchema": {
        "$ref": "#/definitions/auto_layout",
        "definitions": {
          "auto_layout": {
            "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
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "set_category_palette",
      "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.",
      "inputSchema": {
        "$ref": "#/definitions/set_category_palette",
        "definitions": {
          "set_category_palette": {
            "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
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "set_theme",
      "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.",
      "inputSchema": {
        "$ref": "#/definitions/set_theme",
        "definitions": {
          "set_theme": {
            "type": "object",
            "properties": {
              "tokens": {
                "type": "object",
                "additionalProperties": {},
                "description": "Partial theme tokens to merge. Example: {\"color\":{\"accent\":\"#fcb400\",\"surface\":{\"canvas\":\"#0e0e0e\"}}}."
              }
            },
            "required": [
              "tokens"
            ],
            "additionalProperties": false
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "register_node_schema",
      "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.",
      "inputSchema": {
        "$ref": "#/definitions/register_node_schema",
        "definitions": {
          "register_node_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
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "select_nodes",
      "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.",
      "inputSchema": {
        "$ref": "#/definitions/select_nodes",
        "definitions": {
          "select_nodes": {
            "type": "object",
            "properties": {
              "nodeIds": {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "minItems": 1
              }
            },
            "required": [
              "nodeIds"
            ],
            "additionalProperties": false
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "clear_selection",
      "description": "Deselect every node.",
      "inputSchema": {
        "$ref": "#/definitions/clear_selection",
        "definitions": {
          "clear_selection": {
            "type": "object",
            "properties": {},
            "additionalProperties": false
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "dive_into_template",
      "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.",
      "inputSchema": {
        "$ref": "#/definitions/dive_into_template",
        "definitions": {
          "dive_into_template": {
            "type": "object",
            "properties": {
              "instanceId": {
                "type": "string"
              }
            },
            "required": [
              "instanceId"
            ],
            "additionalProperties": false
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "dive_out",
      "description": "Pop out of the current subgraph back to its parent. Optional `toDepth` jumps multiple levels at once (0 = back to Root).",
      "inputSchema": {
        "$ref": "#/definitions/dive_out",
        "definitions": {
          "dive_out": {
            "type": "object",
            "properties": {
              "toDepth": {
                "type": "integer",
                "minimum": 0
              }
            },
            "additionalProperties": false
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "find_nodes",
      "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.",
      "inputSchema": {
        "$ref": "#/definitions/find_nodes",
        "definitions": {
          "find_nodes": {
            "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
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "describe_node",
      "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.",
      "inputSchema": {
        "$ref": "#/definitions/describe_node",
        "definitions": {
          "describe_node": {
            "type": "object",
            "properties": {
              "nodeId": {
                "type": "string"
              }
            },
            "required": [
              "nodeId"
            ],
            "additionalProperties": false
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "screenshot",
      "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.",
      "inputSchema": {
        "$ref": "#/definitions/screenshot",
        "definitions": {
          "screenshot": {
            "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
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "node_screenshot",
      "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?\".",
      "inputSchema": {
        "$ref": "#/definitions/node_screenshot",
        "definitions": {
          "node_screenshot": {
            "type": "object",
            "properties": {
              "nodeId": {
                "type": "string"
              },
              "format": {
                "type": "string",
                "enum": [
                  "png",
                  "jpeg"
                ]
              },
              "scale": {
                "type": "number",
                "description": "Default 2 (retina)."
              }
            },
            "required": [
              "nodeId"
            ],
            "additionalProperties": false
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "list_recipes",
      "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.",
      "inputSchema": {
        "$ref": "#/definitions/list_recipes",
        "definitions": {
          "list_recipes": {
            "type": "object",
            "properties": {},
            "additionalProperties": false
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    },
    {
      "name": "instantiate_recipe",
      "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.",
      "inputSchema": {
        "$ref": "#/definitions/instantiate_recipe",
        "definitions": {
          "instantiate_recipe": {
            "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
          }
        },
        "$schema": "http://json-schema.org/draft-07/schema#"
      }
    }
  ]
}