Skip to content

Authentication

The MCP server supports three authentication strategies depending on how it is deployed.


Token types

Service role key (stdio mode only)

In stdio mode the server reads SUPABASE_SERVICE_ROLE_KEY from the environment at startup. No Bearer header is required — the service role key is pre-configured and used for all requests. This bypasses Supabase RLS entirely, so restrict stdio mode to trusted local environments.

MCP token (lf_mcp_*)

MCP tokens are long-lived credentials issued by the MCP server's OAuth flow. They follow the format lf_mcp_<64 hex characters>.

Resolution flow:

  1. Token is extracted from Authorization: Bearer lf_mcp_….
  2. RPC fn_mcp_resolve_token(token) returns { lenser_id, supabase_refresh_token }.
  3. The refresh token is exchanged for a fresh Supabase JWT.
  4. The fresh JWT is used to build the user-scoped Supabase client (RLS applies normally).

MCP tokens are suitable for AI agent automations or CI pipelines where re-triggering the OAuth login flow is impractical.

Supabase JWT (advanced)

When using the HTTP transport with a short-lived Supabase JWT, include:

http
Authorization: Bearer <supabase-jwt>

The server validates the token via sb.auth.getUser(token), resolves the caller's lenser_id via RPC, and creates a user-scoped client. Supabase JWTs expire after the configured session lifetime (default 1 hour) — MCP tokens do not expire unless revoked.


OAuth 2.1 PKCE flow

When Claude.ai or another HTTP client connects, it reads the OAuth discovery document and starts a PKCE authorization flow.

Discovery documents

GET /.well-known/oauth-authorization-server
GET /.well-known/oauth-protected-resource
GET /.well-known/oauth-protected-resource/mcp

The authorization server document advertises:

json
{
  "issuer": "https://<server-base-url>",
  "authorization_endpoint": "https://<server-base-url>/oauth/authorize",
  "token_endpoint": "https://<server-base-url>/oauth/token",
  "registration_endpoint": "https://<server-base-url>/oauth/register",
  "response_types_supported": ["code"],
  "grant_types_supported": ["authorization_code"],
  "code_challenge_methods_supported": ["S256"],
  "token_endpoint_auth_methods_supported": ["none"]
}

Dynamic client registration

The server supports RFC 7591 dynamic client registration. Clients call POST /oauth/register to receive a client_id:

http
POST /oauth/register
Content-Type: application/json

{
  "client_name": "Claude",
  "redirect_uris": ["https://claude.ai/api/mcp/auth_callback"]
}

Response:

json
{
  "client_id": "lf_mcp_client_<generated>",
  "redirect_uris": ["https://claude.ai/api/mcp/auth_callback"],
  "token_endpoint_auth_method": "none"
}

LF Cloud note: Dynamic registration works correctly via https://mcp.lenserfight.com — the Cloudflare proxy serves the MCP server at the domain root, so OAuth discovery resolves as expected.

Flow summary

Key design notes

  • The code returned in the redirect is the lf_mcp_* bearer token. On POST /oauth/token, if the code already starts with lf_mcp_, the server returns it directly. This makes the flow compatible with clients (like Claude.ai) whose token exchange call comes from a cloud backend that cannot reach localhost.
  • The local dev client lf_mcp_client_localdev is created automatically on every server boot via fn_mcp_ensure_local_dev_client. It is PKCE-only (requires_secret = false) and is never lost by a DB reset.

Request headers (HTTP mode)

HeaderValueRequired
AuthorizationBearer lf_mcp_<hex> or Bearer <supabase-jwt>Yes
mcp-session-idOpaque string issued at session startRecommended
Content-Typeapplication/jsonYes

If mcp-session-id is omitted, the server creates a new in-memory session for each request, which is less efficient but still correct.


Security considerations

  • The service role key bypasses RLS on every table. Never expose it in client-side code or commit it to a public repository.
  • In stdio mode, shell access is equivalent to service role key access — restrict it to your own machine.
  • MCP tokens are long-lived. Avoid logging them. If a token is leaked, revoke it by deleting the corresponding row from lensers.mcp_tokens.
  • The HTML login form uses escapeHtml to prevent XSS in error messages.