Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ This repository contains a number of directories:
- [cedar-policy-mcp-schema-generator](./rust/cedar-policy-mcp-schema-generator/) : A crate for auto-generating a Cedar Schema for an MCP Server's tool descriptions.
* [js](./js/) which contains JavaScript packages that enable Agents to make use of Cedar and its Analysis Capabilities.
- [cedar-analysis-mcp-server](./js/cedar-analysis-mcp-server) : A package that creates an MCP server that exposes an interface for Agents to use [Cedar's analysis capabilities](https://github.com/cedar-policy/cedar-spec/tree/main/cedar-lean-cli#analysis).
- [protect-mcp-cedar-integration](./js/protect-mcp-cedar-integration) : Example showing auto-generation of Cedar schemas from MCP tool descriptions for runtime policy enforcement via [protect-mcp](https://www.npmjs.com/package/protect-mcp).

## Security

Expand Down
207 changes: 207 additions & 0 deletions js/protect-mcp-cedar-integration/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
# Cedar Schema Integration for MCP Tool Governance

TypeScript integration that auto-generates Cedar authorization schemas from MCP tool descriptions and uses them for runtime policy enforcement.

Built on [protect-mcp](https://www.npmjs.com/package/protect-mcp), the security gateway for MCP servers and Claude Code. This example shows how to generate Cedar schemas from MCP `tools/list`, enabling typed policies that reference tool input attributes.

## What this does

1. **Reads MCP tool descriptions** (JSON Schema for each tool's inputs)
2. **Generates a Cedar schema** with per-tool actions and typed input context
3. **Passes the schema to `@cedar-policy/cedar-wasm`** for validated policy evaluation
4. **Every allow/deny decision produces an Ed25519-signed receipt** (IETF Internet-Draft: `draft-farley-acta-signed-receipts`)

## Schema generation

The schema generator maps MCP tool descriptions to Cedar entity/action types:

| MCP Tool | Cedar Action | Cedar Context |
|---|---|---|
| `read_file(path: string)` | `Action::"read_file"` | `context.input.path: String` |
| `execute_command(command: string, args: string[])` | `Action::"execute_command"` | `context.input.command: String, context.input.args: Set<String>` |

All per-tool actions are children of a blanket `Action::"MCP::Tool::call"` action, so policies can match individual tools or all tools:

```cedar
// Match all tool calls
forbid(principal, action == Action::"MCP::Tool::call", resource);

// Match specific tool with typed input
permit(principal, action == Action::"read_file", resource)
when { context.input.path like "./workspace/*" };
```

### JSON Schema to Cedar type mapping

| JSON Schema | Cedar Type |
|---|---|
| `string` | `String` |
| `integer` / `number` | `Long` |
| `boolean` | `Bool` |
| `array` | `Set<T>` |
| `object` (with properties) | Record type |

Required properties are mapped without `?`. Optional properties are mapped with `?`.

## Quick start

### With Claude Code (recommended)

```bash
npx protect-mcp init-hooks # generates Cedar policies + hook config + signing keys
npx protect-mcp serve # starts hook server with schema-validated Cedar evaluation
```

### Programmatic usage

```typescript
import { generateCedarSchema, evaluateCedar, loadCedarPolicies } from 'protect-mcp';

// MCP tools (from tools/list response)
const tools = [
{
name: 'read_file',
description: 'Read a file',
inputSchema: {
type: 'object',
properties: { path: { type: 'string' } },
required: ['path'],
},
},
{
name: 'execute_command',
description: 'Run a shell command',
inputSchema: {
type: 'object',
properties: {
command: { type: 'string' },
args: { type: 'array', items: { type: 'string' } },
},
required: ['command'],
},
},
];

// Generate Cedar schema from tool descriptions
const schema = generateCedarSchema(tools);
console.log(schema.schemaText); // Human-readable .cedarschema
console.log(schema.schemaJson); // JSON for Cedar WASM

// Load Cedar policies from disk
const policies = loadCedarPolicies('./policies');

// Evaluate with schema validation
const result = await evaluateCedar(policies, {
tool: 'read_file',
tier: 'unknown',
toolInput: { path: './workspace/README.md' },
}, { schemaJson: schema.schemaJson });

console.log(result);
// { allowed: true, metadata: { policy_digest: 'a3f8...' } }
```

## Generated schema example

For the tools above, `generateCedarSchema()` produces:

```cedarschema
namespace ScopeBlind {

entity Agent = {
"tier": String,
"agent_id": String?
};

entity Tool;

type read_file_Input = {
"path": String
};

type execute_command_Input = {
"command": String,
"args": Set<String>?
};

action "read_file" in [Action::"MCP::Tool::call"] appliesTo {
principal: [Agent],
resource: [Tool],
context: {
"input": read_file_Input,
"tier": String
}
};

action "execute_command" in [Action::"MCP::Tool::call"] appliesTo {
principal: [Agent],
resource: [Tool],
context: {
"input": execute_command_Input,
"tier": String
}
};

action "MCP::Tool::call" appliesTo {
principal: [Agent],
resource: [Tool],
context: {
"tier": String
}
};

}
```

## Compatibility with cedar-policy-mcp-schema-generator (Rust)

This TypeScript implementation is compatible with the [Rust schema generator](../rust/cedar-policy-mcp-schema-generator/):

- Same entity model: `Agent` (principal), `Tool` (resource)
- Same action hierarchy: per-tool actions as children of `MCP::Tool::call`
- Same type mapping: JSON Schema → Cedar types
- Schema stubs generated by `generateSchemaStub()` use `@mcp_principal` / `@mcp_resource` annotations

The TypeScript version is designed for Node.js environments where the Rust binary is not available (e.g., `npx protect-mcp` installs). For environments with Rust tooling, the Rust generator provides additional features (union types, tagged entities, output schemas).

## Example Cedar policies

```cedar
// Block shell execution (prevents prompt injection attacks)
@id("block-shell")
forbid(
principal,
action == Action::"MCP::Tool::call",
resource == Tool::"execute_command"
);

// Allow reads only within workspace
@id("workspace-reads")
permit(
principal,
action == Action::"read_file",
resource
)
when { context.input.path like "./workspace/*" };

// Rate-limit write operations for untrusted agents
@id("untrusted-write-limit")
forbid(
principal,
action == Action::"write_file",
resource
)
when { context.tier == "unknown" };
```

## Links

- [protect-mcp on npm](https://www.npmjs.com/package/protect-mcp) (v0.5.2, MIT)
- [Source: cedar-schema.ts](https://github.com/scopeblind/scopeblind-gateway/blob/main/src/cedar-schema.ts)
- [Source: cedar-evaluator.ts](https://github.com/scopeblind/scopeblind-gateway/blob/main/src/cedar-evaluator.ts)
- [IETF Internet-Draft: draft-farley-acta-signed-receipts](https://datatracker.ietf.org/doc/draft-farley-acta-signed-receipts/)
- [ScopeBlind docs](https://scopeblind.com/docs/protect-mcp)

## License

Apache-2.0 (matches this repository)
114 changes: 114 additions & 0 deletions js/protect-mcp-cedar-integration/example.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/**
* Example: Generate a Cedar schema from MCP tool descriptions.
*
* This demonstrates how protect-mcp auto-generates typed Cedar
* authorization schemas from MCP tools/list responses, enabling
* policies that reference tool input attributes.
*
* Run: node example.mjs
*/

import { generateCedarSchema, generateSchemaStub } from 'protect-mcp';

// ── Sample MCP tools (from a typical tools/list response) ──

const tools = [
{
name: 'read_file',
description: 'Read the contents of a file at the given path',
inputSchema: {
type: 'object',
properties: {
path: { type: 'string', description: 'Absolute path to file' },
encoding: { type: 'string', description: 'File encoding (default: utf-8)' },
},
required: ['path'],
},
},
{
name: 'write_file',
description: 'Write content to a file',
inputSchema: {
type: 'object',
properties: {
path: { type: 'string' },
content: { type: 'string' },
create_dirs: { type: 'boolean', description: 'Create parent directories' },
},
required: ['path', 'content'],
},
},
{
name: 'execute_command',
description: 'Execute a shell command',
inputSchema: {
type: 'object',
properties: {
command: { type: 'string' },
args: { type: 'array', items: { type: 'string' } },
timeout_ms: { type: 'integer', description: 'Timeout in milliseconds' },
working_directory: { type: 'string' },
},
required: ['command'],
},
},
{
name: 'search_web',
description: 'Search the web for information',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string' },
max_results: { type: 'integer' },
},
required: ['query'],
},
},
{
name: 'get_status',
description: 'Get server status (no inputs)',
// No inputSchema — tools with no parameters
},
];

// ── Generate the Cedar schema ──

console.log('=== Cedar Schema Generation from MCP Tools ===\n');

const result = generateCedarSchema(tools, {
namespace: 'MyMcpServer',
includeTier: true,
includeAgentId: true,
includeTimestamp: true,
});

console.log(`Generated schema for ${result.toolCount} tools: ${result.tools.join(', ')}\n`);
console.log('--- .cedarschema (human-readable) ---\n');
console.log(result.schemaText);

console.log('--- Schema JSON (for Cedar WASM) ---\n');
console.log(JSON.stringify(result.schemaJson, null, 2));

// ── Generate a schema stub ──

console.log('\n--- Schema stub (for customization) ---\n');
console.log(generateSchemaStub('MyMcpServer'));

console.log('\n=== Policies enabled by this schema ===\n');
console.log(`With this schema, you can write Cedar policies like:

// Allow reads only within workspace
permit(principal, action == Action::"read_file", resource)
when { context.input.path like "./workspace/*" };

// Block shell execution entirely
forbid(principal, action == Action::"execute_command", resource);

// Allow web search with result limits
permit(principal, action == Action::"search_web", resource)
when { context.input.max_results <= 10 };

// Block writes for untrusted agents
forbid(principal, action == Action::"write_file", resource)
when { context.tier == "unknown" };
`);
14 changes: 14 additions & 0 deletions js/protect-mcp-cedar-integration/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"name": "protect-mcp-cedar-integration",
"version": "0.1.0",
"description": "Example: auto-generating Cedar schemas from MCP tool descriptions for runtime policy enforcement",
"license": "Apache-2.0",
"type": "module",
"scripts": {
"generate-schema": "node example.mjs",
"test": "node test.mjs"
},
"dependencies": {
"protect-mcp": "^0.5.2"
}
}
Loading