fix: correct anonymous-access claim for OAuth-enabled (production) deployments (#498)
Prior edit this session claimed publicServer answers with zero Authorization
header in all cases. Verified against internal/server/server.go's actual
/mcp handler: when cfg.OAuth.Enabled (production's real config), every
request without a bearer gets a 401 challenge by deliberate design (RFC 6750
discovery), regardless of which tool tier it would otherwise resolve to.
The #450 ungated-read invariant is real but operates one layer up, at the
scope-ACL check once a caller already has a token (even read-scope, via
self-serve DCR) -- it was never a bearerless-access guarantee in an
OAuth-enabled deployment. TestAnonymousServerExposesReadTools passes with
OAuth disabled, which is not production's config.
docs: update wiki to reflect the #450 2-scope model (read/write)
Several pages still described the pre-#450 four-tier scope model
(content.read/content.write/site.admin/system.admin with mcp as the
only alias) as if it were still active. Since #450 the server enforces
exactly two scopes, read and write, with every older scope string
accepted only as a compatibility alias.
Home, Documentation-Source-of-Truth, OAuth-Clients-and-Scopes,
Scope-and-Tool-Matrix, Pitfalls-Index, Client-Compatibility, and
Smoke-Tests-and-Live-Verification updated to describe the current
model as current, with the old model kept only as historical context
where relevant.
Pitfall-Anonymous-vs-Authenticated-Server rewritten more substantially:
it described a four-MCP-server-instance architecture
(anonServer/readServer/writeServer/siteAdminServer) that no longer
exists in the code -- the server now runs two instances
(publicServer/writeServer), and read-only tools that used to require
content.read (validate_site, build_agent_context, etc.) are fully
ungated since #450, not just relabeled.
Security-Proof-Client-Credential-Isolation gets a terminology note
rather than a rewrite -- the security property it demonstrates
(redirect URI isolation blocks cross-client credential reuse) is
scope-model-independent, so the historical scope names are left
in place with a pointer to the current model.
docs: add 5 pitfall pages + update Home index
- Pitfall: Claude auth_callback vs oauth_callback (redirect URI path mismatch)
- Pitfall: Redirect URI exact matching (rules, wildcards, common mistakes)
- Pitfall: OAuth client max scope (scope clamping, RFC 6749 §3.3, log output)
- Pitfall: Anonymous vs authenticated server (tool boundary, test, Claude UI label)
- Pitfall: openid-configuration 404 (ChatGPT OIDC probe, expected, no fix needed)