Live endpoint

prompts-mcp is live β€” here's how it works

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.

POST https://prompts-mcp-sooty.vercel.app/api/mcp
search_promptsrandom_promptget_promptget_statsimprove_prompt
↓ scroll to see what happens when you call it
On the wire

Every message is one JSON-RPC frame

"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)

{ "jsonrpc": "2.0", "id": 7, "method": "tools/call", "params": { "name": "search_prompts", "arguments": { "model": "Veo", "limit": 2 } } }
idThe reply echoes this exact number. Requests need one; fire-and-forget notifications omit it and get no reply.
methodThe verb. The whole protocol is a fixed menu: initialize, tools/list, tools/call, resources/read…
paramsThe typed payload. Each tool's schema is the contract the model fills in here.

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 core idea

One server. Two doors.

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.

the server

One definition

Named ummerr-prompts. Wraps the observatory's 1,381 prompts. Identical no matter which door a request comes through.

search_promptsrandom_promptget_promptget_statsimprove_prompt
↓   connect a transport   ↓
Door 1 Β· local

stdio

A desktop app launches the server as a process and pipes JSON-RPC over stdin/stdout. Private to one machine.

Door 2 Β· remote

Streamable HTTP

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.

Lineage

Transport, three generations

"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.

stdiogen 1 Β· local only
Newline-delimited JSON over a process's stdin/stdout. One private pipe, one client, launched on demand. Perfect locally, meaningless over a network.
local door
HTTP + SSEgen 2 Β· deprecated
The first remote attempt used two endpoints: a POST for client→server, and a separate, always-open stream for server→client. Powerful but clingy — a long-lived connection per client, awkward and costly on serverless.
retired
Streamable HTTPgen 3 Β· current
One endpoint. A POST answers with a plain JSON body or upgrades into a stream when it needs to. Sessions optional. This is what runs cleanly on serverless β€” and what this server uses.
this door

🧬 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.

Serverless mechanics

A server that lives for one request

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.

01
POST arrives
A JSON-RPC frame hits /api/mcp.
02
Server is born
A fresh server instance, just for this call.
03
Handle it
e.g. search_prompts β†’ fetch the live observatory.
04
Answer as JSON
One 200 response body.
05
Torn down
Instance discarded. Nothing kept.
β—‚ the server exists only for this shaded span β–Έ
Why this is safe β€” and the one feature it costs

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.

Securing the door

Right now, anyone can knock. How you'd change that.

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.

There are two servers, on purpose

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.

Resource Server Β· the bouncer

The MCP server

prompts-mcp-sooty.vercel.app

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.

checks
tickets
from β–Έ
Authorization Server Β· the ID office

The token issuer

a shared key, or an identity provider

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 "token" is a signed note anyone can read

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.

eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJhdXRoIiwiYXVkIjoiL2FwaS9tY3AiLCJzY29wZSI6InByb21wdHM6cmVhZCIsImV4cCI6MTc1M30.Kx8f…q2wZ
β–  Header
alg
RS256
Which signature algorithm was used to seal it.
β–  Payload β€” the claims
iss
who issued it
aud
/api/mcp
scope
prompts:read
exp
expires
The assertions: issuer, who it's for (aud), what it may do, when it dies.
β–  Signature
Kx8f…q2wZ
The tamper-proof seal. Change one character of the payload and it no longer matches.

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.

The handshake, step by step

Every locking scheme runs the same exchange. Walk through it:

Step 0 of 6 β€” nobody's knocked yet
1
Client
Calls a tool with no credentials β€” it doesn't know one is needed yet.
2
Server
Refuses: 401, plus a header saying "here's where to learn how to get in."
3
Client
Reads that metadata doc β€” it names which authority issues tokens.
4
Client ⇄ issuer
Gets a token. This step is the whole choice β€” a pasted key, or a real browser login.
5
Client
Retries the call, now carrying Authorization: Bearer …
6
Server
Validates the token β†’ 200. Tools run. Strangers don't.
The one rule you can't skip β€” check the audience

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.

And the second rule β€” don't be a courier

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.

Putting it together

The full version, end to end

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.

1
client→RS
Calls a tool with no token.
2
RS→client
401 + WWW-Authenticate pointing at the metadata doc. (discovery)
3
client→RS
Reads /.well-known/oauth-protected-resource β†’ learns which AS to trust.
4
client→AS
Reads the AS's own metadata β†’ its authorize / token / registration endpoints + signing keys.
5
client→AS
Registers itself on the fly β†’ gets a client_id. (dynamic client registration β€” no manual signup)
6
client
Invents a secret, hashes it into a code_challenge. (PKCE β€” keep the secret, send the hash)
7
client→AS→user
Opens the browser to the authorize endpoint β€” carrying the challenge and resource= this server's URL.
8
user→AS
Logs in and consents. The only human moment in the whole flow.
9
AS→client
Redirects back with a short-lived authorization code β€” not yet a token.
10
client→AS
Trades code + the original PKCE secret for an access token stamped aud = this server. (PKCE proven)
11
client→RS
Retries the original call, now with Authorization: Bearer …
12
RS
Verifies the signature against the AS's keys, checks 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.

Pocket glossary

The words, in one place

JSON-RPC 2.0
The "call a method, get a result" format every MCP message uses. A frame with method + params, answered by one with the matching id.
Transport
How the frames physically travel. stdio (a local pipe) or Streamable HTTP (the network).
Capability
A feature a side declares it supports during initialize. This server advertises tools, resources, prompts.
Stateless
No memory kept between requests. Why the remote server is born and dies per request.
Bearer token
A ticket sent in Authorization: Bearer … Whoever bears it gets in β€” so it must be secret and audience-bound.
JWT
A common token format: header.payload.signature, signed not encrypted. Readable, but tamper-evident.
Claim
One assertion inside a token. iss, aud, scope, exp.
Audience (aud)
Who a token is for. A server must reject tokens whose aud isn't itself.
Resource Server
The thing being protected; checks tokens. This MCP server. The bouncer.
Authorization Server
The thing that issues tokens. A shared key, or an identity provider. The ID office.
Scope
What a token is allowed to do. e.g. prompts:read.
PKCE
A hash handshake proving the app finishing a login is the one that started it. Mandatory in OAuth 2.1.
Dynamic Client Registration
Clients registering themselves on the fly. Why any MCP client can pair with any server, no manual signup.
Protected Resource Metadata
The /.well-known/ doc a 401 points to. Tells a client which authorization server to visit.