575125a5dc
A well-formed but non-existent/inaccessible spaceId made the space-permissions
check answer with an opaque 404 ("Space permissions not found"), which the agent
could not self-correct from. Add an enrich-on-404 wrapper at the client-method
level (Variant A — no backend change) that, ONLY on that 404, replaces the server
text with a factual message naming the bad spaceId and the spaces the token can
actually see, pointing at listSpaces.
Mechanism (context.ts):
- getAccessibleSpaceIndex(): the token's accessible spaces from the single source
of truth (/spaces), with a per-instance short-TTL cache (MCP_SPACES_CACHE_TTL_MS,
default 60s; 0 disables) and single-flight dedup. Only a COMPLETE (untruncated)
result is cached and only a complete result may drive the rewrite. The in-flight
promise is nulled on BOTH resolve and reject so a transient /spaces blip is never
memoized. Cache invalidated on every identity change, mirroring collabTokenCache
(login() + the 401/403 reauth interceptor).
- paginateAllWithMeta(): surfaces the `truncated` flag paginateAll swallows;
paginateAll now delegates to it (unchanged contract, getSpaces untouched).
- withSpaceAccessDiagnostics(spaceId, mcpName, fn): abort/cap wins FIRST (by the
toolAbortSignal flag, NOT e.name — a cap may be TimeoutError/custom); only a 404
is enrichable; FAILS OPEN (rethrows the original server error) on every source of
uncertainty (fetch failed / !complete / spaceId present / aborted).
Wrapped only the paths where the sole 404 cause is the space membership check:
getTree, listPages (tree + recent-with-spaceId), search (with spaceId),
checkNewComments. createPage/getPageContext are deliberately NOT wrapped.
formatSpaceNotAccessible (errors.ts) composes the message (<=10 spaces + "(+N ещё)"
tail; distinct zero-spaces variant).
Tests: 11 new unit tests (stub client) covering all 9 acceptance criteria +
message shape; full mcp unit suite green (701 tests).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>