We build a lot of sites by prompt. It’s the whole point of shipping a CMS with a native MCP server: you point Claude at a finished HTML page, and it comes back as a component-driven site with every headline, link and image editable.
So we did what you should always do with your own product — we sat down and timed it.
Our original build recipe took around 30 minutes for a single landing page. We rewrote it, cut the redundant steps, and got that to 14 minutes. Good progress. But then we looked at where those 14 minutes actually went, and the answer was uncomfortable.
The site itself — fifteen components, a layout, a page, all wired up — took 3 minutes 40 seconds. The remaining ten minutes were five images. Fourteen kilobytes of them.
Five small images cost nearly three times more than the entire rest of the site. That’s not a slow step you optimise. That’s a design flaw, and no amount of prompt tuning was going to fix it — it had to be fixed in the server.
Why images were slow (it isn’t what you’d guess)
The obvious theory is network or storage. It was neither — it was the AI.
When an agent uploads a file through a tool call, the bytes travel as base64 text — and that text has to be generated by the model, token by token. A 5 KB image becomes roughly 7,000 characters the model has to type out, one piece at a time. The CMS wasn’t slow. The upload was slow because a language model was laboriously spelling out a picture.
It was also fragile in a way that’s genuinely hard to debug. One of our test files failed twice with a padding error. The cause turned out to be a long run of repeated characters inside the base64 getting collapsed somewhere in transit — the server received 1,621 characters where 1,624 were sent. It doesn’t fail loudly at the transport layer; it shows up much later as a corrupt-file error that points you at entirely the wrong problem.
The second issue was quieter but bigger in aggregate: our API was too chatty. Every write returned the full saved record. Save a page with eleven elements and you got back every element’s complete component definition — template, CSS, JavaScript, form schema — repeated once per element. That’s about 55 KB of JSON echoing back content the caller had just sent. Multiply across a whole build and the majority of the token cost wasn’t instructions. It was us talking to ourselves.
What we shipped
Two changes, both at the protocol level.
1. Images can now be ingested inline. Instead of uploading a file and then wiring its ID into your content, you put the URL directly in the element data:
"data": {
"image": { "sourceUrl": "https://example.com/studio.jpg" }
}
Enter fullscreen mode Exit fullscreen mode
The server fetches it, stores it, and substitutes the real file ID — inside the same save, on the same transaction. No base64. No separate upload step. The bytes never pass through the model at all.
2. Responses can be quiet. Every write tool now takes an optional verbose: false:
{ "id": 53 }
Enter fullscreen mode Exit fullscreen mode
That’s the whole response now, instead of three kilobytes. For pages and layouts you still get back the element ID mapping — the one thing the caller genuinely can’t work out for itself — and nothing else. It defaults to true, so nothing breaks for anyone already calling it.
The numbers
Same page, same components, same five images, three points in the journey:
Original recipe Rewritten recipe After the server changes Full build with images ~30 min ~14 min 4 min 0 s Image phase ~10 min 10 min 25 s 0 s — folded into the page save Tokens — ~225,000 ~40,000 Tool calls — 21 + uploads 19Worth separating those two jumps honestly, because they’re different kinds of work.
Getting from 30 to 14 minutes was prompt engineering — fewer redundant calls, better use of defaults, dropping steps that existed out of superstition. Useful, but we’d taken it about as far as it went.
Getting from 14 to 4 was protocol work, and it’s the bigger result: roughly 3.5× faster and 6× cheaper on an identical build. The saving comes from deleting a phase rather than speeding one up. Because a page can now be created with its elements and resolve image URLs in one call, the sequence went from “create empty page → upload five files → write everything back” to a single save.
Which is the general lesson for anyone building agent tooling: once you’ve tidied the prompt, the remaining cost is almost always in the API’s shape, not the agent’s instructions.
Why this matters if you build on Neleto
Your agent runs cost less. Token usage on a standard page build dropped by roughly 85%. If you’re running builds repeatedly — client sites, templates, a course, a test suite — that’s the difference between an experiment and something you do routinely.
There’s one less way to break your content. Our element data is replace-semantics: send a partial update and the omitted values are cleared. The old image workflow forced a second write pass over content you’d already saved, which is exactly the situation where that bites. We managed to blank an entire page’s text this way during testing — every section still rendered, just empty. With inline ingest there is no second pass, so the trap is gone. (It’s the same class of silent failure we built self-verifying checks to catch — deleting the failure mode outright is even better than catching it.)
It composes with everything else. sourceUrl accepts any URL the server can reach: a client’s existing media library, a CDN, a generated image, or a file already in Neleto. For an agency migrating a site, that means images can move over in the same call that creates the page.
The honest part
A few edges worth knowing before you hit them.
Inline ingest needs a URL the server can reach. If your images only exist on your laptop, you still need to get them somewhere hosted first — the base64 path still exists for small files, and it’s fine at a few KB. It’s large payloads where it falls apart.
There’s also a rough edge we found in our own API while testing this: a file’s serveUrl returns a 400 if you fetch it bare, without a scaling query string appended. Which means the most obvious thing you’d try — ingesting from a file already in your Neleto media library — fails unless you add ?options=w_800,f_webp. We’re fixing it. Until then, append the options.
And one lesson that isn’t about images at all. The inline ingest worked the instant it deployed, because element data is free-form — no contract change. The verbose flag needed a schema change, and schemas are negotiated once when a client connects, so it stayed invisible to our own editor until we reconnected. If you build MCP tooling: behaviour-level changes reach agents immediately, schema-level ones need a client round-trip. Plan your rollouts accordingly.
Try it
Both changes are live. If you’re already connected to the Neleto MCP server, inline ingest works right now — put a URL where a file ID goes. For verbose, reconnect your client so it picks up the updated schema.
If you haven’t built a site by prompt yet, this is a decent moment. The slowest, most fragile part of the workflow just stopped existing.
Try it yourself: start a free Neleto project, point an agent at it, and drop an image URL straight into the element data — watch the whole upload phase disappear. That’s what a native MCP server is for, and if you want the deep dive on how it works, we wrote one.
Fast websites. Easy content. AI native.