One catalogue, two faces
klink's control surface has two faces: the Python client KLinkClient and the same tools exposed over MCP. Both come from the live plugin's method registry — no hand-maintained list that can drift. Every tool's function, parameters and examples are in the MCP Tool Reference; this page is about the interactive loop that ties them together.
from klink import KLinkClient
with KLinkClient() as c:
print([m["name"] for m in c.methods()["methods"]])
SEND selection memory — what "this area" means
The biggest friction in human + agent collaboration is "which area do you mean?". klink solves it with SEND: you select geometry in KLayout and click the plugin's SEND toolbar action (or an agent calls selection.send_context), and the selection is recorded as a stable id such as sel_0006 in session-scoped memory (.klink/sessions/<id>/interaction_context.jsonl). From then on "just sent", "this area", "that one" resolve to exact geometry, not a screenshot.
| Tool | Use |
|---|---|
interaction.selection.recent | Recent SENDs (default latest 5, by order, not age). |
interaction.selection.latest | The latest SEND. |
interaction.selection.get | Read an exact record by id. |
interaction.selection.label | Name/describe an important selection. |
interaction.context | Live selection + recent SEND memory together. |
selection.get is the live current selection; interaction.* is the durable memory of what was explicitly SENT. Because minutes of layout work can pass between a SEND and the message referring to it, memory is resolved by order/count, not age. interaction.context returns the current selection and recent SENDs together so the agent can tell which one the user means.
Two deliberate design choices: (1) explicit SEND rather than passively watching every click — only a selection the user actively sends becomes a sel_000N, filtering out exploratory-click and mis-select noise; (2) memory lives outside the plugin (on the bridge) — the plugin holds only "right-now" zero-layer facts (selection.get + the event stream), while session-scoped memory, id assignment and language resolution live in the MCP bridge's interaction.*, persisted per session to disk JSONL.
# user: select A → SEND, select B → SEND
interaction.selection.recent limit=2 # → [sel_0007(B), sel_0006(A)]
interaction.selection.label id="sel_0006" label="probe pad"
One agent, many KLayouts
Each KLayout window binds a port (the first free one in 8765–8799) and registers as a session. One MCP bridge can drive all of them at once — they are equal peers, no port has a "working" or "LVS" role; you always say which session you mean.
klink.session_list
klink.session_label session_id="klayout-8766" label="scratch" aliases=["test"]
klink.session_resolve query="scratch" # → klayout-8766
klink.session_use session_id="klayout-8766"
In practice: when a real working layout and a demo/test window are open at once, label each window first so the agent refers to "scratch" instead of a port number and destructive ops never hit the working tab.
Cross-session transfer — two-phase, confirmation-safe
Moving geometry between windows is two-phase: prepare first, dry-run on the target, then commit to actually write. That catches "wrong target" and "wrong package" before anything lands in the target window.
klink.transfer_prepare source_session="klayout-8765" target_session="klayout-8766" \
copy_mode="flat_selection" target_cell="IMPORTED" # dry-run built in
klink.transfer_commit package_id="pkg_0001"
copy_mode: flat_selection (default) flat-copies visible geometry without hierarchy; shallow_instance moves instance references — and if the target window lacks the child cell, it blocks the commit instead of silently making an empty shell. Nothing lands until commit, so a wrong target is caught at dry-run. Plus layer_map remapping and translate_um offset during transfer.
Port 8082: klive-compatible display, a full klive replacement
Besides RPC on 8765, the plugin runs a klive-protocol-compatible display server on 127.0.0.1:8082. It is a drop-in replacement for the original klive: any gdsfactory / external script that hard-codes localhost:8082 — especially Component.show() — pushes layout into KLayout with no changes. You do not install klive separately.
| Capability | Detail |
|---|---|
| Protocol compatible | Byte-compatible with klive 0.4.1 request/response ({"gds":…, "keep_position":…, "libraries":…, "technology":…, "lyrdb":…, "l2n":…}) and replies {"version":"0.4.1", "type":"open"|"reload", …}. |
c.show() out of the box | gdsfactory's Component.show() pushes straight into KLayout, reloads if open, keep_position keeps your view. |
| More than GDS | The klive protocol also carries lyrdb (DRC markers) and l2n (netlist extraction), shown in the browser. |
| Multi-session forward | Single window loads via pya directly; with many windows, 8082 is a fixed entrypoint forwarding to the registered target session — pick it with klink.session_set_klive_target / session.mark_klive_target. |
| Never disturbs RPC | If 8082 can't bind, only display is affected; klink RPC on 8765 is unaffected. |
import gdsfactory as gf
c = gf.components.mzi()
c.show() # via 127.0.0.1:8082 → klink's klive-compat server → shown in KLayout
klink.session_set_klive_target session_id="klayout-8766" # scratch window receives c.show()
photonics.import_gf takes over a finished gdsfactory script, later photonics.reroute refreshes the display through the same path.Record → replayable script
The recorder turns a whole working session — manual GUI edits and agent RPC edits alike — into a replayable script. It is a replay-script generator, not a literal call log: it records whatever rebuilds the final layout state, so a bulk RPC or an exec.python snippet may expand into per-object actions.
| Tool | Use |
|---|---|
recorder.start | Begin recording (optional output_path). Idempotent. |
recorder.status | Recording? how many events/actions so far, output path. Safe anytime. |
recorder.stop | Stop and write; returns stats + wrote. Idempotent. |
Stopping writes two artifacts:
<name>.py— aKLinkClientreplay script, annotated with# user command:lines naming each menu action.<name>_pya.py— a standalonepyacompanion that runs inside KLayout with no klink installed.
recorder.status
recorder.start
# … typed RPC edits + manual GUI edits …
recorder.stop # writes .py and _pya.py
recorder.status before starting your own recording so you never clobber one already in progress.So a one-off manual fix becomes a script you can re-run and re-parameterize, and an agent can regenerate from it.
Profiles & tool discovery
--profile filters by intent (read/write/verify/escape/all) and domain (one of 11 tokens). Default read,write,verify,escape; klink.find_tools navigates narrower tools mid-task. Full detail on MCP Reference · Profiles.
python -m klink.mcp --profile read,write,verify,escape
klink.find_tools domain="routing_backends"
klink.status
Escape hatch
exec.python runs controlled pya for cases no typed RPC covers; it still schedules recorder + layout-diff detection. Prefer typed RPCs — they validate input, return structured errors with a next_action, and appear in the recorder as intent rather than opaque code. See MCP Reference · Escape hatch.