Skip to main content
Glama
mcp-auth-kit

mcp-auth-kit

by bradburch

mcp-oauth-kit

A production-minded MCP server kit: OAuth 2.1/PKCE, rate limiting, scope-gated tools, and two-phase confirm — bring your own tools, identity, and storage.

⚠️ Status: pre-1.0 (0.4.0). The OAuth core is independently reviewed and tested, but the API may still change and there are deliberate design limitations to understand before production use — most importantly: single-use code redemption, refresh-token rotation, and confirm idempotency are best-effort, not exactly-once on a storage backend without compare-and-swap; and per-IP rate limiting needs a correct ipExtractor when you're not behind Cloudflare. Read SECURITY.md before deploying, and pin a version.

New here? Start with the How-to-use guide for a step-by-step walkthrough (including an end-to-end OAuth flow you can run with curl). This README is the full config/API reference.

Install

npm install mcp-oauth-kit

Peer dependencies (not bundled):

npm install hono @modelcontextprotocol/server zod

TypeScript projects that type-check libraries (skipLibCheck: false) also need @types/node and "types": ["node"] in tsconfig.json — the SDK's published types reference Buffer, and current TypeScript no longer includes @types/* packages unless listed.

Node 22+ is required, and your project must be ESM ("type": "module" in package.json) — the package publishes an ESM-only exports map with no require condition.

On Node you also need the Hono adapter as a runtime dependency: npm install @hono/node-server (Workers, Lambda and Vercel use their own adapters — see docs/deploy.md).

Related MCP server: Model Context Protocol Template

Quick start

npm install @hono/node-server   # Node adapter; see docs/deploy.md for Workers, Lambda, Vercel
// server.ts — run with `node server.ts` (Node 22.18+; or save as server.js)
import { serve } from "@hono/node-server";
import { z } from "zod";
import { createMcpServer, createMemoryStorage, defineTool } from "mcp-oauth-kit";

// Stand-in for your user store — replace with a real lookup.
const users = new Map([["ada@example.com", { id: "user-ada", code: "123456" }]]);

const app = createMcpServer({
  // Your public https:// URL in production; http:// is allowed only for localhost.
  baseUrl: "http://localhost:3000",
  storage: createMemoryStorage(), // swap for createCloudflareKvStorage in production
  scopes: [
    { name: "account:read", default: true },
    { name: "write", default: true },
  ],
  identity: {
    fields: [
      { name: "email", label: "Email", type: "email", required: true },
      {
        name: "code",
        label: "Verification Code",
        type: "text",
        required: true,
      },
    ],
    verify: async (fields) => {
      // Return a stable userId string on success, null to reject.
      const user = users.get(fields.email);
      return user && user.code === fields.code ? user.id : null;
    },
  },
  tools: [
    defineTool({
      name: "list_slots",
      description: "List available appointment slots for today.",
      inputSchema: z.object({}),
      handler: async () => "09:00, 10:00, 11:00", // strings become text content
    }),
    defineTool({
      name: "book_slot",
      description: "Book an appointment slot.",
      scope: "write",
      inputSchema: z.object({ slot: z.string() }),
      mutating: {
        // `slot` is typed from inputSchema; `data` in execute is typed from preview's return.
        preview: async ({ slot }) => ({ summary: `book ${slot}`, data: { slot } }),
        execute: async ({ slot }) => `Booked ${slot}.`,
      },
    }),
  ],
});

// app is a Hono instance — wrap it in your runtime's adapter.
serve({ fetch: app.fetch, port: 3000 }, () => console.log("listening on http://localhost:3000"));

Point an MCP client at http://localhost:3000/mcp and it will run the OAuth flow against the built-in login page.

Plain JavaScript works too. Nothing above needs TypeScript: save it as server.js and run node server.js. defineTool is only a typing helper — in JS you can pass the tool objects directly. (Node 22.18+ / 23.6+ runs the .ts file as-is via built-in type stripping; a relative import of another .ts file must then spell the .ts extension — see the how-to guide.)

Adding your own routes

createMcpServer returns a plain Hono app, so you can mount it inside your own:

import { Hono } from "hono";

const app = new Hono();
app.get("/", (c) => c.text("Hello"));
app.route("/", createMcpServer({/* ... */}));

Mount it at / (see the origin-root requirement under Endpoints) and keep your routes off the paths the kit owns: /.well-known/oauth-authorization-server, /.well-known/oauth-protected-resource (and its /mcp sub-path), /register, /authorize, /token, /revoke, and /mcp. The kit's login page creates no browser session or cookie — it only issues an OAuth code to the MCP client — so a web UI of your own needs its own auth.

