You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Follow-up to #94 / PR #95, which shipped anonymous web chat over the public corpus and deliberately left MCP closed (/mcp without a token is still 401).
Goal
A coding agent with no token can use the graph tools over MCP for the public entries only:
claude mcp add --transport http timeplus-knowledge https://<host>/mcp # no --header
The anonymous principal; auth.resolve_scope() returns the enabled public entry keys for it, and the MCP guard (mcp_http.make_guard) calls exactly that — so once an anonymous caller reaches a tool call, all six tools are scoped to public entries with no new filtering code.
read_source requires source:view, which anonymous never holds → raw source stays closed automatically.
Isolation tests for MCP scope already exist (tests/test_mcp_http.py); they only need an anonymous caller added.
Change
Own switch: TPK_ANONYMOUS_MCP / [server].anonymous_mcp (default false), independent of TPK_ANONYMOUS_ACCESS, so either can be on without the other.
McpAuth: no Authorization header + switch on → the anonymous principal. A bad/expired/non-tpk_ token is still 401 (never downgraded to anonymous — same rule as chat). Denial logging unchanged; tool-call log line says user=anonymous.
Capability gate: /mcp requires explore today and anonymous holds only chat. Do not widen anonymous's capabilities; instead let McpAuth admit the anonymous principal explicitly when the switch is on (the gate stays explore for real users). Keep anonymous out of read_source (already true).
Per-IP rate limit on /mcp — required before this goes on in production. MCP tools make no LLM calls, so the token budget does not apply: the exposure is database load from an unbounded loop of search_entities / neighbors calls (each call is bounded, the loop is not). Limiter keyed on client IP (needs the real client address — check the NLB / proxy-protocol setup on the k8s deployment; behind the NLB every request may otherwise share one address), e.g. N tool calls per minute per IP, 429 with Retry-After. Same limiter should apply to anonymous /chat and can later cover authenticated MCP (Remote MCP follow-ups: audit trail, rate limiting, token hygiene, OAuth #80).
initialize's serverInfo/instructions mention that the unauthenticated view covers public documentation only.
Acceptance
Switch off: /mcp without a token is 401 (today's tests unchanged).
Switch on: initialize, tools/list, search_entities, get_entity, neighbors, path_between, list_communities return only public-entry data for an anonymous caller — per-tool isolation tests, including that an internal entity's id/name never appears anywhere in a response (hints included); read_source is a tool error; a bad token is still 401; a real token still gets its own scope.
Rate limit: the (N+1)th call within the window is 429 with Retry-After; tests with a fake clock.
Follow-up to #94 / PR #95, which shipped anonymous web chat over the public corpus and deliberately left MCP closed (
/mcpwithout a token is still 401).Goal
A coding agent with no token can use the graph tools over MCP for the public entries only:
Already in place (from #95)
anonymousprincipal;auth.resolve_scope()returns the enabled public entry keys for it, and the MCP guard (mcp_http.make_guard) calls exactly that — so once an anonymous caller reaches a tool call, all six tools are scoped to public entries with no new filtering code.read_sourcerequiressource:view, which anonymous never holds → raw source stays closed automatically.tests/test_mcp_http.py); they only need an anonymous caller added.Change
TPK_ANONYMOUS_MCP/[server].anonymous_mcp(defaultfalse), independent ofTPK_ANONYMOUS_ACCESS, so either can be on without the other.McpAuth: noAuthorizationheader + switch on → the anonymous principal. A bad/expired/non-tpk_token is still 401 (never downgraded to anonymous — same rule as chat). Denial logging unchanged; tool-call log line saysuser=anonymous./mcprequiresexploretoday and anonymous holds onlychat. Do not widen anonymous's capabilities; instead letMcpAuthadmit the anonymous principal explicitly when the switch is on (the gate staysexplorefor real users). Keep anonymous out ofread_source(already true)./mcp— required before this goes on in production. MCP tools make no LLM calls, so the token budget does not apply: the exposure is database load from an unbounded loop ofsearch_entities/neighborscalls (each call is bounded, the loop is not). Limiter keyed on client IP (needs the real client address — check the NLB / proxy-protocol setup on the k8s deployment; behind the NLB every request may otherwise share one address), e.g. N tool calls per minute per IP, 429 withRetry-After. Same limiter should apply to anonymous/chatand can later cover authenticated MCP (Remote MCP follow-ups: audit trail, rate limiting, token hygiene, OAuth #80).initialize'sserverInfo/instructions mention that the unauthenticated view covers public documentation only.Acceptance
/mcpwithout a token is 401 (today's tests unchanged).initialize,tools/list,search_entities,get_entity,neighbors,path_between,list_communitiesreturn only public-entry data for an anonymous caller — per-tool isolation tests, including that an internal entity's id/name never appears anywhere in a response (hints included);read_sourceis a tool error; a bad token is still 401; a real token still gets its own scope.Retry-After; tests with a fake clock.Related: #94 (decisions), #80 (MCP rate limiting / audit — share the limiter).