Auth modes
The server runs in one of two modes, selected by theISH_AUTH_MODE environment variable.
| Mode | Where it runs | Inbound validation | Token source for backend calls |
|---|---|---|---|
jwt | Hosted (mcp.ishlabs.io), default | Validates the inbound Supabase JWT | The validated access token |
local | Local dev only | None | ~/.ish/config.json, refreshed on demand |
The hosted server you connect to always runs in
jwt mode. The local mode exists for
running the server yourself against a local backend.jwt mode
Injwt mode the server validates every inbound request and acts as an OAuth resource
server.
Validation. Inbound Supabase access tokens are verified as ES256 against the project’s
JWKS, with audience authenticated. The backend re-validates the same token, so a request
is checked twice (defense in depth).
Discovery. The server advertises Supabase as its authorization server using two
discovery documents: OAuth 2.0 Protected Resource metadata (RFC 9728) at
/.well-known/oauth-protected-resource, and Authorization Server metadata (RFC 8414) at
/.well-known/oauth-authorization-server. Supabase serves its OAuth metadata at
/.well-known/openid-configuration rather than the RFC 8414 path, so the server forwards
the OIDC document at the RFC 8414 path and injects a registration_endpoint (Supabase’s
Dynamic Client Registration endpoint, which the OIDC document omits).
That metadata is what lets a client (Claude Code, Cursor, and others) bootstrap sign-in
with no pre-issued token: it discovers Supabase, registers itself over Dynamic Client
Registration, runs the OAuth code flow, and presents the resulting access token to the
server.
Selects the auth mode.
jwt for the hosted server, local for dev.Public base URL of this server. Required in
jwt mode so the discovery metadata
advertises the correct base.Supabase project reference. Derives the project URL, the auth base, and the JWKS URI.
Required
aud claim on inbound tokens.OAuthProxy
When the upstream OAuth client credentials are set, the server fronts Supabase with a FastMCPOAuthProxy instead of pointing clients straight at Supabase.
Why it exists. Supabase’s OAuth server validates redirect_uri by exact string match
and does not implement the RFC 8252 loopback exception (where an auth server ignores the
port for http://127.0.0.1 and http://localhost redirects). A client that binds a fresh
ephemeral loopback port per session, with VS Code being the notable one, reuses a cached
registered client_id whose redirect_uris no longer match the port it bound, so Supabase
rejects the authorize request with invalid redirect_uri. Cursor, Claude Code, and the app
builders keep a consistent redirect through register, authorize, and callback, so they never
hit this.
What the proxy does. It advertises this server as the authorization server, presents a
full Dynamic Client Registration interface that accepts any loopback port, and holds one
fixed redirect ({ISH_MCP_BASE_URL}/auth/callback) registered with Supabase. It translates
between the client’s dynamic loopback URI and the fixed upstream one, so every client works
regardless of how it picks its redirect. The proxy runs its own PKCE leg upstream
(S256). It does not forward RFC 8707 resource indicators, since Supabase does not implement
them.
Client ID of a confidential OAuth client registered with Supabase whose
redirect_uris
is exactly {ISH_MCP_BASE_URL}/auth/callback. Set with the secret to enable the proxy.Secret for the upstream client (
token_endpoint_auth_method client_secret_basic).Token forwarding
Tools never read headers or the local config directly. Each tool obtains theAuthorization header to send to api.ishlabs.io through a single chokepoint.
- jwt mode
- local mode
The forwarded token is the validated access token, which is the upstream Supabase JWT
under both the OAuthProxy and the direct provider.Under the proxy, the inbound bearer is a short reference token the proxy mints to the
client; the proxy resolves it server-side to the stored upstream Supabase JWT, and that
JWT is what gets forwarded. Under the direct provider, the resolved token equals the
inbound bearer. If the resolved access-token context is unavailable, the server forwards
the inbound
Authorization header verbatim; a request with no bearer is rejected.local mode
local mode is for running the server against a local backend during development. It does
no inbound validation: the request is trusted, and the bearer comes from the CLI’s saved
session via the token-forwarding path above.
Config directory the server reads
config.json from. Shared with the CLI.Override the Supabase project used for token refresh. Pair with
ISH_SUPABASE_ANON_KEY
to point a dev session at the dev project; otherwise the project is resolved from the
token’s iss claim.Publishable key for the refresh project. Required alongside
ISH_SUPABASE_URL.Run
ish login before starting the server in local mode. With no config.json, an
expired token and no refresh_token, or a config written by an older CLI without
oauth_client_id, tool calls fail with a message telling you to log in again.Confirm the session
Read theish://identity/me resource, or call a read-only tool
like workspace_get, to see whose account the session is acting as. A read draws no
credits. See Connecting for the full check.