Simulator MCP server
The SECS/GEM simulator can run as an MCP (Model Context Protocol) server. AI tools such as Claude Code or Cursor, or your own scripts, call its MCP tools to create simulator sessions, establish communication and exercise GEM capabilities such as reports, alarms, commands and process programs.
Three ways to run it
Section titled “Three ways to run it”| How you start it | What it controls | Tools |
|---|---|---|
pdcomm-simulator-mcp --stdio |
Sessions owned by this process. No window, no Qt. | 169 |
pdcomm-simulator --mcp-stdio |
The sessions and window of this GUI process | 175 |
pdcomm-simulator --mcp-local <descriptor> plus pdcomm-simulator-mcp-attach --descriptor <descriptor> |
The same GUI, already running | 175 |
The two GUI modes add six simulator.ui.* tools that drive the window. Separate processes never share sessions: if you start pdcomm-simulator-mcp on its own, it can’t see the sessions of a GUI you already opened.
Register the server
Section titled “Register the server”An MCP client starts the server as a child process and talks to it over standard input and output. Most clients use a JSON configuration like this one; replace the path with your installation folder.
{ "mcpServers": { "pdcomm-simulator": { "command": "C:\\pdcomm-tutorial\\simulator\\pdcomm-simulator-mcp.exe", "args": ["--stdio"] } }}{ "mcpServers": { "pdcomm-simulator": { "command": "/opt/pdcomm-tutorial/simulator/pdcomm-simulator-mcp", "args": ["--stdio"] } }}{ "mcpServers": { "pdcomm-simulator": { "command": "/opt/pdcomm-tutorial/simulator/pdcomm-simulator-mcp", "args": ["--stdio"] } }}To let the client drive the GUI instead, set command to the GUI executable (pdcomm-simulator, or pdcomm-simulator.app/Contents/MacOS/pdcomm-simulator on macOS) and args to ["--mcp-stdio"]. The client then opens the GUI window, so you need a desktop session.
Options
Section titled “Options”| Option | Description |
|---|---|
--stdio (--mcp-stdio for the GUI) |
Serve MCP over standard input and output. Pass it exactly once. |
--allow-endpoint <IPv4:port> |
Allow a non-loopback address as a session address. Repeat up to 32 times. 0.0.0.0 isn’t accepted. |
--request-deadline-ms <1-600000> |
The time limit for one request. Defaults to 10000. |
By default, sessions can only use loopback addresses such as 127.0.0.1. To connect to equipment or a host on another computer, allow its address with --allow-endpoint.
Exit codes
Section titled “Exit codes”| Code | Meaning |
|---|---|
| 0 | The client closed standard input. |
| 1 | An I/O error, such as a failed write to standard output |
| 2 | Invalid command-line arguments |
| 130 | Stopped by SIGINT or SIGTERM (Ctrl+C, Ctrl+Break or closing the console on Windows) |
Connect and list the tools
Section titled “Connect and list the tools”The server supports MCP protocol version 2025-11-25 (the usual initialize handshake) and 2026-07-28 (_meta on every request). Most clients use initialize:
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"docs-walkthrough","version":"1.0"}}}{"id":1,"jsonrpc":"2.0","result":{"capabilities":{"resources":{"listChanged":false,"subscribe":false},"tools":{"listChanged":false}},"instructions":"Discover available simulator operations before calling tools.","protocolVersion":"2025-11-25","serverInfo":{"name":"pdcomm-simulator-mcp","version":"0.1.0"}}}Then send the notifications/initialized notification and call tools/list. Read tool names and input schemas from that list; don’t hard-code the number of tools in your client.
Tool groups
Section titled “Tool groups”| Group | Tools | What they do |
|---|---|---|
simulator.capabilities |
1 | Supported capabilities and limits |
simulator.session |
4 | Create, list, get and destroy sessions |
simulator.lifecycle |
6 | Start, stop, Select, Deselect, establish communication, poll |
simulator.status_data |
5 | List variables, change equipment values, query names and values from the host |
simulator.reports_events |
9 | Report and event catalogs, emit events, inspect and acknowledge received events, remote report configuration |
simulator.alarms |
9 | Alarm catalogs, observe, publish, enable, list and acknowledge |
simulator.commands |
8 | Remote command catalogs, requests, equipment decisions and results |
simulator.programs |
53 | Unformatted and formatted process programs, verification, selection, execution and capability catalogs |
simulator.spooling |
17 | Spooling configuration, capture, host controls and delivery proofs |
simulator.constants |
8 | Equipment constant catalogs, queries, changes and decisions |
simulator.data_variables |
5 | Data variable catalogs and caches, events with samples |
simulator.directories |
5 | Static event directories, data and event directory requests |
simulator.report_queries |
5 | Report queries and replies |
simulator.profiles |
5 | Equipment profiles |
simulator.scenarios |
7 | Scenario definitions and runs |
simulator.trace |
4 | Read, export, inspect and clear the trace |
simulator.project |
2 | Export and replace project definitions |
simulator.diagnostics |
1 | Session diagnostics |
simulator.transport |
3 | Arm and release the write backpressure fault |
simulator.secs1 |
10 | The SECS-I virtual pair |
simulator.gem300 |
2 | The GEM300 capability catalog |
simulator.ui (GUI modes only) |
6 | Window state, workspace navigation, theme, session binding, open and close a trace |
The same information is also available as resources. resources/list returns pdcomm://capabilities, pdcomm://sessions, pdcomm://profiles and pdcomm://scenarios, and resources/templates/list returns per-session resources such as pdcomm://sessions/{session_id}/trace.
Session identity and change rules
Section titled “Session identity and change rules”Each MCP session is identified by:
session_id: the server’s name for the session, such assim-1. It’s not the HSMS session ID (protocol_session_id).generation: a new number every time the session starts. It increases across the whole server, so it isn’t consecutive per session.revision: increases by one whenever the session changes.
Every tool that changes a session takes the current revision in expected_revision. If it doesn’t match, the call fails with REVISION_CONFLICT and nothing changes. Always pass the latest generation and revision from the previous response.
Tools whose effects persist, such as catalog configuration, event and alarm publishing and commands, also take an operation_id of 1 to 64 bytes. If you repeat a call with the same operation_id and the same input, you get the earlier result with replayed: true, and nothing runs twice. After a timeout or a dropped connection, call again with the same ID to learn the outcome instead of sending a new ID.
Walkthrough: create an equipment and a host, then read values
Section titled “Walkthrough: create an equipment and a host, then read values”These requests and responses come from a real run against the standalone server (pdcomm-simulator-mcp --stdio). Only the structuredContent of each response is shown, shortened.
1. Create and start an equipment session
Section titled “1. Create and start an equipment session”{"name":"simulator.session.create","arguments":{"role":"equipment","mode":"passive","address":"127.0.0.1:0","protocol_session_id":7}}{"session_id":"sim-1","generation":1,"revision":0,"role":"equipment","mode":"passive","lifecycle":"created","tcp_state":"stopped","local_address":""}{"name":"simulator.lifecycle.start","arguments":{"session_id":"sim-1","generation":1,"expected_revision":0}}{"session_id":"sim-1","generation":2,"revision":1,"lifecycle":"running","tcp_state":"listening","local_address":"127.0.0.1:49677","control_state":"host_offline"}Starting the session assigns a new generation and resets revision to 1. Because you asked for port 0, read the real port from local_address.
2. Create and start a host session
Section titled “2. Create and start a host session”{"name":"simulator.session.create","arguments":{"role":"host","mode":"active","address":"127.0.0.1:49677","protocol_session_id":7}}{"name":"simulator.lifecycle.start","arguments":{"session_id":"sim-2","generation":3,"expected_revision":0}}{"session_id":"sim-2","generation":4,"revision":1,"lifecycle":"running","tcp_state":"connecting"}3. Poll and establish communication
Section titled “3. Poll and establish communication”Sessions of the standalone server only move bytes when you call simulator.lifecycle.poll. Poll both sessions in turn. When the host reports tcp_state: "selected" and communication_state: "not_communicating", call simulator.lifecycle.begin_communication. Keep polling until both sessions report "communicating"; in this run that took five rounds.
{"name":"simulator.lifecycle.begin_communication","arguments":{"session_id":"sim-2","generation":4,"expected_revision":3}}{"session_id":"sim-2","generation":4,"revision":4,"tcp_state":"selected","communication_state":"waiting_ack"}4. Read status variables
Section titled “4. Read status variables”The host queries names first (simulator.status_data.query_names) and only then values (simulator.status_data.query_values). After polling, the results appear with remote: true in the host session’s simulator.status_data.list.
{"name":"simulator.status_data.query_names","arguments":{"session_id":"sim-2","generation":4,"expected_revision":6,"ids":[1001,1002]}}After the values arrive, simulator.status_data.list returns:
{"variables":[ {"kind":"status","id":1001,"name":"EquipmentState","units":"state","format":16,"count":4,"value":"IDLE","value_present":true,"remote":true,"editable":false}, {"kind":"status","id":1002,"name":"WaferCount","units":"wafers","format":44,"count":1,"value":"0","value_present":true,"remote":true,"editable":false}]}format is the SECS-II format code: 16 (octal 20) is ASCII and 44 (octal 54) is U4.
5. Check the trace and clean up
Section titled “5. Check the trace and clean up”simulator.trace.read returns the session’s most recent records, up to 256. Each record’s category is activity, received or error, and its source is service, tcp or gem. Message bodies aren’t recorded.
Finally, call simulator.lifecycle.stop and simulator.session.destroy for each session. When the client closes standard input, the server exits with code 0.
Handle errors
Section titled “Handle errors”Errors come in two layers:
- JSON-RPC errors: malformed requests, unknown methods (
-32601), unknown tools or invalid parameters (-32602), request deadline exceeded (-32603) and so on. - Tool errors: a normal
resultwithisError: trueand the details instructuredContent.error.
Here’s the real response to a wrong expected_revision:
{"error":{"code":"REVISION_CONFLICT","field":"expected_revision","message":"session revision does not match"}}| Tool error code | Meaning |
|---|---|
INVALID_ARGUMENT |
The input doesn’t match the schema or a value is out of range. |
SESSION_NOT_FOUND |
The session_id doesn’t exist. |
STALE_GENERATION |
The generation belongs to an earlier start. Get the session again. |
REVISION_CONFLICT |
expected_revision doesn’t match the current value. |
INVALID_STATE |
Not allowed in the current state, for example a query before communication is established, or a catalog change after start. |
ROLE_FORBIDDEN |
Wrong role, for example begin_communication on an equipment session. |
LIMIT_EXCEEDED |
A limit was reached, such as the maximum of eight sessions, or an event report (S6F11) doesn’t fit the message limits. Nothing is sent in that case; see Event report size limits. |
DEADLINE_EXCEEDED, CANCELLED |
The request timed out or was cancelled before it changed anything. |
UNSUPPORTED_CAPABILITY, CORE_FAILURE, SHUTTING_DOWN |
Unsupported capability, internal failure, or the server is shutting down |
A rejected call never changes the session. Treat both isError and a JSON-RPC error as failures.
Event report size limits
Section titled “Event report size limits”Equipment and host sessions use the same message limits: a message body of up to 8,514 bytes, with up to 4,096 bytes of value in any single item.
- An equipment session sends only event reports (
S6F11) that fit these limits. For a larger event,simulator.reports_events.emitandsimulator.data_variables.emit_with_samplesreturnLIMIT_EXCEEDEDand send nothing. Both sessions keep communicating. - A host session accepts every event report within the limits. It keeps the encoded values of each received event until you acknowledge the event.
limits.max_received_value_bytesin thesimulator.capabilities.getresult is the value budget for one event. - A host session keeps at most 16 reports per event, 16 values per report and 64 values in total. It refuses a valid event report beyond that, for example from another vendor’s equipment: it replies
S6F12with ACKC6 1 (error, not accepted), adds areports_events.event_refusederror record with codeLIMIT_EXCEEDEDto the trace, and keeps communicating. The reason appears inlast_error_messageofsimulator.trace.readandsimulator.diagnostics.get, and the refused event isn’t listed as received. Only anS6F11body that can’t be parsed is aborted withS6F0. - Other messages that a session keeps for you follow the same rule. When a valid message holds content the simulator can’t represent, such as a PPID with control characters that the simulator doesn’t accept, the session answers it instead of keeping it. A host answers
S7F27withS7F28, which carries no acknowledgement code. An equipment answersS7F23withS7F24ACKC7 1 (permission not granted),S7F31withS7F32ACKC7 1, andS7F25withS7F26from its program store, which is an empty list for such a PPID. Each refusal adds an error record to the trace (programs.verification_report_refused,programs.formatted_transfer_refused,programs.verification_request_refusedorprograms.formatted_query_refused) with the reason inlast_error_message, and both sessions keep communicating. - A message that exceeds the limits configured in the PDComm library, for example more than 16 parameters in one command or more than 16 issues in
S7F27, is still aborted withSxF0; an equipment can also sendS9F7(illegal data) first. - When 16 events are pending, or their values add up to 65,536 bytes (
limits.received_value_backpressure_bytes), the host stops reading new messages, andsimulator.reports_events.list_receivedreturnsbackpressured: true. Acknowledge an event withsimulator.reports_events.ack_receivedto resume. This budget keeps the list of pending events within the 1 MiB MCP response limit.
Attach to a running GUI
Section titled “Attach to a running GUI”To drive a GUI that is already open, start it with --mcp-local and let your client run pdcomm-simulator-mcp-attach.
-
Start the GUI with a descriptor file path. If a file already exists at that path, the GUI exits with code 3.
터미널 창 "$PDCOMM_SIMULATOR/pdcomm-simulator" --mcp-local "$HOME/pdcomm-tutorial/results/gui-descriptor.json"The GUI writes a descriptor that only its owner can read (mode 0600). It holds the local connection name and an authentication token (capability token).
-
In your MCP client configuration, set
commandtopdcomm-simulator-mcp-attachandargsto["--descriptor", "<descriptor path>"]. On macOS, the program ispdcomm-simulator.app/Contents/MacOS/pdcomm-simulator-mcp-attach.
The attach program relays standard input and output to the GUI unchanged. From then on, the MCP conversation is the same as before, with 175 tools. For example, simulator.ui.navigate with {"workspace_id":"trace_diagnostics"} switches the GUI to the Message Trace & Diagnostics workspace. When you close the GUI, it removes the descriptor file.
| Attach exit code | Meaning |
|---|---|
| 0 | Standard input was closed. |
| 2 | Invalid arguments |
| 3 | The descriptor is missing, malformed, or has the wrong owner or permissions. |
| 4 | Connection or authentication failed. |
| 5 | The GUI disconnected, or writing standard output failed. |
| 6 | Reading standard input failed, or an input line exceeded 1 MiB. |
Related pages
Section titled “Related pages”- Automate the simulator and reproduce errors
- Headless simulator
- Simulator GUI
- Use the docs from AI tools, a separate server that searches these docs