콘텐츠로 이동
무료 평가 시작

시뮬레이터 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"]
}
}
}

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"}

독립 서버의 세션은 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"}

호스트는 먼저 이름을 조회(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입니다.

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를 MCP로 조작하려면 GUI를 --mcp-local로 실행하고, 클라이언트가 pdcomm-simulator-mcp-attach를 실행하게 합니다.

  1. GUI를 서술자(descriptor) 파일 경로와 함께 실행합니다. 경로에 파일이 이미 있으면 GUI가 종료 코드 3으로 끝납니다.

    터미널 창
    "$PDCOMM_SIMULATOR/pdcomm-simulator" --mcp-local "$HOME/pdcomm-tutorial/results/gui-descriptor.json"

    GUI는 소유자만 읽을 수 있는(0600) 서술자 파일을 만듭니다. 이 파일에 로컬 연결 이름과 인증 토큰(capability token)이 들어 있습니다.

  2. 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를 넘는 입력 줄