MCP / RPC reference

Complete MCP tool reference

klink's control plane is one tool catalogue with two faces: a typed Python client (KLinkClient) and the same methods exposed as MCP tools. Both are generated from the live plugin's method registry — there is no hand-maintained list that can drift. This page lists all 83 plugin RPCs + 39 MCP local tools (122 total) across 11 domains: function, parameters, examples.

Overview & conventions

Tool names are stable namespace.verb. Each maps to exactly one domain; that domain token is both a klink.find_tools domain=<token> navigation key and a --profile <token> filter.

  • name* in a parameter list means required.
  • Coordinates: _um = microns (most natural); _dbu = integer database units. micron = dbu × layout.dbu, where dbu comes from layout.info.
  • Layers: layer index int, "L/D" string (e.g. "1/0"), or {layer, datatype} object.
  • Badges: read write verify escape mutates (undoable) long (separate timeout).
Batch first. Never author generated layout one RPC per object — the loop pays TCP + JSON + transaction + GUI bookkeeping per call and is often hundreds of times slower. Use shape.insert_boxes / shape.insert_many / instance.insert_many / instance.insert_pcell_many. Singleton inserts are for debugging one object.

Discover tools live

The catalogue is queried live, never memorized:

from klink import KLinkClient
with KLinkClient() as c:
    print([m["name"] for m in c.methods()["methods"]])   # meta.methods
klink.find_tools                          # no args → domain index
klink.find_tools domain="routing_backends"  # that area's tools + usage
klink.find_tools query="lvs route"          # ranked matches across all tools

klink.guide reports what is open, the on-disk intent state (declared nets / LVS reports / spec files), and the literal next call for each available intention.

Profiles

--profile filters along two orthogonal axes — intent and domain. Default is read,write,verify,escape.

IntentExposes
readRead-only: layout.info, cell.list, shape.query, view.*, pcell.*, recorder.
writeEditing: shape.insert_*, cell.create, layer.ensure, instance.insert*, edit.undo.
verifyChecks: drc.run, lvs.run.
escapeEscape hatches: exec.python, exec.reset, events.*.
allEverything, no filtering.
python -m klink.mcp --profile read,write,verify,escape   # default
python -m klink.mcp --profile read,device_photonics      # mix both axes
python -m klink.mcp --profile routing_backends           # one domain only

All local tools are always included under any intent profile; klink.find_tools / klink.status / klink.reconnect are always on. Legacy aliases: basic→read, draw→write, advanced→escape, drc→verify.

1 · Connection, self-check, discovery & view connection_and_view · 15 tools

klink.find_tools domain="connection_and_view"

Start here when unsure. klink.status reports connection, active session, interpreter and optional capabilities; klink.guide reports what is open + on-disk intent state + the literal next call; klink.find_tools navigates the rest; klink.reconnect recovers a dropped link. A freshly created cell is invisible until view.show_cell. Screenshots (view.screenshot) are a user-requested artifact only — never an agent verification step; prefer geometry queries.

ToolParamsFunction
klink.statusMCP bridge connection status, active session, interpreter, last connection error. First stop when debugging.
klink.reconnectClose a stale client and try to reconnect to KLayout.
klink.guideReports what is open, on-disk intent state, and the literal call for each available intention + a suggested next action.
klink.find_toolsdomain, queryDiscover tools by domain or keyword.
helloclient, protocolIntroduce the client, receive server info + capabilities. Recommended first call per connection.
meta.pingLiveness probe; echoes params + trace id.
meta.methodsFull RPC method catalogue with descriptions + JSON schemas, ready for LLM function-calling.
meta.debug_signalsfireSignalHub diagnostics; optionally fire a synthetic event to test delivery.
view.list_tabsList layout tabs (index, title, file, active cell, current).
view.activate_tab mutatesindex*Switch the current tab; all single-layout RPCs then act on it.
view.show_cellcell*, zoom_fitSet the displayed top cell (makes a new cell visible), zoom-fits by default.
view.screenshotmode, width_px, height_px, bbox_dbu, pathRender a PNG. base64 data URL or path to disk. User-requested only.
view.zoom_fit mutatesFit the whole layout into the viewport.
view.zoom_box mutatesbbox_dbu*Zoom to exactly the given bbox (dbu).
view.viewportReport current viewport: visible bbox (dbu), pixel size, cellview index.
view.show_lvsdbpath*, kindLoad a saved LVS/netlist DB into the Netlist Browser and show it. lvs.lvsdb, l2n.l2n.
klink.status
klink.guide
view.list_tabs

