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

@@ -57,11 +57,12 @@ export function createHttpApp(config: Config, services?: Services): express.Expr
res.json({ status: 'ok', sessions: sessions.size, index: services?.store ? 'on' : 'off' });
});
// One token guards both surfaces: the MCP endpoint and the CLI's file/manifest
// API. Splitting them was considered and rejected as unnecessary ceremony for
// a single-user deployment.
// MCP_AUTH_TOKEN opens both surfaces: the MCP endpoint and the CLI's API. The
// connector token opens /mcp alone. claude.ai stores it as a request header,
// and a credential held by a third party should reach the read-only tools,
// not /api, which can replace the Schulcloud token and stream the file mirror.
if (config.authToken) {
app.use(MCP_PATH, bearerAuth(config.authToken));
app.use(MCP_PATH, bearerAuth(config.connectorToken ? [config.authToken, config.connectorToken] : config.authToken));
app.use(API_PATH, bearerAuth(config.authToken));
} else {
console.warn(