feat: TypeScript Cedar schema generation from MCP tool descriptions (protect-mcp)#63
Conversation
Adds js/protect-mcp-cedar-integration/ — a working example showing how to auto-generate Cedar authorization schemas from MCP tools/list responses and use them for runtime policy enforcement via protect-mcp. - Generates per-tool Cedar actions with typed input context - Maps JSON Schema types to Cedar types (String, Long, Bool, Set<T>) - Compatible with the Rust cedar-policy-mcp-schema-generator - Includes example policies, schema generation demo, and tests - TypeScript-native (no Rust binary dependency) - Every allow/deny decision produces an Ed25519-signed receipt Reference: cedar-policy/cedar#2266 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Thank you for your contribution! It's great to see increased adoption of Cedar for agents. One concern we have with the approach of re-implementing the schema generator code in TypeScript is that the two implementations might diverge, and we would want to have consistent behavior across the two languages. Something we have done in the past is WASM bindings for the Rust code. Would this work for your use case or are there any reasons why that is not a good fit? There are a couple of corner cases in the current schema generator we had to be careful about handling correctly that we would want to make sure are consistent across the two versions:
There is also some work on utility code to map from the actual MCP tool inputs to a Cedar request to make integration easier. |
|
@lianah -- you are right. Implementation divergence is the real risk here, and the three edge cases you flagged (number/float handling, additionalProperties as tagged entities, and namespaced type deduplication) are exactly the places where a TypeScript reimplementation would silently produce different schemas. WASM bindings for the Rust generator is the better path. I checked and the crate does not have wasm-bindgen today -- would it be useful if I contributed WASM bindings as a PR to the schema generator crate? The scope would be:
This way protect-mcp (and any other TypeScript/Node.js MCP tool) gets the exact same behavior as the Rust generator, including all the edge cases around decimal encoding, entity vs record modeling, and namespace deduplication. I am already using @cedar-policy/cedar-wasm (v4.9.1) for policy evaluation in protect-mcp, so the WASM integration pattern is familiar. Happy to start on this if the approach works for you. On the utility code for mapping MCP tool inputs to Cedar requests -- I built a basic version of this in protect-mcp (maps tool_name to resource, agent tier to principal, tool input to context). Would be glad to align it with whatever interface the Rust RequestGenerator exposes through WASM. |
|
@lianah -- I built the WASM bindings as a proof-of-concept and they work. Here is a working demo: WASM crate: `cedar-policy-mcp-schema-generator-wasm` -- a thin `wasm-bindgen` wrapper around the existing Rust `SchemaGenerator`. What it exposes to JavaScript/TypeScript: ```js const result = generateSchema(stubCedarSchema, toolsJson, configJson); Tested in Node.js -- produces correct output: ``` type read_fileInput = { entity McpServer; action "call_tool"; Implementation details:
Two options for how to proceed:
Which approach works better for the cedar-for-agents project? Happy to do either. |
|
That's awesome! I think submitting the WASM bindings as a PR against the |
Adds `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. Exposes a single function `generateSchema(schemaStub, toolsJson, configJson?)` that returns the generated schema as both human- readable `.cedarschema` text and JSON for `isAuthorized()`. All schema generation logic, type mapping (including number/decimal handling, additionalProperties as tagged entities, and namespaced type deduplication) is delegated to the Rust crate. The WASM bindings add no independent logic, ensuring zero divergence. - 3 tests passing (cargo test) - clippy clean with workspace lints - 2.5MB WASM binary (wasm-opt applied) - Tested in Node.js: correct output for multi-tool schemas Motivated by the discussion in cedar-policy#63, where @lianah recommended WASM bindings over a TypeScript reimplementation to avoid implementation divergence.
|
@lianah -- thank you! Agreed, the TypeScript reimplementation is no longer needed. I submitted the WASM bindings as #64 instead. The WASM crate wraps the existing Rust SchemaGenerator via wasm-bindgen with zero independent logic. All type mapping, edge cases, and namespace handling is delegated to the Rust implementation. 3 tests passing, clippy clean with workspace lints, tested in Node.js. Closing this PR in favor of #64. Best, |
Adds `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. Exposes a single function `generateSchema(schemaStub, toolsJson, configJson?)` that returns the generated schema as both human- readable `.cedarschema` text and JSON for `isAuthorized()`. All schema generation logic, type mapping (including number/decimal handling, additionalProperties as tagged entities, and namespaced type deduplication) is delegated to the Rust crate. The WASM bindings add no independent logic, ensuring zero divergence. - 3 tests passing (cargo test) - clippy clean with workspace lints - 2.5MB WASM binary (wasm-opt applied) - Tested in Node.js: correct output for multi-tool schemas Motivated by the discussion in cedar-policy#63, where @lianah recommended WASM bindings over a TypeScript reimplementation to avoid implementation divergence.
|
Thank you! Closing this PR, I will review #64 . |
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>
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>
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>
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>
Summary
Adds a TypeScript integration example showing how to auto-generate Cedar authorization schemas from MCP tool descriptions, enabling typed policies for runtime tool governance.
This builds on the discussion in cedar-policy/cedar#2266, where @lianah recommended integrating the cedar-policy-mcp-schema-generator and using static schemas with Cedar for validation. This example provides a TypeScript-native path for Node.js environments where the Rust binary is not available.
What this adds
js/protect-mcp-cedar-integration/— a working example with:README.mdexample.mjstest.mjspolicies/mcp-governance.cedarpackage.jsonprotect-mcp(npm, MIT)How it works
This enables typed policies:
Compatibility with the Rust generator
Agent(principal),Tool(resource)MCP::Tool::call@mcp_principal/@mcp_resourceannotationsContext
protect-mcp (v0.5.2, MIT) is a security gateway for MCP servers and Claude Code that uses Cedar WASM for runtime policy enforcement. Every allow/deny decision produces an Ed25519-signed receipt following the IETF Internet-Draft format.
This PR was motivated by @lianah's recommendation in cedar-policy/cedar#2266 to move from
schema: nullto validated schemas and to integrate with the cedar-for-agents schema generation tooling.