Serve MCP at a secret path, so claude.ai can connect
claude.ai's connector dialog takes a name and a URL. Sending a bearer token needs a "Request headers" beta most accounts lack, and OAuth is not built yet, so with MCP_PATH_SECRET set the endpoint is also served at /<secret>/mcp without the bearer token — a trial until OAuth replaces it. The path is the credential there. It is compared in constant time, and a wrong one answers 404 like any unknown path. The config refuses fewer than 32 URL-safe characters and never echoes the value, nothing in the server logs request paths, and the Caddy snippet rewrites the segment before an access log entry is written (verified against Caddy 2.11). Claude Code and the CLI keep the bearer token; DEPLOYMENT.md says what the path trades away. 178 tests. Smoke 76/76 and 74/74 on the local instance. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
18
CLAUDE.md
18
CLAUDE.md
@@ -39,8 +39,8 @@ index.
|
||||
read-only with respect to Schulcloud. Run `smoke` after touching `src/core/`,
|
||||
`src/mcp/` or `src/http/` — the unit tests cover only pure functions.
|
||||
|
||||
Run smoke **both ways**: with `DATABASE_URL` set (74 checks, index-backed) and
|
||||
without (72 checks, live-only). The degradation path is a supported mode, not a
|
||||
Run smoke **both ways**: with `DATABASE_URL` set (76 checks, index-backed) and
|
||||
without (74 checks, live-only). The degradation path is a supported mode, not a
|
||||
fallback nobody exercises. Every Schulcloud check fails with 401 when the live
|
||||
session has lapsed — check the container's keepalive log before suspecting code.
|
||||
|
||||
@@ -78,8 +78,9 @@ bin/cli.ts ──HTTP──────────┘ cli/{config,client,sync
|
||||
- `session-token.ts` — the Schulcloud token, replaceable at runtime: checked
|
||||
with `GET /me` (same `userId`), swapped into `config.jwt`, saved to
|
||||
`STATE_DIR`. **Read `config.jwt` at the moment of use; never keep a copy.**
|
||||
- **`http/`** — besides `/mcp` and `/api`: `/token`, a page that PUTs a fresh
|
||||
token to `/api/token`. docs/AUTH.md says why it exists.
|
||||
- **`http/`** — besides `/mcp` and `/api`: the optional `/<secret>/mcp` for
|
||||
claude.ai (`MCP_PATH_SECRET`), and `/token`, a page that PUTs a fresh token to
|
||||
`/api/token`. docs/AUTH.md and docs/DEPLOYMENT.md say why each exists.
|
||||
- **`store/`** — crawl generations, identity diffs, `german` + `pg_trgm` FTS.
|
||||
`Store.open` returns `undefined` when Postgres is down; callers degrade.
|
||||
- **`indexer/`** — crawl → persist → mirror bytes → extract text → index.
|
||||
@@ -128,8 +129,9 @@ user explicitly asking for one and understanding this.
|
||||
**Never log or echo secrets.** `TSC_JWT_COOKIE` grants full read access to the
|
||||
account; `MCP_AUTH_TOKEN` guards the endpoint. Neither belongs in
|
||||
logs, error messages, or tool output. `.env` is git-ignored — keep it that way.
|
||||
One more counts as a secret: a token replaced at runtime, which lives only in
|
||||
`STATE_DIR`, mode 0600.
|
||||
Two more count as secrets: a token replaced at runtime (it lives only in
|
||||
`STATE_DIR`, mode 0600) and, when `MCP_PATH_SECRET` is set, **request paths** —
|
||||
so nothing may log a URL path, and config errors describe the rule, not the value.
|
||||
|
||||
**Live behaviour beats upstream source.** The clones in `vendor/` track `main`
|
||||
and may be ahead of what is deployed. When they disagree with the instance, the
|
||||
@@ -328,8 +330,8 @@ bundle (2.1.272), not its docs:
|
||||
|
||||
## Environment
|
||||
|
||||
`.env` holds `TSC_URL`, `TSC_JWT_COOKIE`, `MCP_AUTH_TOKEN`; docker-compose sets
|
||||
`STATE_DIR`. See `.env.example` for the
|
||||
`.env` holds `TSC_URL`, `TSC_JWT_COOKIE`, `MCP_AUTH_TOKEN`, and optionally
|
||||
`MCP_PATH_SECRET`; docker-compose sets `STATE_DIR`. See `.env.example` for the
|
||||
full set and `docs/AUTH.md` for refreshing the JWT — `schulcloud token set`,
|
||||
no restart. `npm run probe` and `schulcloud token` report the clocks: days until
|
||||
hard expiry and the session budget.
|
||||
|
||||
Reference in New Issue
Block a user