Skip to content

feat: add pre-execution tool policy interface - #377

Draft
barryroodt wants to merge 12 commits into
mainfrom
barryroodt/ai-1091-tool-policy-foundation
Draft

feat: add pre-execution tool policy interface#377
barryroodt wants to merge 12 commits into
mainfrom
barryroodt/ai-1091-tool-policy-foundation

Conversation

@barryroodt

Copy link
Copy Markdown
Contributor

Stacked PR: PR A of three for AI-1091, targeting main. PR B will stack on this public foundation with the private runtime.

What

Adds the product-neutral tool policy interface described in RFC v0.4 to mcp-utils: public ToolPolicy, ToolPolicyDecision, and ToolRequestContext types.

Policies can select contextual schemas, normalize known fields before strict parsing, return a result without business execution, or pass a typed resolution into exact (args, resolution) execution. Policy-backed structured results are normalized, and telemetry failures cannot change the tool result.

requestState?: ServerOptions['requestState'] passes to the SDK unchanged. The SDK continues to own verification fields and -32602 errors. Policy-free tool discovery, call ordering, and result bytes remain unchanged.

Why

This creates the public policy interface needed by PR B while keeping mcp-utils product-neutral. It adds no user-visible elicitation or Supabase-specific policy.

Verification

All checks below passed.

  • CI=1 mise exec -- pnpm --filter @supabase/mcp-utils test run
  • CI=1 mise exec -- pnpm --filter @supabase/mcp-server-supabase test run --project unit
  • mise exec -- pnpm build
  • mise exec -- pnpm format:check
  • mcp-server-postgrest base/head differential reproduced the same failure set with identical normalized received values
  • Diff is limited to five files under packages/mcp-utils

Absorbs the plan's `feat: normalize MCP structured tool results` boundary.

Structured result normalization and the policy seam cannot be split into two
byte-safe commits. A policy-free tool must keep its `tools/list` and
`tools/call` bytes identical to fc54ea2 on every serving path, and on the
frozen public surface an attached `policy` is the only thing that can
activate normalization: `formatResult` must never change byte shape, no new
normalization toggle may be added, and `outputSchema` stays required on
`Tool` so its presence cannot discriminate.

A commit introducing normalization before the policy seam existed would
therefore either normalize unconditionally, breaking policy-free bytes, or
carry no observable normalization contract at all. Landing both together
keeps every commit individually byte-safe with its own tests green.
@coveralls

coveralls commented Aug 25, 2026

Copy link
Copy Markdown

Coverage Report for CI Build 32894951660

Coverage increased (+0.001%) to 96.521%

Details

  • Coverage increased (+0.001%) from the base build.
  • Patch coverage: No coverable lines changed in this PR.
  • No coverage regressions found.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 3036
Covered Lines: 2951
Line Coverage: 97.2%
Relevant Branches: 385
Covered Branches: 351
Branch Coverage: 91.17%
Branches in Coverage %: Yes
Coverage Strength: 51.14 hits per line

💛 - Coveralls

Guard the decision before the audit record is built, so an unrecognized
decision can no longer reach the sink as a raw string outside the declared
union, and a nullish decision no longer throws before any record exists.

The union narrows first; the fire-and-forget callback is then scheduled
exactly once per request, ahead of all three branches. `decision` widens to
'execute' | 'result' | 'rejected', where 'rejected' is this package's own
outcome for a decision it could not recognize.

Also correct two JSDoc claims: the audit callback is not awaited and its
completion is neither ordered before the response nor guaranteed, and
`resolveParameters` guarantees one shared hook call, not identical
advertised and enforced schemas, since `.strict()` applies at parse only.
Assert the 'rejected' audit record on both fail-closed shapes, an unknown
decision type and a nullish decision, and that a never-settling audit sink
cannot delay the tool result.

Repair the widening fixture so it demonstrates a servable pattern: the
resolution carries the era and execute satisfies the schema the request
advertised. The added modern call pins the hook-supplied input schema on
the call path, and a legacy call pins that a contextually hidden tool stays
callable while absent from tools/list.

Drop the racing negative console assertion from the no-telemetry test; the
positive record assertion carries that invariant.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants