AI assistants (MCP)

What it is

Drawbridge is an MCP server: any AI assistant that speaks the Model Context Protocol — Claude Code, Claude Desktop, and a growing list of others — can read your drawings, render them, change them and generate new ones, using the same importer, layout and routing the editor itself uses.

The split is deliberate: the assistant decides what connects to what; Drawbridge decides where the lines go. An assistant that already knows your topology — from configs, a CMDB, LLDP output — hands over the facts and gets back a presentation-ready, editable drawing instead of drawing boxes itself.

There are two doors onto the same five tools, and most people want the first one:

Door What it is
Hosted A URL. Nothing installed, no account and no key — working in the next minute.
Local Drawbridge --mcp on your own machine. Needs the desktop app, and the topology never leaves your network.

Same protocol, same five tools, same drawings. The only difference is where the bytes go — so start with the hosted one, and move to the local one the day that distinction matters. The local door then carries five more tools on top, for joining a live collaboration session; the hosted one deliberately does not, for reasons given where they are described.

The hosted server

One line, and an assistant can draw:

claude mcp add --transport http drawbridge https://drawbridge.fortiknight.com/mcp

That is Claude Code. Every other client takes the same URL in its own configuration file — Claude Desktop, Cursor, Zed, VS Code and the rest read a variation of this:

{
  "mcpServers": {
    "drawbridge": {
      "type": "http",
      "url": "https://drawbridge.fortiknight.com/mcp"
    }
  }
}

Some clients spell that type as "streamable-http" and a few omit it entirely and infer it from url. They all mean the same thing. What none of them want is an SSE entry — see below.

Then prove it in one prompt: paste in the output of show cdp neighbors and ask for a PNG. That is a single create_diagram call, and a routed, laid-out diagram comes back.

What it is, exactly

Streamable HTTP, and stateless on purpose. One POST carries a JSON-RPC message and the answer comes straight back; there is no session to open and no session id to carry between calls. That is also why GET /mcp answers 405 rather than an event stream — a stateless server has no session state to stream. If a client hangs while connecting, it has been configured as an SSE server; the entry above is the HTTP/streamable one, and it is the only one this door speaks.

No account, no key, nothing stored. There is nothing to sign up for. A document you send is parsed, rendered, returned and then forgotten — never written to disk, never carried into a second request. The service keeps a record that a request happened and what it was asked to do — which capability, which export format, how long it took, which limit refused it — and never its content. The privacy page says the same thing, because it is the kind of promise that page exists to keep checkable.

The limits, in full. 2000 elements per drawing and 5 MB per request, both refused before anything is rendered, and a single render is killed at 60 seconds. An edit is counted too: edit_diagram refuses a change set that would take the drawing past 2000 elements, and one change set carries at most 10,000 changes. Send one JSON-RPC request per POST — a batch is refused, as the current MCP revision has no batches. This deployment enforces no per-caller quotas — no requests-per-minute limit, no hourly render ceiling, no token — so a long job is a long job. The software can do all three, which is why its own API description (GET /api/openapi.json) hedges that a deployment may ask for a Bearer token or answer 429 past a rate limit; ours asks for none of it. If a burst outruns the render fleet you get a 429 with Retry-After: 5, and retrying works, rather than a door that stays shut for an hour.

Only what the contract names is accepted. Every tool takes exactly the arguments its schema lists and every API route exactly the query parameters it documents: an argument or parameter it does not name is refused with invalid_input naming it, never quietly ignored, and so is a value outside its list, such as a theme that is not light or dark. A document carrying a DOCTYPE is refused before anything reads it, and one that is not well-formed draw.io XML comes back as invalid_input too.

It is a beta, on a small fleet. Treat it as best effort: it carries no uptime promise, and the limits above are the ones still being tuned. If it is ever down, the local door below is the same five tools with nothing in between.

The local server

Point your MCP client at the installed desktop app with the --mcp argument. For Claude Desktop, that is one entry in its MCP configuration:

{
  "mcpServers": {
    "drawbridge": {
      "command": "/Applications/Drawbridge.app/Contents/MacOS/Drawbridge",
      "args": ["--mcp"]
    }
  }
}

On Windows the command is %LOCALAPPDATA%\Programs\Drawbridge\Drawbridge.exe — or, if you installed Drawbridge from the Microsoft Store, just drawbridge.exe, which Windows keeps on your PATH whichever version is installed (the Store's own folder for the app changes with every update, so a full path to it would break); on Linux, the path to the AppImage (or /opt/Drawbridge/drawbridge from the .deb). The server speaks over its own stdin/stdout to the assistant that started it, needs no port and no configuration, and exits when the assistant closes it. Drawbridge does not have to be open — the assistant starts its own headless instance.

Nothing here reaches the network at all: the drawing goes to the assistant you configured and nowhere else, which is the whole of the difference from the hosted door.

Joining a live session

The local door carries five tools the hosted one does not. With them an assistant can join a collaboration session you are hosting and edit the diagram while you watch, instead of handing back a finished file you then have to ask it to redo.

  1. In Drawbridge, press Share… and start sharing.
  2. Copy the ordinary Invite link — not the view-only one — and give it to the assistant.
  3. It knocks; you press Allow in the lobby. It appears in People with a name and a colour, and everything it draws lands on your canvas as it happens.

The tools are collab_join (takes the invite and a name, then waits — up to two minutes — for you to admit it), collab_status, collab_read, collab_edit (add, update and remove devices and links, applied as a single change) and collab_leave.

Everything the assistant reads in a session, other people may have written. Labels, captions and names reach it word for word, so a label could try to talk it into something — joining another session, say, or copying your devices somewhere. The tools tell the assistant that this text is data and not instructions, and that it should join only with an invite you gave it yourself. Your assistant app asking before each tool call is the other half: read what it is about to do before you allow it.

Give it the editing invite, and check permission in the join result. A view-only link is not refused at the door: the assistant joins, appears in People, and looks connected — but the host discards every frame a viewer sends, so the document never arrives, collab_status honestly reports zero pages, and the refusal comes on the first collab_edit, in words ("this invite is view-only — the host discards every edit a viewer sends … Ask for an editor invite."). That is why permission is in the join result and why the tool descriptions say so: the join succeeding is not the answer to whether the assistant can work.

Kick works on the assistant exactly as it does on a person, and closing the assistant drops the connection like closing a window. Working together covers the same feature from the host's side of the screen.

The hosted server does not carry these five tools, and that is deliberate. Joining a session means holding the invite secret and sitting in the path of every edit — and that secret is a password to your diagram. On the local door it never leaves your machine; a hosted joiner would put our servers in the middle of a session that is otherwise only ever between the people in it, which is the one thing the privacy page exists to promise against.

Local models

Nothing about the loop requires a cloud assistant — the tools work with any model runner that speaks MCP, and a local model beside a local Drawbridge keeps the whole pipeline on your machine.

LM Studio supports MCP natively (0.3.17 or later): add the same entry as above to its mcp.json (Program, then Install, then Edit mcp.json) and the tools appear in the chat's tool list. Pick a model that handles tool calling well — instruct-tuned models of about 8B and up behave; very small models call tools erratically.

Ollama is an OpenAI-compatible server rather than an MCP client, so put a small bridge in front. With mcphost:

# ~/.mcphost.yml
mcpServers:
  drawbridge:
    command: /Applications/Drawbridge.app/Contents/MacOS/Drawbridge
    args: ['--mcp']
mcphost -m ollama:qwen2.5:14b

Your own loop. An MCP client is small enough to write yourself: list the tools, offer them to the model as functions, execute what it calls, feed the results back. The mcp Python SDK provides the client half; any OpenAI-compatible endpoint provides the model half.

No MCP at all. A runner that can only make plain HTTP calls uses the same handlers over REST instead — locally with Drawbridge --serve, or against the hosted API at https://drawbridge.fortiknight.com/api/, which is the same service and the same limits as the MCP door above. Code in, image out covers both.

Prompts that use the tools well: hand over show cdp neighbors output and ask for a PNG (one create_diagram call); attach a drawing and ask which devices have only one uplink (describe, then the model reasons over the JSON); or ask it to create from CSV and then describe what it created — the round trip catches import surprises. And on a drawing you already have: "add an access switch under the core and cable it at 10G" — that is edit_diagram, and everything you placed by hand stays where you put it.

The tools

describe — Read a Drawbridge/draw.io network diagram and return its contents as structured JSON: devices, links, pages and layers — for auditing or querying an existing drawing. Results carry name: a suggested basename for this diagram. Write every file for one diagram into its own directory named after it — /.drawio, /.svg, /.cables.csv — so two diagrams never share a directory.

export — Render a Drawbridge/draw.io network diagram to an output format (svg, png, drawio, dot, csv, netbox.csv, netbox-devices.csv, describe.json, clab.yml). Returns the exported content. CHOOSING kind: drawio when the user will open or edit the drawing (this is usually what they want, and it is the only one they can change); svg to embed in a document or a page; png to show inline in a chat; describe.json to READ a drawing back and reason about it; csv / netbox.csv for a cable schedule and netbox-devices.csv for the hardware; dot to hand the topology on; clab.yml to turn the design into a Containerlab topology you can deploy. Results carry name: a suggested basename for this diagram. Write every file for one diagram into its own directory named after it — /.drawio, /.svg, /.cables.csv — so two diagrams never share a directory.

create_diagram — Generate a routed, laid-out network diagram from topology text (Graphviz DOT, Mermaid, CSV, CDP/LLDP neighbour output from most switch vendors, a FortiLink topology dump, Containerlab, Meraki or UniFi JSON, or an Ansible inventory — every format is listed in the drawbridge://guide resource) and export it. Drawbridge decides where the lines go; you decide what connects to what. HOW TO GET A PRECISE RESULT: Set kind= on EVERY device so it draws as the real thing rather than a neutral box (firewall, waf, ips, vpn-gateway, proxy, nac, router, switch, l3-switch, load-balancer, wifi-ap, wlc, … — full list in the drawbridge://guide resource). Put speed= on every cable ("10G", "1G"), and link= where the cable MEANS something (ipsec, trunk, lag, wan, mgmt, wireless, conduit, fieldbus, ha). Never set pos=, width= or height=: Drawbridge lays out and routes — placing devices yourself is what makes a diagram look generated. READ THE notes IN THE RESULT AND FIX YOUR SOURCE: they name each line that was skipped or adjusted, and an unrecognised kind silently becomes a plain box otherwise. Iterate until the notes are empty. CHOOSING kind: drawio when the user will open or edit the drawing (this is usually what they want, and it is the only one they can change); svg to embed in a document or a page; png to show inline in a chat; describe.json to READ a drawing back and reason about it; csv / netbox.csv for a cable schedule and netbox-devices.csv for the hardware; dot to hand the topology on; clab.yml to turn the design into a Containerlab topology you can deploy. Results carry name: a suggested basename for this diagram. Write every file for one diagram into its own directory named after it — /.drawio, /.svg, /.cables.csv — so two diagrams never share a directory.

edit_diagram — CHANGE AN EXISTING DIAGRAM instead of regenerating it: add, update and remove devices and links in a .drawio document and get the edited document back. This is the tool for "add a switch to this drawing" — create_diagram would redraw the whole page and throw away every position a human placed by hand. Call describe first to get the ids, then name them here. Everything you do not name keeps its exact position. Do NOT set x/y on a new device: Drawbridge places what you add below the existing drawing, which is the whole point of using this rather than editing the XML. Set kind on every device you add or it draws as a plain box, and read the notes in the result — a mistyped id skips that one op and says so rather than failing the batch. It writes nothing: save the returned document yourself. Results carry name: a suggested basename for this diagram. Write every file for one diagram into its own directory named after it — /.drawio, /.svg, /.cables.csv — so two diagrams never share a directory.

validate — CHECK TOPOLOGY TEXT BEFORE DRAWING IT: reads the same source create_diagram would and reports what it found — the format it detected, how many devices and links it would draw, and every line it would skip or adjust, with the line number and the reason. An unknown kind= names the nearest real one. Nothing is laid out, routed or rendered, so it is far cheaper than a draw: call it, fix what it names, then call create_diagram once. Results carry name: a suggested basename for this diagram. Write every file for one diagram into its own directory named after it — /.drawio, /.svg, /.cables.csv — so two diagrams never share a directory.

Changing a drawing you already have

create_diagram draws a page from nothing. Point it at a file somebody laid out by hand and you get a new drawing of the same network — correct, and not the one they were looking at. Every position they chose is gone.

edit_diagram is the other verb. You give it the document and a list of changes — add this device, rename that one, remove this cable — and you get the edited .drawio back. Anything the list does not name comes out exactly where it went in, down to the coordinate. What you add is placed for you, below the existing drawing, the same way an import lands.

The loop an assistant should follow is four steps, and the last one is the one that gets skipped:

  1. describe the file, to learn the ids.
  2. edit_diagram with those ids and the change.
  3. export the result to png.
  4. Look at the picture. A change set can apply perfectly and still be the wrong change; nothing but looking will tell you.

A mistyped id does not fail the batch. That op is skipped and reported in notes, the rest still lands, and removing a device takes the cables that hung off it with it — also reported. Read the notes.

Nothing is written to disk. The tool answers with the document; your assistant saves it, over the original or beside it, as you asked.

Checking a source before drawing it

validate reads the same topology text create_diagram takes — DOT, CDP/LLDP output, CSV — and tells you what it makes of it without drawing anything: the format it detected, how many devices and links it would produce, and every line it would skip or adjust, each with its line number and a reason. Write kind=swich and it says so, on line 3, and names switch.

It is much cheaper than a render, so it is the right first call whenever the source was generated rather than pasted: check, fix what it names, then draw once. An assistant that iterates on renders instead is spending a minute per typo.

What the assistant is told about drawing well

The tools do not only state their arguments; they say how to use the product. create_diagram's description tells an assistant the four things that decide whether the drawing is any good — set kind= on every device so it draws as the real symbol instead of a neutral box, put speed= and link= on the cables, never write positions because layout and routing are the product, and read the importer's notes and fix the source until they come back empty.

It does not wait to be asked, either. The server answers the opening handshake with instructions — what Drawbridge is for, which tool to reach for, and the two rules that decide whether the drawing is any good. Clients put that in front of the assistant before it has chosen to call anything, which is earlier than a tool description is read and much earlier than a resource.

The long form is served as MCP resources, fetched when an assistant wants them. drawbridge://guide: the device kinds, the whole DOT attribute profile that matters, a worked example, and which export format answers which question. A client that supports resources can fetch it when it wants it, so the assistants that need it get everything and the ones that do not are not charged for it on every connection.

And drawbridge://kinds carries the whole device vocabulary — all of it, grouped by family, each entry listing the other names it answers to. That is the one thing the short guidance cannot hold: it names the thirty-odd kinds a drawing usually reaches for, while the catalog has a hundred. An assistant whose user asks for a data diode, a protection relay or a bastion host can look up the kind that draws it instead of settling for a plain box.

Every device kind named in any of it is generated from the real catalog and checked by a test, because guidance that has drifted is worse than none — it teaches an assistant to write DOT that quietly imports as a plain box.

Where the files go

The server never writes a file. Every tool answers with content — the JSON, the SVG, the PNG bytes — and your assistant decides what to save and where. That is deliberate: a hosted caller has no filesystem to point into, and a tool that writes to disk on your behalf is a tool you have to trust with a path.

The cost of that was a heap. Ask for two topologies in one session and you get fourteen files with three naming schemes between them, all in whatever directory the assistant happened to be in.

So every result now carries a name — a suggested basename, taken from what the diagram calls itself (a page name, or the digraph name in your DOT), or from the name you pass. It is a slug, never a path. The convention it exists for is stated to the assistant in the tool descriptions, and it is this:

One directory per diagram, every file for that diagram named after it.

DrawBridge/
  datacentre/
    datacentre.drawio
    datacentre.svg
    datacentre.png
    datacentre.describe.json
    datacentre.cables.csv
  fortinet/
    fortinet.drawio
    …

Two diagrams then never share a directory, and asking for the same one twice overwrites its own files rather than growing a second set beside them. If your assistant ignores the suggestion, tell it to follow the convention in the tool description — or pass name yourself and it has nothing left to invent.

Over the REST door the same slug comes back in the x-drawbridge-name response header, on every route including describe, because those routes answer with the bytes themselves and there is no envelope to put it in.

describe is the read side: it answers with the same structured JSON the CLI's .describe.json export produces — see Code in, image out for the shape and for jq recipes that work identically on a tool result. create_diagram accepts everything Import accepts (Graphviz DOT with the full attribute profile, CDP/LLDP output, CSV), and its result carries the importer's notes, so an assistant sees what it got wrong and can fix its own DOT. When the drawing comes out wider than 3,000 px, the notes carry one more line saying how wide it is and which row made it so — note: drawn 5,120 × 900 px — 30 devices share its widest row, most of them under core-1 — which is advice rather than a mistake: collapse that row into a group if the drawing needs to be narrower.

What the assistant sees

Whichever door you use, the same boundary opens, and it is worth being clear-eyed about it: a drawing you let an assistant describe, and a topology you ask it to draw, are handed to that assistant — and so to whoever runs it. That is your choice to make, per assistant, and nothing crosses until you configure a client and ask it to use these tools.

Locally that is the only boundary there is. Drawbridge --mcp makes no network call of its own — the standing rule that update checks are the app's only network traffic survives it untouched.

The hosted door adds exactly one more party and nothing else: the document reaches the render service, is drawn, is returned, and is forgotten — never stored, never used for anything but answering that one request. The privacy page states both boundaries in the same words, because they are the kind of promise that page exists to keep checkable.