Skip to content

feat: emit structured block effect on shadow-MCP deny responses - #5081

Open
bradcypert wants to merge 3 commits into
mainfrom
brad/dno-802-featserver-emit-structured-block-effect-on-shadow-mcp-deny
Open

feat: emit structured block effect on shadow-MCP deny responses#5081
bradcypert wants to merge 3 commits into
mainfrom
brad/dno-802-featserver-emit-structured-block-effect-on-shadow-mcp-deny

Conversation

@bradcypert

@bradcypert bradcypert commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Part 1 of Request access for blocked AI tool calls (DNO-802).

Why

When a shadow-MCP policy denies a tool call, the "Request access" link is buried as prose in the deny message the AI tool prints. The device agent wants to surface a native request flow, and it can't (and shouldn't) parse prose.

What

  • Shadow-MCP denies on the canonical ingest path now carry a structured effects["block"] entry mirroring the minted request link: category, rpbr2. token + URL + expiry, observed server, policy, tool, and the durable block-row URL.
  • Absence of the key is the "not requestable" signal — scan denies (PII, secrets, spend, prompt policies) carry nothing.
  • Duplicate deliveries re-mint the link (the prose re-sends it too) but omit block_url, matching the existing no-second-block-row rule.
  • Collected via a context carrier (same pattern as withRiskScanTracker) so evaluateCanonicalHook's ten deny branches keep their signature.

Test plan

  • New ingest-level tests: effect present on a shadow-MCP deny; absent on allow, scan deny, and mint-failure; duplicate-delivery shape.
  • go test ./internal/hooks/ ./internal/risk/ green; golangci-lint clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LqNdUoJxcz85saAAyPodoW


Summary by cubic

Adds a structured effects["block"] payload to shadow‑MCP deny responses on the canonical ingest path so device agents can start a native “Request access” flow without parsing prose. Part of DNO-802 (Request access for blocked AI tool calls).

  • New Features

    • Emits effects["block"] with v=1, category: "shadow_mcp", requestable: true, request_token (rpbr2.*), request_url, request_expires_at, plus server_name/server_url, policy_name, tool_name, and block_url (first delivery only).
    • No effect on allow, scan-based denies (PII/secrets/spend/prompt), or when link minting fails. Duplicate deliveries re-mint the link but omit block_url.
    • Added withBlockEffectCollector/withBlockEffect and setBlockEffect/setBlockEffectBlockURL; wired into the canonical ingest deny path.
    • shadow_mcp_request_link now returns URL, token, expiry and sets the effect; PolicyName plumbed through canonical and per-provider handlers (Claude/Codex/Cursor). GeneratePolicyBypassRequestURL now returns (url, token, expiry, err).
  • Bug Fixes

    • Redacts server_url in the block effect to the inventory convention (scheme/host/path only); strips credentials, query, and fragment.

Written for commit aed18f5. Summary will update on new commits.

Review in cubic

…es (DNO-802)

When the canonical ingest path denies a tool call for a shadow-MCP policy
and mints a "Request access" link, mirror that link as a structured
effects["block"] entry (category, request token/URL/expiry, observed
server, policy, tool, block-row URL) so the hooks binary can hand the
device agent something it can act on without parsing prose. Absence of
the key is the "not requestable" signal: scan denies (PII, secrets,
spend, prompt policies) carry nothing.

Duplicate deliveries re-mint the link (the prose re-sends it too) but
never a second block row, so their effect omits block_url.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqNdUoJxcz85saAAyPodoW
@bradcypert
bradcypert requested a review from a team as a code owner August 8, 2026 01:09
@linear-code

linear-code Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

DNO-802

@changeset-bot

changeset-bot Bot commented Aug 8, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: aed18f5

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cubic analysis

All reported issues were addressed across 6 files

Linked issue analysis

Linked issue: DNO-802: feat(server): emit structured block effect on shadow-MCP deny responses

Status Acceptance criteria Notes
Emit a structured effects["block"] payload on shadow‑MCP denies with v=1, category "shadow_mcp", requestable=true, a short rpbr2.* request_token, request_url containing the fragment token, and request_expires_at in RFC3339. The new withBlockEffect builds the payload including v, category, requestable, request_token, request_url, and formatted request_expires_at. Tests parse and assert token prefix, URL fragment, and RFC3339 expiry.
Include server_name and server_url when available, plus policy_name and tool_name in the effect payload. withBlockEffect maps server_name/server_url/policy_name/tool_name when present; shadowMCP approval link returns ServerName/ServerURL, and tests assert policy_name, tool_name, and server_name presence and server_url absence in identity-only cases.
Attach block_url (durable block-row URL) on the first delivery only; duplicate deliveries must omit block_url while still including a re‑minted request_token/request_url. evaluateCanonicalShadowMCP calls setBlockEffectBlockURL when a block row is created; withBlockEffect includes block_url only if present. Tests verify first delivery contains block_url and retry omits block_url but still contains an rpbr2.* token.
Do not emit effects["block"] for allow decisions, scan-based denies (PII/prompt policies), or when link minting fails (e.g. missing site URL). The collector is only populated for requestable shadow‑MCP denies; code paths and checks prevent creating the effect when siteURL is nil or when the deny is from scans. Tests assert absence on allow, scan denies, and mint-failure scenarios.

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread server/internal/hooks/shadow_mcp_request_link.go Outdated
bradcypert and others added 2 commits August 7, 2026 22:05
NormalizeAccessEvidence keeps the query string and any embedded
userinfo; the machine-readable channel must not re-expose credentials
the deny prose never carried. scheme/host/path only, matching
CanonicalizeInventoryURL.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqNdUoJxcz85saAAyPodoW
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.

1 participant