A Model Context Protocol server over the prompts observatory β ~1,381 real generative-AI prompts. It's also a working tour of how a remote MCP server actually runs, from the JSON on the wire to the OAuth that guards the door.
"MCP" sounds like a big protocol. Underneath, it's JSON-RPC 2.0 β a plain convention for "call a function on the other side, get an answer back." A whole conversation is just small JSON objects flying both ways. Here's a real one: a client asking this server to run search_prompts. (hover the dotted labels)
initialize, tools/list, tools/call, resources/readβ¦The server answers with a mirror frame β same id: 7, but a result instead of a method (or an error, never both). That request/response rhythm is the entire protocol.
The tools, resources, and prompts a server exposes are transport-agnostic β they don't care how the bytes arrive. So the same server can be reached two completely different ways, without changing a line of tool code.
Named ummerr-prompts. Wraps the observatory's 1,381 prompts. Identical no matter which door a request comes through.
A desktop app launches the server as a process and pipes JSON-RPC over stdin/stdout. Private to one machine.
A host turns each HTTP request into MCP messages and hands back a response. This is the door you're reading about β reachable by anyone.
π‘ This server is deployed on Vercel. Getting there meant swapping the local stdio door for the HTTP one β a stdio server, run as a web function, just crashes (500), because it's waiting on a pipe that never comes. Same tools, different door.
"Streamable HTTP" is an odd name until you see what it replaced. MCP has tried three ways of moving frames, and the remote door here is the newest of them.
𧬠A subtle inheritance: Streamable HTTP still permits that optional serverβclient stream β a vestige of gen 2's second endpoint. A stateless server has no session to feed it, so it answers that GET with 405: "this door only speaks request/response." Skip that and every client hangs on connect, waiting for a stream that never sends.
On a laptop, one server process stays alive for hours, holding the conversation in memory. Serverless functions can't β they're spun up on demand and share no memory between requests. So the remote server does something that sounds absurd and works perfectly: it's born and dies inside a single HTTP request.
/api/mcp.search_prompts β fetch the live observatory.This mode is stateless: no session is kept between requests, so a brand-new instance can answer any call directly. That's fine for request/response tools. What it gives up is sampling β a tool that calls back to the client's model mid-request. That round-trip needs an open channel back to the client and a server still alive to receive the reply. Stateless has neither, so the server detects it can't make the callback and degrades gracefully instead of hanging.
This endpoint is currently open β anyone with the URL can call the tools. MCP's spec has a precise way to lock that down: the server becomes an OAuth 2.1 Resource Server. Two ideas make the whole thing click.
OAuth splits "the server" into two jobs. An MCP server should check tickets, not issue them β like a venue's bouncer checks IDs but doesn't run the DMV.
Holds the thing worth protecting β the tools. Its only auth job: read the token on each request, verify it's genuine and meant for this door, then allow or reject. Never handles passwords.
Handles the human stuff β who are you, prove it, here's a signed token that expires. Could be as simple as one shared key, or a full login service like Stytch or Auth0.
A bearer token isn't a random password. It's usually a JWT β three base64 chunks joined by dots. Not encrypted (you can read it), but signed, so the server can prove the ID office issued it and nobody tampered with it.
aud), what it may do, when it dies.The server's whole check: recompute the signature with the issuer's public key, then confirm aud is itself and exp is in the future. No database call β that's why bearer tokens scale.
Every locking scheme runs the same exchange. Walk through it:
Authorization: Bearer β¦The aud claim says "this token is for /api/mcp and nowhere else." The server must reject any token whose audience isn't itself. Skip that and you've built a confused deputy β a server that will act on a ticket meant for someone else, letting a stolen or misdirected token in. It's one line of code and the single most important one in any resource server.
Once a good token gets in, never forward it onward. If the server needs to call an upstream API, it uses its own credentials, not the user's token. The MCP token is a key to this door only. Inbound: verify it's yours. Outbound: use your own authority. Those two rules avoid the classic ways MCP servers leak trust.
The step-through above is the shape every scheme shares. Here's the complete OAuth 2.1 flow behind a "log in with your account" MCP server β a single connect, across three players: client the tool, AS the ID office, RS this server.
WWW-Authenticate pointing at the metadata doc. (discovery)/.well-known/oauth-protected-resource β learns which AS to trust.client_id. (dynamic client registration β no manual signup)code_challenge. (PKCE β keep the secret, send the hash)resource= this server's URL.aud = this server. (PKCE proven)Authorization: Bearer β¦aud is itself and exp is future β 200. Steps 3β10 happen once, then cache; later calls are just 11β12.A simple shared-key gate collapses steps 3β10 into "paste a key." Full OAuth is this entire diagram, automated by the client. Steps 1β2 and 11β12 are identical either way β which is why the simple gate is a real seam you can upgrade through, not throwaway work.
method + params, answered by one with the matching id.initialize. This server advertises tools, resources, prompts.Authorization: Bearer β¦ Whoever bears it gets in β so it must be secret and audience-bound.header.payload.signature, signed not encrypted. Readable, but tamper-evident.iss, aud, scope, exp.aud isn't itself.prompts:read./.well-known/ doc a 401 points to. Tells a client which authorization server to visit.