UNPKG

@withpica/mcp-server

Version:

MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog

628 lines • 30.9 kB
/** * Tool Registry for MCP Server * Manages all available tools that AI assistants can call */ import type { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { PicaClient } from "@withpica/mcp-sdk"; import { ServerConfig } from "../config.js"; import { McpAuditLogger, type CallerContext as BaseCallerContext } from "@withpica/mcp-utils"; import type { Skill } from "../skills/index.js"; /** ADR-271 Phase 2: extends the base caller context with org-authored skills. */ export interface CallerContext extends BaseCallerContext { /** Org-authored skills to merge into the SkillsRegistry for this caller. */ extraSkills?: Skill[]; } import type { ToolScope, Audience } from "@withpica/mcp-utils"; import { type SessionState } from "@withpica/mcp-utils"; /** * ADR-199 NextStep hint shape — declared on a ToolDefinition, * surfaced on the success result by the dispatcher. Consumed by * agents to discover the happy-path follow-on tool without reading * description prose. */ export interface NextStepHint { /** Must resolve to a registered tool name; registry validates at init. */ tool: string; /** Single-sentence plain-English explanation. */ reason: string; /** * When this step applies: * - on_success: standard happy path * - on_partial: partial-success branches (subset processed) * - on_subset_returned: paginated reads where more pages exist */ when: "on_success" | "on_partial" | "on_subset_returned"; } /** * ADR-214 — workflow membership for a tool. Used by the schema-resource * registry (`mcp-server/src/resources/required-schemas.source.ts` — W2) * and the lint-required-schemas CI check (W3) to prove every tool that * participates in a workflow is referenced in that workflow's resource * (as `primary_tool` or `companion_calls[].tool`). * * `"infrastructure"` is the explicit opt-out for tools not part of any * user-facing workflow. * * Optional in W1; W4 flips it to required after W2's resource registry * lands so every tool is forced to declare its workflow at compile time. */ export type WorkflowTag = "work-required" | "recording-required" | "work-credits-required" | "recording-credits-required" | "person-required" | "audio-upload-required" | "enrichment-resolve-required" | "agreement-required" | "multimedia-required" | "export-required" | "claim-required" | "session-required" | "split-sheet-required" | "recording-splits-required" | "sync-placement-required" | "relationship-required" | "handover-required" | "infrastructure"; /** * ADR-226 Decision 2 — vernacular intent posture for every tool. * * - `creator-entry` — Atlas covers it; `Use when the user asks: '...'` block * in the description is required (Phase 7 lint enforces). * - `internal` — programmatic handoff between tools (e.g. presigned-PUT * finalize step). Requires a one-line `vernacular_reason`. * - `specialist` — mutating / destructive / not-yet-covered context; * invoked by exact name. Requires a one-line `vernacular_reason`. * * Universal coverage: every tool declares its kind. Phase 7 lint promotes * the field to a required-with-blocking check; Phase 3 (this commit) * leaves it optional during the bulk-classification ramp and flips it to * required in the same commit once every registered tool has been populated by * `scripts/bulk-classify-vernacular-kind.ts`. */ export type VernacularKind = "creator-entry" | "internal" | "specialist"; /** * ADR-230 — authority tier declared on every tool definition. * * - `read` — pure query, no state mutation. Maps to readOnlyHint:true, * risk_level:"safe", required scope `read:*` (or `write:*` * via the read-implies-write rule in `hasScope`). * - `draft` — produces a proposal/preview/hint without persisting * state. Same hints as `write` (readOnlyHint:false, * risk_level:"mutating"). No confirmation token. * - `write` — persistent state change. May opt into per-tool * confirmation via `previewMode: "two_step_token"`. * - `destructive` — irreversible state change (delete / merge / send * broadcast / disconnect). destructiveHint:true, * risk_level:"destructive", confirmation token required, * requires elevated `destructive:*` scope (admin satisfies). * * The 4-value `tier` is PICA's authority surface. The 3-value `risk_level` * on `mcp_audit_log` is derived from `tier` (read→safe, draft|write→mutating, * destructive→destructive) — sister of the MCP-standard `readOnlyHint` / * `destructiveHint` fields, kept for ADR-199 client compatibility. */ export type Tier = "read" | "draft" | "write" | "destructive"; /** * ADR-230 — derive the MCP-standard 3-value risk classification from the * declared tier. Sourced once at registration and at audit-write time so * the contract that hints, audit row, and confirmation requirement all * agree is mechanical. */ export declare function tierToRiskLevel(tier: Tier): "safe" | "mutating" | "destructive"; /** * ADR-230 — derive the required API-key scope from the declared tier. * * - `read` → `read:<resource>` (write:* satisfies via `hasScope`). * - `draft|write` → `write:<resource>`. * - `destructive` → `destructive:*` (admin satisfies via `hasScope`); the * write:<resource> scope is also required for the * underlying write surface. * * Returns the *additional* scope tier beyond per-resource. Resource scope * is composed at call-site by `lib/services/mcp-scopes.ts`. */ export declare function tierToScopeKind(tier: Tier): "read" | "write" | "destructive"; export interface ToolDefinition { name: string; /** * ADR-230 — authority tier. Required for every customer-facing tool. * Sourced from per-tool declaration; drives MCP hints, audit-log * `risk_level`, confirmation-token requirement, and required scope. * See {@link Tier} above for semantics. */ tier: Tier; /** * ADR-248 — the resource scope (or `"always"`) gating this tool on the * authenticated HTTP surface. The verb must agree with `tier` (a * `tier:"read"` tool may not declare a `write:*` scope). Required on every * customer-facing tool (parity test enforces verb/audience agreement on * top of this compile-time presence check). See {@link ToolScope}. */ scope: ToolScope; /** * Further scopes the credential must ALSO hold, when the operation needs * more than one family: e.g. an export that reads the catalogue (the * route's requiredScope, write:catalog) and writes a file (the REST write * floor, write:files). Each entry is checked with `hasScope` exactly like * `scope`: at visibility (deriveHttpToolNames), at call time * (getRequiredScopesForTool) and in the pica_execute inner gate. Pinned * against the routes by lib/auth/__tests__/mcp-tool-scope-floor-contract.test.ts. */ alsoRequires?: Exclude<ToolScope, "always">[]; /** * ADR-248 — the surfaces this tool is listed on. Each MCP surface (stdio / * authenticated HTTP / lobby / public-discover) is a pure derived view * over `audiences ∋ <surface>`. "session-auth-only" = the absence of * `"http"`. Required and must be non-empty (parity test enforces * non-emptiness). See {@link Audience}. */ audiences: Audience[]; description: string; /** * ADR-214 — workflow(s) this tool belongs to. See `WorkflowTag` above. * Required as of W4 (2026-04-30): every tool must declare its workflow * membership at compile time. Use `"infrastructure"` for tools not * part of any user-facing workflow. */ workflows: WorkflowTag | WorkflowTag[]; /** * ADR-226 Decision 2 — vernacular intent posture. See `VernacularKind` * above. Optional during Phase 3 ramp; Phase 7 lint flips it to a * required-with-blocking check once every tool is populated. */ vernacular_kind?: VernacularKind; /** * ADR-226 Decision 2 — required when `vernacular_kind` is `'internal'` * or `'specialist'`. One-line documentation of why the tool is not a * creator-vernacular entry point (e.g. "Invoked after presigned PUT * completes" or "Specialist context; not yet covered by the Creator * Question Atlas"). Phase 7 lint enforces presence on non-`creator-entry` * kinds. */ vernacular_reason?: string; inputSchema: { type: string; properties: Record<string, any>; required?: string[]; additionalProperties?: boolean; }; /** * One-of-N required argument groups that JSON-Schema `required` can't * express. Each inner array is a set of interchangeable aliases of which AT * LEAST ONE must be present (e.g. `[["id","work_id"]]` for an inspect tool * that accepts either). The pica_execute dispatcher's WRAPPED_ARGS_MISSING * guard honours this so alias-required tools (which must declare * `required:[]`) still hard-reject empty/stripped args with a transport * error instead of a confusing downstream domain error (feedback 6d516171). */ requiredOneOf?: string[][]; /** * ADR-199 — formal output shape so agents can predict where IDs live. * Optional; existing tools register unchanged. Surface via pica_tool_details. */ outputSchema?: { type: string; properties?: Record<string, any>; required?: string[]; additionalProperties?: boolean; }; /** * ADR-199 — happy-path follow-on tools. Dispatcher copies onto success * results (in `structuredContent.next_steps`) so per-tool executors * don't have to remember. */ nextSteps?: NextStepHint[]; /** * ADR-199 — for inspect tools on entities with a state machine, the * map from current-state → array of valid next states. Source of truth * is `lib/utils/state-machine.ts` (work + agreement) and the corresponding * DB CHECK constraint. Build-step assertion catches drift. */ validTransitionsFrom?: Record<string, string[]>; /** * ADR-199 — declarative preview affordance. * - dry_run: tool accepts dry_run:boolean and short-circuits * - two_step_token: tool requires confirmation token (existing pattern) * - none: immediate execution * The runtime still enforces — this field is for discoverability * + the compliance-score lint. */ previewMode?: "dry_run" | "two_step_token" | "none"; /** * ADR-250 — human-in-the-loop approval. When `"human"`, a delegated-agent * caller (OAuth connector / pica_grant_*) cannot execute this tool directly: * the HTTP dispatcher (`app/api/mcp/route.ts`) stages a `pending_human_approvals` * row, emails the grant owner an out-of-band approve/deny link, and returns * `human_approval_required`. The human approval substitutes for `destructive:*`, * so a tool may declare `tier:"destructive"` + `approvalMode:"human"` and carry * the `http` audience (connector-reachable, gated by approval rather than * blanket exclusion). Read structurally by the dispatcher; absent ⇒ no gate. * A tool so declared MUST have a server-side executor (`human-approval-executor` * EXECUTORS map) — `approvalToolsWithoutExecutor` enforces this. */ approvalMode?: "human"; annotations?: { title?: string; readOnlyHint?: boolean; destructiveHint?: boolean; idempotentHint?: boolean; openWorldHint?: boolean; /** * ADR-199 annotation classes derived from metadata.ts at registration. * Populated by the registration loop; ChatGPT/Claude.ai connectors * read these directly from pica_tool_details. */ riskLevel?: "safe" | "mutating" | "destructive"; category?: string; requiresConfirmation?: boolean; requiresAuthScope?: string; createsExternalSideEffects?: boolean; returnsPaginated?: boolean; requiresFollowUpInspection?: boolean; }; /** * ADR-230 follow-up — explicit advertised-hint overrides for the rare tool * whose CLIENT-FACING hint must legitimately differ from its scope `tier`. * The dispatcher (`pica_execute`) is `tier:"read"` so a read-scoped key can * route reads through it — but scope + destructive confirmation are enforced * on the WRAPPED tool (executeTool re-derives gates from the inner tool's * tier; `app/api/mcp/route.ts` re-checks the inner tool's required scopes), * NOT on the wrapper. The wrapper itself can therefore cause writes/deletes, * so it must advertise `readOnlyHint:false` + `destructiveHint:true` + * `openWorldHint:true`. * `deriveAnnotations()` merges these over the tier-derived hints, so both the * flat and the layered surface see them. Advisory only — no effect on scope, * confirmation, freshness, or audit enforcement. */ annotationOverrides?: { readOnlyHint?: boolean; destructiveHint?: boolean; idempotentHint?: boolean; openWorldHint?: boolean; }; _meta?: { ui?: { resourceUri: string; }; "ui/resourceUri"?: string; [key: string]: unknown; }; } export type ToolCategory = "catalog" | "enrichment" | "business" | "discovery" | "media" | "comms" | "settings"; export interface ToolMetadata { category: ToolCategory; /** * @deprecated ADR-230 — use the declared `tier` on the tool * definition (`ToolDefinition.tier` — required by lint Rule 13) as * the source of truth. `risk` is retained as a quiet fallback for * the unlikely case of a tool that bypasses lint (legacy tolerance * inside `ToolRegistry.listTools`) and to drive the * `injectMetadataIntoDescription` annotations + `retry_safe` hints * exposed via `getToolMetadata`. Do NOT add new consumers; route * tier-aware logic through the tool definition's declared `tier`. */ risk: "safe" | "mutating" | "destructive"; retry_safe: boolean; display_name: string; /** * Structured deprecation (surface-hygiene governance, 2026-06-11). * `deprecated: true` marks the tool as deprecated; `removal_target` * is the semver at which it MUST be gone — lint-mcp-tools fails the * publish when the package version reaches it, so deprecations * expire loudly instead of overstaying (pica_dashboard_pulse lesson). */ deprecated?: true; removal_target?: string; } /** * Build a tool description with metadata injected. * Format: "[category] original description" */ export declare function injectMetadataIntoDescription(description: string, metadata: ToolMetadata): string; export declare const CATEGORY_DISPLAY: Record<ToolCategory, string>; /** * Result envelope returned by tool executors. The legacy shape was * `Array<{ type: string; text: string }>` (text-only) — ADR-200 Phase 1 * widens it to permit `resource_link` content blocks per the spec's * universal floor for portable upload/download surfaces. Keeping the * shape structural rather than importing the SDK union avoids forcing * 30+ existing executors to satisfy every variant. */ export interface ToolResult { content: Array<{ type: "text"; text: string; } | { type: "resource_link"; uri: string; name: string; description?: string; mimeType?: string; } | { type: string; text?: string; [key: string]: unknown; }>; structuredContent?: Record<string, unknown>; isError?: boolean; /** * Result-level `_meta` carries dispatcher-attached hints. Two keys are * known today; the index signature keeps the shape open for future * additions without per-add interface churn: * - `session_state` (ADR-208 Primitive B) — ambient catalog counters, * attached to successful write results by `attachSessionState`. * - `schemas` (P3c) — `pica://schemas/<workflow>-required` URIs derived * from the tool's `workflows` tag, attached to every successful * workflow-tagged tool result by `attachSchemaHints`. * Read tools may carry `schemas` (Option X) but skip `session_state` * (high-volume read path stays cheap). */ _meta?: { session_state?: SessionState; schemas?: string[]; [key: string]: unknown; }; } /** * Lift error attribution (code + message) off an `isError` ToolResult for * audit telemetry. Prefers `structuredContent.error_code`/`error_message`; * falls back to parsing the first text content item as a JSON error * envelope (`error_code`/`error` + `error_message`/`message`) — the shape * the pica_execute dispatcher and fail-quiet wrappers return. Without the * fallback those errors landed in mcp telemetry with a NULL code, so the * ops_issues pipeline (which groups on a code) never saw them — 26 opaque * ChatGPT dispatcher failures in 14 days, invisible to triage. Exported * for unit testing. */ export declare function extractErrorAttribution(result: Pick<ToolResult, "content" | "structuredContent"> | undefined): { code?: string; message?: string; }; /** * ADR-200 Phase 1 — context passed to executors that need access to the * connected MCP transport (e.g. for `server.elicitInput()` capability * checks). Optional so the legacy `(args) => ...` executor signature * keeps working unchanged for the ~99% of tools that don't need it. */ export interface ToolExecutorContext { server?: Server; } export type ToolExecutor = (args: Record<string, any>, ctx?: ToolExecutorContext) => Promise<any>; /** * Sanitize tool parameters for `mcp_audit_log.parameters`. Pure function * so it's directly testable; the registry's private method delegates here. * * Security audit 2026-05-11 P2. The audit log is queryable by anyone with * team-portal access. Tools like `pica_sign_in`, `pica_share_links_*`, * and `team_comms_send` previously landed bare emails / share tokens / * OAuth refresh tokens in the log because the only redaction was a * `delete sanitized.confirmation_token` + length truncation. * * Deny-list is keyed off the param name (case-insensitive substring * match) — covers `password`, `api_key`, `token`, `secret`, * `authorization`, plus `confirmation_token` via the `token` substring. * Sister keys like `refresh_token` / `id_token` / `access_token` / * `share_token` / `client_secret` also match. * * Email values (regardless of param key) get the local-part masked to * the first two characters: `jane@example.com` → `ja***@example.com`. * Locals ≤ 2 chars become all-asterisk. * * Strings > 500 chars are truncated to 500 + `...[truncated]`. * * Exported under `_internal` so test files can exercise the function * without instantiating a `ToolRegistry`. */ declare function sanitizeAuditParams(args: Record<string, any>): Record<string, unknown>; export declare const _internal: { sanitizeAuditParams: typeof sanitizeAuditParams; }; /** * The four MCP-spec annotation hints plus `title`, derived from one place. * * Before this existed the derivation lived inline in `listTools()`, so the * layered-discovery surface (`pica_tool_details` → `buildToolDetail` in * discovery.ts) fell back to the tool's *declared* `annotations` — which most * tools never declare, so it answered `annotations: null` for the large * majority of the registry while the flat surface answered real hints for the * same tool (measured 2026-08-24). Connector clients only ever see the layered * surface, so they could not tell a delete from a read. * * ADR-230 — declared `tier` is the source of truth for the two hints that * describe AUTHORITY. Declared `annotations.readOnlyHint` / * `annotations.destructiveHint` are NOT read: they would let a declaration * contradict the tier that actually drives the confirmation token and the * `destructive:*` scope. `metadata.risk` is a quiet fallback for a tool that * somehow bypassed lint Rule 13. * * Two hints a declaration CAN set, because neither describes authority and * nothing enforces against either: * * - `openWorldHint` — a declaration may RAISE it (never lower it); the name * patterns in `EXTERNAL_SIDE_EFFECT_PATTERNS` raise it on their own. * - `idempotentHint` — a declared value WINS outright (ruling 2026-08-24). * The tier-derived value is a guess from `TOOL_METADATA.retry_safe` plus * "destructive is never idempotent", and it was wrong on four tools whose * authors had said otherwise in the definition — `pica_storage_disconnect` * is destructive-tier and its own description reads "Idempotent — calling * on an already-PICA-managed org is a no-op". The author knows; the * heuristic does not. * * `title` does not describe authority either: a declared `annotations.title` * wins, then `TOOL_METADATA.display_name`. Before 2026-10-08 the declaration * was discarded, and sign_in/out declared none, so all five tools on the * layered surface (the only tools a connector lists) advertised their raw * names as titles. `src/__tests__/tools/tool-titles.test.ts` fails a tool * with neither, and a catalog tool whose declared title disagrees with its * display_name (pica_tool_details shows both). * * `annotationOverrides` still merges last, so it beats all of the above. * * Agreement between the two surfaces is pinned by "listTools() and * pica_tool_details agree on the four core hints" in * `src/__tests__/tools/tool-details-contract.test.ts`; the precedence rules * themselves are pinned by the synthetic-definition cases in the same file. * `scripts/ci/check-agent-readability.mjs` (`tier-annotation-mismatch`) * applies the identical rule when it looks for declarations the derivation * silently discards. */ export declare function deriveAnnotations(definition: ToolDefinition): { title?: string; readOnlyHint: boolean; destructiveHint: boolean; idempotentHint: boolean; openWorldHint: boolean; }; export declare class ToolRegistry { private tools; private pica; private config?; private reinitializeCallback?; private signOutCallback?; private auditLogger?; private callerContext; /** Caller-specific "can run it here" judgement — see setToolVisibility. */ private toolVisibility?; constructor(pica: PicaClient | null, config?: ServerConfig, reinitializeCallback?: (apiKey: string) => void, callerContext?: CallerContext, signOutCallback?: () => void); setAuditLogger(logger: McpAuditLogger): void; setCallerContext(context: CallerContext): void; /** * Install the caller's "can run it on this connection" judgement so * discovery ranks and flags with the predicate the dispatcher enforces. * The HTTP route passes `allowedToolNames.has` (ADR-248: audiences ∋ http * AND scope held) — and only while `isInnerGateEnforced()` * (lib/services/mcp-inner-gate-shadow.ts) is true, because in * `PICA_EXECUTE_INNER_GATE_MODE=shadow` pica_execute lets a scope-gated * inner tool run and a "not callable" flag would then be wrong in the * worst direction. Without a predicate, `isToolVisibleOnSurface` falls * back to audience only: a tool is visible when its declared audiences * include the caller's surface (lobby-mode → "lobby", else the * transport). 2026-08-26: a ChatGPT caller was ranked five finance-gated * tools first and read one's schema three times before leaving. */ setToolVisibility(predicate: (name: string) => boolean): void; isToolVisibleOnSurface(name: string): boolean; /** * Read clientInfo from the MCP `initialize` handshake. SDK populates this * on the per-request `Server` for HTTP and on the long-lived `Server` for * stdio; either way getClientVersion() returns the same shape after the * handshake completes. Returns empty when ctx is absent or the handshake * hasn't run (lobby-mode dispatches that bypass the per-request transport). * * Stamped onto audit rows as provenance only — never used as a permission * boundary, since clientInfo is self-declared by the client. */ private extractClientInfo; /** * Register all available tools */ private registerAllTools; /** * ADR-230 — declared tier lookup by tool name. Used by the HTTP MCP * dispatcher (`app/api/mcp/route.ts`) to enforce the elevated * `destructive:*` scope on top of the resource-level scope from * `lib/services/mcp-scopes`. Returns undefined for unknown tools or * tools that haven't declared tier yet (legacy tolerance). */ getToolTier(name: string): Tier | undefined; /** * ADR-248 — declared resource scope (or `"always"`) lookup by tool name. * Used by the HTTP MCP dispatcher to compose the required scope set from * the tool's own declaration instead of a separate inverse map. Returns * undefined for unknown tools. */ getToolScope(name: string): ToolScope | undefined; /** The tool's `alsoRequires` scopes (empty when it declares none). */ getToolAlsoRequires(name: string): string[]; /** * ADR-248 — every registered tool's raw definition, WITHOUT the * `discoveryMode` filter that {@link listTools} applies and without the * description/annotation transformation. This is the source the HTTP * dispatcher folds over to derive the authenticated tool surface * (`audiences ∋ "http"` AND scope held) — it must see the full set even * in layered-discovery mode, where `listTools()` returns only the meta * tools. Reflects lobby/discovery REGISTRATION (what's in the registry), * just not the listing filter. */ allDefinitions(): ToolDefinition[]; /** * List all available tools with write-safety prefixes injected. * When discoveryMode is enabled, only the 5 handshake-visible tools are returned. * All other tools remain registered and callable via pica_execute. */ listTools(): ToolDefinition[]; /** * ADR-199 — derive the extended annotation classes (riskLevel, category, * requiresConfirmation, createsExternalSideEffects, returnsPaginated, * requiresFollowUpInspection) from existing metadata + tool name patterns. * * Called from listTools() for every tool. Pure derivation: zero per-tool * edits required for the retrofit. Future tool authors can override by * setting fields directly on definition.annotations. */ private deriveExtendedAnnotations; /** * Build a human-readable preview of what a destructive operation will affect. */ private buildDestructivePreview; /** * Sanitize tool parameters for audit logging — delegates to the * pure-function impl below so it can be exercised directly in tests. */ private sanitizeParams; /** * ADR-208 Primitive B — opaque per-org cache key. * * Stdio: `config.picaApiKey` is the org-bound API key from credentials, * unique per org. HTTP non-discovery: `callerContext.callerIdentity` is * `http:${authContext.keyId}` set by `app/api/mcp/route.ts`, also unique * per org. This is the cross-org cache isolation invariant required by * acceptance criterion 4 — two orgs sharing a worker process never share * a cacheKey. Returns `null` (and the wrapper skips session_state) when * neither identifier is present. */ private getSessionCacheKey; /** * ADR-208 Primitive B + ADR-208 Phase 2 + ADR-210 Phase 1 — fetch the * org-scoped counts + billing slice via `pica.catalogStats()`. The * endpoint composes the billing slice server-side via * `subscriptionService.getOrgBillingSlice` and the pending-uploads count * via the new `organisations.pending_uploads_count` column so stdio + * HTTP transports stay symmetric. * * `completeness_pct` is stubbed at 0 in Phase 1 and will be wired in a * Phase 1.5 follow-up. The stub keeps the shape contract green without * forcing a separate endpoint extension into this PR. * * Fail-soft: when `this.pica` is missing (HTTP-discovery without bearer * scope, mostly), every count is 0 and the billing slice falls back to * a conservative "trial" shape so the agent doesn't see misleading * "active" billing for an uninitialized context. */ private fetchSessionCounts; /** * P3c — attach `_meta.schemas` (workflow-resource URIs) to a successful * workflow-tagged tool result. Mirrors `attachSessionState`'s merge * semantics: spread existing `_meta`, add the new key, leave other * keys (e.g. `session_state`) untouched. * * Applies to ALL workflow-tagged tools (Option X), including read * companions like `pica_sessions_query`, so the agent gets the schema * pointer no matter which verb they hit first. Skips when: * - the tool's `workflows` is or contains only `"infrastructure"` * (no schema resource exists), or * - the result is an error (the workflow didn't progress). * * Fail-open like `attachSessionState`: derivation is pure today, but * any future throw must not turn a successful tool result into a * failure. */ private attachSchemaHints; /** * ADR-208 Primitive B — bump the session-mutation counters and attach * the resolved `_meta.session_state` to a successful write-tool result. * Mutates `result` in-place to keep the existing return contract. * Fail-open: any error here is swallowed — ambient state is best-effort * and must not turn a successful mutation into a tool failure. */ private attachSessionState; /** * Execute a tool by name. `ctx.server` carries the transport-scoped * MCP `Server` reference so executors that need `server.elicitInput()` * (ADR-200 Phase 1) can reach it. Each per-request HTTP server is * different; passing it through positionally avoids registry-level * mutable state that would race between concurrent requests. */ executeTool(name: string, args: Record<string, any>, ctx?: ToolExecutorContext): Promise<any>; /** * `executeTool` after the catalogue is settled. `args` are the arguments as * called (gates, tokens, audit); `executorArgs` are what the tool receives. */ private executeToolInCatalogue; } export {}; //# sourceMappingURL=index.d.ts.map