
Web Claude reads images. It analyzes photos, parses layouts, and accepts reference images for generation. But it can’t create them. Give it a text prompt and ask for an image — it declines, because there’s no tool for that. Our team fixed this by building a Claude MCP connector ourselves.
[toc]
🔍 The Gap: Web Claude Has No Image Generation Tool
Claude MCP (Model Context Protocol) is the standard way AI clients talk to external tool servers. If you already have a local MCP server, Claude Code and Desktop can use it right away. Web Claude is a different story.
Our team had been running an image generation MCP server. It wraps three providers — Gemini, xAI Grok, and GPT-Image — behind a single interface: text-to-image, instruction-based editing, reference-based generation, and four-panel comic generation. All of it worked fine in Claude Code and Desktop.
The obvious idea was to point web Claude at that same server. The obvious idea doesn’t work, and the reason is simple and fundamental.
📡 Why a Local stdio Server Can’t Reach the Web Client
A stdio MCP server communicates over standard input and output on the same machine. The web client doesn’t run on your machine. When you add a custom connector, the web client reaches your server from Anthropic’s cloud, over the public internet. A local process is invisible to it. No amount of connector configuration makes a local stdio server reachable.
Introduced in spec 2025-03-26 for remote clients. A single HTTPS endpoint supports both POST and GET, with optional SSE streaming. It replaces the older HTTP+SSE approach from spec 2024-11-05. The MCP spec explicitly requires that publicly-exposed servers be served over HTTPS.
The conclusion was clear: to attach tools to the web client, you need a public HTTPS endpoint. Either build a new server from scratch, or layer HTTP on top of the existing tools.
We left the existing stdio server untouched. We wrote a separate HTTP launcher that imports the same tool functions. One server definition, two transports. The image generation logic didn’t change by a line — stdio clients keep using the original path, and web clients reach the same tools over HTTP.

🛠️ The Real Challenge: Getting Images Back to the Client
Adding a second transport wasn’t the hard part. The real challenge was delivering generated images back to the client.
A local client is simple: the server saves the file to disk and returns the path, and the client opens the file. The web client has no access to your disk. The image itself has to travel inside the tool result.
Attempt 1: Full-resolution PNG → base64 inline → failed
The most obvious approach: encode the generated image as base64 and return it directly in the tool result. It failed. A full-resolution PNG is several megabytes. Base64 adds roughly 33% overhead. Claude’s tool result limit is 25,000 tokens, with a 5MB image cap. Several megabytes of base64 is rejected by the client sandbox as a payload error. Instead of an image, you get an error message.
Attempt 2: Thumbnail downsizing → fits but useless
To solve the size problem, we shrank the image. A few-hundred-pixel preview fits through the sandbox. But it was only useful for “did the model understand the prompt” — not for judging character consistency, color accuracy, or anything you’d actually use. Not usable quality.
The fix: Return three formats simultaneously
The final structure is a hybrid. Each tool result carries the image in three forms at once. Each client takes what it can use.
| Format | Contents | Use |
|---|---|---|
| Inline medium JPEG | Max 1280px on longest side, moderate quality. Low hundreds of kilobytes. | Immediate web client preview. Passes the sandbox. Renders directly in the conversation. |
| Short-lived signed URL | Full-resolution original. 1-hour expiry, unguessable token. | Actual use: download, further editing. Non-enumerable structure. |
| Local file path | Disk path, as-is. | stdio client backward compatibility. Preserves existing workflows. |

The medium JPEG is for looking; the signed URL is for using. Having both means any client can take what it needs. This is a deliberate design response to the trade-off between context cost and quality: a few hundred kilobytes of base64 buys an immediate, legible preview, and the full file is one click away when it matters.
The same problem shows up in reverse for reference image input. A local client can pass a file path; the web client can’t point to the server’s filesystem. So we made the server accept input as base64, URL, or path — and auto-detect the format from the leading magic bytes. Reference-based generation works identically from either client.