See examples/appointments/server.ts for a complete working server.

Config reference

createMcpServer(config: McpServerConfig) accepts:

Field

Type

Required

Description

baseUrl

string

Yes

Public base URL of this server (used in OAuth discovery and redirect URIs). Must be https:// unless the hostname is localhost/127.0.0.1 — the server throws at construction time otherwise.

name / version

string

No

Server identity sent to MCP clients as serverInfo — in every result's _meta (2026-07-28), and in initialize for 2025-era clients. Defaults to mcp-oauth-kit / the kit version.

storage

KvLike

Yes

Key-value store for tokens, rate-limit counters, and idempotency records.

scopes

ScopeConfig[]

Yes

OAuth scopes the server advertises.

identity

IdentityConfig

No

Built-in login-form identity provider. Without it GET /authorize answers 400, so omit it only when you compose the low-level pieces with your own /authorize (see the how-to guide §4).

tools

Array<ToolDef | MutatingToolDef>

Yes

Tool definitions registered on the MCP server.

rateLimits

RateLimitConfig

No

Per-hour thresholds for tool calls and OAuth endpoints.

hooks

ObservabilityHooks

No

Async callbacks for tool calls, OAuth lifecycle events, and mutation audit.

ipExtractor

(req: Request, env: unknown) => string

No

Override how the trusted client IP is derived for per-IP rate limiting (see below). env is Hono's c.env.

tokenTtl

TokenTtlConfig

No

Override token lifetimes: { accessTokenSeconds?, refreshTokenSeconds? } (defaults 3600 / 7776000 — 1 hour / 90 days).

allowClientIdMetadataDocuments

boolean

No

Resolve unregistered https:// client_ids as Client ID Metadata Documents instead of requiring Dynamic Client Registration. Off by default (see "OAuth / PKCE client flow" below).

allowedOrigins

string[]

No

Exact-match allowlist for the browser Origin header on POST /mcp (DNS-rebinding protection). Requests with no Origin header are always allowed; a request WITH one is rejected unless it's in this list. http://localhost:3000 and http://127.0.0.1:3000 are different origins — list each one you use.

ScopeConfig

Field

Type

Description

name

string

Scope name (e.g. "account:read").

description

string?

Human-readable description, shown on the login page.

default

boolean?

Granted when the client requests no specific scopes.

IdentityConfig

Field

Type

Description

fields

IdentityField[]

Fields rendered on the built-in login form.

branding

Branding?

App name, logo URL, and accent colour for the form.

verify

(fields, { request, ip }) => Promise<string | null>

Validate credentials. Return a stable userId string or null to reject.

Every field value reaches verify as a string (missing fields are ""); non-password fields are trimmed, type: "password" fields are passed verbatim and never refilled after a failed sign-in. The login page lists the scopes being granted. GET /authorize answers 400 instead of rendering the form when the client_id is unknown or the redirect_uri isn't registered.

Per-account lockout is your job. The kit rate-limits /authorize per IP only (ipAuthorizePerHour), which doesn't stop an attacker spreading guesses across IPs. If your credential is guessable (a 6-digit code), count failures per account in verify — the second argument carries the raw request and the trusted client ip (from ipExtractor).

IdentityField

Field

Type

Description

name

string

HTML input name and key in the submitted record.

label

string

Human-readable label.

type

string?

HTML input type ("text", "password", "email", …).

required

boolean?

Whether the field is required.

Branding

Field

Type

Description

appName

string

App name shown in the UI heading.

logoUrl

string?

URL to a logo image.

accentColor

string?

Hex accent colour (e.g. "#3b82f6").

RateLimitConfig

All limits are per-hour. Omit a field to use the default.

Field

Type

Default

Description

userPerHour

number?

50

Max authenticated POST /mcp requests per user per hour — every request counts (tools/list, server/discover, …), not just tool calls.

ipAuthorizePerHour

number?

10

Max OAuth authorize attempts per IP per hour (brute-force guard).

ipTokenPerHour

number?

30

Max requests per IP per hour to /token, /register, and /revoke.