from klink import KLinkClient
with KLinkClient() as c:
    print(c.hello(client="my-script"))
    print(c.layout_info(verbosity="summary"))

2 · Multi-session registry & cross-session transfer multi_session_transfer · 15 tools

klink.find_tools domain="multi_session_transfer"

klink drives many KLayout sessions from one MCP bridge (each a port in the local registry, typically 8765, 8766, …). Sessions are equal peers — pass the one you mean explicitly. Transfer is two-phase and confirmation-safe: transfer_prepare builds a package, dry-runs it on the target, and persists it pending; transfer_commit writes it.

ToolParamsFunction
klink.session_listinclude_staleEnumerate discoverable sessions.
klink.session_statussession_id, include_staleOne session record (default: active).
klink.session_labelsession_id*, label*, aliases, descriptionAttach a human label and aliases.
klink.session_resolvequery*Resolve id / label / alias / active cell / top cell to a session.
klink.session_usesession_id*Repoint the bridge's primary RPC target.
klink.session_set_klive_targetsession_id*Choose the klive-compatible 8082 endpoint (where c.show() lands).
klink.transfer_preparesource_session*, target_session*, target_cell, copy_mode, layer_map, translate_umBuild a flat-selection package and dry-run it on the target.
klink.transfer_commitpackage_id*, dry_runCommit a prepared package.
session.mark_klive_target(plugin) Mark this window as the klive/8082 target.
session.label_setsession_id*, label*, aliases, description(plugin) Set label/aliases in the shared registry.
transfer.pending_setpackage*Store a reviewed package in this window.
transfer.pending_statusPending package status for this window.
transfer.pending_clearClear the pending package (no geometry written).
transfer.paste_pending mutatesdry_run, clear_afterPaste the pending package (already contains final layers/coords).
transfer.import_cell_tree_package mutates longpath*, source_cell*, dry_runImport one cell tree from a GDS/OAS via native Cell.copy_tree; conflicts get $N.
klink.session_list
klink.session_label session_id="klayout-8766" label="scratch" aliases=["test"]
klink.transfer_prepare source_session="klayout-8765" target_session="klayout-8766" copy_mode="flat_selection"
klink.transfer_commit package_id="pkg_0001"

3 · Geometry & cell-structure authoring geometry_authoring · 28 tools

klink.find_tools domain="geometry_authoring"

The core drawing surface. Read first: layout.info, cell.list/cell.tree, layer.list, shape.query, instance.query, pcell.*. Author with batch RPCs. layer.ensure before drawing. Edits are transactional; edit.undo/redo/status. Destructive layout.clear/cell.delete only on disposable test cells.

Read

ToolParamsFunction
layout.infoverbositySnapshot: views, active cellview, top cell, source file, dbu, top-cell list, layers. Refresh your world view.
cell.listname_prefix, top_only, with_bbox, offset, limitFlat paginated cell list.
cell.treeroot, max_depth, max_nodesHierarchical cell tree (bounded).
layer.listAll layers: layer_index, layer/datatype, name, dbu_um.
shape.query longcell*, layers, bbox_dbu, kinds, limitRead shapes from ONE cell (no recursion) as JSON. Narrow with layers+bbox; paginate (default 500, max 5000).
instance.queryparent*, child, bbox, limitDirect child instances: name, bbox, transform, array, PCell metadata, per-layer shape counts.
pcell.librariesAvailable PCell libraries (Basic + PDKs).
pcell.listlibraryPCell names in a library (default Basic).
pcell.infolibrary, pcell*Parameters of a PCell (name/type/default/description/choices).

Write · cells, layers, shapes, instances

