From 9a050656357f9334d27e8dd9f5690e5f7e431bfe Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 10:54:32 +0000 Subject: [PATCH 1/2] docs: fix stale intent nodes (reserved _meta key, endpoint files, cache key) - meta/mode: a catalog with a _meta key makes the ModuleResolver constructor throw (RESERVED_TOOLSET_KEYS); the nodes described a ToolRegistry collision at registration. - http: custom endpoint helpers live in http.utils.ts and http.types.ts, not customEndpoints.ts / endpointRegistration.ts. - session: the "default" suffix gives a plain clientId cache key, and the hash covers values as well as keys. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01VmUXGTni8Lia5eQCbh2XUa --- src/http/AGENTS.md | 2 +- src/meta/AGENTS.md | 4 ++-- src/mode/AGENTS.md | 11 ++++++----- src/session/AGENTS.md | 2 +- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/http/AGENTS.md b/src/http/AGENTS.md index 01fe3b5..399d17f 100644 --- a/src/http/AGENTS.md +++ b/src/http/AGENTS.md @@ -8,7 +8,7 @@ Provides Fastify-based HTTP transport for the MCP protocol. Handles SSE streams, - **FastifyTransport** (`FastifyTransport.ts`) — Main HTTP transport. Per-client bundles via ClientResourceCache, optional SessionContextResolver for context differentiation. - **Origin validation** (`originValidation.ts`) — `normalizeAllowedOrigins()` + `createOriginGuard()`; each transport applies the guard as an `onRequest` hook on `/mcp` POST/GET/DELETE only, when `allowedOrigins` is set. -- **Custom Endpoints** (`customEndpoints.ts`, `endpointRegistration.ts`) — Type-safe endpoint definitions with Zod schemas. `defineEndpoint()` / `definePermissionAwareEndpoint()` helpers. +- **Custom Endpoints** (`http.utils.ts`, types in `http.types.ts`) — Type-safe endpoint definitions with Zod 4 schemas. `defineEndpoint()` / `definePermissionAwareEndpoint()` helpers build them; `registerCustomEndpoints()` mounts them and enforces the reserved paths. Validation failures answer 400 `VALIDATION_ERROR` with Zod 4 issues in `error.details`. ## Endpoints diff --git a/src/meta/AGENTS.md b/src/meta/AGENTS.md index 93297e0..6677684 100644 --- a/src/meta/AGENTS.md +++ b/src/meta/AGENTS.md @@ -18,13 +18,13 @@ Registers meta-tools on MCP servers for runtime toolset management. Called by Se ## Invariants -1. **`_meta` is a reserved toolset key** — If a catalog uses `"_meta"` as a toolset key, ToolRegistry will detect a name collision at registration time. Never use this key in the catalog. +1. **`_meta` is a reserved toolset key** — `RESERVED_TOOLSET_KEYS` (`src/mode/mode.types.ts`) makes the `ModuleResolver` constructor throw when a catalog has a `"_meta"` key, and `validateToolsetName()` also refuses it ("reserved for internal use"). Never use this key in the catalog. 2. **Meta-tools registered via ToolRegistry** — Same collision detection path as user tools. Meta-tools appear in `toolRegistry.list()` and `toolRegistry.listByToolset()`. 3. **`enable_toolset` / `disable_toolset` annotated with `destructiveHint: false, idempotentHint: true`** — They change which tools are exposed but destroy nothing, and repeating either call has no further effect on state. Visible to LLM clients via MCP tool annotations. Discovery tools (`list_*`, `describe_*`) use `readOnlyHint: true`. ## Anti-patterns -- Using `_meta` as a catalog key (collision with meta-tools) +- Using `_meta` as a catalog key (the resolver throws at construction) - Registering meta-tools outside of `registerMetaTools()` (bypasses ToolRegistry) - Expecting meta-tools in permission-based servers (they are never registered — `registerMetaTools(false)`) diff --git a/src/mode/AGENTS.md b/src/mode/AGENTS.md index 6b3c303..ca736f3 100644 --- a/src/mode/AGENTS.md +++ b/src/mode/AGENTS.md @@ -11,10 +11,11 @@ Handles toolset resolution, validation, and module loading. Bridges toolset defi ## Invariants -1. **Module loaders fail silently** — Errors caught, logged as warnings, skipped. Toolset activates with whatever tools succeeded. This is intentional — one broken loader should not block the rest. -2. **ToolsetValidator is in ModeResolver.ts** — Historical naming quirk. `ToolsetValidator.ts` just re-exports from `ModeResolver.ts`. Import from either file. -3. **ModuleResolver stores catalog; ToolsetValidator takes it as parameter** — Different validation signatures. ModuleResolver validates against its stored catalog; ToolsetValidator is stateless. -4. **Context passed to module loaders** — Enables dynamic tool generation based on runtime context (e.g., per-client tools). +1. **`_meta` is rejected** — `RESERVED_TOOLSET_KEYS` (`mode.types.ts`): the `ModuleResolver` constructor throws if the catalog has such a key, and `validateToolsetName()` refuses it again as defense in depth. `moduleLoading.ts` is an empty placeholder (no code, nothing imports it). +2. **Module loaders fail silently** — Errors caught, logged as warnings, skipped. Toolset activates with whatever tools succeeded. This is intentional — one broken loader should not block the rest. +3. **ToolsetValidator is in ModeResolver.ts** — Historical naming quirk. `ToolsetValidator.ts` just re-exports from `ModeResolver.ts`. Import from either file. +4. **ModuleResolver stores catalog; ToolsetValidator takes it as parameter** — Different validation signatures. ModuleResolver validates against its stored catalog; ToolsetValidator is stateless. +5. **Context passed to module loaders** — Enables dynamic tool generation based on runtime context (e.g., per-client tools). ## Module Loading Flow @@ -25,7 +26,7 @@ resolveToolsForToolsets([toolsetName], context) 2. For each module in definition.modules[]: → Look up loader in moduleLoaders map → Call loader(context) — may be async - → Catch errors → warn and skip (invariant #1) + → Catch errors → warn and skip (invariant #2) → Append returned tools Return flattened McpToolDefinition[] ``` diff --git a/src/session/AGENTS.md b/src/session/AGENTS.md index 4dd715e..b8691d0 100644 --- a/src/session/AGENTS.md +++ b/src/session/AGENTS.md @@ -14,7 +14,7 @@ Manages per-session context resolution and client resource caching. Enables mult 1. **Fail-secure parsing** — Invalid base64/JSON encoding returns `{}`, not an error. Malformed client input never crashes the server. 2. **Disallowed keys silently filtered** — No logging to prevent information leakage about what keys exist. -3. **Cache key includes context hash** — Format: `${clientId}:${sha256suffix}`. Uses SHA-256 of sorted config keys — property order does not affect the hash. +3. **Cache key includes context hash** — Format: `${clientId}:${sha256suffix}`, built by the transport's `resolveSessionContext()`. When the resolver returns the suffix `"default"` (`enabled: false`, no `?config=` or nothing left after filtering, or a throwing `contextResolver`) the key is the plain `clientId`. The suffix is SHA-256 (first 16 hex chars) of the filtered config as JSON, top-level keys sorted — the values are part of the hash, property order is not. 4. **LRU eviction triggers cleanup** — onEvict callback called on removal. Transport uses this to close all sessions in an evicted bundle. 5. **Background pruning interval** — Removes TTL-expired entries periodically. `stop(true)` clears all entries and triggers cleanup. From 1c24d8da8657f60de3e2de4c4d5c21899d776174 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 5 Nov 2026 10:59:21 +0000 Subject: [PATCH 2/2] docs: address TS review of the intent node fixes Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01VmUXGTni8Lia5eQCbh2XUa --- src/mode/AGENTS.md | 5 +++-- src/session/AGENTS.md | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/mode/AGENTS.md b/src/mode/AGENTS.md index ca736f3..308daed 100644 --- a/src/mode/AGENTS.md +++ b/src/mode/AGENTS.md @@ -7,11 +7,12 @@ Handles toolset resolution, validation, and module loading. Bridges toolset defi ## Key Components - **ModuleResolver** (`ModuleResolver.ts`) — Stores catalog, validates toolset names, resolves tools by collecting direct tools + calling module loaders. +- **moduleLoading.ts** — empty placeholder: no code, nothing imports it. - **ToolsetValidator** (`ModeResolver.ts`) — Validates toolset names against a provided catalog, parses comma-separated toolset strings. Also resolves startup mode from env/args. ## Invariants -1. **`_meta` is rejected** — `RESERVED_TOOLSET_KEYS` (`mode.types.ts`): the `ModuleResolver` constructor throws if the catalog has such a key, and `validateToolsetName()` refuses it again as defense in depth. `moduleLoading.ts` is an empty placeholder (no code, nothing imports it). +1. **`_meta` is rejected** — `RESERVED_TOOLSET_KEYS` (`mode.types.ts`): the `ModuleResolver` constructor throws if the catalog has such a key, and `validateToolsetName()` refuses it again as defense in depth. 2. **Module loaders fail silently** — Errors caught, logged as warnings, skipped. Toolset activates with whatever tools succeeded. This is intentional — one broken loader should not block the rest. 3. **ToolsetValidator is in ModeResolver.ts** — Historical naming quirk. `ToolsetValidator.ts` just re-exports from `ModeResolver.ts`. Import from either file. 4. **ModuleResolver stores catalog; ToolsetValidator takes it as parameter** — Different validation signatures. ModuleResolver validates against its stored catalog; ToolsetValidator is stateless. @@ -33,7 +34,7 @@ resolveToolsForToolsets([toolsetName], context) ## Anti-patterns -- Throwing on invalid toolset names (return validation result instead) +- Throwing on invalid toolset names at runtime (return a validation result instead; only a reserved key in the catalog throws, at construction) - Assuming module loaders are synchronous (always await) - Using `ToolsetValidator.ts` directly when `ModeResolver.ts` is clearer diff --git a/src/session/AGENTS.md b/src/session/AGENTS.md index b8691d0..9c28c95 100644 --- a/src/session/AGENTS.md +++ b/src/session/AGENTS.md @@ -14,7 +14,7 @@ Manages per-session context resolution and client resource caching. Enables mult 1. **Fail-secure parsing** — Invalid base64/JSON encoding returns `{}`, not an error. Malformed client input never crashes the server. 2. **Disallowed keys silently filtered** — No logging to prevent information leakage about what keys exist. -3. **Cache key includes context hash** — Format: `${clientId}:${sha256suffix}`, built by the transport's `resolveSessionContext()`. When the resolver returns the suffix `"default"` (`enabled: false`, no `?config=` or nothing left after filtering, or a throwing `contextResolver`) the key is the plain `clientId`. The suffix is SHA-256 (first 16 hex chars) of the filtered config as JSON, top-level keys sorted — the values are part of the hash, property order is not. +3. **Cache key includes context hash** — Format: `${clientId}:${sha256suffix}`, built by the transport's `resolveSessionContext()`. When the resolver returns the suffix `"default"` (`enabled: false`; no usable `?config=`, meaning absent, malformed or empty after filtering; or a throwing `contextResolver`) the key is the plain `clientId`. The suffix is SHA-256 (first 16 hex chars) of the filtered config as JSON, top-level keys sorted — the values are part of the hash, property order is not. 4. **LRU eviction triggers cleanup** — onEvict callback called on removal. Transport uses this to close all sessions in an evicted bundle. 5. **Background pruning interval** — Removes TTL-expired entries periodically. `stop(true)` clears all entries and triggers cleanup.