Skip to content

HTTP endpoints ​

Everything mcp-hub serves, and what guards it.

MCP endpoints ​

PathAuthPurpose
/<name>BearerStreamable HTTP endpoint of one server (canonicalized to /<name>/mcp)
/<name>/mcpBearersame, canonical form
/hubBeareraggregate endpoint exposing the six meta-tools

<name> is a key from mcpServers. Both routes accept the HTTP methods the Streamable HTTP transport uses.

A server that is not up answers 503 with a JSON-RPC error naming its state:

json
{
  "jsonrpc": "2.0",
  "error": { "code": -32000, "message": "Server \"paperless\" is starting" },
  "id": null
}

Status ​

PathAuthPurpose
/liveznoneprocess liveness; always 200 {"status":"ok"} while the process runs
/healthBearer for /hubper-server state; 200 when every server is up or sleeping, 503 otherwise

/health reports the same fleet-wide view as the /hub aggregate, so it takes the same resource: a token issued for one server's path gets 401 here.

/livez is what the image HEALTHCHECK calls, and it is the endpoint external monitoring should poll. A degraded child does not make the container unhealthy.

/health response:

json
{
  "status": "degraded",
  "servers": {
    "paperless":     { "state": "up",   "kind": "stdio",  "restarts": 0, "tools": 14, "hub": true },
    "internal-only": { "state": "down", "kind": "remote", "restarts": 2, "tools": 0,  "hub": false },
    "scraper":           { "state": "up",   "kind": "docker", "restarts": 0, "tools": 84, "hub": true,
                       "image": "scraper-mcp@sha256:…", "container": "mcp-sandbox-scraper" }
  }
}

state is one of starting, up, down, sleeping, unauthorized. kind is stdio, remote, docker or socket. restarts counts supervisor restarts since boot. hub says whether the server appears in the /hub aggregate. A sleeping on-demand server counts as healthy — it is resting by design, and its cached tools count stays visible. unauthorized is a remote server whose upstream OAuth needs attention; it is counted as degraded and, unlike down, is not retried until somebody acts. A sandboxed server also reports the image and container it runs as — a local tag and a name, not credentials, and the difference between "scraper is down" and something you can act on.

A server with allowTools or denyTools also carries a toolFilter object — exposed, hidden and any unmatched entries. It is absent for every server without a filter, and tools keeps its meaning: the number a client can see. hidden and unmatched appear only once the server has really listed its tools: a sleeping server restored from the tool cache holds a snapshot that was already filtered, so there is nothing honest to say about them yet, and the two keys stay away rather than reporting a zero.

OAuth 2.1 ​

PathAuthPurpose
/authorizesession / passwordauthorization endpoint (PKCE required); accepts an https URL as client_id
/tokenclient secret, private_key_jwt or nonetoken and refresh endpoint
/registernonedynamic client registration (RFC 7591); 404 when disabled
/register/<client_id>registration_access_tokenthe client's own registration (RFC 7592): GET to read, PUT to change, DELETE to remove it; 404 when dynamic registration is disabled
/upstream/callbacksigned state + hub sessionwhere an upstream sends the browser back after an upstream login; single use
/.well-known/mcp-hub-client/<id>.jsonnonethe hub's own client metadata document, one per oauth.mode: "cimd" upstream; 404 for an identifier nobody publishes
/revokeclient credentialstoken revocation (RFC 7009)
/interaction/<uid>/—the page an unfinished authorization sends the browser to: the password form, or the Approve / Deny page for a signed-in operator and a client not yet confirmed
/interaction/<uid>/loginpassworda correct password approves the requesting client; 503 while no usable password is configured
/interaction/<uid>/consentsession + CSRF tokenApprove / Deny for a client not yet confirmed

Auth responses carry Cache-Control: no-store. The interactive pages deny framing and carry a restrictive CSP.

A client_id that is an https URL with a path is treated as a Client ID Metadata Document and fetched from the client; anything else is looked up as a dynamically registered client. Which mechanisms are accepted is set with CLIENT_REGISTRATION.

Rate limits are listed on the security page.

Discovery documents ​

PathAuthPurpose
/.well-known/oauth-authorization-servernoneRFC 8414 authorization-server metadata
/.well-known/oauth-authorization-server/<suffix>nonesame document, path-inserted form
/.well-known/oauth-protected-resourcenoneRFC 9728 protected-resource metadata
/.well-known/oauth-protected-resource/<suffix>nonepath-scoped variant
/.well-known/openid-configurationnonealias serving the same RFC 8414 document
/.well-known/openid-configuration/<suffix>nonesame alias, path-inserted form

The authorization-server document advertises both registration mechanisms:

json
{
  "client_id_metadata_document_supported": true,
  "registration_endpoint": "https://mcp.example.net/register",
  "token_endpoint_auth_methods_supported": ["client_secret_post", "none", "private_key_jwt"],
  "token_endpoint_auth_signing_alg_values_supported": ["RS256", "…", "EdDSA"]
}

client_id_metadata_document_supported is what makes a spec-compliant client prefer CIMD; registration_endpoint is absent when dynamic registration is turned off, and the CIMD fields are absent when metadata documents are.

The hub is not an OpenID Connect provider — there is no userinfo endpoint and no id_token. The alias exists because several clients, ChatGPT among them, probe the OIDC path before the RFC 8414 one, and the document answers every field they read there. Enabling a client's OIDC option on top of it does not work.

The path-scoped resource document is what makes resource-bound tokens work without client configuration. A client connecting to /paperless/mcp looks up /.well-known/oauth-protected-resource/paperless/mcp and gets:

json
{
  "resource": "https://mcp.example.net/paperless/mcp",
  "authorization_servers": ["https://mcp.example.net"],
  "bearer_methods_supported": ["header"],
  "resource_name": "mcp-hub"
}

It then requests a token for exactly that resource. One authorization server covers every resource the hub exposes.

Reserved names ​

These cannot be used as server names, because the hub serves them itself:

mcp · hub · authorize · token · register · login · consent · health · livez · revoke · jwks · interaction · session · userinfo · upstream · .well-known

The check is case-insensitive and happens when the config is parsed.

Released under the MIT License. Not affiliated with Anthropic; “Claude” is a trademark of Anthropic PBC.