A limited OAuth request gets 429 {"error":"temporarily_unavailable","error_description":"rate limit exceeded (<key>)"} naming the exhausted key. Every 429 (OAuth endpoints and /mcp) carries Retry-After set to the seconds until the hourly window resets. Windows are fixed clock hours, so a client can spend up to twice a limit across an hour boundary. Every OAuth round trip (including a scope step-up) costs one /authorize attempt, so during local development raise ipAuthorizePerHour or restart the (memory-storage) server when iterating on a client.

The /register and /revoke endpoints share the per-IP ipTokenPerHour bucket so that unauthenticated requests can't be used for storage-exhaustion abuse.

Rate-limit counters use a non-atomic read-modify-write (KV has no atomic increment) — counts may under-count under high concurrency. For strict enforcement, wrap createRateLimiter with a Durable Object counter or equivalent.

Trusted client IP. By default the per-IP source is CF-Connecting-IP (authoritative on Cloudflare) falling back to the first hop of X-Forwarded-For. X-Forwarded-For is client-spoofable unless a trusted proxy overwrites it, so off-Cloudflare deployments must pass a custom ipExtractor that derives the IP from a source you control — otherwise the brute-force guards can be bypassed by rotating the header. Its second argument is Hono's c.env; on @hono/node-server that carries the socket, so with no proxy in front use the real peer address:

import type { HttpBindings } from "@hono/node-server";

createMcpServer({
  // ...
  ipExtractor: (_req, env) => (env as HttpBindings).incoming.socket.remoteAddress ?? "unknown",
});

Behind a proxy, read the header that proxy sets — e.g. (req) => req.headers.get("True-Client-IP") ?? "unknown". That is only safe if the proxy always overwrites the header; otherwise a client can send its own.

ObservabilityHooks

All callbacks are fire-and-forget except onMutation (which is awaited). Errors are swallowed so a throwing hook never fails the request. On runtimes with waitUntil (Cloudflare Workers), the fire-and-forget hooks are registered with it so they finish after the response is sent — pass the execution context through (fetch: (req, env, ctx) => app.fetch(req, env, ctx)).

Field

Type

Description

onToolCall

(event) => Promise<void>

