Skip to content

feat: WASM bindings for cedar-policy-mcp-schema-generator#64

Merged
victornicolet merged 1 commit intocedar-policy:mainfrom
tomjwxf:feat/wasm-bindings
Apr 15, 2026
Merged

feat: WASM bindings for cedar-policy-mcp-schema-generator#64
victornicolet merged 1 commit intocedar-policy:mainfrom
tomjwxf:feat/wasm-bindings

Conversation

@tomjwxf
Copy link
Copy Markdown
Contributor

@tomjwxf tomjwxf commented Apr 7, 2026

Summary

Adds rust/cedar-policy-mcp-schema-generator-wasm/, a thin wasm-bindgen wrapper around the existing Rust SchemaGenerator. This enables JavaScript and TypeScript environments (Node.js, browsers) to generate Cedar schemas from MCP tool descriptions with the exact same behavior as the Rust implementation.

Motivated by @lianah's recommendation in #63 to use WASM bindings instead of a TypeScript reimplementation, to avoid implementation divergence.

What it exposes

A single function:

const { generateSchema } = require('@cedar-policy/mcp-schema-generator-wasm');

const result = JSON.parse(generateSchema(schemaStub, toolsJson, configJson));
// result.schema     -> human-readable .cedarschema text
// result.schemaJson -> JSON for Cedar WASM isAuthorized()
// result.error      -> null if successful
// result.isOk       -> boolean

All SchemaGeneratorConfig options are exposed: includeOutputs, objectsAsRecords, eraseAnnotations, flattenNamespaces, numbersAsDecimal.

What it delegates

All schema generation logic, type mapping, and edge case handling is delegated to the Rust SchemaGenerator. The WASM layer adds no independent logic. This ensures:

  • JSON number handling (Long vs Decimal) is identical
  • additionalProperties as tagged entities is identical
  • Namespaced type deduplication is identical
  • Any future changes to the Rust generator are automatically available in WASM

Testing

  • 3 Rust unit tests passing (cargo test)
  • cargo clippy clean with workspace lints (including strict unwrap_used, expect_used, panic denials)
  • Tested in Node.js: produces correct output for multi-tool schemas

Example output:

namespace TestServer {
  type execute_commandInput = {
    command: String,
    timeout?: Long
  };

  type read_fileInput = {
    path: String
  };

  entity McpServer;
  entity User;

  action "call_tool" appliesTo {
    principal: [User],
    resource: [McpServer],
    context: {}
  };

  action "execute_command" appliesTo {
    principal: [User],
    resource: [McpServer],
    context: { input: execute_commandInput }
  };

  action "read_file" appliesTo {
    principal: [User],
    resource: [McpServer],
    context: { input: read_fileInput }
  };
}

Build

wasm-pack build --target nodejs --scope cedar-policy

Output: 2.5MB WASM binary (wasm-opt applied).

Relationship to #63

This PR replaces the TypeScript reimplementation in #63 with WASM bindings that wrap the existing Rust code. I will close #63 since the TypeScript implementation is no longer needed.

Open questions

  • npm scope: Should the published package be @cedar-policy/mcp-schema-generator-wasm? Happy to adjust naming.
  • RequestGenerator: This PR exposes SchemaGenerator only. Should RequestGenerator also be exposed via WASM in this PR, or in a follow-up?

@tomjwxf
Copy link
Copy Markdown
Contributor Author

tomjwxf commented Apr 8, 2026

Rebased onto current main and updated cedar-policy-core pin from 4.8.2 to 4.9.1 to align with the dependency bump in #65.

  • All 3 tests passing
  • Clippy clean (with workspace lints)
  • cargo fmt clean

Ready for review when you have a chance! Happy to address any feedback.

Copy link
Copy Markdown

@victornicolet victornicolet left a comment

Choose a reason for hiding this comment

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

Thank you for the contribution!

I think the main improvements would be integration tests on the JS side calling the bindings, and removing the locked version on cedar-policy-core by adding some functionality in cedar-policy-mcp-schema-generator.

serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
uuid = { version = "1.19.0", features = ["v4", "js"] }
getrandom = { version = "0.3", features = ["wasm_js"] }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is this dependency used? I can't see where it is, I think it could be removed.

You might want to also bump the dependencies (the same way you bumped the cedar-policy-core dependency) to match the cedar-policy-mcp-schema-generator.

Comment thread rust/cedar-policy-mcp-schema-generator-wasm/Cargo.toml

// Parse schema stub
let extensions = Extensions::all_available();
let fragment = match Fragment::<RawName>::from_cedarschema_str(schema_stub, extensions) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This parsing functionality (which depends on cedar-policy-core) could go in the cedar_policy_mcp_schema_generator crate, so that the bindings crate doesn't have a dependency on cedar-policy-core.