🔐 Authentication: Why Static Bearer Tokens Failed
The first version of the HTTP server used a static bearer token: set a secret, send it in the header, done. It was the simplest possible thing, and it worked in local testing.
It didn’t survive contact with the real client. The Claude custom connector flow expects an OAuth handshake — the client discovers the server’s auth metadata, dynamically registers itself (Dynamic Client Registration, RFC 7591), and walks the user through a standard login flow. There’s no field in the UI to paste a static token into.
⚠️ Since MCP spec 2025-11-25: Publicly-exposed MCP servers are required to use OAuth 2.1 + PKCE(S256). Static bearer tokens don’t satisfy the spec, and major AI clients require the OAuth handshake. Dynamic Client Registration (RFC 7591) is effectively mandatory.
The final setup is OAuth provider + allowlist. OAuth handles authentication, with an account-level filter so only approved accounts can call the tools. We used the FastMCP framework, which handles the OAuth metadata endpoints, DCR, and token validation out of the box. The most practical lesson here: the easiest auth to build isn’t always the auth the client implements.
🔒 File Serving Security on a Public Origin
The moment the server hosts files on a public HTTPS origin, it inherits a class of problems a local stdio server never had. Two things needed fixing before exposing anything.
Content-type enforcement
A file-serving endpoint that echoes back whatever was stored becomes a stored-XSS vector if it can be coaxed into serving HTML or SVG. The fix is to sniff the actual leading bytes and serve only real raster formats — PNG, JPEG, WEBP, GIF. Unrecognized formats are rejected. Responses also carry X-Content-Type-Options: nosniff to prevent browsers from second-guessing the content type or executing anything.
Fail-closed upload path
An early version allowed uploads when the upload secret was unset. That’s exactly backwards. No secret configured means the endpoint is disabled. Fail closed. A misconfigured state should be a locked gate, not an open one.
- Enforce content-type via magic bytes (PNG/JPEG/WEBP/GIF only)
- Set
X-Content-Type-Options: nosniffon responses - Upload path is fail-closed — missing secret = endpoint disabled
- Signed URLs with short expiry + unguessable tokens
- Path traversal prevention (block access outside storage directory)
Neither fix is clever. Both are obvious in hindsight and easy to miss when you’re focused on getting an image to render. A public server is a different threat surface from a local process — the sooner you treat it that way, the less you have to retrofit later.
📊 Results: Image Generation in Web Claude from Any Device
With the connector in place, the result is straightforward: say “generate an image” in a web Claude session, the tool call fires, and the medium JPEG preview appears directly in the conversation. No local install needed. Log in from any device and the same tools are there.
The same server routes across multiple providers, so you can use a cheaper model for quick drafts and a higher-quality one for finals. Heavier work — consistent character generation, four-panel comics, reference-based edits — stays inside the web Claude session instead of being a detour to another tool. That was the original goal.
Demo 1: Inline image rendering in a web Claude session

Demo 2: Reference image auto-detection — consistent character generation

📚 References
- MCP Official Spec — Transports (spec 2025-11-25)
- Claude Official Guide — Building Custom Connectors via Remote MCP Servers
- FastMCP — OAuth Implementation Guide
- Related: AI Agent Memory vs RAG: Designing Memory for AI in 2026

✅ Summary: Six Takeaways
- stdio is local-only. Connecting tools to the web client is a separate, deliberate step. Configuring a connector doesn’t make a local server visible.
- Image delivery is a size problem first. The medium inline JPEG + signed URL combination beats both “inline everything” and “send a thumbnail.”
- Input also needs to handle remote clients. Reference images must accept raw base64. Remote clients can’t point to the server’s filesystem.
- Check the client’s actual auth flow early. The easiest auth to build isn’t necessarily what the client implements. OAuth DCR is annoying but unavoidable.
- Once you serve files from a public origin, content-type and upload paths become a security surface.
- Fail closed. A missing secret should disable an endpoint, not open it. The default should always be more restrictive.