@vaultys/mcp-agent 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,194 @@
1
+ /**
2
+ * Policy Middleware — wraps every MCP tool call through ExecutionManager.
3
+ *
4
+ * For each tool invocation the middleware:
5
+ * 1. Maps the MCP tool name + arguments → taxonomy capability strings
6
+ * 2. Creates and signs an ExecutionIntent via the server's IdManager
7
+ * 3. Evaluates the intent against the loaded signed PolicyBundle
8
+ * 4. If allowed, executes the real handler and signs a receipt
9
+ * 5. If denied, returns a denial message and signs a deny receipt
10
+ *
11
+ * All receipts are persisted to ./audit/ as JSON files.
12
+ */
13
+ import * as fs from "node:fs";
14
+ import * as path from "node:path";
15
+ import { ExecutionManager } from "@vaultys/id";
16
+ /**
17
+ * Default mappings from MCP tool names to taxonomy capabilities.
18
+ *
19
+ * Scopes must match the format used in the policy's scope patterns.
20
+ * For filesystem tools: full resolved paths (e.g. "/workspace/src/file.ts")
21
+ * For process tools: the binary name (e.g. "ls", "npm")
22
+ * For network tools: the hostname (e.g. "api.example.com")
23
+ *
24
+ * The `workspaceRoot` parameter is passed to `extractScope` so that filesystem
25
+ * tools can resolve relative paths to absolute paths matching the policy globs.
26
+ */
27
+ export const DEFAULT_TOOL_MAPPINGS = {
28
+ read_file: {
29
+ capability: "fs.read",
30
+ extractScope: (args, workspaceRoot) => {
31
+ const raw = String(args.path ?? args.file ?? "");
32
+ return resolveWorkspacePath(raw, workspaceRoot);
33
+ },
34
+ },
35
+ write_file: {
36
+ capability: "fs.write",
37
+ extractScope: (args, workspaceRoot) => {
38
+ const raw = String(args.path ?? args.file ?? "");
39
+ return resolveWorkspacePath(raw, workspaceRoot);
40
+ },
41
+ },
42
+ list_directory: {
43
+ capability: "fs.list",
44
+ extractScope: (args, workspaceRoot) => {
45
+ const raw = String(args.path ?? args.directory ?? ".");
46
+ return resolveWorkspacePath(raw, workspaceRoot);
47
+ },
48
+ },
49
+ run_command: {
50
+ capability: "proc.exec",
51
+ extractScope: (args) => {
52
+ const cmd = String(args.command ?? "");
53
+ // Extract the binary name (first token)
54
+ return cmd.split(/\s+/)[0] ?? cmd;
55
+ },
56
+ },
57
+ fetch_url: {
58
+ capability: "net.egress.http",
59
+ extractScope: (args) => {
60
+ try {
61
+ return new URL(String(args.url ?? "")).hostname;
62
+ }
63
+ catch {
64
+ return String(args.url ?? "");
65
+ }
66
+ },
67
+ },
68
+ };
69
+ /**
70
+ * Resolve a tool argument path to an absolute path rooted at workspaceRoot.
71
+ * This ensures capability strings like "fs.read:/workspace/subdir/file.txt"
72
+ * match policy glob patterns like "/workspace/**".
73
+ */
74
+ function resolveWorkspacePath(raw, workspaceRoot) {
75
+ if (!raw)
76
+ return workspaceRoot ?? "/workspace";
77
+ if (path.isAbsolute(raw))
78
+ return raw;
79
+ return path.join(workspaceRoot ?? "/workspace", raw);
80
+ }
81
+ // ── Audit persistence ──
82
+ const AUDIT_DIR = path.resolve("audit");
83
+ function ensureAuditDir() {
84
+ if (!fs.existsSync(AUDIT_DIR)) {
85
+ fs.mkdirSync(AUDIT_DIR, { recursive: true });
86
+ }
87
+ }
88
+ function persistReceipt(jobId, receipt, meta) {
89
+ ensureAuditDir();
90
+ const record = {
91
+ job_id: jobId,
92
+ timestamp: new Date().toISOString(),
93
+ ...meta,
94
+ receipt,
95
+ };
96
+ const filePath = path.join(AUDIT_DIR, `${jobId}.json`);
97
+ fs.writeFileSync(filePath, JSON.stringify(record, null, 2));
98
+ }
99
+ /**
100
+ * Create policy-enforced middleware that wraps MCP tool handlers.
101
+ */
102
+ export function createPolicyMiddleware(options) {
103
+ const { serverIdManager, signedPolicy, toolMappings = DEFAULT_TOOL_MAPPINGS, workspaceRoot = process.cwd(), } = options;
104
+ const em = new ExecutionManager(serverIdManager);
105
+ /**
106
+ * Wrap a tool call: evaluate policy, run handler if allowed, sign receipt.
107
+ */
108
+ async function enforce(toolName, args, handler) {
109
+ const mapping = toolMappings[toolName];
110
+ // Build capability strings
111
+ const requestedCaps = [];
112
+ let argv = [];
113
+ if (mapping) {
114
+ const scope = mapping.extractScope(args, workspaceRoot);
115
+ requestedCaps.push(`${mapping.capability}:${scope}`);
116
+ argv = toolName === "run_command"
117
+ ? String(args.command ?? "").split(/\s+/)
118
+ : [toolName, scope];
119
+ }
120
+ else {
121
+ // Unknown tool → treat as a catch-all with the tool name as capability
122
+ requestedCaps.push(`tool.${toolName}`);
123
+ argv = [toolName];
124
+ }
125
+ // Create and sign the intent
126
+ const intent = await em.createIntent({
127
+ tool: toolName,
128
+ argv,
129
+ cwd: workspaceRoot,
130
+ requested_caps: requestedCaps,
131
+ });
132
+ // Evaluate against the policy
133
+ const evaluation = em.evaluateIntent(intent, signedPolicy);
134
+ if (evaluation.decision === "deny") {
135
+ // Sign a deny receipt
136
+ const denyOutcome = {
137
+ started: new Date().toISOString(),
138
+ ended: new Date().toISOString(),
139
+ exit_code: -1,
140
+ stdout_hash: "",
141
+ stderr_hash: "",
142
+ };
143
+ const receipt = await em.signReceipt(intent, signedPolicy, denyOutcome);
144
+ persistReceipt(intent.job_id, receipt, {
145
+ tool: toolName,
146
+ args,
147
+ decision: "deny",
148
+ denied_caps: evaluation.denied_caps,
149
+ });
150
+ const reason = evaluation.denied_caps?.join(", ") ?? "unknown";
151
+ console.error(`[POLICY DENY] ${toolName}: ${reason}`);
152
+ return {
153
+ decision: "deny",
154
+ content: [{ type: "text", text: `⛔ DENIED by policy: ${reason}` }],
155
+ receipt,
156
+ jobId: intent.job_id,
157
+ };
158
+ }
159
+ // Policy allows → execute
160
+ console.error(`[POLICY ALLOW] ${toolName}: ${requestedCaps.join(", ")}`);
161
+ const started = new Date().toISOString();
162
+ let stdout = "";
163
+ let exitCode = 0;
164
+ try {
165
+ stdout = await handler();
166
+ }
167
+ catch (err) {
168
+ stdout = err.message ?? String(err);
169
+ exitCode = 1;
170
+ }
171
+ const ended = new Date().toISOString();
172
+ const outcome = {
173
+ started,
174
+ ended,
175
+ exit_code: exitCode,
176
+ stdout_hash: Buffer.from(stdout).toString("base64").slice(0, 64),
177
+ };
178
+ const receipt = await em.signReceipt(intent, signedPolicy, outcome);
179
+ persistReceipt(intent.job_id, receipt, {
180
+ tool: toolName,
181
+ args,
182
+ decision: "allow",
183
+ allowed_caps: evaluation.allowed_caps,
184
+ constraints: evaluation.constraints,
185
+ });
186
+ return {
187
+ decision: "allow",
188
+ content: [{ type: "text", text: stdout }],
189
+ receipt,
190
+ jobId: intent.job_id,
191
+ };
192
+ }
193
+ return { enforce, em };
194
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * MCP Resources — expose policy, identity, and audit trail as readable resources.
3
+ *
4
+ * Resources:
5
+ * vaultys://policy/current — the active signed policy (JSON)
6
+ * vaultys://identity/server — the server's VaultysID DID + public key info
7
+ * vaultys://receipts/list — summary of all signed receipts
8
+ * vaultys://receipt/{id} — individual receipt (verifiable)
9
+ */
10
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
11
+ import type { PolicyBundle } from "@vaultys/id";
12
+ import type { IdManager } from "@vaultys/id";
13
+ export declare function registerResources(server: McpServer, serverIdManager: IdManager, signedPolicy: PolicyBundle): void;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * MCP Resources — expose policy, identity, and audit trail as readable resources.
3
+ *
4
+ * Resources:
5
+ * vaultys://policy/current — the active signed policy (JSON)
6
+ * vaultys://identity/server — the server's VaultysID DID + public key info
7
+ * vaultys://receipts/list — summary of all signed receipts
8
+ * vaultys://receipt/{id} — individual receipt (verifiable)
9
+ */
10
+ import * as fs from "node:fs";
11
+ import * as path from "node:path";
12
+ const AUDIT_DIR = path.resolve("audit");
13
+ export function registerResources(server, serverIdManager, signedPolicy) {
14
+ // ── Current Policy ──
15
+ server.resource("current-policy", "vaultys://policy/current", { mimeType: "application/json", description: "The active signed policy bundle" }, async () => {
16
+ const { signature, ...policyWithoutSig } = signedPolicy;
17
+ return {
18
+ contents: [{
19
+ uri: "vaultys://policy/current",
20
+ mimeType: "application/json",
21
+ text: JSON.stringify({
22
+ ...policyWithoutSig,
23
+ signature_present: !!signature,
24
+ signature_length: signature?.length ?? 0,
25
+ }, null, 2),
26
+ }],
27
+ };
28
+ });
29
+ // ── Server Identity ──
30
+ server.resource("server-identity", "vaultys://identity/server", { mimeType: "application/json", description: "The server's VaultysID (DID + key info)" }, async () => {
31
+ const vid = serverIdManager.vaultysId;
32
+ return {
33
+ contents: [{
34
+ uri: "vaultys://identity/server",
35
+ mimeType: "application/json",
36
+ text: JSON.stringify({
37
+ did: vid.did,
38
+ fingerprint: vid.fingerprint,
39
+ type: vid.type === 0 ? "person" : "machine",
40
+ algorithm: vid.keyManager.constructor.name,
41
+ }, null, 2),
42
+ }],
43
+ };
44
+ });
45
+ // ── Receipt List ──
46
+ server.resource("receipts-list", "vaultys://receipts/list", { mimeType: "application/json", description: "Summary of all signed execution receipts" }, async () => {
47
+ const receipts = listReceiptFiles();
48
+ return {
49
+ contents: [{
50
+ uri: "vaultys://receipts/list",
51
+ mimeType: "application/json",
52
+ text: JSON.stringify({
53
+ total: receipts.length,
54
+ receipts: receipts.map((r) => ({
55
+ job_id: r.job_id,
56
+ timestamp: r.timestamp,
57
+ tool: r.tool,
58
+ decision: r.decision,
59
+ })),
60
+ }, null, 2),
61
+ }],
62
+ };
63
+ });
64
+ }
65
+ function listReceiptFiles() {
66
+ if (!fs.existsSync(AUDIT_DIR))
67
+ return [];
68
+ return fs.readdirSync(AUDIT_DIR)
69
+ .filter((f) => f.endsWith(".json"))
70
+ .map((f) => {
71
+ try {
72
+ return JSON.parse(fs.readFileSync(path.join(AUDIT_DIR, f), "utf-8"));
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ })
78
+ .filter((r) => r !== null)
79
+ .sort((a, b) => b.timestamp.localeCompare(a.timestamp));
80
+ }
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * VaultysID Policy-Enforced MCP Server
4
+ *
5
+ * Stdio transport — plug directly into Claude Desktop, Cursor, or any MCP client.
6
+ *
7
+ * Zero-config: just run `npx @vaultys/mcp-agent` — a default policy is
8
+ * auto-generated on first start. Customize with `vaultys-mcp-init`.
9
+ *
10
+ * Every tool call goes through:
11
+ * tool invocation → capability mapping → policy evaluation → execute/deny → signed receipt
12
+ *
13
+ * Environment variables:
14
+ * WORKSPACE_ROOT — working directory for file/shell operations (default: cwd)
15
+ * POLICY_FILE — path to a custom signed policy file
16
+ * AUTHORITY_FILE — path to the authority identity export
17
+ * VAULTYS_CONFIG_DIR — config directory (default: ~/.vaultys-mcp)
18
+ */
19
+ export {};
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * VaultysID Policy-Enforced MCP Server
4
+ *
5
+ * Stdio transport — plug directly into Claude Desktop, Cursor, or any MCP client.
6
+ *
7
+ * Zero-config: just run `npx @vaultys/mcp-agent` — a default policy is
8
+ * auto-generated on first start. Customize with `vaultys-mcp-init`.
9
+ *
10
+ * Every tool call goes through:
11
+ * tool invocation → capability mapping → policy evaluation → execute/deny → signed receipt
12
+ *
13
+ * Environment variables:
14
+ * WORKSPACE_ROOT — working directory for file/shell operations (default: cwd)
15
+ * POLICY_FILE — path to a custom signed policy file
16
+ * AUTHORITY_FILE — path to the authority identity export
17
+ * VAULTYS_CONFIG_DIR — config directory (default: ~/.vaultys-mcp)
18
+ */
19
+ import * as fs from "node:fs";
20
+ import * as path from "node:path";
21
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
23
+ import { autoInit, getConfigDir } from "./autoInit.js";
24
+ import { createPolicyMiddleware } from "./policyMiddleware.js";
25
+ import { registerFilesystemTools } from "./tools/filesystem.js";
26
+ import { registerShellTool } from "./tools/shell.js";
27
+ import { registerNetworkTool } from "./tools/network.js";
28
+ import { registerResources } from "./resources/index.js";
29
+ // ── Configuration ──
30
+ const WORKSPACE_ROOT = path.resolve(process.env.WORKSPACE_ROOT ?? process.cwd());
31
+ // ── Main ──
32
+ async function main() {
33
+ console.error(`[VaultysID] Config dir: ${getConfigDir()}`);
34
+ // 1. Auto-initialize (identity + policy — generates defaults on first run)
35
+ const { serverIdm, signedPolicy, authorityDid } = await autoInit(WORKSPACE_ROOT);
36
+ // 2. Check time bounds
37
+ const now = Date.now();
38
+ if (signedPolicy.not_before && now < signedPolicy.not_before) {
39
+ console.error(`[VaultysID] ERROR: Policy is not yet valid`);
40
+ process.exit(1);
41
+ }
42
+ if (signedPolicy.not_after) {
43
+ const remaining = Math.round((signedPolicy.not_after - now) / 60000);
44
+ console.error(`[VaultysID] Policy expires in ${remaining} minutes`);
45
+ }
46
+ // 3. Ensure workspace exists
47
+ if (!fs.existsSync(WORKSPACE_ROOT)) {
48
+ fs.mkdirSync(WORKSPACE_ROOT, { recursive: true });
49
+ }
50
+ console.error(`[VaultysID] Workspace: ${WORKSPACE_ROOT}`);
51
+ // 4. Create policy middleware
52
+ const middleware = createPolicyMiddleware({
53
+ serverIdManager: serverIdm,
54
+ signedPolicy,
55
+ workspaceRoot: WORKSPACE_ROOT,
56
+ });
57
+ // 5. Create MCP Server
58
+ const mcpServer = new McpServer({
59
+ name: "vaultys-mcp-agent",
60
+ version: "0.1.0",
61
+ }, {
62
+ capabilities: {
63
+ resources: {},
64
+ tools: {},
65
+ },
66
+ });
67
+ // 6. Register tools (all wrapped by policy middleware)
68
+ registerFilesystemTools(mcpServer, middleware, WORKSPACE_ROOT);
69
+ registerShellTool(mcpServer, middleware, WORKSPACE_ROOT);
70
+ registerNetworkTool(mcpServer, middleware);
71
+ // 7. Register resources (audit trail, policy, identity)
72
+ registerResources(mcpServer, serverIdm, signedPolicy);
73
+ // 8. Connect via stdio transport
74
+ const transport = new StdioServerTransport();
75
+ await mcpServer.connect(transport);
76
+ console.error(`[VaultysID] MCP server running on stdio`);
77
+ console.error(`[VaultysID] Server DID: ${serverIdm.vaultysId.did}`);
78
+ if (authorityDid) {
79
+ console.error(`[VaultysID] Authority DID: ${authorityDid}`);
80
+ }
81
+ console.error(`[VaultysID] Tools: read_file, write_file, list_directory, run_command, fetch_url`);
82
+ console.error(`[VaultysID] Resources: vaultys://policy/current, vaultys://identity/server, vaultys://receipts/list`);
83
+ }
84
+ main().catch((err) => {
85
+ console.error("Fatal error:", err);
86
+ process.exit(1);
87
+ });
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Filesystem MCP tools — read_file, write_file, list_directory
3
+ *
4
+ * All operations are sandboxed to the workspace root via policy evaluation.
5
+ */
6
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import type { createPolicyMiddleware } from "../policyMiddleware.js";
8
+ type Middleware = ReturnType<typeof createPolicyMiddleware>;
9
+ export declare function registerFilesystemTools(server: McpServer, middleware: Middleware, workspaceRoot: string): void;
10
+ export {};
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Filesystem MCP tools — read_file, write_file, list_directory
3
+ *
4
+ * All operations are sandboxed to the workspace root via policy evaluation.
5
+ */
6
+ import * as fs from "node:fs";
7
+ import * as path from "node:path";
8
+ import { z } from "zod";
9
+ export function registerFilesystemTools(server, middleware, workspaceRoot) {
10
+ // ── read_file ──
11
+ server.tool("read_file", "Read a file from the workspace. Returns the file contents.", { path: z.string().describe("Relative or absolute path to the file") }, async (args) => {
12
+ const result = await middleware.enforce("read_file", args, async () => {
13
+ const filePath = path.resolve(workspaceRoot, args.path);
14
+ return fs.readFileSync(filePath, "utf-8");
15
+ });
16
+ return { content: result.content, isError: result.decision === "deny" };
17
+ });
18
+ // ── write_file ──
19
+ server.tool("write_file", "Write content to a file in the workspace.", {
20
+ path: z.string().describe("Relative or absolute path to the file"),
21
+ content: z.string().describe("Content to write to the file"),
22
+ }, async (args) => {
23
+ const result = await middleware.enforce("write_file", args, async () => {
24
+ const filePath = path.resolve(workspaceRoot, args.path);
25
+ const dir = path.dirname(filePath);
26
+ if (!fs.existsSync(dir))
27
+ fs.mkdirSync(dir, { recursive: true });
28
+ fs.writeFileSync(filePath, args.content, "utf-8");
29
+ return `File written: ${args.path} (${args.content.length} bytes)`;
30
+ });
31
+ return { content: result.content, isError: result.decision === "deny" };
32
+ });
33
+ // ── list_directory ──
34
+ server.tool("list_directory", "List the contents of a directory in the workspace.", { path: z.string().describe("Relative or absolute path to the directory").default(".") }, async (args) => {
35
+ const result = await middleware.enforce("list_directory", args, async () => {
36
+ const dirPath = path.resolve(workspaceRoot, args.path);
37
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
38
+ return entries
39
+ .map((e) => `${e.isDirectory() ? "📁" : "📄"} ${e.name}`)
40
+ .join("\n");
41
+ });
42
+ return { content: result.content, isError: result.decision === "deny" };
43
+ });
44
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Network MCP tool — fetch_url
3
+ *
4
+ * Fetches content from a URL with policy enforcement on the hostname.
5
+ */
6
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import type { createPolicyMiddleware } from "../policyMiddleware.js";
8
+ type Middleware = ReturnType<typeof createPolicyMiddleware>;
9
+ export declare function registerNetworkTool(server: McpServer, middleware: Middleware): void;
10
+ export {};
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Network MCP tool — fetch_url
3
+ *
4
+ * Fetches content from a URL with policy enforcement on the hostname.
5
+ */
6
+ import { z } from "zod";
7
+ export function registerNetworkTool(server, middleware) {
8
+ server.tool("fetch_url", "Fetch content from a URL. Subject to egress policy.", {
9
+ url: z.string().url().describe("The URL to fetch"),
10
+ method: z.enum(["GET", "POST", "PUT", "DELETE"]).default("GET").describe("HTTP method"),
11
+ body: z.string().optional().describe("Request body (for POST/PUT)"),
12
+ }, async (args) => {
13
+ const result = await middleware.enforce("fetch_url", args, async () => {
14
+ const init = { method: args.method };
15
+ if (args.body && (args.method === "POST" || args.method === "PUT")) {
16
+ init.body = args.body;
17
+ init.headers = { "Content-Type": "application/json" };
18
+ }
19
+ const response = await fetch(args.url, init);
20
+ const text = await response.text();
21
+ return `HTTP ${response.status} ${response.statusText}\n\n${text.slice(0, 10000)}`;
22
+ });
23
+ return { content: result.content, isError: result.decision === "deny" };
24
+ });
25
+ }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Shell MCP tool — run_command
3
+ *
4
+ * Executes shell commands with policy enforcement.
5
+ * Shell metacharacters are blocked by the `no_shell_features` constraint.
6
+ */
7
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
+ import type { createPolicyMiddleware } from "../policyMiddleware.js";
9
+ type Middleware = ReturnType<typeof createPolicyMiddleware>;
10
+ export declare function registerShellTool(server: McpServer, middleware: Middleware, workspaceRoot: string): void;
11
+ export {};
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Shell MCP tool — run_command
3
+ *
4
+ * Executes shell commands with policy enforcement.
5
+ * Shell metacharacters are blocked by the `no_shell_features` constraint.
6
+ */
7
+ import { execSync } from "node:child_process";
8
+ import { z } from "zod";
9
+ export function registerShellTool(server, middleware, workspaceRoot) {
10
+ server.tool("run_command", "Run a shell command in the workspace directory. Subject to policy constraints.", {
11
+ command: z.string().describe("The command to execute (e.g. 'ls -la')"),
12
+ timeout: z.number().describe("Timeout in milliseconds").default(30000),
13
+ }, async (args) => {
14
+ const result = await middleware.enforce("run_command", args, async () => {
15
+ const output = execSync(args.command, {
16
+ cwd: workspaceRoot,
17
+ timeout: args.timeout,
18
+ encoding: "utf-8",
19
+ maxBuffer: 1024 * 1024, // 1MB
20
+ stdio: ["pipe", "pipe", "pipe"],
21
+ });
22
+ return output;
23
+ });
24
+ return { content: result.content, isError: result.decision === "deny" };
25
+ });
26
+ }
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SRP Policy Grant CLI
4
+ *
5
+ * Uses the VaultysID Secure Remote Protocol (SRP) to establish a policy
6
+ * agreement between a producer (authority) and an executor (MCP server).
7
+ * This is the canonical VaultysID pattern — both sides exchange certificates,
8
+ * and the policy is cryptographically bound to the SRP metadata.
9
+ *
10
+ * This replaces the manual file-based grant-policy.ts when both parties
11
+ * can communicate over a channel (e.g. in-process via MemoryChannel,
12
+ * or over a network via WebSocket/TCP).
13
+ *
14
+ * Usage:
15
+ * pnpm srp-grant # runs a local in-process SRP handshake
16
+ *
17
+ * What happens:
18
+ * 1. Producer and executor identities are loaded or created
19
+ * 2. A MemoryChannel (bidirectional) connects them
20
+ * 3. grantPolicy() and acceptPolicy() run concurrently
21
+ * 4. Both sides store the agreed policy + SRP certificate
22
+ * 5. The executor can now evaluate intents against the stored policy
23
+ */
24
+ export {};