PRJEDU_robot_mcp — an AI assistant runs a robot cell, inside rules it cannot change¶
Every file of this example — the XML, the scripts, the page — is in
PRJEDU_robot_mcp on GitHub.
Run it with run.bat (Windows) or ./run.sh (Linux) from that folder.
The robot cell of PRJEDU_robotarm — a six-axis arm that picks parts and puts them down, and the cell controller around it — with one more door: an MCP endpoint an AI assistant (Claude Code, Claude Desktop, any MCP client) comes in through.
The assistant can see the cell, start and stop the program and set the speed. It cannot switch the robot on, release its brakes, unlock a protective stop, go above its speed ceiling, see the robot's own document, or write into the person's channel — and a person at the page can switch it off altogether. None of that is in the assistant or in the page: it is in the engine, where neither can change it.
run.bat
Then open http://127.0.0.1:8098/ and connect an assistant (below), or see every call an assistant makes, without one:
try.bat what the assistant may and may not do
try.bat --run play the program and watch it (release the brakes on the page first)
On Linux: ./run.sh, then ./try.sh (try.py, Python 3 standard library).
| Document | What it is |
|---|---|
ROB/S00 |
the robot, simulated — as in PRJEDU_robotarm (dashboard server TCP 29999, Modbus TCP 1504) |
CEL/S00 |
the cell controller — the rules, the part handshake, the feeder, the page — and the assistant's channel. On the MCP whitelist (mcp_pub="1") |
AI/S00 |
the MCP endpoint http://127.0.0.1:8099/mcp (key demo-robot-key, role 3 Operator) and the project's three tools. Not on the whitelist |
Two operators, two channels¶
| the person, at the page | the assistant, over MCP | |
|---|---|---|
| writes | CMD, FEED, SPEED, AI_ENABLE |
AI_CMD, AI_SPEED |
| as | role 0 (the page) | role 3, Operator (pro_mcp_role) |
| kept apart by | xdb_acl="0xFF111113" — role 0 writes, every other role reads |
xdb_acl="0xFF113111" — role 3 writes, every other role reads |
So the page cannot write AI_CMD, and the assistant cannot write CMD —
the engine's built-in set_value on it is refused like any other write
xdb_acl does not allow. Both channels go through the same rules
(main.refusal): play only when the robot is RUNNING and its safety
NORMAL, power off not while a program plays. The assistant's channel adds
three of its own (main.on_ai_cmd, main.on_ai_speed):
- only what a program needs:
load pick_place.urp,load home.urp,play,pause,stop. Powering on, releasing the brakes and unlocking a protective stop stay with a person at the cell — someone has to look at why the robot stopped. - a speed ceiling:
AI_SPEED_MAX(50 %). - the person's switch:
AI_ENABLE = 0refuses everything the assistant asks. It does not stop the robot; the person decides what happens to what is running.
Every request and the cell's verdict go into AI_LOG; the page shows it.
What the assistant can see is decided by the whitelist: CEL/S00 has
mcp_pub="1", ROB/S00 and AI/S00 do not. The robot's own document — where
the guard door and the "walk into the arm" button live — does not exist for
it, and neither does the endpoint's own configuration (its key, its role).
The project's tools¶
The engine's own MCP tools know documents, elements, values, alarms and logs.
They do not know that this is a robot cell. Three tools in db/db_AI_mcp.js
say that — their descriptions are the instructions the assistant reads:
| tool | does |
|---|---|
cell_status |
the whole cell in one call: robot mode, safety, program and step, parts waiting and put down, time per part, speed, joints, tool position, the feeder (running/stopped, a part every N s), whether the assistant is allowed |
robot_command |
load / play / pause / stop — writes AI_CMD and returns the cell's verdict |
set_speed |
writes AI_SPEED and returns the verdict |
A tool runs with the endpoint's role: it is not a way around xdb_acl. The
verdict comes back in the same call — the cell's val_fnc runs inside the
write, so AI_OK and AI_LAST are already there when the write returns.
"Accepted" means sent to the robot, not done: the tool's description tells
the assistant to look at cell_status a second later.
Connecting an assistant¶
Claude Code — this directory has a .mcp.json that names the endpoint.
Start Claude Code in this directory while run.bat is running and approve
the server. Or add it by hand:
claude mcp add --transport http aymon-robot http://127.0.0.1:8099/mcp --header "Authorization: Bearer demo-robot-key"
Claude Desktop needs the mcp-remote bridge (Node.js 18+), as in
PRJEDU_mcp: URL http://127.0.0.1:8099/mcp, header
Authorization:${AUTH_HEADER}, AUTH_HEADER = Bearer demo-robot-key.
Then ask, for example:
- "How is the robot cell doing?"
- "Start the pick-and-place job." — before the brakes are released it will be told why not, and should ask you to do it on the page
- "Run it at 80 % speed." — and hear about the ceiling
- "The robot stopped. Unlock it and carry on." — try it after Walk into the arm on the page
- switch assistant allowed off on the page, and ask it to pause
The key is in this public repository: it is an example, not a secret.
What it measured¶
On a Windows 11 laptop and on Linux (WSL, Ubuntu 22.04), engine 4.3.4.467,
with try.py making the calls an assistant makes (not with an assistant):
system_info |
lists CEL/S00 only |
robot_command play, robot POWER_OFF |
refused: "play needs the robot RUNNING (it is POWER_OFF) - power on, brake release" |
robot_command 'brake release' (not in the tool's list; sent anyway) |
refused: "is for a person at the cell, not for the assistant" |
set_value on CEL/S00/CMD |
refused — xdb_acl for role 3; an ALERT line in the cell's log |
set_value on ROB/S00/DOOR |
"document not found or not published" |
set_speed 80 / 40 |
refused (ceiling 50) / written to register 132 |
| the person releases the brakes, the assistant loads and plays | the program runs; parts are put down; pause pauses it |
| the person switches the assistant off | pause and set_speed refused: "the operator has switched the assistant off"; on again, accepted |
| protective stop (the person walks into the arm) | the assistant's unlock refused (a person's job), its play refused (safety PROTECTIVE_STOP); the person unlocks after 5 s, the assistant's play resumes the program |
| a wrong key | HTTP 401; a FAIL line in AI/S00's log, which raises that document's alarm (sta 0x034B) |
| a tool call, round trip | cell_status 30 ms median, read_points 31 ms; set_speed 141 ms — it waits for the Modbus write to the robot |
Try this¶
| Do | See |
|---|---|
| ask the assistant to run the job while the guard door is open | it is refused with the reason; does it tell you what to do? |
change a tool's description in db/db_AI_mcp.js, reload |
the assistant behaves differently — the description is its instruction. (Restart the client: it caches the list.) |
raise AI_SPEED_MAX to 80, or add power on to ai_allowed |
what would you want to be true before you let an assistant do that? |
give the endpoint pro_mcp_role="2" (Monitor) |
every write is refused: the role decides, not the tools |
add mcp_pub="1" to ROB/S00 |
the assistant can now read the robot's document — and would it be able to open the door? (xdb_acl on DOOR) |
write a tool cell_report |
parts an hour, time per part, stops in the last hour — what an assistant would need to answer "how was the shift?" |
Licence¶
Seven connections: the robot's two, the cell's four, the MCP endpoint, in four documents. The FREE edition allows eight connections and five documents (since engine 4.3.4.561; before it six and four, and one connection was refused without the EDU licence), so this example runs with no licence at all. MCP itself is not in FREE, but works on local connections (127.0.0.1), which is all this example uses.