Called when a tool is invoked (including a mutating tool's preview call), concurrently with the handler. event.input is the same object the handler receives.

onAudit

(event) => Promise<void>

Called on OAuth lifecycle events (client_registered, token_issued, token_refreshed, token_revoked). The token events include userId.

onMutation

(event) => Promise<void>

Called (awaited) after a mutating tool's execute phase succeeds, with { userId, toolName, summary, data, result, idempotencyKey }. Not called when confirm_request replays a cached result, and only for two-phase tools — log plain tools' side effects in their handler.

onToolError

(event) => Promise<void>

Called when a handler, preview, or execute throws or returns an unserializable value, or a storage call in the mutating flow fails. event.phase is handler/preview/execute/storage; event.error is the real error the client never sees; event.expected is true for a ToolError (a deliberate rejection — filter it out of error alerting). A storage event after execute means the side effect happened but its result wasn't cached.

Tool definitions

Wrap each tool in defineTool(...) so input is typed from inputSchema (and a mutating tool's execute data from preview) — the FastMCP-style ergonomics. A handler may return a string (one text block), an MCP { content: [...] } result (passed through), or any other JSON-serializable value (serialized as text). createMcpServer throws at startup on duplicate tool names, a tool named confirm_request, a scope missing from scopes, or an inputSchema that isn't a z.object — wrappers like .transform() are rejected because they'd advertise an empty schema; .refine() on the object is allowed and runs. Zod 4 (^4.2) is required.

Errors. A thrown error reaches the client as a generic "Tool execution failed" (and is reported to the onToolError hook). To reject with a reason the client should see, throw ToolError (instances of ToolError, or an Error carrying its Symbol.for("mcp-oauth-kit.ToolError") brand so duplicate package copies interoperate, are forwarded — an unrelated error that merely has name === "ToolError" stays generic):

import { ToolError } from "mcp-oauth-kit";

preview: async ({ slot }) => {
  if (await isBooked(slot)) throw new ToolError(`${slot} is already booked`);
  return { summary: `book ${slot}`, data: { slot } };
},

Each tool in the tools array is either a ToolDef (has a handler — called directly) or a MutatingToolDef (has a mutating.preview and mutating.execute — uses the two-phase confirm flow). The two shapes are mutually exclusive.

Standard tool (ToolDef)

{
  name: "list_slots",
  description: "List available slots.",
  inputSchema: z.object({}),
  scope: "account:read",   // optional — omit for no scope check
  handler: async (input, ctx) => ({
    content: [{ type: "text", text: "..." }],
  }),
}

ctx is a ToolContext:

interface ToolContext {
  userId: string;
  scopes: string[];
  storage: KvLike;
  // The Hono request's `c.env` — Cloudflare Worker bindings when deployed there, or
  // whatever your Hono adapter supplies for other runtimes (often `undefined`/empty on
  // Node, Lambda, Vercel unless you've typed your own Hono `Env` generic). Cast to your
  // own type.
  env: unknown;
  hooks: ObservabilityHooks;
  // The runtime's waitUntil on Cloudflare Workers / edge (undefined on Node): hand it a
  // promise to keep background work alive after the response is sent.
  waitUntil?: (promise: Promise<unknown>) => void;
}

Scope gating

If a tool specifies scope, the kit checks the caller's token at dispatch time. The tool is always listed in tools/list regardless of the caller's granted scopes — a client can discover it exists and request the scope via step-up authorization. A tools/call for it from a caller whose token lacks the scope is answered, before the handler runs, with HTTP 403 and a WWW-Authenticate: Bearer error="insufficient_scope", scope="<required>", resource_metadata="..." challenge (RFC 6750 §3, MCP 2026-07-28) — including when the call is inside a legacy JSON-RPC batch. Scopes flagged default: true in the server config are automatically granted when the client requests no explicit scopes. ctx.scopes inside a handler reflects the token's full granted scope list.

Mutating tool (MutatingToolDef) — two-phase preview → confirm

Mutating tools never execute their side effect on the first call. The flow is:

  1. Preview phase — the MCP client calls the tool. mutating.preview(input, ctx) runs, returns a { summary, data } preview. The kit stores it under a single-use confirmation token (5-minute TTL) and returns a PreviewResult ({ status: "preview", summary, confirmationToken }) to the client.

  2. Confirm phase — the MCP client calls the built-in confirm_request tool with the confirmationToken from step 1 and a unique idempotencyKey. mutating.execute(data, ctx) runs, and the result is returned (and cached for 10 minutes under the idempotency key). The confirmation token is bound to the user who previewed it: a confirm from a different user is rejected and does not consume the token. Reusing an idempotencyKey with a different confirmation token is refused rather than replaying the other result. Refusals come back as isError: true with text {"success":false,"error":"..."}.

data is stored as JSON, so it must be JSON-serializable (no Date/Map/BigInt), and execute can run up to 5 minutes after preview — re-check anything that may have changed (availability, balances) instead of trusting the preview. summary is shown before the user confirms and passed to onMutation after, so phrase it tense-neutral ("Book 09:00", not "Booked 09:00").

Idempotency — best-effort, not exactly-once: The kit writes a "pending" sentinel before executing, so a concurrent retry that sees it backs off and asks the caller to retry. A retry that sees a cached result replays it without re-executing. Limitation: The underlying KV store has no compare-and-swap. The pending sentinel narrows — but does not fully close — the double-execute window. True exactly-once delivery requires a strongly consistent store (a Durable Object or equivalent). On execute failure the sentinel is deleted so a legitimate retry can re-run.

defineTool({
  name: "book_slot",
  description: "Book an appointment slot.",
  scope: "write",
  inputSchema: z.object({ slot: z.string() }),
  mutating: {
    preview: async ({ slot }) => ({ summary: `book ${slot}`, data: { slot } }),
    execute: async ({ slot }, ctx) => {
      // carry out the side effect here
      return `Booked ${slot}.`;
    },
  },
});

confirm_request tool

The kit registers one shared confirm_request tool automatically — listed for a caller only when at least one mutating tool's scope is granted to them. Its input schema:

z.object({
  confirmationToken: z.string(), // from the preview response
  idempotencyKey: z.string(), // caller-generated, unique per logical operation
});

Endpoints mounted by createMcpServer

The Hono app returned by createMcpServer must be served at the origin root (i.e. https://mcp.example.com/). RFC 8414 requires /.well-known/oauth-authorization-server to resolve at the domain root, and the protected resource is advertised as ${baseUrl}/mcp — mounting under a path prefix would break discovery and token validation for spec-compliant clients.

Method

Path

Description

GET

/.well-known/oauth-authorization-server

RFC 8414 authorization server metadata

GET

/.well-known/oauth-protected-resource

RFC 9728 protected resource metadata

GET

/.well-known/oauth-protected-resource/mcp

Same metadata at the RFC 9728 path-suffixed location

POST

/register

Dynamic Client Registration (RFC 7591)

GET

/authorize

Render built-in login form

POST

/authorize

Process login, issue auth code, 302 redirect

POST

/token

Token exchange (authorization_code + refresh_token)

POST

/revoke

Token revocation (RFC 7009)

POST

/mcp

MCP transport (stateless streamable-HTTP)

GET

/mcp

405 — stateless mode, no GET stream

DELETE

/mcp

405 — stateless mode, no sessions

Request body limit

All request bodies — OAuth endpoints and POST /mcp — are capped at 1 MB (HTTP 413 if exceeded). If your tools accept large inputs (e.g. document contents), pre-process or chunk them before sending.

OAuth / PKCE client flow

Protocol versions. The MCP transport is built on the v2 SDK (@modelcontextprotocol/server) and serves the 2026-07-28 revision (per-request _meta envelope, server/discover, required MCP-Protocol-Version / Mcp-Method / Mcp-Name headers). Clients that still open with a 2025-era initialize are served by the SDK's stateless legacy fallback, whose responses arrive as a single-event SSE stream. The authorization surface below targets 2026-07-28 too.

The kit implements OAuth 2.1 with PKCE (S256). A standards-compliant MCP client discovers and authenticates as follows:

  1. Discovery — GET /.well-known/oauth-authorization-server (RFC 8414) returns server metadata including authorization_endpoint, token_endpoint, and registration_endpoint.

  2. Client registration — either Dynamic Client Registration: POST /register with { "redirect_uris": ["https://your-client/callback"] } returns a client_id; or, if allowClientIdMetadataDocuments is enabled, a Client ID Metadata Document: use an https:// URL hosting a { client_id, redirect_uris, ... } JSON document as the client_id directly, with no registration call (MCP 2026-07-28 deprecates DCR in favor of this — DCR remains fully supported here).

  3. Authorization — redirect the user to GET /authorize with response_type=code, client_id, redirect_uri, code_challenge (S256 PKCE), and optionally scope. The built-in identity form collects credentials and calls your identity.verify. On success, the server 302-redirects to redirect_uri?code=<auth_code>&iss=<baseUrl> (the iss parameter, RFC 9207, lets a compliant client detect an authorization-server mix-up before redeeming the code).

  4. Token exchange — POST /token with grant_type=authorization_code, code, client_id, redirect_uri, and code_verifier. Returns { access_token, refresh_token, expires_in, token_type: "Bearer" }.

  5. Call tools — send MCP JSON-RPC to POST /mcp with Authorization: Bearer <access_token>. A call to a tool whose scope the token lacks gets a 403 insufficient_scope challenge; the client re-runs authorization for that scope (step-up) — non-default scopes are obtained this way, since the 401 challenge only advertises default scopes.

  6. Token refresh — POST /token with grant_type=refresh_token and refresh_token. Issues a new access + refresh token pair (rotation). Note: the prior access token remains valid until its TTL (1 hour by default; see tokenTtl) expires naturally. Reuse detection (RFC 9700): all tokens rotated from one authorization share a family; presenting a refresh token that has already been rotated out revokes the entire family — every access token in the lineage, older generations still inside their TTL included, plus the live refresh token — containing a stolen token. Detection is eventually-consistent — like single-use code redemption, a concurrent race isn't fully closed without a strongly-consistent store. Client implication: always refresh with the newest refresh token and never retry a refresh using a previously-rotated token — doing so is indistinguishable from theft and will revoke the whole session.

  7. Revocation — POST /revoke with the access or refresh token to invalidate it immediately. This revokes the whole family, like reuse detection.

Authorization codes are single-use: a failed redemption (wrong PKCE verifier, client, or redirect URI) consumes the code, and replaying an already-redeemed code revokes the tokens minted from it (RFC 6749 §4.1.2). A code authorized without resource still yields tokens bound to ${baseUrl}/mcp.

The official TypeScript SDK client (@modelcontextprotocol/client) does all of this for you — see Connect with the TypeScript SDK client.

Bring your own storage

The default createMemoryStorage() is suitable for tests only — it is not persistent and is not shared across isolates or instances; never use it in production. createCloudflareKvStorage(kv) wraps a Cloudflare KV namespace for production. For any other backend, implement KvLike (three methods: get, put, delete) and pass it as storage.

See docs/storage-adapters.md for the interface definition and adapter examples (Redis, DynamoDB, Postgres).

Deploy

See docs/deploy.md for runtime-specific entry-point wrappers (Cloudflare Workers, Node, AWS Lambda, Vercel).

Public API

Primary API (start here)

  • createMcpServer(config) — factory; returns a Hono app

  • createMemoryStorage() — in-memory KvLike for tests

  • createCloudflareKvStorage(kv) — wraps a Cloudflare KV namespace

  • defineTool(tool) — typed tool helper (infers handler input from inputSchema)

  • ToolError — throw to reject a tool call with a client-visible message

  • isMutating(t) — type guard: true when t is a MutatingToolDef

Types: McpServerConfig, ScopeConfig, IdentityField, IdentityConfig, Branding, ObservabilityHooks, ToolContext, ToolDef, MutatingToolDef, PreviewResult, RateLimitConfig, ToolErrorPhase, KvLike, KVNamespaceLike, AuthorizePageParams

Advanced / low-level API

Reach for these when you need to compose your own Hono app — custom middleware, a custom OAuth UI, or wiring in your own routes alongside these — rather than using createMcpServer directly. The origin-root requirement above still applies — none of these lower-level pieces lift it; RFC 8414 discovery breaks under a path prefix regardless of which API layer mounts the routes.

  • OAuthError(code, message, status = 400) — what the built-in provider throws; /token and /revoke answer with its code and status. Throw it from a custom OAuthProvider — any other error from /token becomes invalid_grant (400) with its message.

  • createOAuthProvider(config) — build the OAuth provider independently. OAuthProviderConfig fields: storage, scopes, baseUrl, optional now?: () => number (injectable clock for deterministic testing), optional tokenTtl?: TokenTtlConfig, and optional allowClientIdMetadataDocuments?: boolean (see the Config reference table above).

  • mountOAuthRoutes(app, deps) — mount /register, /authorize, /token, /revoke onto an existing Hono app. OAuthRouteDeps: provider, baseUrl, and optional identity, hooks, rateLimiter (omit it and these endpoints are unthrottled), ipExtractor, scopes.

  • mountDiscovery(app, deps) — mount /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource (plus its /mcp sub-path). DiscoveryDeps: baseUrl, scopes, optional clientIdMetadataDocumentsSupported.

  • createRateLimiter({ storage, config? }) — build the rate limiter independently.

  • handleMcpRequest(req, deps) — handle a single POST /mcp request; returns a Promise<Response>. McpRequestDeps fields (see the how-to guide §4 for a full example):

    • provider — the OAuthProvider that verifies Bearer tokens.

    • rateLimiter — the RateLimiter enforcing userPerHour.

    • storage — the KvLike passed to tools as ctx.storage (and used by two-phase confirm).

    • tools — the ToolDef / MutatingToolDef array.

    • baseUrl — the server's public base URL (used in the 401/403 challenges).

    • serverName / serverVersion — the serverInfo reported to clients.

    • env — the runtime env (Hono's c.env), exposed as ctx.env.

    • hooks — ObservabilityHooks ({} for none).

    • defaultScopes — scope names hinted in the 401 WWW-Authenticate challenge.

    • allowedOrigins? — Origin allowlist (default []: any request with an Origin is rejected).

    • waitUntil? — the runtime's waitUntil for fire-and-forget hooks; omit on Node.

  • registerMutatingTool(server, tool, ctx) / registerConfirmTool(server, ctx, mutatingTools) — register a two-phase tool / the shared confirm_request tool on your own McpServer (what handleMcpRequest does per request).

  • renderAuthorizePage(identity, params) — render the built-in login form HTML (use when building a custom /authorize handler).

Types: OAuthProvider, OAuthProviderConfig, TokenPair, TokenTtlConfig, OAuthRouteDeps, DiscoveryDeps, RateLimiter, McpRequestDeps

License

MIT

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    A production-ready MCP server that authenticates agents via OAuth 2.1 Bearer tokens, validates JWTs with JWKS, enforces tool-level scopes and roles, and logs the full delegation chain.
    -
  • A
    license
    Not graded
    quality
    D
    maintenance
    A production-ready MCP server template that connects LLMs and AI agents to external data, tools, and services with built-in OAuth 2.1 authentication, Redis-backed session management, and a modular tools engine.
    1
    MIT
  • A
    license
    A
    quality
    D
    maintenance
    A production-ready foundation for building secure, observable MCP servers with built-in authentication, rate limiting, and reference tools like database-query and semantic-search.
    15
    17 npm
    MIT