OAuth for your MCP server. Ship it today, not next quarter.
Drop-in OAuth 2.1 authorization server and SDK for MCP servers. Implements Dynamic Client Registration (RFC 7591) out of the box.
import express from "express";
import { mcpAuth } from "getmcpauth";
const app = express();
app.use(
"/mcp",
mcpAuth({ registrationSecret: process.env.MCPAUTH_SECRET })
);
// Unauthenticated or invalid requests never reach this handler.
app.post("/mcp", handleMcpRequest);MCP auth is broken today
The MCP spec expects clients to register themselves dynamically via Dynamic Client Registration (RFC 7591) — a client shows up, no human has pre-configured it in a dashboard, and it registers itself on the spot. Almost no OAuth provider supports that. General-purpose identity platforms are built around a fixed, pre-registered set of apps, not clients that appear at runtime.
So builders end up with three bad options: ship the MCP server with no auth at all, hand-roll a static API key check that has no scopes, no expiry, and no revocation, or bolt on a full enterprise CIAM platform meant for SSO and SCIM provisioning when all they actually needed was OAuth for an MCP client.
How it works
- 1
npm install getmcpauth
Add the SDK to your MCP server project. Peer dependencies are @modelcontextprotocol/sdk and express.
- 2
Wrap your server with mcpAuth()
One middleware call protects your MCP route. It wraps the official SDK's requireBearerAuth middleware, so bad or missing tokens get a spec-correct 401 before they ever reach your handlers.
- 3
Clients auto-discover the rest
GET /.well-known/oauth-authorization-server lists every endpoint mcpauth exposes — registration, authorize, token, revoke, introspect — so an MCP client only needs your base URL.
| POST | /api/oauth/register | Dynamic Client Registration (RFC 7591) |
| GET | /oauth/authorize | Browser-facing OAuth 2.1 authorize + consent screen |
| POST | /api/oauth/token | Authorization-code and refresh-token grants |
| POST | /api/oauth/revoke | Token revocation (RFC 7009) |
| POST | /api/oauth/introspect | Token introspection (RFC 7662) |
| POST | /api/oauth/token/exchange | Server-to-server token minting |
| GET | /.well-known/oauth-authorization-server | Discovery document (RFC 8414) |
Before and after
Most MCP servers today either skip auth entirely or check a static key with a middleware like this:
// Hand-rolled API key check
app.use("/mcp", (req, res, next) => {
const key = req.headers["x-api-key"];
if (!key || key !== process.env.MCP_API_KEY) {
return res.status(401).json({ error: "unauthorized" });
}
// No scopes, no per-user identity, no token
// expiry, no revocation, no discovery for
// clients that expect real OAuth.
next();
});With mcpauth, the same protection — plus real per-user identity, scopes, token expiry, and revocation — is three lines:
// mcpauth
app.use(
"/mcp",
mcpAuth({ registrationSecret: process.env.MCPAUTH_SECRET })
);Test it live, before you install anything
This isn't a mock. It's a real MCP server, in production, protected by mcpauth's own published SDK — the same package you'd install. Point the official MCP Inspector at it:
npx @modelcontextprotocol/inspector \
https://getmcpauth.dev/api/demo/mcp --transport httpInspector hits the server, gets a 401 with a WWW-Authenticate header pointing at mcpauth, registers itself automatically (Dynamic Client Registration — no client ID to configure), and walks you through the real GitHub-login consent screen in your browser. Approve it, and Inspector can call the two tools this demo server exposes — a live, end-to-end proof of the whole flow.
Who it's for
New MCP server
Building an MCP server from scratch. Install the SDK, wrap it with mcpAuth(), and users sign in through GitHub and a consent screen — no auth code to write.
Existing product
Adding MCP to a product that already has its own users. Your backend calls /api/oauth/token/exchange directly to mint a token for an identity it already authenticated, skipping the consent screen entirely. Read the token-minting docs →
Self-hosting
Prefer to run the authorization server yourself rather than use the hosted dashboard. The SDK and endpoints are the same either way.
Already have users? Skip the consent screen entirely
If you're bolting an MCP server onto a product that already has its own login, don't make your users authenticate twice. Your backend, which already knows who's signed in, mints the token directly:
- Your user logs into your product, as usual — not mcpauth.
- Your backend calls
mintToken()(or/api/oauth/token/exchangedirectly), server-to-server, vouching for that user. - The resulting token is handed straight to the MCP client — no GitHub login, no browser redirect, no separate consent screen.
import { mintToken } from "getmcpauth";
// After your own login/session check —
// mcpauth never authenticates this user itself.
const { accessToken } = await mintToken({
registrationSecret: process.env.MCPAUTH_SECRET,
clientId,
subject: currentUser.id,
scopes: ["read:files"],
});
// Hand accessToken to the MCP client. No
// GitHub login, no consent screen — your
// backend already vouched for this user.Pricing
Free
FreeFor building and testing an MCP server.
- 1 project
- Up to 1,000 monthly active tokens
- Full OAuth 2.1 + Dynamic Client Registration
- GitHub-authenticated dashboard
- Community support
Pro
$29/moFor MCP servers in real production use.
- Unlimited projects
- 10,000 monthly active tokens included
- $5 per additional 1,000 monthly active tokens
- Server-to-server token minting
- Priority support