ToolParamsFunction
cell.create mutatesnameNew cell (dup name → $1…). Not idempotent.
cell.rename mutatescell*, new_name*, allow_suffixRename; dup name errors unless allow_suffix=true.
cell.delete mutatescell*, recursiveDelete a cell (recursive drops orphaned children). Destructive.
layer.ensure mutateslayer*, datatype, nameUpsert a GDS layer; returns layer_index.
shape.insert_boxes mutatescell, layer, boxes_um/boxes_dbuBatch many rectangles on one cell/layer.
shape.insert_many mutatescell*, items*, dry_runBatch mixed box/polygon/path/text.
shape.insert_box mutatescell, layer, bboxOne rectangle.
shape.insert_polygon mutatescell, layer, pointsOne polygon (hull only), auto-closed.
shape.insert_path mutatescell, layer, points, width, begin_ext, end_ext, round_endsOne path (center line + width).
shape.insert_text mutatescell, layer, position, size_umOne text label (annotation, no mask geometry).
shape.delete mutatescell, layer(s), bbox, kinds, dry_runDelete shapes by selector; dry_run previews count. One transaction.
instance.insert_many mutatesparent*, items*, dry_runBatch child-cell instances.
instance.insert_pcell_many mutatesparent*, items*, dry_runBatch PCell instances.
instance.insert mutatesparent*, child*, position, rotation, mirror, magnification, arrayPlace child in parent; optional array grid.
instance.insert_pcell mutatesparent*, library, pcell*, params, position, …Build a PCell variant then insert. Call pcell.info first.
instance.delete mutatesparent*, child, bbox, all, limit, dry_runDelete instances by selector (non-destructive to the child cell).
pcell.register_fittedname*, fit_table*Register a fitted-device PCell at runtime into klink_structdevice; no plugin reload.

Write · layout-level & edit history

ToolParamsFunction
layout.show_file mutates longpath*, mode, keep_position, technologyLoad GDS/OAS (reload if open). replace or new tab.
layout.save_file mutatespath*, cellview_indexSave; extension picks GDSII/OASIS.
layout.clear mutatescellview_indexDestructive: clear the whole layout.
edit.undo mutatesUndo last undoable op; returns before/after stack.
edit.redo mutatesRedo.
edit.statusdebugUndo/redo availability.
layout.info verbosity="summary"
cell.create name="MYBLOCK"
layer.ensure layer=1 datatype=0 name="M1"
shape.insert_boxes cell="MYBLOCK" layer="1/0" boxes_um=[[0,0,10,4],[20,0,30,4],[40,0,50,4]]
shape.insert_many cell="MYBLOCK" items=[
  {"kind":"path","layer":"2/0","points_um":[[0,20],[50,20]],"width_um":2},
  {"kind":"text","layer":"63/0","position_um":[0,26],"text":"MYBLOCK","size_um":4}
]
view.show_cell cell="MYBLOCK"

pcell.info library="Basic" pcell="CIRCLE"
instance.insert_pcell parent="MYBLOCK" library="Basic" pcell="CIRCLE" params={"l":"1/0","r":5.0,"n":64} position_um=[100,0]

4 · Selection & SEND interaction memory selection_and_send_memory · 11 tools

klink.find_tools domain="selection_and_send_memory"

Two distinct things. selection.* is the live current selection; interaction.* is the durable, session-scoped memory of selections the user explicitly SENT (recorded as ids like sel_0006). Resolve by order/count, not age. Bind user phrases ("just sent", "this area", "that one") to these ids, not to screenshots.

ToolParamsFunction
selection.getlimitCurrent selection (shape or instance). Empty = empty list, not an error.
selection.set_box mutatescell*, bbox_dbu*, layers, limitSelect all shapes intersecting a box on given layers; replaces current selection.
selection.clear mutatesClear the current selection.
selection.send_contextsource, max_itemsEmit the current selection as a selection_sent event (agent-side SEND).
interaction.selection.latestLatest stored SEND.
interaction.selection.recentlimitRecent SENDs (default latest 5, by order).
interaction.selection.getid*One stored selection by id.
interaction.selection.labelid*, label, descriptionAttach a label/description.
interaction.selection.clear_sessionconfirm*Clear this session's interaction context after confirmation.
interaction.contextinclude_current_selectionCurrent selection + recent SEND memory together.
# user selects A → SEND, selects B → SEND
interaction.selection.recent limit=2      # → [sel_0007(B), sel_0006(A)]
interaction.selection.label id="sel_0006" label="probe pad"

5 · Ports & anchors (routing markers) ports_and_anchors · 16 tools

klink.find_tools domain="ports_and_anchors"

