A few months after the coding agents started opening pull requests, the support team asked the obvious question. The assistant can read the code; can it read the device's logs? Can it look up the ticket, check the firmware version, and tell me what is wrong, instead of me copying and pasting all of that into a chat window? The answer was yes, and the way we said it was the Model Context Protocol. This article is about what MCP is, how we built our first servers, and the rules we put in place before an agent got any tools at all. The rules are the important part.
What MCP is
The Model Context Protocol is an open protocol for connecting AI applications to tools and data. It started at Anthropic in late 2024 and has since been adopted across the industry, so the same server works with clients from several vendors. The idea is simple: an MCP server exposes tools the model can call, resources it can read and prompts it can use, described with typed schemas. An MCP client, which is the AI application, discovers what a server offers and lets the model use it. The two talk JSON-RPC over standard input and output for local tools, or over HTTP for remote ones.
The analogy everyone uses is a standard connector. Before MCP, every AI application integrated with every system in its own way. After it, you build one server for your system and every client can use it. For a company with a REST API and a dozen internal tools, that is the difference between a project and a product.
From OpenAPI to MCP in an afternoon
Our first server wrapped the REST API behind SafeControl. That API has had an OpenAPI description since the REST article, and it turned out that an OpenAPI operation and an MCP tool are nearly the same thing: a name, a description, typed parameters and a typed result. The server is a thin C# service built on the official SDK that sits next to the Delphi backend and calls it. The Delphi code did not change at all, which I mention because people assume you need to rewrite something to make it AI-ready. You need an API with a good description, which you should have had anyway.
A tool in that SDK is a method with an attribute and a description. This one is the first we shipped:
[McpServerToolType]
public sealed class DeviceTools(ISafeControlApi api)
{
[McpServerTool, Description("Returns the current status of one device: online state, firmware version, last contact and open alarms.")]
public Task<DeviceStatus> GetDeviceStatus(
[Description("Device serial number as printed on the label")] string serial)
=> api.GetDeviceStatusAsync(serial);
}
The description is not documentation for humans. It is the text the model reads to decide when to call the tool and what to put in it, and the quality of your tool descriptions is the quality of your agent. We rewrote ours three times.
The first server exposed only reads: list devices, get device status, fetch filtered logs, search tickets, look up a firmware release note. The support engineer asks a question in the assistant, the assistant calls two or three tools, and the answer arrives with the device's actual status and the relevant log lines, instead of a guess from memory.
The log tool, specifically
The naive version of a log tool returns the log. A device log can be tens of megabytes, which is far more than a model should read and far more than you want to pay for. Our tool takes a serial number, a time window and a minimum severity, and returns a structured excerpt: the matching lines, the lines around any error, and counts of everything it left out. Filtering is deterministic code, summarising is the model's job, and the split is what makes the result trustworthy. The model never claims to have read lines it was not given.
The rules
This is the chapter I would ask you to read even if you skip the rest. We build systems that decide whether a safe deposit locker opens. Before any agent could call any tool, these were written down and agreed with the people who own the risk.
- Read-only by default. The first server could not change anything, and most tools still cannot. Tools that write, such as acknowledging an alarm or changing a schedule, live in a separate server, are listed separately, and require the user to approve each call in the client before it runs.
- Nothing that opens a safe is reachable. Not behind an approval, not behind a role. The operations that release a locker or change a lock's access rules are not exposed through MCP at all. That decision took five minutes and I expect it to stand for years.
- The server acts as the user. There is no service account with broad rights. The signed-in engineer's identity and permissions pass through to the API, so the assistant can see exactly what that engineer could see on their own, and nothing more. Every call is logged with who asked, what was called, with which parameters, and what came back.
- Tool results are untrusted input. A log line or a ticket can contain text that looks like an instruction to the model, written by anyone from a customer to an attacker. The model is told, in the system prompt and in every tool result, that data is data. Our evaluation set includes poisoned logs with planted instructions, and a tool call that follows one is a failed test, the same as a crash.
- No MCP server on the open internet. Local tools use standard input and output on the engineer's machine. Remote servers sit behind the same authentication as the API they wrap, inside the same network boundary.
- Third-party servers are dependencies. An MCP server somebody published is code that runs with access to your systems. We vendor it, pin it, read it and review updates, exactly as the DevSecOps article says to do with any library. Most of the ones we looked at, we did not adopt.
None of this is specific to MCP. It is the same least-privilege, audit-everything, trust-nothing-from-outside discipline we apply to the product. MCP just made the question concrete, because for the first time a piece of software was going to decide, on its own, which calls to make.
What it changed
The support engineers stopped copying logs into chat windows. Time to a first diagnosis dropped, most visibly for the engineers who joined recently and do not yet know which log lines matter. And the transparency turned out to be a feature: the assistant shows which tools it called and with what, so an engineer can see that it looked at the right device and the right window, and can distrust it when it did not.
The next step is using the same tools inside the product itself, for log analysis, troubleshooting and triage, which is the subject of the next article.
Questions for you
- Which of your systems has an API description good enough to become a tool catalogue today, and which one would embarrass you?
- Where would you draw the line between read-only tools, approved writes and operations that are never exposed?
- Have you tested your agent against data that contains instructions? What happened?
Email me at bjorn.nostdahl@nostdahl.com. If you run MCP servers in a regulated environment, I want to hear how you handle identity, because that is the part the tutorials skip.