Security
What is protected by default, and what is yours to do.
An MCP endpoint is an API whose callers are language models acting on instructions you did not write. h3-mcp therefore ships its protections on, and validates every client-supplied value at the transport boundary rather than inside your handlers.
#On by Default
| Protection | Default |
|---|---|
| Origin validation | Requests with an Origin header are denied unless allowlisted; requests without one are allowed |
| Body size limit | 4 MiB, enforced before parsing |
| Content-Type check | application/json only |
| Input validation | Every method's params type-checked at the boundary |
| Error opacity | Handler error messages are never forwarded on the modern era |
| Cache scope | private, ttlMs: 0 — results are never shared |
| Header/body agreement | Mirrored modern headers must match the body (-32020) |
#Off by Default
| Protection | Why it is opt-in |
|---|---|
| Transport auth | Many MCP servers run behind a gateway that already authenticates |
| Sessions | Legacy only, and stateful — most servers don't need them |
maxSessions cap | Only meaningful once sessions are on — set it when they are |
#Your Responsibilities
The library cannot know your threat model. These stay with you:
- Authorize each operation. Auth answers "is this a valid caller"; only your handler knows whether this caller may read that record. Tool arguments are model-generated; treat them as untrusted input from the internet.
- Keep results scoped. A per-user result must never be
cacheScope: "public"— see caching. - Integrity-protect
requestState. It round-trips through the client. Sign it if it influences authorization — see MRTR. - Don't mirror secrets into headers.
x-mcp-headerannotations are visible to intermediaries — see request headers. - Bound what you keep. Your own maps of sessions, subscriptions, and replay buffers grow until you cap them.
- Watch what you say. Tool results, log messages, and completion values all reach the client, and often the model. No credentials, internal hostnames, or stack traces.
#Deployment Checklist
defineMcpHandler({
name: "my-server",
version: "1.0.0",
// 1. Who may call at all
auth: { tokens: [process.env.MCP_TOKEN!] },
// 2. Which browsers may call from a page
origin: { allow: ["https://app.example.com"] },
// 3. How much you accept per request
limits: { maxBodySize: 512 * 1024 },
// 4. What may be cached, and by whom
cache: { ttlMs: 0, cacheScope: "private", discover: { ttlMs: 3_600_000, cacheScope: "public" } },
tools: [/* validate inside every handler too */],
});Then confirm, against the deployed endpoint, that:
- An unauthenticated request is
401, and a wrong-Originrequest is403. - A 10 MiB body is
413andtext/plainis415. - A tool called with wrong argument types fails without reaching your logic.
- A tool handler that throws returns
-32603 "Internal error"— with no path, query, or upstream detail in the message.