Ports are net endpoints (klink_Port PCells: net + orientation + width). Anchors are routing constraints (klink_Anchor PCells) whose kind is waypoint_region, bend_region, or corridor. Routing tools default port_layer=999/99, anchor_layer=999/1. Keepouts are NOT an anchor kind — pass your own design's obstacle_layers to routing tools. These are the input to routing: mark Ports+Anchors, then call a routing.* tool.

ToolParamsFunction
port.set_layer mutateslayer*Configure the default Port marker layer.
port.mark mutatescell*, name, center, orientation, width_um, port_type, net, target_layer, …Create one klink_Port PCell (a net endpoint).
port.listcell*, layer, sortList Ports in a cell.
port.update mutatescell*, name*, orientation, width_um, net, …Update one Port by immutable name.
port.transform mutatescell*, names / selection, rotate_delta, net, …Batch-update Ports by names or GUI selection.
port.repair_names mutatescell*, layer, prefixRepair duplicate/empty Port names (GUI-inserted).
port.unmark mutatescell*, name*Delete one Port.
port.delete_all mutatescell*, layerDelete all Ports in a cell.
anchor.set_layer mutateslayer*Configure the default Anchor marker layer.
anchor.mark mutatescell*, kind, center, net, radius_um, width_um, height_um, path_points, …Create one klink_Anchor PCell (routing constraint).
anchor.listcell*, layer, sortList Anchors in a cell.
anchor.update mutatescell*, id*, new_id, kind, net, …Update one Anchor by immutable id.
anchor.transform mutatescell*, ids / names / selection, kind, …Batch-update Anchors.
anchor.repair_ids mutatescell*, layer, prefixRepair duplicate/empty Anchor ids.
anchor.unmark mutatescell*, id*Delete one Anchor.
anchor.delete_all mutatescell*, layerDelete all Anchors in a cell.
port.mark cell="NET1" name="A" center_um=[0,0]   orientation="E" width_um=2 net="sig"
port.mark cell="NET1" name="B" center_um=[80,20] orientation="W" width_um=2 net="sig"
anchor.mark cell="NET1" kind="waypoint_region" center_um=[40,40] radius_um=6 net="sig"
routing.tapered_hybrid_cell cell="NET1" angle_mode="manhattan" obstacle_layers=["10/0"]

6 · Routing backends routing_backends · 9 tools

klink.find_tools domain="routing_backends"

All read Port/Anchor PCells and write routes. Pick by topology/quality. Always inspect the structured result: ok=false, obstacle_hit_count>0, sibling overlaps, short route_count all mean failure. Pass your own design's obstacle_layers (no default).

ToolParamsFunction
routing.tapered_hybrid_cellcell*, spacing_um, angle_mode, clear, port_layer, anchor_layer, obstacle_layersMain path+patch backend. angle_mode: any/manhattan/fortyfive.
routing.tapered_polygon_cellcell*, …, route_layer, corner_style, obstacle_layersContinuous taper polygons (first-class).
routing.steiner_cellcell*, route_layer, root_ports, clear, obstacle_layersMulti-terminal nets (>2 ports).
routing.damped_segment_cellcell, damping_distance_um, obstacle_layers, …Extra obstacle clearance, segment output.
routing.damped_polygon_cellcell, damping_distance_um, corner_style, …Damped continuous taper polygons.
routing.damped_steiner_cellcell, damping_distance_um, root_ports, …Damped multi-terminal trunk/branch.
routing.global_channel_cellcell*, spacing_um, angle_mode, safe_distance_um, obstacle_layersStronger global-decision router: candidate assignment + corridor-capacity load-balancing, reuses hybrid geometry.
routing.multilayer_escape_cellcell*, route_layer*, bridge_layer*, via_layer*, spacing_um, obstacle_layersWall-blocked nets via bridge layer + vias.
routing.gdsfactory_portscell*, route_layer*, router, cross_section, separation_um, radius_um, waypoints_um, path_length_match, obstacle_bboxes_um, …Route Port markers with one named gdsfactory strategy. Needs gdsfactory in the interpreter.

routing.gdsfactory_ports routers: bundle (default, Manhattan river routing), electrical (sharp corners + electrical typing), sbend (offset facing ports), all_angle (non-Manhattan + optional backbone_um spine), single (per-pair Manhattan), dubins (arc-based any-heading), astar (experimental grid A* — klink verifies and errors on wall-crossing). A parameter the chosen router can't honor is an error, never silently ignored.

