KLayout Observer MCP Contract¶
Purpose¶
This document is the normative handoff contract for the MVP implementation. If the design brief and this contract conflict, follow this contract for implementation details.
Fixed Implementation Choices¶
- Implement the server in Python only for MVP.
- Use Python 3.11 or newer.
- Use the official Python MCP SDK, but keep tool names and request or response payloads defined here even if SDK ergonomics differ.
- Use standalone KLayout Python modules for in-process layout work:
klayout.dbfor layout loading and geometry inspectionklayout.layfor deterministic rendering throughLayoutView- Use external KLayout batch mode only for DRC deck execution:
klayout -b -r <script>- Do not implement GUI attach mode in MVP.
- Do not implement geometry editing in MVP.
Runtime Configuration¶
Support these environment variables:
KLAYOUT_MCP_ARTIFACT_ROOT- Default:
<repo>/.artifacts KLAYOUT_MCP_SESSION_TTL_SECONDS- Default:
3600 KLAYOUT_BIN- Default:
klayout
Runtime Artifact Layout¶
Store runtime artifacts under:
.artifacts/
sessions/
<session_id>/
session.json
renders/
<render_id>.png
drc/
<run_id>/
layout.gds|oas
stdout.txt
stderr.txt
report.lyrdb
markers.json
crops/
All paths returned to callers must be absolute paths.
Session Lifecycle¶
session_idformat:ses_<12-char-lowercase-hex>- Session IDs are unique per process lifetime.
- A session is created only by
open_layout. - A session expires after
KLAYOUT_MCP_SESSION_TTL_SECONDSof inactivity. - Expired sessions are deleted lazily on the next server operation.
close_sessiondeletes the in-memory session plus its artifact directory.- Shape IDs are stable only within the session that created them.
Global Response Rules¶
- Tool responses must be structured JSON objects, not free-form prose.
- Tool failures must return the shared
ErrorObjectpayload with the exact contractcode. - Coordinates provided by callers are in microns unless a field explicitly says otherwise.
- Output must include:
dbu- micron-scale values
- dbu-scale values where measurement or bbox precision matters
- Collections must have deterministic ordering:
- layers sorted by
layer, thendatatype - cells sorted by name
- shapes sorted by
layer,kind, then bbox coordinates - Large responses must include explicit truncation metadata.
Shared Types¶
MicronBox¶
{
"left": 0.0,
"bottom": 0.0,
"right": 100.0,
"top": 50.0
}
LayerRef¶
{
"layer": 1,
"datatype": 0,
"name": "WG"
}
name is optional on input and optional on output.
ArtifactRef¶
{
"kind": "render",
"path": "/abs/path/to/file.png",
"media_type": "image/png"
}
ShapeRef¶
{
"id": "shp_8a4f6d2f",
"kind": "path",
"cell": "TOP",
"instance_path": ["TOP", "MMI_LEFT"],
"layer": {
"layer": 1,
"datatype": 0,
"name": "WG"
},
"bbox_um": {
"left": 0.0,
"bottom": 0.0,
"right": 10.0,
"top": 2.0
}
}
id must be deterministic within a session for the same queried object. It does not need to be stable across sessions.
ErrorObject¶
{
"code": "FILE_NOT_FOUND",
"message": "Layout file does not exist",
"details": {
"path": "/abs/path/to/missing.gds"
}
}
When a tool call fails, return this object directly as the structured tool result.
Error Codes¶
Use these exact codes:
FILE_NOT_FOUNDUNSUPPORTED_FORMATTOP_CELL_NOT_FOUNDSESSION_NOT_FOUNDSESSION_EXPIREDINVALID_BOXINVALID_LAYERINVALID_TARGETQUERY_TOO_LARGETOOL_LIMIT_EXCEEDEDRENDER_FAILEDDRC_RUN_FAILEDINTERNAL_ERROR
Tool Contracts¶
open_layout¶
Request:
{
"path": "/abs/path/to/layout.gds",
"top_cell": "TOP",
"format": "gds"
}
Rules:
pathis required and must be absolute.- Supported formats:
gds,gdsii,oas,oasis. - If
formatis omitted, infer from extension. - If
top_cellis omitted and the file has multiple top cells, choose the alphabetically first one and return the full list.
Response:
{
"session_id": "ses_a1b2c3d4e5f6",
"source": {
"path": "/abs/path/to/layout.gds",
"format": "gds",
"sha256": "..."
},
"selected_top_cell": "TOP",
"top_cells": ["TOP"],
"dbu": 0.001,
"bbox_um": {
"left": 0.0,
"bottom": 0.0,
"right": 1200.0,
"top": 600.0
},
"bbox_dbu": {
"left": 0,
"bottom": 0,
"right": 1200000,
"top": 600000
},
"layer_count": 7,
"artifact_root": "/abs/path/to/.artifacts/sessions/ses_a1b2c3d4e5f6"
}
Implementations may include additional debug artifacts in artifacts such as markers.json, stdout.txt, and stderr.txt. The drc_report artifact must always be present when the run succeeds.
close_session¶
Request:
{
"session_id": "ses_a1b2c3d4e5f6"
}
Response:
{
"session_id": "ses_a1b2c3d4e5f6",
"closed": true,
"artifact_dir_deleted": true
}
Closing an already-missing session should return:
{
"session_id": "ses_missing",
"closed": false,
"artifact_dir_deleted": false
}
list_cells¶
Request:
{
"session_id": "ses_a1b2c3d4e5f6",
"max_depth": 2
}
Response:
{
"session_id": "ses_a1b2c3d4e5f6",
"cells": [
{
"name": "TOP",
"is_top": true,
"bbox_um": {
"left": 0.0,
"bottom": 0.0,
"right": 100.0,
"top": 50.0
},
"child_instance_count": 4,
"shape_count": 18
}
]
}
describe_cell¶
Request:
{
"session_id": "ses_a1b2c3d4e5f6",
"cell": "TOP",
"depth": 1
}
Response fields:
cellbbox_umbbox_dbuinstanceslabelsshape_counts_by_layerdepth_used
Each instance must include:
namechild_cellarraytransformbbox_um
list_layers¶
Request:
{
"session_id": "ses_a1b2c3d4e5f6"
}
Response:
{
"session_id": "ses_a1b2c3d4e5f6",
"layers": [
{
"layer": 1,
"datatype": 0,
"name": "WG",
"visible": true,
"shape_count": 128
}
]
}
query_region¶
Request:
{
"session_id": "ses_a1b2c3d4e5f6",
"box": {
"left": 0.0,
"bottom": 0.0,
"right": 20.0,
"top": 10.0
},
"cell": "TOP",
"layers": [
{
"layer": 1,
"datatype": 0
}
],
"hierarchy_mode": "recursive",
"max_shapes": 200,
"max_instances": 100
}
Allowed hierarchy_mode values:
toprecursiveflattened
Response:
{
"session_id": "ses_a1b2c3d4e5f6",
"box_um": {
"left": 0.0,
"bottom": 0.0,
"right": 20.0,
"top": 10.0
},
"cell": "TOP",
"hierarchy_mode": "recursive",
"summary": {
"shape_count": 3,
"instance_count": 1,
"text_count": 2
},
"shapes": [
{
"id": "shp_8a4f6d2f",
"kind": "path",
"cell": "TOP",
"instance_path": ["TOP"],
"layer": {
"layer": 1,
"datatype": 0,
"name": "WG"
},
"bbox_um": {
"left": 0.0,
"bottom": 0.0,
"right": 10.0,
"top": 1.0
},
"point_count": 2,
"path_width_um": 0.5
}
],
"instances": [],
"texts": [],
"truncation": {
"shapes_dropped": 0,
"instances_dropped": 0,
"texts_dropped": 0
}
}
measure_geometry¶
Request:
{
"session_id": "ses_a1b2c3d4e5f6",
"mode": "edge_gap",
"target_ids": ["shp_a", "shp_b"]
}
Supported mode values and required target counts:
path_width: 1segment_length: 1centerline_distance: 2edge_gap: 2bend_radius_estimate: 1overlap: 2label_distance: 2port_spacing: 2
Response:
{
"session_id": "ses_a1b2c3d4e5f6",
"mode": "edge_gap",
"target_ids": ["shp_a", "shp_b"],
"value_um": 0.35,
"value_dbu": 350,
"details": {
"method": "edge_to_edge_bbox_or_polygon_distance"
}
}
For overlap, return:
value_um: overlapping area in square micronsvalue_dbu: overlapping area in square dbu
analyze_waveguide¶
Request:
{
"session_id": "ses_a1b2c3d4e5f6",
"target_id": "shp_a"
}
target_id must refer to a previously queried path shape. v1 is intentionally limited to path-backed waveguide geometry and does not infer ports or labels.
Response:
{
"session_id": "ses_a1b2c3d4e5f6",
"target_id": "shp_a",
"kind": "path",
"cell": "TOP",
"layer": {
"layer": 1,
"datatype": 0,
"name": "WG"
},
"bbox_um": {
"left": 0.0,
"bottom": -0.25,
"right": 40.0,
"top": 0.25
},
"center_um": {
"x": 20.0,
"y": 0.0
},
"path_width_um": 0.5,
"segment_length_um": 40.0,
"bend_radius_estimate_um": null,
"orientation": "horizontal",
"is_path": true,
"is_axis_aligned": true,
"analysis_warnings": []
}
Errors:
INVALID_TARGET: target id is missing or does not refer to a path shape
set_view¶
Request:
{
"session_id": "ses_a1b2c3d4e5f6",
"box": {
"left": 0.0,
"bottom": 0.0,
"right": 50.0,
"top": 25.0
},
"cell": "TOP",
"layers": [
{
"layer": 1,
"datatype": 0
}
]
}
Response:
{
"session_id": "ses_a1b2c3d4e5f6",
"view": {
"cell": "TOP",
"box_um": {
"left": 0.0,
"bottom": 0.0,
"right": 50.0,
"top": 25.0
},
"layers": [
{
"layer": 1,
"datatype": 0
}
]
}
}
render_view¶
Request:
{
"session_id": "ses_a1b2c3d4e5f6",
"box": {
"left": 0.0,
"bottom": 0.0,
"right": 50.0,
"top": 25.0
},
"cell": "TOP",
"layers": [
{
"layer": 1,
"datatype": 0
}
],
"image_size": {
"width": 1200,
"height": 800
},
"annotations": [
{
"kind": "shape_outline",
"target_ids": ["shp_a"],
"color": "#ff3b30"
}
],
"style": "light"
}
Allowed style values:
lightdarkmask
Response:
{
"session_id": "ses_a1b2c3d4e5f6",
"render_id": "rnd_1234abcd",
"box_um": {
"left": 0.0,
"bottom": 0.0,
"right": 50.0,
"top": 25.0
},
"image": {
"kind": "render",
"path": "/abs/path/to/.artifacts/sessions/ses_a1b2c3d4e5f6/renders/rnd_1234abcd.png",
"media_type": "image/png"
},
"width": 1200,
"height": 800,
"style": "light"
}
run_drc_script¶
Request:
{
"session_id": "ses_a1b2c3d4e5f6",
"script_path": "/abs/path/to/deck.drc",
"script_type": "ruby",
"params": {
"WG_MIN_SPACE": "0.2"
}
}
Rules:
script_pathmust be absolute.- The server must invoke KLayout in batch mode.
- The implementation must export a copy of the session layout into the session DRC run directory and run the deck against that copy.
- The DRC run must produce a
.lyrdbor another machine-readable marker artifact.
Response:
{
"session_id": "ses_a1b2c3d4e5f6",
"run_id": "drc_ab12cd34",
"script_path": "/abs/path/to/deck.drc",
"script_type": "ruby",
"return_code": 0,
"marker_count": 4,
"rule_counts": {
"wg_min_space": 3,
"pin_overlap": 1
},
"artifacts": [
{
"kind": "drc_report",
"path": "/abs/path/to/report.lyrdb",
"media_type": "application/octet-stream"
}
]
}
extract_markers¶
Request:
{
"session_id": "ses_a1b2c3d4e5f6",
"run_id": "drc_ab12cd34",
"include_crops": true,
"crop_size_um": {
"x": 20.0,
"y": 20.0
}
}
Response:
{
"session_id": "ses_a1b2c3d4e5f6",
"run_id": "drc_ab12cd34",
"markers": [
{
"rule": "wg_min_space",
"box_um": {
"left": 10.0,
"bottom": 10.0,
"right": 12.0,
"top": 12.0
},
"crop": {
"kind": "render",
"path": "/abs/path/to/crops/marker_0001.png",
"media_type": "image/png"
}
}
]
}
If include_crops is false, omit crop.
Security Rules¶
- Never execute arbitrary shell from tool input.
- Never pass unvalidated paths to
subprocesscalls. - Treat script parameters as data, not as shell fragments.
Determinism Rules¶
- Always serialize numeric outputs as JSON numbers, not strings.
- Always report microns rounded to 6 decimal places.
- Always report dbu integers when applicable.
- Always include truncation metadata when limits are applied, even if all drop counts are zero.
- Render output for identical input plus identical session state must be byte-stable when the environment is unchanged.
Acceptance Criteria¶
The MVP handoff is implemented correctly only if:
- every tool listed here exists under the exact tool name
- every tool accepts the fields defined here
- every tool returns the required fields defined here
- errors use the exact codes listed here
- session cleanup works both explicitly and by TTL
- artifacts are written to the documented directory layout