Auth & permissions

apphub-mcp only accepts OAuth 2.0 Bearer tokens. Three-tier scopes (read/write/admin) plus per-app permissions are checked on every call.

Only OAuth tokens, please

Personal API keys (PAT) are gone

The old PAT path is fully removed. The MCP endpoint only accepts OAuth 2.0 Bearer tokens. app-key tokens work for 4 read-only API tools (we'll cover those below).

Send Authorization: Bearer <token> on every /mcp request. Missing or expired tokens come back as 401 Unauthorized with a WWW-Authenticate challenge. The JSON-RPC error code is -32001.

401 response example
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://api.jocodingax.ai/.well-known/oauth-protected-resource"
Content-Type: application/json

{"jsonrpc":"2.0","id":1,"error":{"code":-32001,"message":"credentials required"}}

How do I get a token?

Start from the metadata

Follow the resource_metadata URL in the 401 response. It tells you which authorization server to go to and which scopes you need.

Terminal
$ curl https://api.jocodingax.ai/.well-known/oauth-protected-resource

The response is RFC 9728 compliant: authorization_servers, scopes_supported, resource.

Exchange for an access token

Use the authorization_servers from that metadata with either client_credentials or authorization_code flow. The server validates the token on every request and extracts the owner's ID and scopes.

Scopes come in three tiers

ScopeWhat it lets you doExample tools
readList, inspect, search onlylist_my_apps, get_app_url, list_apis, search_table, preflight_check
writeChange statedeploy_app, rollback_deploy, restart_app, env_vars, records (insert/update)
adminOrg/security changesimport_and_deploy, set_app_spec, manage_oauth_client, connect_repo, manage_api_key

Having admin implicitly grants read and write.

Some tools vary scope by action

For certain tools the required scope depends on the action argument:

  • manage_app: updatewrite, archive/unarchive/deleteadmin
  • manage_ci: listread, rerunwrite, enable/disableadmin
  • manage_table: list/describeread, create/dropwrite
  • records: queryread, insert/update/delete/bulk_insertwrite
  • connect_repo: all actions → admin

Two-phase authorization

Every tools/call goes through both phases, in order.

Phase 1 — global · scope · auth-type

  1. Deny list — if the tool is temporarily blocked, short-circuit.
  2. Scope check — does the token carry the required scope?
  3. Auth-type restrictionapp-key tokens can only reach these 4 tools:
list_apis, get_api_schema, test_api, discover_apis

Phase 2 — per-app OAuth authorization

For OAuth tokens, ResolveApp(args) finds the target app and UserEffectiveScopes(user, app) computes the user's actual permissions on that app (canManage, scopes).

  • Non-read action but canManage is false → forbidden (endpoint_forbidden)
  • User lacks the required scope → forbidden (missing_scope)

Session ownership

The Mcp-Session-Id header is validated on both JSON-RPC requests and SSE streams. If session owner ≠ token owner, the header is dropped and resource subscriptions / task calls fail with Mcp-Session-Id required.

Error catalog

JSON-RPC codeHTTPMeaning
-32001401Auth missing or failed (WWW-Authenticate emitted)
-32600400Session ID missing on resource subscribe
-32601200Method/tool doesn't exist
-32602200Parameter validation failed
-32603200Internal server error (includes panic recovery)
forbidden200Scope/permission denied — detailed reason: oauth_only, missing_scope, resource_scope, endpoint_forbidden