For example, adding SchemaGenerator::from_cedarschema_str and SchemaGenerator::get_schema_as_` if the schemas only need to be represented/parsed as Strings in the bindings.

[dependencies]
cedar-policy-mcp-schema-generator = { path = "../cedar-policy-mcp-schema-generator" }
mcp-tools-sdk = { path = "../mcp-tools-sdk" }
cedar-policy-core = "=4.9.1"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ideally we would not have to depend on a pinned version of cedar-policy-core in this crate. I think it would be possible to add limited functionality to the cedar-policy-mcp-schema-generator crate to avoid that.

Comment thread rust/Cargo.lock
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Thank you for updating cedar-policy-core to 4.9.1 after the upstream changes. You might want to also bump other upstream dependency changes for CI to pass.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@tomjwxf I think this is the last outstanding item; the diff still shows many dependencies downgraded relative to mainline in Cargo.toml.

@tomjwxf
Copy link
Copy Markdown
Contributor Author

tomjwxf commented Apr 8, 2026

@victornicolet Thank you for the thorough review. Addressed all five points in the latest push:

1. Removed uuid and getrandom dependencies -- you were right, both were unused. They were leftover from an earlier iteration that generated receipt IDs client-side. Removed from Cargo.toml.

2. Moved schema parsing into the generator crate -- added SchemaGenerator::from_cedarschema_str() and from_cedarschema_str_with_config() to cedar-policy-mcp-schema-generator. The WASM crate now calls these instead of doing Fragment::from_cedarschema_str directly. Also added get_schema_as_str() for callers who only need the human-readable output.

3. Removed cedar-policy-core from the WASM crate -- the WASM crate now has zero direct dependency on cedar-policy-core. All parsing and schema resolution goes through the generator crate's API. Added a SchemaParseError(String) variant to SchemaGeneratorError for parse failures from the new convenience methods.

4. Cargo.lock / dependency bumps -- the lock file updated naturally with the dependency removals. If CI still needs further bumps for other workspace deps, happy to align.

5. Integration tests on the JS side -- acknowledged, I didn't include JS-side integration tests in this push. Would you prefer those as wasm-bindgen-test tests (Rust-side but running in a WASM environment), or as a separate JS test file that loads the built WASM module? Happy to add whichever pattern fits the repo's test conventions.

All 32 tests passing (29 generator + 3 WASM), clippy clean, cargo fmt clean.

@victornicolet
Copy link
Copy Markdown

5. Integration tests on the JS side -- acknowledged, I didn't include JS-side integration tests in this push. Would you prefer those as wasm-bindgen-test tests (Rust-side but running in a WASM environment), or as a separate JS test file that loads the built WASM module? Happy to add whichever pattern fits the repo's test conventions.

wasm-bindgen-test tests would be sufficient. Please also add small tests for new APIs in the cedar-policy-mcp-schema-generator (even if the functionality changes are minimal, two new APIs and one new error were added).

@tomjwxf tomjwxf force-pushed the feat/wasm-bindings branch from bcfed1a to e2fe475 Compare April 9, 2026 01:02
@tomjwxf
Copy link
Copy Markdown
Contributor Author

tomjwxf commented Apr 9, 2026

@victornicolet Added both. Squashed into a single commit with DCO sign-off.

wasm-bindgen-test integration tests (8 tests in tests/wasm_integration.rs):

  • Basic schema generation with single tool (verifies schema text, schemaJson, and isOk)
  • Multi-tool schema with integer-to-Long mapping
  • Config options: numbersAsDecimal producing Decimal instead of Long
  • Error handling: invalid schema stub, invalid tools JSON, invalid config JSON
  • Empty tools producing minimal schema (namespace + base action preserved)
  • Config defaults: None and {} produce identical output

Generator API tests (6 tests in generator/schema.rs):

  • from_cedarschema_str basic parsing
  • from_cedarschema_str_with_config with custom config
  • from_cedarschema_str invalid input returns SchemaParseError
  • from_cedarschema_str output matches manual Fragment + new() constructor
  • get_schema_as_str contains namespace name
  • get_schema_as_str output matches Display implementation

Total: 38 tests passing (35 generator + 3 WASM unit), plus the 8 wasm-bindgen-test integration tests (which run under wasm-pack test --node, not regular cargo test). Clippy clean, cargo fmt clean.

@tomjwxf
Copy link
Copy Markdown
Contributor Author

tomjwxf commented Apr 9, 2026

@victornicolet thanks, the Cargo.lock downgrades were the root issue. I rebased onto the cedar-policy-core 4.9.1 bump, the lock file got regenerated with older transitive dependencies instead of preserving the versions from #65. This pulled in time v0.3.44 (CVE) and yanked keccak v0.1.5, which caused the cargo audit failure.

Fixed in the latest push! Rebased cleanly onto current main and restored the upstream lock file before adding the WASM crate's dependencies on top. The diff now only contains legitimate additions (wasm-bindgen, wasm-bindgen-test, etc.). All 237 tests pass locally, clippy clean, fmt clean.

The new CI run will need a workflow approval since it's from a fork. Appreciate the patience with the iteration!

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 9, 2026

Coverage Report

Head Commit: b0d6875b7588e42af4b3c67c8643555f2da45bbb

Base Commit: c835f5618cb1bd4ebf900a0f3c3b2d3d8740451a

Download the full coverage report.

Coverage of Added or Modified Lines of Rust Code

Required coverage: 80.00%

Actual coverage: 75.12%

Status: FAILED ❌

Details
File Status Covered Coverage Missed Lines
cedar-policy-mcp-schema-generator-wasm/src/lib.rs 🔴 41/92 44.57% 49-51, 54-62, 66-73, 118, 122, 132-140, 164-170, 175-180, 189-195
cedar-policy-mcp-schema-generator/src/generator/schema.rs 🟢 113/113 100.00%

Coverage of All Lines of Rust Code

Required coverage: 80.00%

Actual coverage: 89.79%

Status: PASSED ✅

Details
Package Status Covered Coverage Base Coverage
cedar-policy-mcp-schema-generator 🟢 1693/1809 93.59% 93.16%
cedar-policy-mcp-schema-generator-wasm 🔴 41/92 44.57% --
mcp-tools-sdk 🟢 1457/1653 88.14% 88.14%

@tomjwxf tomjwxf force-pushed the feat/wasm-bindings branch 2 times, most recently from d9d67b1 to df3bf4f Compare April 10, 2026 16:37
@tomjwxf
Copy link
Copy Markdown
Contributor Author

tomjwxf commented Apr 10, 2026

@victornicolet @john-h-kastner-aws Updated with additional test coverage for the uncovered code paths (nested objects, array types, all config options, error field completeness, generate_schema_inner match arms, SchemaParseError Display). 56 tests total, clippy clean, fmt clean - coverage now ✅ ✅

@github-actions
Copy link
Copy Markdown

Coverage Report

Head Commit: df3bf4fb265b3a03c42afdefc15c3797bdcdef07

Base Commit: c835f5618cb1bd4ebf900a0f3c3b2d3d8740451a

Download the full coverage report.

Coverage of Added or Modified Lines of Rust Code

Required coverage: 80.00%

Actual coverage: 92.68%

Status: PASSED ✅

Details
File Status Covered Coverage Missed Lines
cedar-policy-mcp-schema-generator-wasm/src/lib.rs 🟢 77/92 83.70% 118, 122, 175-180, 189-195
cedar-policy-mcp-schema-generator/src/generator/schema.rs 🟢 113/113 100.00%

Coverage of All Lines of Rust Code

Required coverage: 80.00%

Actual coverage: 91.11%

Status: PASSED ✅

Details
Package Status Covered Coverage Base Coverage
cedar-policy-mcp-schema-generator 🟢 1693/1809 93.59% 93.16%
cedar-policy-mcp-schema-generator-wasm 🟢 77/92 83.70% --
mcp-tools-sdk 🟢 1468/1653 88.81% 88.14%

Copy link
Copy Markdown

@victornicolet victornicolet left a comment

Choose a reason for hiding this comment

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

Thank you for the contribution. We will need to think about adding other APIs to the WASM bindings down the line (e.g. RequestGenerator), but I think this is a good start.

Copy link
Copy Markdown
Contributor

@john-h-kastner-aws john-h-kastner-aws left a comment

Choose a reason for hiding this comment

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

one small suggestion, on the code, but happy to merge as is

};

// Parse tool descriptions
let server_desc = match ServerDescription::from_json_str(tools_json) {
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.

nit (here and elsewhere): could write this as let Ok(server_desc) = .... else { return ... ; };

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@john-h-kastner-aws Thanks John, applied let-else throughout generate_schema_inner and extracted an err_result helper to reduce the repeated struct construction. Also converted the JSON serialization path at the end... All 18 tests passing, clippy clean, fmt clean. Appreciate the review!

@tomjwxf tomjwxf force-pushed the feat/wasm-bindings branch from df3bf4f to 8989fe4 Compare April 15, 2026 01:24
Add `cedar-policy-mcp-schema-generator-wasm`, a thin wasm-bindgen
wrapper around the existing Rust SchemaGenerator. Enables JavaScript
and TypeScript environments (Node.js, browsers) to generate Cedar
schemas from MCP tool descriptions with identical behavior to the
Rust implementation.

Motivated by @lianah's recommendation in cedar-policy#63 to use WASM bindings
instead of a TypeScript reimplementation.

Changes to cedar-policy-mcp-schema-generator:
- Add SchemaGenerator::from_cedarschema_str() and
  from_cedarschema_str_with_config() convenience constructors
- Add SchemaGenerator::get_schema_as_str() for human-readable output
- Add SchemaParseError variant to SchemaGeneratorError
- 6 new unit tests for the above APIs

WASM bindings crate:
- Single generateSchema() function exposed via wasm-bindgen
- All SchemaGeneratorConfig options exposed (camelCase JS naming)
- Returns JSON with schema, schemaJson, error, and isOk fields
- Zero direct dependency on cedar-policy-core (all parsing
  delegated to generator crate's from_cedarschema_str)
- 3 Rust unit tests
- 8 wasm-bindgen-test integration tests (basic generation,
  multi-tool, config options, error handling, config defaults)

Signed-off-by: tommylauren <tfarley@utexas.edu>
@tomjwxf tomjwxf force-pushed the feat/wasm-bindings branch from 8989fe4 to b92f450 Compare April 15, 2026 13:48
@github-actions
Copy link
Copy Markdown

Coverage Report

Head Commit: b92f450564d03532a34d350befc1ba0a74127354

Base Commit: c835f5618cb1bd4ebf900a0f3c3b2d3d8740451a

Download the full coverage report.

Coverage of Added or Modified Lines of Rust Code

Required coverage: 80.00%

Actual coverage: 95.26%

Status: PASSED ✅

Details
File Status Covered Coverage Missed Lines
cedar-policy-mcp-schema-generator-wasm/src/lib.rs 🟢 68/77 88.31% 118, 122, 169, 177-182
cedar-policy-mcp-schema-generator/src/generator/schema.rs 🟢 113/113 100.00%

Coverage of All Lines of Rust Code

Required coverage: 80.00%

Actual coverage: 91.24%

Status: PASSED ✅

Details
Package Status Covered Coverage Base Coverage
cedar-policy-mcp-schema-generator 🟢 1693/1809 93.59% 93.16%
cedar-policy-mcp-schema-generator-wasm 🟢 68/77 88.31% --
mcp-tools-sdk 🟢 1468/1653 88.81% 88.14%

@victornicolet victornicolet merged commit af35ea5 into cedar-policy:main Apr 15, 2026
8 checks passed
tomjwxf pushed a commit to tomjwxf/wshobson-agents that referenced this pull request Apr 15, 2026
…ceipts

Addresses wshobson#471. Adds the first cryptographic governance plugin to the
marketplace: every Claude Code tool call is evaluated against a Cedar
policy before running, and every decision produces an Ed25519-signed
receipt that anyone can verify offline.

Contents of plugins/protect-mcp/:
- skills/protect-mcp-setup/SKILL.md — full setup + hook config guide
- agents/policy-enforcer.md — Cedar policy author (opus) with example
  policies for research, development, and production contexts
- agents/receipt-verifier.md — verifier/chain expert (sonnet) that
  diagnoses signature mismatches, chain breaks, and malformed receipts
- commands/verify-receipt.md — /verify-receipt <path>
- commands/audit-chain.md — /audit-chain [--last N]
- hooks/hooks.json — PreToolUse (Cedar evaluate) + PostToolUse (sign)
- README.md + plugin.json

The plugin depends on two published npm packages:
- protect-mcp (10K+ monthly downloads) — the hooks runtime
- @veritasacta/verify — offline receipt verification CLI

Receipts follow IETF draft-farley-acta-signed-receipts, use RFC 8032
Ed25519 signatures, and RFC 8785 JCS canonicalization. Cedar is AWS's
formally-verified authorization engine (the WASM bindings were
upstreamed in cedar-policy/cedar-for-agents#64).

Marketplace entry added under category "security". Category
"governance" does not exist in the marketplace today; happy to propose
one in a follow-up if the maintainer prefers.
tomjwxf pushed a commit to tomjwxf/agency-agents that referenced this pull request Apr 15, 2026
Two new specialized agents for agent governance workflows:

specialized/receipt-auditor.md — Forensic specialist for Ed25519 signed
receipt chains. Verifies authenticity, walks hash chains, diagnoses
tampering, and explains verification failures in plain English. Reads
receipts produced by protect-mcp, Microsoft Agent Governance Toolkit,
and Cedar-enforced gateways.

specialized/cedar-policy-reviewer.md — Cedar policy specialist who
reviews authorization rules for agent tool calls, identifies
over-permissive allow rules, catches missing deny rules on dangerous
operations, and validates policies against schemas. Fluent in Cedar
for Claude Code, MCP gateways, and AGT deployments.

Both agents are designed to complement (not duplicate) the existing
Agentic Identity & Trust Architect — the Architect designs the
system, the Auditor and Reviewer are the execution-level specialists
who verify individual artifacts day-to-day.

References:
- Cedar WASM bindings merged: cedar-policy/cedar-for-agents#64
- Microsoft AGT integration merged: microsoft/agent-governance-toolkit#667
- protect-mcp plugin submitted to wshobson/agents#484
- IETF draft-farley-acta-signed-receipts
tomjwxf pushed a commit to tomjwxf/skills-curated that referenced this pull request Apr 15, 2026
Guidance-only skill that teaches Claude to verify Ed25519-signed decision
receipts produced by agent governance tooling (protect-mcp, Microsoft
Agent Governance Toolkit, Cedar-enforced MCP gateways following IETF
draft-farley-acta-signed-receipts).

## Summary

Covers: Ed25519 signature verification (RFC 8032), hash-chain integrity
walking, tamper diagnosis (which field was altered), Cedar policy digest
matching, offline verification via @veritasacta/verify (Apache-2.0, no
network calls).

Fills a gap in the Security category: existing skills (security-awareness,
scv-scan, ghidra-headless, ffuf-web-fuzzing, wooyun-legacy) are advisory
or offensive. This teaches the missing "did the agent follow its policy,
and can we prove it?" layer — forensic verification of signed audit logs.

## Source

- Upstream: https://github.com/ScopeBlind/scopeblind-gateway
- Author: Tom Farley / ScopeBlind
- License: MIT

## Why this fits ToB's audience

MCP operators and anyone running Claude Code against production tools need
proof-of-policy, not just policy. Specific fit:

- **Formal verification story.** Cedar (referenced throughout) has Lean
  proofs from AWS. Not hand-rolled authorization.
- **Crypto is boring on purpose.** Ed25519 per RFC 8032, canonical JSON
  per RFC 8785 (JCS). Verifier is small, no dependencies beyond
  @noble/ed25519.
- **Offline verification.** Receipts verify with just the issuer's public
  key — no vendor backend, no phone-home.
- **No hooks, no network, no runtime code.** This is a guidance skill only.
  It does not ship hooks, run background services, or make network calls
  from Claude Code. Runtime enforcement is a separate tool (protect-mcp
  npm) users install independently.

## Distribution signals

- npm protect-mcp: ~2,300 downloads/month (10K+/month across 16 packages)
- Microsoft AGT adapter merged: microsoft/agent-governance-toolkit#667
- Cedar-for-agents WASM bindings merged: cedar-policy/cedar-for-agents#64
- wshobson/agents (Claude Code marketplace, 33K stars) PR #484 pending
- IETF Internet-Draft: draft-farley-acta-signed-receipts

## Review notes

- Follows ToB SKILL.md structure: When to Use / When NOT to Use /
  Rationalizations to Reject
- Third-person description, gerund form name (verifying-agent-receipts)
- Uses {baseDir} convention (no hardcoded paths)
- No hooks, no scripts with network access
- allowed-tools restricted to Read, Glob, Bash, Grep (minimum needed)
- Single-level reference structure (SKILL.md → standards docs, no chains)
tomjwxf pushed a commit to tomjwxf/cedar-for-agents that referenced this pull request Apr 16, 2026
Addresses cedar-policy#72. Per Victor's guidance: single crate, sync-only, camelCase.

Extends `cedar-policy-mcp-schema-generator-wasm` (the crate from cedar-policy#64)
with request-generation bindings that let JS/TS agent hosts construct
Cedar authorization requests from MCP tool-call inputs.

## What this adds

### Generator crate (`cedar-policy-mcp-schema-generator`)

- `AuthorizationComponents` struct — serializable principal/action/
  resource/entities from a Cedar authorization request.
- `RequestGenerator::get_action_uid_string(tool_name)` — returns the
  fully-qualified Cedar EntityUID string for a tool action.
- `RequestGenerator::generate_request_components(input, principal,
  resource, context, entities, output)` — returns
  `AuthorizationComponents` ready for JSON serialization.

### WASM crate (`cedar-policy-mcp-schema-generator-wasm`)

- `generateRequest(schemaStub, toolsJson, inputJson, principalType,
  principalId, resourceType, resourceId, configJson?)` — sync JS
  function that returns `{ principal, action, resource, entitiesJson,
  error, isOk }` in camelCase matching cedar-wasm conventions.
- 6 unit tests (basic, invalid-input, invalid-stub, namespace
  qualification, entities JSON, error-field completeness).

## Why this matters

Together with cedar-policy#64 (schema generation), this gives the complete
build-time + runtime WASM toolchain for Cedar-based agent
authorization:

1. **Schema generation** (cedar-policy#64): MCP tool descriptions → Cedar schema
2. **Request generation** (this PR): MCP tool call → Cedar request
3. **Authorization**: pass request + schema to `cedar-wasm`
   `isAuthorized()` for the decision

All three steps now run in any JS/TS environment via WASM without
a Rust toolchain.

## Verification

- 237 tests passing (all workspace tests)
- cargo clippy clean (workspace lints, `too_many_arguments` expected)
- cargo fmt clean

Signed-off-by: tommylauren <tfarley@utexas.edu>
wshobson pushed a commit to tomjwxf/wshobson-agents that referenced this pull request Apr 16, 2026
…ceipts

Addresses wshobson#471. Adds the first cryptographic governance plugin to the
marketplace: every Claude Code tool call is evaluated against a Cedar
policy before running, and every decision produces an Ed25519-signed
receipt that anyone can verify offline.

Contents of plugins/protect-mcp/:
- skills/protect-mcp-setup/SKILL.md — full setup + hook config guide
- agents/policy-enforcer.md — Cedar policy author (opus) with example
  policies for research, development, and production contexts
- agents/receipt-verifier.md — verifier/chain expert (sonnet) that
  diagnoses signature mismatches, chain breaks, and malformed receipts
- commands/verify-receipt.md — /verify-receipt <path>
- commands/audit-chain.md — /audit-chain [--last N]
- hooks/hooks.json — PreToolUse (Cedar evaluate) + PostToolUse (sign)
- README.md + plugin.json

The plugin depends on two published npm packages:
- protect-mcp (10K+ monthly downloads) — the hooks runtime
- @veritasacta/verify — offline receipt verification CLI

Receipts follow IETF draft-farley-acta-signed-receipts, use RFC 8032
Ed25519 signatures, and RFC 8785 JCS canonicalization. Cedar is AWS's
formally-verified authorization engine (the WASM bindings were
upstreamed in cedar-policy/cedar-for-agents#64).

Marketplace entry added under category "security". Category
"governance" does not exist in the marketplace today; happy to propose
one in a follow-up if the maintainer prefers.
tomjwxf pushed a commit to tomjwxf/wshobson-agents that referenced this pull request Apr 19, 2026
…ceipts

Addresses wshobson#471. Adds the first cryptographic governance plugin to the
marketplace: every Claude Code tool call is evaluated against a Cedar
policy before running, and every decision produces an Ed25519-signed
receipt that anyone can verify offline.

Contents of plugins/protect-mcp/:
- skills/protect-mcp-setup/SKILL.md — full setup + hook config guide
- agents/policy-enforcer.md — Cedar policy author (opus) with example
  policies for research, development, and production contexts
- agents/receipt-verifier.md — verifier/chain expert (sonnet) that
  diagnoses signature mismatches, chain breaks, and malformed receipts
- commands/verify-receipt.md — /verify-receipt <path>
- commands/audit-chain.md — /audit-chain [--last N]
- hooks/hooks.json — PreToolUse (Cedar evaluate) + PostToolUse (sign)
- README.md + plugin.json

The plugin depends on two published npm packages:
- protect-mcp (10K+ monthly downloads) — the hooks runtime
- @veritasacta/verify — offline receipt verification CLI

Receipts follow IETF draft-farley-acta-signed-receipts, use RFC 8032
Ed25519 signatures, and RFC 8785 JCS canonicalization. Cedar is AWS's
formally-verified authorization engine (the WASM bindings were
upstreamed in cedar-policy/cedar-for-agents#64).

Marketplace entry added under category "security". Category
"governance" does not exist in the marketplace today; happy to propose
one in a follow-up if the maintainer prefers.
tomjwxf pushed a commit to tomjwxf/cedar-for-agents that referenced this pull request Apr 20, 2026
Addresses cedar-policy#72. Per Victor's guidance: single crate, sync-only, camelCase.

Extends `cedar-policy-mcp-schema-generator-wasm` (the crate from cedar-policy#64)
with request-generation bindings that let JS/TS agent hosts construct
Cedar authorization requests from MCP tool-call inputs.

## What this adds

### Generator crate (`cedar-policy-mcp-schema-generator`)

- `AuthorizationComponents` struct — serializable principal/action/
  resource/entities from a Cedar authorization request.
- `RequestGenerator::get_action_uid_string(tool_name)` — returns the
  fully-qualified Cedar EntityUID string for a tool action.
- `RequestGenerator::generate_request_components(input, principal,
  resource, context, entities, output)` — returns
  `AuthorizationComponents` ready for JSON serialization.

### WASM crate (`cedar-policy-mcp-schema-generator-wasm`)

- `generateRequest(schemaStub, toolsJson, inputJson, principalType,
  principalId, resourceType, resourceId, configJson?)` — sync JS
  function that returns `{ principal, action, resource, entitiesJson,
  error, isOk }` in camelCase matching cedar-wasm conventions.
- 6 unit tests (basic, invalid-input, invalid-stub, namespace
  qualification, entities JSON, error-field completeness).

## Why this matters

Together with cedar-policy#64 (schema generation), this gives the complete
build-time + runtime WASM toolchain for Cedar-based agent
authorization:

1. **Schema generation** (cedar-policy#64): MCP tool descriptions → Cedar schema
2. **Request generation** (this PR): MCP tool call → Cedar request
3. **Authorization**: pass request + schema to `cedar-wasm`
   `isAuthorized()` for the decision

All three steps now run in any JS/TS environment via WASM without
a Rust toolchain.

## Verification

- 237 tests passing (all workspace tests)
- cargo clippy clean (workspace lints, `too_many_arguments` expected)
- cargo fmt clean

Signed-off-by: tommylauren <tfarley@utexas.edu>
victornicolet pushed a commit that referenced this pull request Apr 20, 2026
…#73)

* feat: RequestGenerator WASM bindings for authorization requests

Addresses #72. Per Victor's guidance: single crate, sync-only, camelCase.

Extends `cedar-policy-mcp-schema-generator-wasm` (the crate from #64)
with request-generation bindings that let JS/TS agent hosts construct
Cedar authorization requests from MCP tool-call inputs.

## What this adds

### Generator crate (`cedar-policy-mcp-schema-generator`)

- `AuthorizationComponents` struct — serializable principal/action/
  resource/entities from a Cedar authorization request.
- `RequestGenerator::get_action_uid_string(tool_name)` — returns the
  fully-qualified Cedar EntityUID string for a tool action.
- `RequestGenerator::generate_request_components(input, principal,
  resource, context, entities, output)` — returns
  `AuthorizationComponents` ready for JSON serialization.

### WASM crate (`cedar-policy-mcp-schema-generator-wasm`)

- `generateRequest(schemaStub, toolsJson, inputJson, principalType,
  principalId, resourceType, resourceId, configJson?)` — sync JS
  function that returns `{ principal, action, resource, entitiesJson,
  error, isOk }` in camelCase matching cedar-wasm conventions.
- 6 unit tests (basic, invalid-input, invalid-stub, namespace
  qualification, entities JSON, error-field completeness).

## Why this matters

Together with #64 (schema generation), this gives the complete
build-time + runtime WASM toolchain for Cedar-based agent
authorization:

1. **Schema generation** (#64): MCP tool descriptions → Cedar schema
2. **Request generation** (this PR): MCP tool call → Cedar request
3. **Authorization**: pass request + schema to `cedar-wasm`
   `isAuthorized()` for the decision

All three steps now run in any JS/TS environment via WASM without
a Rust toolchain.

## Verification

- 237 tests passing (all workspace tests)
- cargo clippy clean (workspace lints, `too_many_arguments` expected)
- cargo fmt clean

Signed-off-by: tommylauren <tfarley@utexas.edu>

* test: add coverage for RequestGenerator WASM and generator-crate APIs

Addresses CI coverage check failure (62.12% -> target 80%).

WASM crate tests added (8):
- Invalid config JSON returns error
- Invalid tools JSON returns error
- Empty config string uses defaults
- Explicit config (numbersAsDecimal) passes through
- Multi-tool schema resolves correct action
- Resource ID appears in formatted EntityUID
- req_err helper produces correct error shape
- WasmRequestResult serializes with camelCase (isOk, entitiesJson)

Generator crate tests added (3):
- get_action_uid_string returns namespace-qualified action
- get_action_uid_string distinguishes multiple tools
- AuthorizationComponents is Clone + Debug with expected fields

312 tests passing, cargo clippy clean, cargo fmt clean.

Signed-off-by: tommylauren <tfarley@utexas.edu>

* test: comprehensive coverage for generate_request_components + all error paths

Addresses CI coverage check (65.91% -> target 80%).

Generator crate tests added (3):
- generate_request_components basic: exercises the full
  serialization pipeline (EntityUID -> String, Entities -> JSON)
- generate_request_components with output: covers the Output
  parameter path
- generate_request_components entities serialization: validates
  the JSON array output format

WASM crate tests added (4):
- No-namespace schema: exercises the None branch for namespace
  qualification (principal/resource without NS:: prefix)
- Explicit None config: confirms None config produces same results
  as omitted config
- Action format: verifies namespace + tool name in action string
- All error paths in generate_request_inner: exercises every early
  return (invalid config, invalid stub, invalid tools, invalid input,
  empty config string) in a single test function

319 tests passing, clippy clean, fmt clean.

Signed-off-by: tommylauren <tfarley@utexas.edu>

* refactor: propagate real entities + string-based request helper

Addresses @chaluli's review feedback on #73:

Q2 — entities bug (correctness)
  The WASM crate was hardcoding entities_json: "[]", which silently
  dropped entities the generator actually produces from MCP tool-call
  inputs containing nulls, floats, or nested objects. Fixed by routing
  the WASM entry point through the generator crate's real request-
  components pipeline — entity data now flows through unchanged.

Q1 — API layering
  The old WASM code path duplicated namespace extraction and principal/
  resource UID construction via format! strings. Replaced with a new
  string-based convenience method on RequestGenerator —
  generate_request_components_from_strings — which takes plain strings
  and builds correctly-namespaced EntityUIDs internally. The WASM crate
  now stays free of cedar-policy-core as a direct dep AND avoids the
  parallel implementation. The generator crate's public API is the
  single source of truth for request construction; WASM is a thin
  JSON-serialization wrapper around it.

Tests
  - generator crate: +2 tests for the new string-based method
    (happy path + invalid principal type error surface).
  - WASM crate: +1 regression test that asserts entities_json parses
    as a real JSON array (not a hardcoded string literal) and that
    principal/action/resource are correctly namespaced against the
    schema.

All 237+3 = 240 workspace tests pass. cargo clippy --all-features
clean. cargo fmt clean.

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Signed-off-by: tommylauren <tfarley@utexas.edu>

* refactor: address all of @victornicolet's review comments

Applies every point from the review on PR #73:

1. Remove duplicate / inconsistent doc block on `generateRequest`.
   The second block described the parameters as if they were a single
   JSON object, which contradicted the flat-argument signature.

2. Remove the explanatory "Previously this hardcoded [] -- see PR
   review" comment on the delegation call. The code is new to this PR;
   the comment belonged in the PR description, not the source.

3. Move `get_action_uid_string` out of the public API. It was only
   called from the two tests that tested it. Removing the method and
   its two standalone tests eliminates the dead export; namespace
   qualification of action UIDs stays covered by the full-pipeline
   tests against `generate_request_components`.

4. Delete every `#[expect(clippy::expect_used, reason = "Test
   assertion")]` (28 sites). Replace with one
   `#![allow(clippy::expect_used, clippy::panic, clippy::unwrap_used)]`
   per test module. Keeps test code noise-free.

