Give claude.ai a token of its own, sent as a request header

claude.ai's connector dialog does offer request headers, on its second step,
after the URL has been probed, so the connector no longer needs the secret
path. MCP_AUTH_TOKEN already worked there as a bearer or X-Api-Key, but it
also opens /api, which can replace the Schulcloud token and stream the file
mirror, and claude.ai stores the header's value.

MCP_CONNECTOR_TOKEN is a second token, accepted on /mcp only and refused on
/api, and rotated without touching Claude Code or the CLI. The config refuses
one shorter than 32 characters, equal to MCP_AUTH_TOKEN, or set without it,
and never echoes a value. Every accepted token is compared in full, so the
timing does not tell which one matched.

The gate also takes a bare Authorization value, because claude.ai sends a
header exactly as typed and its docs warn that most servers reject a token
entered without "Bearer ". It takes X-Auth-Token too, the other name its
dialog offers.

The docs now set up the header; the secret path stays as a fallback for
clients that cannot send one. 184 tests. Smoke 79/79 and 77/77 on the local
instance.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-09-16 22:03:56 +02:00
parent bfccb3f343
commit ab581aa5ca
15 changed files with 279 additions and 79 deletions

View File

@@ -24,10 +24,17 @@ TSC_JWT_COOKIE=
# openssl rand -hex 32
MCP_AUTH_TOKEN=
# claude.ai only: serve MCP at /<this value>/mcp WITHOUT the bearer token,
# because its connector dialog cannot send a header. The URL becomes the
# credential — see "Connecting Claude" in docs/DEPLOYMENT.md before using it.
# At least 32 URL-safe characters; unset = off. Generate one with:
# claude.ai: the token its connector sends as a request header
# (`authorization: Bearer <token>`). Accepted on /mcp only, never on /api — which
# can replace the Schulcloud token — because claude.ai stores it. At least 32
# characters, different from MCP_AUTH_TOKEN; unset = off. Generate one with:
# openssl rand -hex 32
# MCP_CONNECTOR_TOKEN=
# Clients that cannot send a header: serve MCP at /<this value>/mcp with no
# token at all. The URL becomes the credential — see "Connecting Claude" in
# docs/DEPLOYMENT.md before using it. At least 32 URL-safe characters; unset =
# off. Generate one with:
# openssl rand -hex 32
# MCP_PATH_SECRET=