AI agents are good at writing code. What they mostly cannot do is operate the software you already run in production: publish the article, schedule the push, fulfill the order. Not because the models are incapable, but because most production apps expose no structured surface an agent can act on.
MCP fixes exactly that. This post is the hands-on setup I use to let an agent operate a live mobile app: real endpoint, real tool calls, real payloads, and the gotchas I hit.
Disclosure up front: I run engineering at GoodBarber, an app platform, so the production app in this post runs on our MCP server. The patterns transfer to any remote MCP server you point an agent at.
The server is a hosted remote MCP server. No npx
, nothing local to run:
https://mcp.goodbarber.dev/mcp/sse
The /sse
path is historical; the server answers both SSE and Streamable HTTP, so every current client works.
Claude Code:
claude mcp add --transport sse goodbarber https://mcp.goodbarber.dev/mcp/sse
or in .mcp.json
:
{
"mcpServers": {
"goodbarber": {
"type": "sse",
"url": "https://mcp.goodbarber.dev/mcp/sse"
}
}
}
Claude (claude.ai): Settings, then Connectors, then add a custom connector with that URL.
ChatGPT: Settings, then Apps & Connectors, enable Developer mode, create a connector with the same URL. Write actions worked on a free account when I tested it; OpenAI's docs gate some of this by plan, so verify on yours.
Codex: add a custom MCP server, transport Streamable HTTP, leave the bearer token field empty. Saving opens the OAuth flow.
The first tool use triggers OAuth in the browser: you sign in with the app's account, and the session is scoped to that single app. No API key to paste anywhere. That scoping does a lot of security work later.
After OAuth, the client pulls the tool list. What the agent sees is not "the API": it is an operations menu, namespaced by domain. The cms_
tools cover articles, events, and media. The shop_
tools cover products, variants, orders, and promo codes. The classic_
tools cover push, analytics, and memberships. The full inventory is public in the server card, which is the file to read before writing any client code:
https://mcp.goodbarber.dev/.well-known/mcp/server-card.json
Push is the scariest operation to hand an agent (a sent push has no undo), which makes it the best test of a server's design.
Me, in the chat:
Schedule a push for 6 PM tonight: "Doors open at 7. First 50 people get the poster."
The agent calls classic_create_push_broadcast
:
{
"message": "Doors open at 7. First 50 people get the poster.",
"send": "at",
"send_at": "2026-07-29T18:00+02:00"
}
Three details in this schema are worth noticing:
message
is capped at 255 characters server-side, so the agent gets a hard error instead of a silently truncated push.send
is an enum, now
or at
; scheduling requires the timezone-aware send_at
, and the tool handles the UTC conversion. The agent does no date math.action_type
is one of open_app
, external_link
, or section
. "Open the tickets section" resolves to a real section id, not a guessed deeplink.The result comes back confirming the scheduled send, along with something rarer: a policy block. Every write on this server returns _mcp_policy.verification_required: true
. The server's own guidance tells the agent to read back what it just wrote before declaring success. There is even a meta tool for this: meta_get_tool_plan
takes a tool name and returns the recommended discover, call, verify sequence plus the failure policy. Agents follow instructions embedded in tool results remarkably well; putting that discipline server-side beats hoping every client prompt remembers it.
Publish an article (CMS). cms_create_article
wants a title and category ids, and category ids come from cms_list_cms_sections
first. Discover, then write:
{
"title": "Matchday guide: what to know before Saturday",
"categories": [4821],
"status": "stock",
"publishedDate": "2026-08-01T08:00:00+02:00"
}
status
is published
, draft
, or stock
; a future publishedDate
requires stock
, which is the scheduled state. Body content is its own resource: cms_create_article_paragraph
, one call per paragraph, with cms_reorder_article_paragraphs
when the agent restructures. There is also an accessTier
field (free
or premium
) that hooks straight into the app's paywall.
Add a product and a variant (shop). shop_create_product
first:
{
"title": "Home Kit Hoodie 2026",
"status": "DRAFT",
"collections": [312]
}
then shop_create_variant
:
{
"product_id": 88410,
"price": "49.00000",
"stock": 120,
"sku": "HK26-M",
"option_values": [{ "option_id": 17, "value": "M" }]
}
Two things I like here. price
is a decimal string, not a float: whoever wrote this schema has met floating-point money. And the variant model is strict: all variants of a product must share the exact same set of option_id
s, so introducing a Size option on one variant forces you to define it on all of them. That is a real invariant of the commerce domain, enforced at the tool layer. A raw database connection would let your agent violate it silently.
Look up and update an order (fulfillment). shop_list_orders
filters by status and date range. shop_update_order_shipping
moves an order along a one-way state machine, PENDING → FULFILLED → DELIVERED, with optional tracking:
{
"order_id": 55231,
"status": "FULFILLED",
"shipping_tracking_num": "6A0301234567",
"shipping_tracking_url": "https://tracking.example.com/6A0301234567"
}
The status
enum only contains FULFILLED
and DELIVERED
. You cannot un-deliver an order through this surface, however confused the agent gets.
Pull the numbers (analytics). classic_list_downloads
and classic_list_page_views
take plain ISO date ranges and return aggregates. The stats family is read-only by construction; there is nothing to break. My standing Monday ask is one sentence: last week's downloads and page views versus the previous week, flag anything odd.
This is what I would look at before connecting an agent to anything in production:
shop_
namespace is simply absent; push not configured, no push tools. The agent cannot call what it cannot see. Corollary if you write client code: never hardcode the tool list, read it at connect time or from the server card._mcp_policy
block rides on every write result.On top of the server, there is an open-source repo of 44 Skills: markdown recipes in the Claude Skills format, one per workflow (create a product with variants, schedule a push campaign, process the morning's orders, and so on). They encode the discover-then-write sequences above so the agent does not rediscover them every session:
https://github.com/goodbarber/goodbarber-skills
The terms allow rebranding and redistribution; they were written for resellers. The server itself is proprietary. The recipes are the open part.
Things I would want to know before recommending this to another engineer:
shop_create_product
calls. Bulk import stays a back-office job; the agent shines on the daily delta, not the migration.cms_list_cms_sections
, shop_list_products
); meta_get_tool_plan
returns exactly that sequence per tool.send: "at"
rather than now
, so there is always a review window between the ask and the broadcast.Everyone is racing to make agents build software. The quieter and, I think, more useful shift is agents operating the software you already have. Building is a one-time event. Operating is every day.
If you want to poke at a live implementation: the server card is public, the Skills repo is open, and the non-dev version of this story lives at goodbarber.com/mcp.