Trade a host API key for a short-lived, audience-bound access token —
POST /api/mcp/token.
scope "/api/mcp" do
pipe_through [:api, :rate_limited_auth] # must skip protect_from_forgery
forward "/token", Noizu.MCP.Auth.Server.ApiKeyTokenPlug, MCPConfig.as_opts()
endA headless caller — a cron job, a CI step — cannot open a browser, and OAuth
without a browser is not OAuth. This endpoint gives it the same kind of token
every other client gets: same aud, same 15-minute ceiling, same scope rules.
Why not just accept the API key at the MCP mount?
You can — Noizu.MCP.Auth.ApiKeyVerifier in a ChainVerifier does exactly
that, and for a single-mount host it is simpler. This endpoint is better when
there are several mounts: the key is presented once, to one endpoint, and
what travels to each mount afterwards is a token bound to that mount alone. A
long-lived key presented on every request is a long-lived key in every log.
No refresh token is issued: the API key is the long-lived credential, and a caller that holds it can always ask again.
Options (api_keys: on the server config)
:validator(required) —{mod, fun}over the presented key, exactly asNoizu.MCP.Auth.ApiKeyVerifieruses it:prefix,:keys,:default_claims,:scopes— passed straight through:client_id— theclient_idrecorded on issued tokens, default"api-key"
The rate-limit hook fires as :api_key_token. Rate limiting here is the point:
without it this endpoint is an offline-free API-key oracle.