Skip to main content
Glama
jungyoseok
by jungyoseok

secs-gem-host

The open-source SECS/GEM host you point at equipment-side software to see whether it actually works. One command runs the SEMI E30 host startup sequence and prints a PASS/FAIL conformance table; the same engine is exposed as an MCP server so an AI agent can drive it.

npx secs-gem-host connect 127.0.0.1:5501 --report

Node ≥ 20. MIT. No runtime dependency except the MCP SDK.

Commands

secs-gem-host connect <host:port> [startup|state] [options]
secs-gem-host mcp

Option

Meaning

--device-id <n>

HSMS session / device id (default 0)

--passive

listen for the equipment instead of connecting to it; the <host:port> positional is the bind address, so use 127.0.0.1:<port> unless you need other machines to reach it

--report

run a conformance report, print the table, exit 1 on any FAIL. The kind is the positional after <host:port>: startup (default) is the 10-step E30 startup; state is the 6 E30 state rules — S1F1 aborted with S1F0 before S1F13, S2F41 refused with HCACK 2 until S1F17 — and must run on a fresh connection

--scenario <file>

run a JSON scenario after startup

--model <file>

equipment model used by startup steps 6–8 (default: bundled models/default.json)

--quiet

suppress the decoded message log

--linktest <sec>

linktest interval, 0 disables (default 30)

--t3 --t5 --t6 --t7 --t8 <sec>

HSMS timers in seconds (E37 defaults 45 10 5 10 5)

Plain connect stays up, prints every message in and out in SML with a timestamp and a direction, and re-runs the whole startup after a reconnect — like a real host.

Related MCP server: squish-mcp

The E30 startup

Step

Send

Expect

PASS when

HSMS

Select.req

Select.rsp

status 0 within T6

1

S1F13 W

S1F14

COMMACK 0, MDLN/SOFTREV present

2

S1F17 W

S1F18

ONLACK 0 or 2

3

S1F3 W (empty)

S1F4

list, within T3

4

S1F11 W (empty)

S1F12

non-empty SVID list

5

S2F29 W (empty)

S2F30

list

6

S2F33 W (delete all, then define)

S2F34

DRACK 0

7

S2F35 W

S2F36

LRACK 0

8

S2F37 W (CEED true, all)

S2F38

ERACK 0

9

S5F3 W (ALED enable, zero-length ALID = all alarms)

S5F4

ACKC5 0

10

S2F31 W (now)

S2F32

TIACK 0

idle

Linktest.req every 30 s

Linktest.rsp

within T6

The host answers S1F13 with S1F14, S1F1 with S1F2, S5F1 with S5F2, S6F11 with S6F12 and S6F1 with S6F2, and sends S9F3/S9F5/S9F9 on the standard conditions.

Beyond the per-step result, --report flags: S9Fx received, unsolicited <SxFy> before S1F14, reply outside T3 on <SxFy>, wrong system bytes on reply <SxFy>, W-bit set on reply <SxFy>, and primary with even function <SxFy>.

Scenarios

A scenario is a JSON array of steps run after startup. Bodies use the same item-tree shape as the SECS-II codec API.

[
  { "send": "S2F41", "body": { "type": "L", "children": [
      { "type": "A", "value": "START" },
      { "type": "L", "children": [] }
  ] } },
  { "expect": "S6F11", "ceid": 10001, "within": 5000 },
  { "wait": 1000 },
  { "drop-ack": "S6F11" },
  { "sleep-t3": true }
]

Step

Effect

{"send": "SxFy", "body": ...}

send a message; an odd function sets the W-bit and waits for the reply

{"expect": "SxFy", "ceid": n, "within": ms}

wait for a received message, optionally matching a CEID

{"wait": ms}

pause

{"drop-ack": "SxFy"}

do not ack the next SxFy primary, to exercise the equipment's retry/spool path

{"sleep-t3": true}

answer the next primary 46 s late, past T3, on purpose

Exit code is 1 if any scenario step or any startup step failed.

Item-tree body syntax

{ "type": "L", "children": [
  { "type": "A", "value": "TEXT" },
  { "type": "U4", "value": [1, 2, 3] },
  { "type": "BOOLEAN", "value": [true] },
  { "type": "B", "value": [0, 255] }
] }

Types: L B BOOLEAN A J I1 I2 I4 I8 U1 U2 U4 U8 F4 F8. Non-list scalar values are arrays; A/J take a string.

MCP server

Register it with Claude Code:

claude mcp add secs-gem-host -- npx -y secs-gem-host mcp

or in .mcp.json:

{
  "mcpServers": {
    "secs-gem-host": { "command": "npx", "args": ["-y", "secs-gem-host", "mcp"] }
  }
}

Tools: connect, disconnect, send, startup, events, status. startup returns the report object (rows, passed, total, flags) plus the rendered table; its optional kind is "startup" (default) or "state" for the six E30 state rules, which needs a fresh connection.

scadathings_feedback is the one tool that does not talk to equipment: it POSTs to scadathings.com to say whether one of its articles helped you solve a SECS/GEM problem, or that the article is wrong or outdated. It takes slug (e.g. secs-gem-s1f13-establish-communications), helped, and optionally a note (≤ 500 chars) and an agent name. Only what you pass is sent.

Equipment model

--model takes a JSON file describing what steps 6–8 should define:

{
  "mdln": "GENERIC-1",
  "softrev": "1.0.0",
  "svs": [{ "id": 101, "name": "SV_101", "type": "U2", "units": "", "value": 0 }],
  "reports": [{ "rptid": 10001, "vids": [10001, 10002] }],
  "events": [{ "ceid": 10001, "name": "EVENT_10001", "rptids": [10001] }],
  "alarms": [{ "alid": 1001, "text": "ALARM_1001" }]
}

The bundled default follows a common industrial numbering scheme: SVIDs 1–3 for the fixed GEM variables, 101+ for process values, 10000+ for named SVs and DVs, RPTID and CEID in parallel from 10001, ALIDs from 1001.

Testing your own equipment: write a model file

The bundled model is a guess. Real equipment has its own ids, and steps 6-8 define reports and link events by number — so with the default model the equipment answers DRACK 4 / LRACK 4 / ERACK 1 ("that id does not exist here"), which is a mismatch, not a defect.

  1. Copy models/default.json next to your run (keep it out of any public repo if the ids are customer data).

  2. Replace svs, reports, events and alarms with the equipment's own SVIDs, VIDs, RPTIDs, CEIDs and ALIDs. Step 4's S1F12 namelist prints the SVIDs the equipment actually has; the run also flags model VIDs not in equipment SVID list: <ids> before it sends S2F33. That flag is advisory — a VID that is a DV or an EC is legitimately absent from the S1F12 namelist.

  3. Run with --model <file>.

node dist/cli.js connect 127.0.0.1:5000 --report --t3 5 --device-id 1 --model site-model.json

--device-id must match the equipment's configured session id. If it does not, the equipment answers every primary with S9F1 (unrecognised device id) and every step fails.

Known findings

Results of running this tool against real equipment implementations.

secs-gem-simulator EQ1 (2026-09-05)

node dist/cli.js connect 127.0.0.1:5501 --report --quiet

Step Sent    Expect  Got     Ack  Time(ms)    Result  Note
------------------------------------------------------------------------
1    S1F13   S1F14   -       -    45009/45000 FAIL    T3 (S1F13) timeout
2    S1F17   S1F18   -       -    45018/45000 FAIL    T3 (S1F17) timeout
3    S1F3    S1F4    -       -    45005/45000 FAIL    T3 (S1F3) timeout
4    S1F11   S1F12   -       -    45026/45000 FAIL    T3 (S1F11) timeout
5    S2F29   S2F30   -       -    45005/45000 FAIL    T3 (S2F29) timeout
6    S2F33   S2F34   -       -    45022/45000 FAIL    T3 (S2F33) timeout
7    S2F35   S2F36   -       -    45010/45000 FAIL    T3 (S2F35) timeout
8    S2F37   S2F38   -       -    45020/45000 FAIL    T3 (S2F37) timeout
9    S5F3    S5F4    -       -    45007/45000 FAIL    T3 (S5F3) timeout
10   S2F31   S2F32   -       -    45021/45000 FAIL    T3 (S2F31) timeout
------------------------------------------------------------------------
E30 startup: 0/10 passed
flag: reply outside T3 on S1F13
flag: reply outside T3 on S1F17
flag: reply outside T3 on S1F3
flag: reply outside T3 on S1F11
flag: reply outside T3 on S2F29
flag: reply outside T3 on S2F33
flag: reply outside T3 on S2F35
flag: reply outside T3 on S2F37
flag: reply outside T3 on S5F3
flag: reply outside T3 on S2F31
exit=1

Every row is the same failure, so it is one finding, not ten.

  • Step 1 — the simulator did not send S1F14; the host waited the full T3 and gave up.

  • Steps 2–10 — S1F18, S1F4, S1F12, S2F30, S2F34, S2F36, S2F38, S5F4 and S2F32 never arrived either, for the same reason. The host sent S9F9 (transaction timer timeout) after each one and carried on to the next step, as E30 requires.

As of 2026-09-05 the simulator completes HSMS Select and answers Linktest, but sends no reply to any SECS-II data message. Every step therefore timed out at T3 (45 s), so the run takes about 7.5 minutes; pass --t3 5 to shorten it.

Changelog

0.2.0

  • --report state: six steps that score the E30 state rules instead of the startup sequence. Run on a fresh connection: S1F1 must come back as S1F0 before S1F13, and S2F41 must be refused (HCACK ≠ 0) until S1F17 has put the tool ON-LINE REMOTE. secs-gem-host connect 127.0.0.1:5501 state --report.

  • The MCP startup tool takes kind: "startup" | "state".

0.1.1

  • S5F3 "enable all alarms" is now E5-correct. The body was L[B 0x80, U4 [0]]; "all alarms" is a zero-length ALID item, L[B 0x80, U4 []]. Equipment that rejects an unknown ALID answered ACKC5 1 to the old body.

  • DRACK 4 / LRACK 4 / ERACK 1 now say what to do: the note names the model mismatch and points at --model, and the SVID namelist from step 4 is compared against the model's report VIDs, so the run flags model VIDs not in equipment SVID list: <ids> before S2F33 goes out.

Development

npm install
npm test        # tsc && chmod +x dist/cli.js && node --test "dist/test/**/*.js"

Licence

MIT © Yoseok Jung

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables AI agents to develop, test, and certify Roku applications by providing direct control over device functions like app deployment, remote input, and SceneGraph inspection. It supports automated workflows including real-time log collection, media monitoring, and certification verification.
    1
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Connects AI agents to Siemens industrial PLCs for automatic monitoring and control of industrial equipment.
    22
    MIT