시뮬레이터 MCP 서버
SECS/GEM 시뮬레이터는 MCP(Model Context Protocol) 서버로 동작할 수 있습니다. Claude Code, Cursor 같은 AI 도구나 직접 만든 스크립트가 MCP 도구(tool)를 호출해 시뮬레이터 세션을 만들고, 통신을 수립하고, 보고서·알람·명령·공정 프로그램 같은 GEM 기능을 실행합니다.
실행 방식 세 가지
섹션 제목: “실행 방식 세 가지”| 실행 방법 | 제어하는 대상 | 도구 수 |
|---|---|---|
pdcomm-simulator-mcp --stdio |
이 프로세스가 소유한 세션. 화면이 없고 Qt가 필요하지 않습니다. | 169 |
pdcomm-simulator --mcp-stdio |
이 GUI 프로세스의 세션과 화면 | 175 |
pdcomm-simulator --mcp-local <descriptor> + pdcomm-simulator-mcp-attach --descriptor <descriptor> |
이미 실행 중인 같은 GUI에 붙습니다. | 175 |
GUI에 붙는 두 방식은 화면을 다루는 simulator.ui.* 도구 6개가 더 있습니다. 서로 다른 프로세스는 세션을 공유하지 않습니다. 예를 들어 pdcomm-simulator-mcp를 따로 실행하면 이미 열린 GUI의 세션을 볼 수 없습니다.
서버를 등록합니다
섹션 제목: “서버를 등록합니다”MCP 클라이언트는 서버를 자식 프로세스(child process)로 실행하고 표준 입출력으로 통신합니다. 대부분의 클라이언트는 다음 형식의 JSON 설정을 씁니다. 경로는 실제 위치로 바꿉니다.
{ "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"] } }}GUI를 직접 조작하게 하려면 command를 GUI 실행 파일(pdcomm-simulator, macOS는 pdcomm-simulator.app/Contents/MacOS/pdcomm-simulator)로, args를 ["--mcp-stdio"]로 바꿉니다. 이 경우 클라이언트가 GUI 창을 띄우므로 데스크톱 세션이 필요합니다.
실행 옵션
섹션 제목: “실행 옵션”| 옵션 | 설명 |
|---|---|
--stdio (GUI는 --mcp-stdio) |
표준 입출력으로 MCP를 제공합니다. 정확히 한 번 줍니다. |
--allow-endpoint <IPv4:port> |
루프백(loopback)이 아닌 주소를 세션 주소로 허용합니다. 32번까지 반복할 수 있습니다. 0.0.0.0은 허용하지 않습니다. |
--request-deadline-ms <1–600000> |
요청 하나의 처리 제한 시간. 기본값은 10000입니다. |
기본으로는 127.0.0.1 같은 루프백 주소만 세션 주소로 쓸 수 있습니다. 다른 컴퓨터의 장비나 호스트와 연결하려면 그 주소를 --allow-endpoint로 허용합니다.
종료 코드
섹션 제목: “종료 코드”| 코드 | 의미 |
|---|---|
| 0 | 클라이언트가 표준 입력을 닫아 정상 종료했습니다. |
| 1 | 표준 출력 쓰기 실패 등 입출력 오류 |
| 2 | 잘못된 실행 인자 |
| 130 | SIGINT/SIGTERM(Windows는 Ctrl+C, Ctrl+Break, 창 닫기)으로 종료 |
연결과 도구 목록
섹션 제목: “연결과 도구 목록”서버는 MCP 프로토콜 버전 2025-11-25(일반적인 initialize 방식)와 2026-07-28(요청마다 _meta를 붙이는 방식)을 지원합니다. 일반적인 클라이언트는 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"}}}이어서 notifications/initialized 알림을 보낸 뒤 tools/list로 도구를 읽습니다. 도구 이름과 입력 스키마(input schema)는 이 목록에서 읽고, 클라이언트 코드에 도구 수를 고정하지 않습니다.
도구 그룹
섹션 제목: “도구 그룹”| 그룹 | 도구 수 | 하는 일 |
|---|---|---|
simulator.capabilities |
1 | 지원 기능과 한도 |
simulator.session |
4 | 세션 생성, 목록, 조회, 삭제 |
simulator.lifecycle |
6 | 시작, 정지, Select, Deselect, 통신 수립, 폴링(poll) |
simulator.status_data |
5 | 상태·데이터 변수 목록, 장비 값 변경, 호스트의 이름·값 조회 |
simulator.reports_events |
9 | 보고서·이벤트 카탈로그, 이벤트 발행, 수신 이벤트 확인과 응답, 원격 보고서 설정 |
simulator.alarms |
9 | 알람 카탈로그, 관찰, 발행, 사용 설정, 목록 조회, 응답 |
simulator.commands |
8 | 원격 명령 카탈로그, 요청, 장비 응답, 결과 |
simulator.programs |
53 | 비정형·정형 공정 프로그램, 검증, 선택, 실행, 기능 카탈로그 |
simulator.spooling |
17 | 스풀링 설정, 캡처, 호스트 제어, 전달 증거 |
simulator.constants |
8 | 장비 상수 카탈로그, 조회, 변경, 응답 |
simulator.data_variables |
5 | 데이터 변수 카탈로그와 캐시, 샘플을 담은 이벤트 발행 |
simulator.directories |
5 | 정적 이벤트 디렉터리, 데이터·이벤트 디렉터리 요청 |
simulator.report_queries |
5 | 보고서 조회와 응답 |
simulator.profiles |
5 | 장비 프로필 |
simulator.scenarios |
7 | 시나리오 정의와 실행 |
simulator.trace |
4 | 추적 읽기, 내보내기, 검사, 지우기 |
simulator.project |
2 | 프로젝트 정의 내보내기와 교체 |
simulator.diagnostics |
1 | 세션 진단 정보 |
simulator.transport |
3 | 쓰기 배압 장애 설정과 해제 |
simulator.secs1 |
10 | SECS-I 가상 쌍 |
simulator.gem300 |
2 | GEM300 기능 카탈로그 |
simulator.ui (GUI 모드만) |
6 | 화면 상태, 작업 공간 이동, 테마, 세션 연결, 추적 열기·닫기 |
리소스(resource)로도 같은 정보를 읽을 수 있습니다. resources/list는 pdcomm://capabilities, pdcomm://sessions, pdcomm://profiles, pdcomm://scenarios를, resources/templates/list는 pdcomm://sessions/{session_id}/trace 같은 세션별 리소스를 알려 줍니다.
세션 식별과 변경 규칙
섹션 제목: “세션 식별과 변경 규칙”MCP 도구의 세션은 다음 값으로 식별합니다.
session_id:sim-1같은 서버 안의 세션 이름입니다. HSMS 세션 ID(protocol_session_id)와 다릅니다.generation: 세션을 시작할 때마다 새로 받는 번호입니다. 서버 전체에서 증가하므로 세션마다 연속되지 않습니다.revision: 세션이 바뀔 때마다 1씩 증가합니다.
세션을 바꾸는 도구는 모두 expected_revision에 현재 revision을 넣어야 합니다. 값이 다르면 REVISION_CONFLICT로 거부되고 상태는 바뀌지 않습니다. 응답으로 받은 최신 generation과 revision을 다음 호출에 씁니다.
카탈로그 설정, 이벤트·알람 발행, 명령처럼 결과가 남는 도구는 operation_id도 받습니다. 1–64바이트의 문자열입니다. 같은 operation_id와 같은 입력으로 다시 호출하면 이전 결과를 replayed: true로 돌려주고 다시 실행하지 않습니다. 시간 초과나 연결 끊김 뒤에는 새 ID로 다시 보내지 말고 같은 ID로 다시 호출해 결과를 확인합니다.
실습: 장비와 호스트를 만들고 값을 읽습니다
섹션 제목: “실습: 장비와 호스트를 만들고 값을 읽습니다”아래 순서는 독립 서버(pdcomm-simulator-mcp --stdio)에서 실제로 실행한 요청과 응답입니다. 응답의 structuredContent만 줄여서 보여 줍니다.
1. 장비 세션을 만들고 시작합니다
섹션 제목: “1. 장비 세션을 만들고 시작합니다”{"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"}시작하면 generation이 새로 바뀌고 revision이 1이 됩니다. 포트 0을 주었으므로 실제 포트는 local_address에서 읽습니다.
2. 호스트 세션을 만들고 시작합니다
섹션 제목: “2. 호스트 세션을 만들고 시작합니다”{"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. 폴링하며 통신을 수립합니다
섹션 제목: “3. 폴링하며 통신을 수립합니다”독립 서버의 세션은 simulator.lifecycle.poll을 호출할 때만 입출력을 진행합니다. 두 세션을 번갈아 폴링하고, 호스트가 tcp_state: "selected", communication_state: "not_communicating"이 되면 simulator.lifecycle.begin_communication을 호출합니다. 두 세션이 모두 "communicating"이 될 때까지 폴링을 계속합니다. 이 실습에서는 다섯 번 만에 통신이 수립되었습니다.
{"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. 상태 변수를 읽습니다
섹션 제목: “4. 상태 변수를 읽습니다”호스트는 먼저 이름을 조회(simulator.status_data.query_names)한 뒤에 값을 조회(simulator.status_data.query_values)할 수 있습니다. 결과는 폴링 뒤 호스트 세션의 simulator.status_data.list에 remote: true로 나타납니다.
{"name":"simulator.status_data.query_names","arguments":{"session_id":"sim-2","generation":4,"expected_revision":6,"ids":[1001,1002]}}값까지 받은 뒤 simulator.status_data.list의 결과입니다.
{"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은 SECS-II 형식 코드입니다. 16(8진수 20)은 ASCII, 44(8진수 54)는 U4입니다.
5. 추적을 확인하고 정리합니다
섹션 제목: “5. 추적을 확인하고 정리합니다”simulator.trace.read는 세션의 최근 기록(최대 256개)을 돌려줍니다. 각 기록의 category는 activity, received, error 중 하나이고, source는 service, tcp, gem 중 하나입니다. 메시지 본문은 기록하지 않습니다.
마지막으로 각 세션에 simulator.lifecycle.stop과 simulator.session.destroy를 호출하고, 클라이언트가 표준 입력을 닫으면 서버가 종료 코드 0으로 끝납니다.
오류 처리
섹션 제목: “오류 처리”오류는 두 층으로 나뉩니다.
- JSON-RPC 오류: 요청 형식, 알 수 없는 메서드(
-32601), 알 수 없는 도구나 잘못된 매개변수(-32602), 처리 제한 시간 초과(-32603) 등입니다. - 도구 오류: 정상
result에isError: true와structuredContent.error가 담깁니다.
revision이 맞지 않을 때의 실제 응답입니다.
{"error":{"code":"REVISION_CONFLICT","field":"expected_revision","message":"session revision does not match"}}| 도구 오류 코드 | 의미 |
|---|---|
INVALID_ARGUMENT |
입력이 스키마와 맞지 않거나 값이 범위를 벗어났습니다. |
SESSION_NOT_FOUND |
없는 session_id입니다. |
STALE_GENERATION |
이전 시작의 generation입니다. 세션을 다시 조회합니다. |
REVISION_CONFLICT |
expected_revision이 현재 값과 다릅니다. |
INVALID_STATE |
지금 상태에서 할 수 없습니다. 예: 통신 수립 전의 조회, 시작 후의 카탈로그 설정 |
ROLE_FORBIDDEN |
역할이 맞지 않습니다. 예: 장비 세션에서 begin_communication |
LIMIT_EXCEEDED |
세션(최대 8개) 같은 한도를 넘었습니다. 메시지 한도에 들어가지 않는 이벤트 보고(S6F11)도 여기에 해당하며, 이때는 아무것도 보내지 않습니다. 이벤트 보고의 크기 한도를 참고합니다. |
DEADLINE_EXCEEDED, CANCELLED |
변경 전에 시간이 초과되었거나 취소되었습니다. |
UNSUPPORTED_CAPABILITY, CORE_FAILURE, SHUTTING_DOWN |
지원하지 않는 기능, 내부 오류, 종료 중 |
거부된 호출은 세션 상태를 바꾸지 않습니다. isError와 JSON-RPC error를 모두 실패로 처리합니다.
이벤트 보고의 크기 한도
섹션 제목: “이벤트 보고의 크기 한도”장비 세션과 호스트 세션은 같은 메시지 한도를 씁니다. 메시지 본문은 8,514바이트까지이고, 아이템 하나의 값은 4,096바이트까지입니다.
- 장비 세션은 이 한도에 들어가는 이벤트 보고(
S6F11)만 보냅니다. 더 큰 이벤트는simulator.reports_events.emit과simulator.data_variables.emit_with_samples가LIMIT_EXCEEDED로 거부하고 아무것도 보내지 않습니다. 두 세션은 계속 통신합니다. - 호스트 세션은 한도 안의 이벤트 보고를 모두 받습니다. 받은 이벤트의 값은 확인 응답(acknowledgement)을 보낼 때까지 인코딩된 바이트로 보관합니다. 이벤트 하나의 값 한도는
simulator.capabilities.get결과의limits.max_received_value_bytes입니다. - 호스트 세션은 이벤트 하나에 보고서 16개, 보고서 하나에 값 16개, 모두 합쳐 값 64개까지 보관합니다. 다른 제조사 장비 등이 이보다 큰 이벤트 보고를 보내면 형식이 올바라도 거부합니다. 이때 호스트는 ACKC6 1(오류, 수락하지 않음)을 담은
S6F12로 응답하고, 추적에reports_events.event_refused오류 레코드(코드LIMIT_EXCEEDED)를 남기며, 통신을 계속합니다. 거부 이유는simulator.trace.read와simulator.diagnostics.get의last_error_message에 나타나며, 거부된 이벤트는 받은 이벤트 목록에 나오지 않습니다. 본문을S6F11로 해석할 수 없을 때만S6F0으로 거래를 중단합니다. - 세션이 보관하는 다른 메시지에도 같은 규칙을 적용합니다. 형식이 올바른 메시지라도 시뮬레이터가 받아들이지 않는 제어 문자가 든 PPID처럼 시뮬레이터가 나타낼 수 없는 내용이 있으면, 세션은 메시지를 보관하지 않고 응답합니다. 호스트는
S7F27에 확인 코드가 없는S7F28로 응답합니다. 장비는S7F23에 ACKC7 1(허가하지 않음)을 담은S7F24로,S7F31에 ACKC7 1을 담은S7F32로,S7F25에 프로그램 저장소의S7F26으로 응답합니다. 이런 PPID의S7F26은 빈 목록입니다. 거부할 때마다 추적에 오류 레코드(programs.verification_report_refused,programs.formatted_transfer_refused,programs.verification_request_refused,programs.formatted_query_refused)가 남고 이유는last_error_message에 나타나며, 두 세션은 통신을 계속합니다. - PDComm 라이브러리에 설정된 한도를 넘는 메시지는 여전히
SxF0으로 중단합니다. 예를 들어 명령 하나의 매개변수가 16개를 넘거나S7F27의 문제 항목이 16개를 넘는 경우입니다. 장비는 그 전에S9F7(잘못된 데이터)을 보낼 수도 있습니다. - 대기 중인 이벤트가 16개가 되거나 그 값이 합쳐 65,536바이트(
limits.received_value_backpressure_bytes)에 이르면, 호스트는 새 메시지를 읽지 않습니다. 이때simulator.reports_events.list_received는backpressured: true를 돌려줍니다.simulator.reports_events.ack_received로 이벤트를 확인하면 다시 읽습니다. 이 한도 덕분에 대기 중인 이벤트 목록이 MCP 응답 한도(1 MiB) 안에 들어갑니다.
실행 중인 GUI에 붙습니다
섹션 제목: “실행 중인 GUI에 붙습니다”이미 연 GUI를 MCP로 조작하려면 GUI를 --mcp-local로 실행하고, 클라이언트가 pdcomm-simulator-mcp-attach를 실행하게 합니다.
-
GUI를 서술자(descriptor) 파일 경로와 함께 실행합니다. 경로에 파일이 이미 있으면 GUI가 종료 코드 3으로 끝납니다.
터미널 창 "$PDCOMM_SIMULATOR/pdcomm-simulator" --mcp-local "$HOME/pdcomm-tutorial/results/gui-descriptor.json"GUI는 소유자만 읽을 수 있는(0600) 서술자 파일을 만듭니다. 이 파일에 로컬 연결 이름과 인증 토큰(capability token)이 들어 있습니다.
-
MCP 클라이언트 설정의
command를pdcomm-simulator-mcp-attach로,args를["--descriptor", "<서술자 경로>"]로 정합니다. macOS에서는pdcomm-simulator.app/Contents/MacOS/pdcomm-simulator-mcp-attach입니다.
attach 프로그램은 표준 입출력과 GUI 사이를 그대로 중계합니다. 이후의 MCP 대화는 앞의 방식과 같고, 도구는 175개입니다. 예를 들어 simulator.ui.navigate에 {"workspace_id":"trace_diagnostics"}를 주면 GUI가 Message Trace & Diagnostics 작업 공간으로 이동합니다. GUI를 닫으면 서술자 파일이 지워집니다.
| attach 종료 코드 | 의미 |
|---|---|
| 0 | 표준 입력이 닫혀 정상 종료 |
| 2 | 잘못된 인자 |
| 3 | 서술자가 없거나, 형식이 틀렸거나, 권한·소유자가 맞지 않음 |
| 4 | 연결 또는 인증 실패 |
| 5 | GUI 연결 끊김, 표준 출력 오류 |
| 6 | 표준 입력 오류 또는 1 MiB를 넘는 입력 줄 |
관련 문서
섹션 제목: “관련 문서”- 시뮬레이터 자동화와 오류 재현
- headless 시뮬레이터
- 시뮬레이터 GUI
- AI 도구에서 PDComm 문서 사용하기: 문서 자체를 MCP로 검색하는 별도 서버입니다.