routing.tapered_hybrid_cell cell="BLOCK" angle_mode="manhattan" spacing_um=20 obstacle_layers=["900/0"]
routing.steiner_cell cell="BLOCK" route_layer="1/0"
routing.gdsfactory_ports cell="BLOCK" route_layer="1/0" router="bundle" separation_um=5 radius_um=10 path_length_match=true
"Done" for a route/P&R stage is a live LVS match=True — marker counts and "looks routed" never substitute.

7 · DRC & LVS verification drc_and_lvs_verification · 2 tools

klink.find_tools domain="drc_and_lvs_verification"

Both are long-running, pure-pya, domain-agnostic. drc.run runs arbitrary DRC DSL you supply — exceptions come back as results, they do NOT fail the RPC. lvs.run extracts the live layout into a device netlist and compares it against a reference netlist you supply, writes a .lvsdb, and (default) opens the Netlist/LVS browser. For structdevice flows prefer structdevice.lvs_check.

ToolParamsFunction
drc.run mutates long(DRC DSL source + optional output_rdb)Run arbitrary DRC DSL in KLayout's Ruby DRC engine. With source() = standalone, else interactive on the current layout.
lvs.run longcell*, conductors*, vias, devices*, reference*, out_lvsdb, showExtract (per-cell extractors + conductor layers) → compare to reference (reference.spice or reference.netlist) → write .lvsdb and show.
drc.run script="m1 = input(1,0)\nm1.space(0.2.um).output('M1_space','M1 spacing < 0.2um')"
lvs.run cell="BLOCK" conductors=["1/0","3/0"] vias=["2/0"] devices={...} reference={"spice":"ref.spice"} out_lvsdb="block.lvsdb" show=true

8 · Custom-device netlist → auto P&R → LVS device_structdevice · 7 tools

klink.find_tools domain="device_structdevice"

Device-agnostic custom-device P&R. A "device" is any cell with an arbitrary parameter set + terminals; klink assumes no device vocabulary. The device library, process profile, and terminal source are example/PDK data passed in explicitly — the tools ship none and return an instructive "write/run an example" error. Compact physical model: the device's own metal layers double as routing layers; mode 2L/3L picks 2 or 3.

ToolParamsFunction
structdevice.build_from_netlistcell*, netlist*, confirm, mode, rows, cols, sessionHeadline one-call flow: device-level netlist → floorplan → single-pass multilayer routes → draw → device-LVS a fresh cell. Confirmation-gated (call once → proposal; again with confirm → build).
structdevice.declare_netsrecent_sends*, cell*, conductors, viasOne SEND framing ≥2 terminals = one declared net (persisted). Example-driven.
structdevice.connect_netscell*, session, route_layer, route_width_um, via_cell, min_spacing_um, min_width_um, conductors, viasWire declared-but-unconnected nets + verify; any LVS mismatch undoes everything.
structdevice.lvs_checkcell*, session, mode, conductors, viasNet-level reconcile; device/both also run device-level NetlistComparer.
structdevice.spec_writecell*, layer_roles*, session, device_class, conductors, viasProject a live cell into a klink.spec.json fact file.
structdevice.register_pcellname*, fit_table*, sessionRegister a fitted-device PCell at runtime; zero plugin reloads.
pcell.register_fittedname*, fit_table*The plugin RPC behind register_pcell.
# step 1: no confirm → proposal
structdevice.build_from_netlist cell="RINGOSC" netlist={"instances":[...], "nets":[...], "groups":[]} mode="3L"
# step 2: same args + confirm token → build
structdevice.build_from_netlist cell="RINGOSC" netlist={...} mode="3L" confirm="CONFIRM-xyz"

# SEND-driven interactive path
structdevice.declare_nets recent_sends=3 cell="BLOCK" conductors=["1/0","3/0"] vias=["2/0"]
structdevice.connect_nets cell="BLOCK" route_layer="3/0" route_width_um=0.5
structdevice.lvs_check cell="BLOCK" mode="both"
structdevice.spec_write cell="BLOCK" layer_roles={"1/0":"gate","3/0":"metal1"}

