What We Learned Letting Claude and ChatGPT Edit Live Websites Over MCP
We run Keepp, a link in bio and website builder for creators and small businesses: one page that holds a website, selling, bookings, forms and chat. A few weeks ago we shipped an MCP server so Claude and ChatGPT can buil
We run Keepp, a link in bio and website builder for creators and small businesses: one page that holds a website, selling, bookings, forms and chat. A few weeks ago we shipped an MCP server so Claude and ChatGPT can build and edit those pages directly. A person says "add my new class times and a booking button" in a chat, and the assistant changes a real, public page.
That is a different job from most MCP servers, which read data or run a search. Ours writes to something strangers will see. Here is what that taught us.
1. Let the model write the whole document, but make stale writes fail
Our first instinct was a dozen small tools: add a block, move a block, change a colour. Models were bad at it. They lost track of order, half-applied changes, and made the page inconsistent.
What worked was one tool, put_page, that replaces the whole page document. The model calls get_page, edits what it got back, and sends the complete list of blocks. Anything left out is deleted, and the tool description says so plainly.
The risk is obvious: the owner edits the page in the editor while the assistant is mid-change, and the assistant overwrites it. So every get_page returns a version, put_page requires it, and a write against an old version fails with a clear error instead of silently winning. Optimistic concurrency turned out to be the single most important guard on the whole server.
2. Keep the schema out of every tool listing
A page has many block types, each with its own fields. Putting all of that in the put_page input schema bloated every tool listing the host sent to the model, on every turn.
We moved the full block catalogue into an MCP resource. The tool description points at it, so the model reads it once when it needs it, rather than carrying it in every request. Tool listings stayed small and the model still had every field when it built a page.
3. Never trust a URL the model gives you
Models love to put image URLs straight into a page, often from wherever they found them. That makes the public page depend on someone else's server, and some of those images vanish a day later.
Every image URL in a write is now downloaded and stored on our side as part of the write. If one cannot be fetched, the whole write fails and names the block. The model gets told exactly what went wrong and usually fixes it on the next try.
4. Return structured results on every path
Hosts handle tool results differently. Some only show the model the text content, some read structuredContent. We declare an outputSchema on every tool and return structured content on every success path, with a readable sentence in the text as well. A test fails the build if any tool is missing either.
The same goes for safety hints. Each tool declares whether it is read-only, destructive, or touches the outside world, from one table in the codebase, so hosts can decide when to ask the user first.
5. OAuth for native clients needs loopback on any port
Remote MCP over OAuth worked first time in browser hosts. It broke for native clients like Claude Code, which sign in through a callback on localhost with a random port. Our redirect check wanted an exact match.
The fix is in the spec for native apps: loopback redirect URIs are allowed on any port. Once we accepted that, sign-in worked everywhere. Client ID Metadata Documents also meant we did not need dynamic client registration for the hosts we cared about.
6. Different hosts need different tool sets
Not every host wants every tool. Some app directories do not allow selling subscriptions inside the assistant, so for those clients we hide the plan tools entirely rather than letting them fail. Filtering by client at listing time is cleaner than refusing at call time.
What it looks like in practice
The result is that a creator can open Claude or ChatGPT, connect Keepp once, and say what they want: a shop with three products, a booking page for sessions, a contact form and a chat. The assistant reads the catalogue, writes the page, and gets back the live URL and a link for each part that opens as its own page.
If you are building an MCP server that writes to anything public, the short version is: give the model whole documents with a version check, keep big schemas in resources, own every asset it hands you, and return structured results everywhere.
Our developer docs, including how to connect the MCP server, are at keepp.link/developers. Keepp itself is at keepp.link.
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.