The previous post covered what an MCP server is and why manufacturing is full of exactly the disconnected data it's built for. This one is the build: a small, read-only MCP server that lets an AI answer real questions about your machines — and, just as importantly, the guardrails that keep it safe on a shop network.
The architecture
It's three layers. Your data source (FOCAS, MTConnect, the ERP database). An MCP server that exposes a few tools — plain functions like get_machine_status or list_late_jobs. And an LLM host (a desktop assistant, an IDE, or your own agent) that connects to the server and calls those tools when a question needs them. The AI never touches your machine directly; it only ever calls the functions you wrote.
from mcp.server.fastmcp import FastMCP
from focas_reader import read_status # your FOCAS wrapper
mcp = FastMCP('shop-floor')
@mcp.tool()
def get_machine_status(machine: str) -> dict:
"""Return live status for one machine: running/idle, feed, rpm, alarm."""
ip = MACHINES[machine] # a fixed, allow-listed lookup
return read_status(ip) # read-only FOCAS call
@mcp.tool()
def list_late_jobs() -> list[dict]:
"""Jobs whose due date has passed and aren't complete."""
return db.query('SELECT * FROM jobs WHERE due < NOW() AND status != "done"')
if __name__ == '__main__':
mcp.run()That's a real, if minimal, server. Each @mcp.tool() function becomes something the AI can call — with a docstring the model reads to know when to call it. Ask "is machine 3 running?" and the host calls get_machine_status('3') and answers from the live result.
Read-only, and mean it
Every tool you expose is a capability you're handing an AI. Start with read-only tools and keep it there until you have a very good reason not to. Never expose a tool that can start a cycle, change an offset, or write a macro without a human confirmation step and a hard allow-list. The failure mode of a chatty read-only agent is a wrong answer; the failure mode of a write-capable one is a crash.
Grounding the agent in more than status
Live status is the start. Add resources — a machine list, the job schedule, the tool library — so the agent has context, not just sensors. And for the tribal-knowledge questions ("why does this part chatter on op 20"), point a retrieval tool at your manuals, setup notes, and past job records so the model can cite real documents instead of guessing.
Running it safely on a shop network
- Keep it local. A local or air-gapped LLM means machine data never leaves the building — often non-negotiable for a real shop.
- Allow-list everything. Machines, tables, and queries the server can touch should be an explicit list, not whatever the AI asks for.
- Audit every call. Log which tool ran, with what arguments, and what it returned. You want a paper trail the day someone asks what the agent did.
- Scope the reads. The server's database user should have read-only permissions on only the tables it needs.
The model is the easy part — you can swap it in an afternoon. The durable work is the boring, careful layer underneath: the tools, the allow-lists, and the audit log.
This is the natural payoff of already being able to read your machine data in code — the agent is just a friendlier front door onto data you can already reach. If you want a read-only shop-floor agent built and secured properly, that's exactly the kind of project I take on.