5. Make `test_generate_request_no_namespace_schema` deterministic by
   removing the "might succeed or fail" branch. The Cedar schema parser
   at this version rejects unqualified `entity` declarations without a
   surrounding namespace block, so the test is retired; a comment
   records why and points at where namespace-absent coverage lives.

6. Surface the underlying `serde_json` error on invalid config, matching
   the schema-generation path (`"Invalid config: {}"` with the parser's
   own message) instead of the opaque `"failed to parse JSON"`.

7. Stop silencing `write_to_json` errors in
   `generate_request_components`. The call now uses `?` so a serializer
   failure surfaces as a `RequestGeneratorError` rather than producing
   a stale empty entity set. `String::from_utf8_lossy` handles the
   (unreachable in practice) non-UTF-8 path without an unwrap.

8. Strengthen the entities regression test. The previous version
   asserted `parsed.is_array()` and `starts_with('[')` / `ends_with(']')`,
   all of which a hardcoded `"[]"` would satisfy. The replacement test
   uses a tool input with a nested-object property plus a float that
   the schema generator must turn into a non-empty entity set, and
   asserts `!arr.is_empty()` with exact principal / action / resource
   UID equality. A regression to the old hardcoded behavior fails.

9. Consolidate duplicated happy-path tests. `test_generate_request_basic`,
   `test_generate_request_namespace_qualification`,
   `test_generate_request_action_contains_namespace_and_tool`, and
   `test_generate_request_with_none_config` all exercised subsets of
   the same success path with weaker assertions. Replaced by the single
   deeper `test_generate_request_entities_propagate_real_generator_output`.

Verification:
  cargo test   --workspace            -> all pass (237 generator-crate,
                                           44 wasm-crate, and adjacent)
  cargo clippy --workspace --all-features -> clean
  cargo fmt    --check                -> clean

Thanks @victornicolet for the careful review; each comment improved the
diff materially.

Signed-off-by: tommylauren <tfarley@utexas.edu>

---------

Signed-off-by: tommylauren <tfarley@utexas.edu>
Co-authored-by: tommylauren <tfarley@utexas.edu>
Co-authored-by: Claude Opus 4 (1M context) <noreply@anthropic.com>
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.

4 participants