The problem: agents invent APIs
I've been building CMS software for web designers since 2015. This year I started building with coding agents every day, and I kept hitting the same failure.
I'd ask an agent to add a section to a site, and it would confidently write a template that called a function that didn't exist. Plausible name, plausible arguments, wrong. Sometimes it came from an older version. Sometimes it was invented whole.
The code looked right, and that was the problem. A broken build is easy to catch. Plausible code against an API that isn't there gets past a quick review, and you find out later.
This isn't a bad-agent problem. The agent works with what it has: training data that's months or years old, and docs on a website that may or may not match the version actually installed.
Why better prompting doesn't fix it
My first instinct was better prompts: paste the docs into context, write a long instructions file. It helps a little. It doesn't solve it, because the docs you paste are a copy, and copies drift.
The real issue is the source of truth. The agent is guessing about a system that knows exactly what it is. The installed app knows its own schemas, its own content, and which functions this version actually exposes. Nothing was asking it.
So the fix had to live on the app's side. The install should be able to describe itself.
Let the install describe itself
Every Total CMS install now ships an MCP server. MCP, the Model Context Protocol, is the open standard Claude, ChatGPT, Cursor and others use to talk to outside tools. Through it, an agent can ask the running site for three things:
- Its schemas. The real collections and fields on this site, not a generic example from the docs.
- Its content. Live records, so the agent can look at what exists before it changes anything.
- Its documentation, for this version. The docs ship with the install and are served from it, so an agent working on 3.6 reads 3.6's Twig functions and field types. No drift between what the docs say and what the site does.
The third one did the most work. Once the agent could look up a real signature instead of recalling one, it stopped guessing.
Customers don't always update right away. An agent reading the latest docs on a website would reach for syntax or features their installed version doesn't have yet. Served from the install, the docs describe exactly what that site can do.
Writes go through the same door as humans
Reading is the easy half. Letting an agent write content is where people rightly get nervous.
The rule I settled on: an agent gets no special path. Every write goes through the same schema validation and fires the same events as a save from the admin UI. Permissions come from the same access groups that govern human editors, and an OAuth token gets exactly the checks a signed-in user gets.
So "can the agent touch this?" is answered in the same place as "can this editor touch this?" One permission system, not a second one bolted on for AI. The admin has a permission matrix showing what each group can reach, agents included.
Access comes in three levels: anonymous reads of public content, API-key access for the site owner, and per-user OAuth 2.1 with scopes, a consent screen, revocation and an audit log.
Guardrails at connect time
MCP lets a server send instructions when a client connects. Mine are short, and each one is a mistake I saw agents make:
- Look before acting.
- Check the docs instead of guessing.
- Change only the fields you mean to change.
- Never invent IDs.
Putting them in the server means every client receives them on connect, no matter who wrote the prompt.
The server also offers guided prompts for common jobs, like modeling a collection, writing content, or auditing SEO. A user starts a task with the right context already loaded instead of describing it from scratch.
One more guardrail sits on the OAuth side. Clients can register themselves, which makes connecting painless, so the consent screen shows exactly where a client will send you after you approve and warns when that destination is unfamiliar.
If you're doing this in your own PHP app
None of this is specific to a CMS. If your app has data and an API, here's what I'd pass on:
- Serve docs from the running code, not a website. Version-matched docs fix more than any prompt will.
- Reuse your permission layer. Give agents their own permission system and you have two to keep in sync, and the second one will be the weaker one.
- Send writes through your normal save path. Whatever a human save triggers, like validation, events and hooks, an agent save should trigger too.
- Put behavior instructions in the server. They travel with every connection, regardless of who's prompting.
- Start read-only. Reading is useful on its own and carries little risk. Add writes once the permission story is solid.
- Plain data formats help. My content is JSON on disk, so it's easy to see exactly what an agent changed. Nice to have, not required.
Building into the app isn't the only option, either. I wrote up the three ways to do this: an instruction file, a separate MCP server, or one built-in — with the tradeoffs of each: How to add an MCP server to a PHP app.
Try it
You can try the read side without installing anything. totalcms.co runs on Total CMS, and its MCP endpoint answers questions about the product's docs. In Claude Code:
claude mcp add --transport http totalcms https://totalcms.co/mcp
That endpoint is read-only: the product docs plus totalcms.co's public content. It won't show an agent building a site; that needs a local install.
Full disclosure: Total CMS is my product. Its a commercial flat-file PHP CMS. If you've made your own app legible to agents, I'd like to hear what you ran into.
Website: totalcms.co
Docs: docs.totalcms.co
Top comments (1)
The version part is what bites me most with Laravel. The agent writes for whichever version it saw the most of, so I get a helper that was removed a few majors ago, and it reads perfectly fine until it runs.
Asking the running install makes sense for exactly that. The install can't be out of date about itself, the docs site can.