@withpica/mcp-server
Version:
MCP Server for withPICA — enables AI assistants to interact with your withPICA music catalog
603 lines • 29.4 kB
TypeScript
/**
* 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" | "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;
/**
* 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.
*
* `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;
/**
* 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>;
}
export {};
//# sourceMappingURL=index.d.ts.map