Add OAuth 2.1 authorization (2025-06-18, opt-in) #17

Open
opened 2026-05-17 15:56:10 +00:00 by claude-noether · 2 comments
Collaborator

Add OAuth 2.1 authorization — the formalised authentication framework introduced in MCP spec 2025-06-18.

Goal

lmcp's current auth is a single shared Bearer token per server (LMCP_TOKEN env, .godparticle conf entry, or --auth flag). That's fine for LAN/single-user deployments and should stay. But spec-compliant clients on public deployments expect OAuth 2.1 flows: dynamic client registration, authorization code with PKCE, token refresh, scope checking.

Spec surface (2025-06-18)

Endpoint Method Notes
/.well-known/oauth-authorization-server GET Discovery document.
/.well-known/oauth-protected-resource GET Identifies the auth server.
/register POST Dynamic client registration (RFC 7591).
/authorize GET Authorization endpoint.
/token POST Token endpoint (code → access_token + refresh_token).
Authorization: Bearer <jwt> on /mcp Token validation per request.

Servers MAY delegate to an external auth server (Auth0, Keycloak, your own) by pointing the discovery document at it. lmcp's role then reduces to: validate incoming JWTs against the discovery doc's JWKS endpoint, check scopes against tool annotations.

API for lmcp

local server = lmcp.new("public-tools", {
    auth = {
        mode = "oauth",                                  -- "bearer" remains default
        issuer = "https://auth.example.com",
        audience = "lmcp",
        required_scopes = { ["tools/call"] = "tools.call" },
    },
})

Or, simpler: delegate to an upstream issuer entirely and lmcp just validates JWTs.

Concerns

  • JWT validation in pure Lua. Requires HMAC-SHA256 / RSA-SHA256 verification. luasocket doesn't ship that. Options:
    • (a) require luacrypto or lua-resty-jwt as optional dep when mode = "oauth",
    • (b) shell out to openssl for the verification step,
    • (c) require an external auth-proxy (nginx + openresty) in front of lmcp.
  • JWKS fetching + caching. Need to GET the issuer's JWKS endpoint at startup, cache, refresh periodically.
  • Per-request overhead. JWT validation is non-trivial; need a small in-process cache keyed on token hash.

Scope (v1)

  • Discovery endpoints (/.well-known/*) returning a config file's contents.
  • Authorization header parsing.
  • JWT validation via openssl dgst -verify shell-out (avoids new Lua deps).
  • Scope check stub (refuse tools/call if missing the right scope, configurable per-tool).
  • Bearer-token mode remains the default. OAuth is opt-in.

Out of scope

  • lmcp acting as its own authorization server (running /authorize and /token).
  • DCR.
  • Audit logging of token use (separate issue).

Priority

Low for the home-LAN use case (Bearer-token + LAN trust covers it). High if lmcp ever ships as a publicly-reachable service. Defer until there's a concrete public-deployment requirement; track here so it doesn't get lost.

Add **OAuth 2.1 authorization** — the formalised authentication framework introduced in MCP spec `2025-06-18`. ## Goal lmcp's current auth is a single shared Bearer token per server (`LMCP_TOKEN` env, `.godparticle` conf entry, or `--auth` flag). That's fine for LAN/single-user deployments and should stay. But spec-compliant clients on public deployments expect OAuth 2.1 flows: dynamic client registration, authorization code with PKCE, token refresh, scope checking. ## Spec surface (2025-06-18) | Endpoint | Method | Notes | |---|---|---| | `/.well-known/oauth-authorization-server` | GET | Discovery document. | | `/.well-known/oauth-protected-resource` | GET | Identifies the auth server. | | `/register` | POST | Dynamic client registration (RFC 7591). | | `/authorize` | GET | Authorization endpoint. | | `/token` | POST | Token endpoint (code → access_token + refresh_token). | | `Authorization: Bearer <jwt>` on `/mcp` | – | Token validation per request. | Servers MAY delegate to an external auth server (Auth0, Keycloak, your own) by pointing the discovery document at it. lmcp's role then reduces to: validate incoming JWTs against the discovery doc's JWKS endpoint, check scopes against tool annotations. ## API for lmcp ```lua local server = lmcp.new("public-tools", { auth = { mode = "oauth", -- "bearer" remains default issuer = "https://auth.example.com", audience = "lmcp", required_scopes = { ["tools/call"] = "tools.call" }, }, }) ``` Or, simpler: delegate to an upstream issuer entirely and lmcp just validates JWTs. ## Concerns - **JWT validation in pure Lua.** Requires HMAC-SHA256 / RSA-SHA256 verification. luasocket doesn't ship that. Options: - (a) require `luacrypto` or `lua-resty-jwt` as optional dep when `mode = "oauth"`, - (b) shell out to `openssl` for the verification step, - (c) require an external auth-proxy (nginx + openresty) in front of lmcp. - **JWKS fetching + caching.** Need to GET the issuer's JWKS endpoint at startup, cache, refresh periodically. - **Per-request overhead.** JWT validation is non-trivial; need a small in-process cache keyed on token hash. ## Scope (v1) - Discovery endpoints (`/.well-known/*`) returning a config file's contents. - `Authorization` header parsing. - JWT validation via `openssl dgst -verify` shell-out (avoids new Lua deps). - Scope check stub (refuse `tools/call` if missing the right scope, configurable per-tool). - **Bearer-token mode remains the default.** OAuth is opt-in. ## Out of scope - lmcp acting as its own authorization server (running `/authorize` and `/token`). - DCR. - Audit logging of token use (separate issue). ## Priority **Low** for the home-LAN use case (Bearer-token + LAN trust covers it). **High** if lmcp ever ships as a publicly-reachable service. Defer until there's a concrete public-deployment requirement; track here so it doesn't get lost.
Owner

Mark this as long term feature request. For now, lmcp stays LAN only.

Mark this as long term feature request. For now, lmcp stays LAN only.
Author
Collaborator

Acked: long-term. lmcp stays Bearer-token + LAN for now. Re-open scope discussion when a public deployment becomes concrete. Awaiting a long-term label from a repo-write account.

Acked: long-term. lmcp stays Bearer-token + LAN for now. Re-open scope discussion when a public deployment becomes concrete. Awaiting a long-term label from a repo-write account.
marfrit added the long-term label 2026-05-17 18:52:40 +00:00
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: marfrit/lmcp#17