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.
Connect it
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
First real call: schedule a push
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:
-
messageis capped at 255 characters server-side, so the agent gets a hard error instead of a silently truncated push. -
sendis an enum,noworat; scheduling requires the timezone-awaresend_at, and the tool handles the UTC conversion. The agent does no date math. - The tap action is structured too:
action_typeis one ofopen_app,external_link, orsection. "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.
Operate the app: one call per domain
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_ids, 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.
The security model (the part that actually matters)
This is what I would look at before connecting an agent to anything in production:
- OAuth per app, not per account. The session is scoped to a single app. Connect App A and the agent cannot tell App B exists. Agencies operating many client apps add one connection per app; there is a per-app URL form for that, on a white-label domain if you resell.
-
Feature-gating shapes the tool list. The tools exposed are a function of what the app has enabled. Connect an app with no shop and the
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. -
Verify-after-write is server policy, not client etiquette. The
_mcp_policyblock rides on every write result. - No design surface. Nothing in the inventory touches layout, navigation, or the build pipeline. The blast radius of a bad agent day is content, campaigns, and commerce state, all inspectable in the back office. Not the app itself.
Skills: the recipes layer
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.
Trade-offs and gotchas
Things I would want to know before recommending this to another engineer:
- The tool list varies per app. A tool name from a tutorial (or this post) may be absent on your app because the feature is off. Check the list, not the docs.
-
Conversational operations do not batch. Three hundred products means three hundred
shop_create_productcalls. Bulk import stays a back-office job; the agent shines on the daily delta, not the migration. - Verify-after-write costs round trips. A multi-step ask (product, three variants, launch push) runs tens of seconds, not milliseconds. That is deliberate, and the right trade for writes on a live app.
-
IDs, not vibes. Agents guess names when you let them. Make them discover first (
cms_list_cms_sections,shop_list_products);meta_get_tool_planreturns exactly that sequence per tool. -
Keep a human on send. My own rule: the agent schedules pushes with
send: "at"rather thannow, so there is always a review window between the ask and the broadcast. - One app per connection. A multi-app morning means multiple connectors. Scoped beats convenient.
Takeaway
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.
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.