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, "inFlight": 1, "hub": true },
    "internal-only": { "state": "down", "kind": "remote", "restarts": 2, "tools": 0,  "inFlight": 0, "hub": false },
    "scraper":           { "state": "up",   "kind": "docker", "restarts": 0, "tools": 84, "inFlight": 0, "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. inFlight counts the requests forwarded to the server whose answer the hub is still waiting for; a call its client cancelled stops counting at once. 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 · unlock

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.