9 · Nanodevices (Hall bar / EBL / flake) device_nanodevice · 2 tools

klink.find_tools domain="device_nanodevice"

nanodevice.hallbar is a one-call closed loop: from a HallBarSpec it computes and draws the whole device (bar + N contact arms + pads + Port markers + labels), then delegates routing to the generic router (overlap validation on, optional EBL writefield walls as keepouts), committing to a disposable cell. Failures return problems/next_action and change nothing.

ToolParamsFunction
nanodevice.hallbarcell, spec, writefield, route_layer, spacing_um, dry_run, sessionBuild + route + validate + commit one Hall bar in one call.
nanodevice.detect_commitcell, traces_path, image, pixel_size_um, coordinate, dry_run, sessionCommit flake traces as polygons. traces_path (no extra deps) or image+pixel_size_um for live detection (needs cv2 + numpy).
nanodevice.hallbar cell="HB1" spec={"bar_length_um":60,"bar_width_um":8,"contact_count":6,"pad_size_um":40,"pitch_um":24,"gap_um":6} route_layer="1/0" dry_run=true
nanodevice.detect_commit cell="FLAKE" traces_path="out/traces.json"

10 · Photonics (gdsfactory import / connect / reroute) device_photonics · 4 tools

klink.find_tools domain="device_photonics"

Photonic circuit flow; needs gdsfactory in the interpreter. photonics.import_gf takes over a finished gdsfactory script in one call; port.harvest_blackbox derives Ports from live blackbox positions via the stub convention. After takeover, drag a component in the GUI → one photonics.reroute re-routes optics and metal together. That drag → reroute loop is the point: the layout stays live and editable.

ToolParamsFunction
photonics.import_gfscript_path*, component, cell, route_layer, port_layer, route, sessionTake over a finished gdsfactory script: import device instances (batch RPC), collapse routed connections to nets, persist port templates + net table, route with klink.
port.harvest_blackboxcell*, tags*, nets, wg_layer*, stub_size_um*, port_layer, clearDerive Ports from PDK blackbox instances via the waveguide stub convention. Re-run after moving instances.
photonics.connectrecent_sends*, cell, width_um, radius_um, separation_um, wg_layer, stub_size_um, route_layerConnect ports the user just SENT: read latest N SENDs → port pairs → auto-name nets → persist → re-harvest → route.
photonics.reroutecell*, session, wg_layer, stub_size_um, route_layerRe-route after the user moved components (reads the persisted net table). Multi-port optical nets need a splitter first.
photonics.import_gf script_path="my_mzi.py" cell="MZI" route_layer="1/0"
# drag a component in KLayout, then:
photonics.reroute cell="MZI"
port.harvest_blackbox cell="MZI" tags=["gc","mmi"] wg_layer="1/0" stub_size_um=0.5
photonics.connect recent_sends=4 cell="MZI" radius_um=10 separation_um=5

11 · Escape hatch (pya exec, events, recorder) escape_hatch · 8 tools

klink.find_tools domain="escape_hatch"

Prefer typed RPCs. exec.python runs raw pya for what no typed RPC covers — it still schedules recorder + layout-diff detection. events.* is the live event stream (usually read interaction.* instead). recorder.* generates a replay script; check recorder.status before tests so you never clobber a user recording.

ToolParamsFunction
exec.python mutates(code + optional reset)Run arbitrary Python on KLayout's Qt main thread. Pre-bound pya, mw, view, layout. State persists per connection (reset=true wipes). stdout/stderr captured.
exec.reset mutatesClear the per-connection namespace.
events.channelsList pushable event channels (NDJSON frames).
events.statusSubscription + SignalHub diagnostics.
events.subscribechannels*Subscribe to channels (unknown silently ignored).
events.unsubscribechannelsUnsubscribe; * drops all.
recorder.startoutput_pathBegin recording into a replayable Python script. Idempotent.
recorder.stopoutput_pathStop and write the script; returns stats + wrote.
recorder.statusRecording state, event/action counts, output path.
recorder.status
recorder.start
# … typed RPC edits + manual GUI edits …
recorder.stop        # writes .py and standalone _pya.py

exec.python code="print(layout.top_cell().name)"
See these tools composed into real loops on the Examples page (8 public demos), or the domain-by-domain onboarding on Tutorials.