@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,117 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Grant Policy CLI
4
+ *
5
+ * Creates an authority identity and signs a policy bundle.
6
+ * Outputs:
7
+ * - policy.signed.json — the signed policy (loaded by the MCP server)
8
+ * - authority.identity.json — the authority's public key (for verification)
9
+ * - authority.secret.json — the authority's full identity (keep safe!)
10
+ *
11
+ * Usage:
12
+ * pnpm grant-policy # uses policy.example.json, 24h validity
13
+ * pnpm grant-policy -- --policy custom.json # custom policy file
14
+ * pnpm grant-policy -- --hours 2 # 2-hour validity window
15
+ */
16
+ import * as fs from "node:fs";
17
+ import * as path from "node:path";
18
+ import { ExecutionManager, MemoryStorage, IdManager } from "@vaultys/id";
19
+ // ── Parse args ──
20
+ const args = process.argv.slice(2);
21
+ function getArg(name, defaultValue) {
22
+ const idx = args.indexOf(`--${name}`);
23
+ if (idx !== -1 && args[idx + 1])
24
+ return args[idx + 1];
25
+ return defaultValue;
26
+ }
27
+ const policyFile = getArg("policy", "policy.example.json");
28
+ const hours = parseInt(getArg("hours", "24"), 10);
29
+ const workspaceRoot = path.resolve(getArg("workspace", "workspace"));
30
+ const AUTHORITY_IDENTITY_FILE = "authority.secret.json";
31
+ const AUTHORITY_PUBLIC_FILE = "authority.identity.json";
32
+ const SIGNED_POLICY_FILE = "policy.signed.json";
33
+ async function main() {
34
+ // 1. Load or create authority identity
35
+ let authorityIdm;
36
+ if (fs.existsSync(AUTHORITY_IDENTITY_FILE)) {
37
+ const data = fs.readFileSync(AUTHORITY_IDENTITY_FILE, "utf-8");
38
+ const store = MemoryStorage().fromString(data);
39
+ const loaded = await IdManager.fromStore(store);
40
+ if (!loaded)
41
+ throw new Error("Failed to load authority identity");
42
+ authorityIdm = loaded;
43
+ authorityIdm.setProtocolVersion(1);
44
+ console.log(`✓ Loaded authority identity: ${authorityIdm.vaultysId.did}`);
45
+ }
46
+ else {
47
+ const store = MemoryStorage();
48
+ authorityIdm = await IdManager.fromStore(store);
49
+ authorityIdm.setProtocolVersion(1);
50
+ fs.writeFileSync(AUTHORITY_IDENTITY_FILE, store.toString());
51
+ console.log(`✓ Generated authority identity: ${authorityIdm.vaultysId.did}`);
52
+ console.log(` Saved to: ${AUTHORITY_IDENTITY_FILE}`);
53
+ }
54
+ // 2. Export authority public identity for the server
55
+ // VaultysId.id contains the full public identity (can be reconstructed with VaultysId.fromId)
56
+ const vid = authorityIdm.vaultysId;
57
+ fs.writeFileSync(AUTHORITY_PUBLIC_FILE, JSON.stringify({
58
+ did: vid.did,
59
+ id: Buffer.from(vid.id).toString("base64"),
60
+ fingerprint: vid.fingerprint,
61
+ }, null, 2));
62
+ console.log(`✓ Authority public identity saved to: ${AUTHORITY_PUBLIC_FILE}`);
63
+ // 3. Load policy template
64
+ if (!fs.existsSync(policyFile)) {
65
+ console.error(`✗ Policy file not found: ${policyFile}`);
66
+ process.exit(1);
67
+ }
68
+ const policyTemplate = JSON.parse(fs.readFileSync(policyFile, "utf-8"));
69
+ console.log(`✓ Loaded policy template: ${policyFile}`);
70
+ // 3a. Substitute {{WORKSPACE}} placeholder with the actual workspace path
71
+ if (policyTemplate.scopes) {
72
+ for (const [key, patterns] of Object.entries(policyTemplate.scopes)) {
73
+ if (Array.isArray(patterns)) {
74
+ policyTemplate.scopes[key] = patterns.map((p) => p.replace(/\{\{WORKSPACE\}\}/g, workspaceRoot));
75
+ }
76
+ }
77
+ }
78
+ console.log(` Workspace root: ${workspaceRoot}`);
79
+ // 4. Add time bounds
80
+ const now = Date.now();
81
+ const policy = {
82
+ ...policyTemplate,
83
+ not_before: now,
84
+ not_after: now + hours * 3600_000,
85
+ };
86
+ console.log(` Valid from: ${new Date(policy.not_before).toISOString()}`);
87
+ console.log(` Expires: ${new Date(policy.not_after).toISOString()}`);
88
+ console.log(` Duration: ${hours} hours`);
89
+ // 5. Sign the policy
90
+ const em = new ExecutionManager(authorityIdm);
91
+ const signed = await em.signPolicy(policy);
92
+ // 6. Serialize (convert Buffer signature to base64 string for JSON storage)
93
+ const serializable = {
94
+ ...signed,
95
+ signature: signed.signature ? Buffer.from(signed.signature).toString("base64") : undefined,
96
+ };
97
+ fs.writeFileSync(SIGNED_POLICY_FILE, JSON.stringify(serializable, null, 2));
98
+ console.log(`\n✓ Signed policy saved to: ${SIGNED_POLICY_FILE}`);
99
+ // Summary
100
+ console.log("\n┌──────────────────────────────────────────────┐");
101
+ console.log("│ Policy signed successfully! │");
102
+ console.log("├──────────────────────────────────────────────┤");
103
+ console.log(`│ Authority DID: ${authorityIdm.vaultysId.did.slice(0, 30)}...│`);
104
+ console.log("│ │");
105
+ console.log("│ To start the MCP server: │");
106
+ console.log("│ pnpm start │");
107
+ console.log("│ │");
108
+ console.log("│ Files generated: │");
109
+ console.log(`│ ${SIGNED_POLICY_FILE.padEnd(41)}│`);
110
+ console.log(`│ ${AUTHORITY_PUBLIC_FILE.padEnd(41)}│`);
111
+ console.log(`│ ${AUTHORITY_IDENTITY_FILE.padEnd(41)}│`);
112
+ console.log("└──────────────────────────────────────────────┘");
113
+ }
114
+ main().catch((err) => {
115
+ console.error("Error:", err);
116
+ process.exit(1);
117
+ });
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Audit Verify CLI
4
+ *
5
+ * Walks through all signed receipts in ./audit/ and verifies each one.
6
+ * Also detects tampering by re-verifying broker signatures.
7
+ *
8
+ * Usage:
9
+ * pnpm audit # verify all receipts
10
+ * pnpm audit -- --tamper <id> # tamper with a receipt then verify (demo)
11
+ */
12
+ export {};
@@ -0,0 +1,133 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Audit Verify CLI
4
+ *
5
+ * Walks through all signed receipts in ./audit/ and verifies each one.
6
+ * Also detects tampering by re-verifying broker signatures.
7
+ *
8
+ * Usage:
9
+ * pnpm audit # verify all receipts
10
+ * pnpm audit -- --tamper <id> # tamper with a receipt then verify (demo)
11
+ */
12
+ import * as fs from "node:fs";
13
+ import * as path from "node:path";
14
+ import { ExecutionManager, IdManager, MemoryStorage } from "@vaultys/id";
15
+ const AUDIT_DIR = path.resolve("audit");
16
+ const IDENTITY_FILE = "server.identity.json";
17
+ function loadServerPublicKey() {
18
+ if (!fs.existsSync(IDENTITY_FILE)) {
19
+ console.error("⚠ No server identity found. Cannot verify receipts.");
20
+ return null;
21
+ }
22
+ // We need the server's serialized identity to extract the public key.
23
+ // For verification we only need the public key, but the serialized format has it.
24
+ // We'll load it as a full IdManager to get the VaultysId.
25
+ return null; // Will use async version
26
+ }
27
+ async function main() {
28
+ const args = process.argv.slice(2);
29
+ const tamperIdx = args.indexOf("--tamper");
30
+ const tamperId = tamperIdx !== -1 ? args[tamperIdx + 1] : null;
31
+ console.log("╔══════════════════════════════════════════════════╗");
32
+ console.log("║ VaultysID Audit Trail Verifier ║");
33
+ console.log("╚══════════════════════════════════════════════════╝");
34
+ console.log();
35
+ // Load server identity for signature verification
36
+ let serverVaultysId = null;
37
+ if (fs.existsSync(IDENTITY_FILE)) {
38
+ // Read the serialized identity to extract the VaultysId
39
+ const data = fs.readFileSync(IDENTITY_FILE, "utf-8");
40
+ const store = MemoryStorage().fromString(data);
41
+ const idm = await IdManager.fromStore(store);
42
+ if (idm) {
43
+ serverVaultysId = idm.vaultysId;
44
+ console.log(`Server DID: ${serverVaultysId.did}`);
45
+ }
46
+ }
47
+ if (!serverVaultysId) {
48
+ console.error("⚠ Cannot load server identity — signature verification disabled");
49
+ }
50
+ console.log();
51
+ // Load all receipts
52
+ if (!fs.existsSync(AUDIT_DIR)) {
53
+ console.log("No audit records found. Run some tool calls first!");
54
+ return;
55
+ }
56
+ const files = fs.readdirSync(AUDIT_DIR).filter((f) => f.endsWith(".json")).sort();
57
+ if (files.length === 0) {
58
+ console.log("No audit records found. Run some tool calls first!");
59
+ return;
60
+ }
61
+ console.log(`Found ${files.length} receipt(s)\n`);
62
+ // Optionally tamper with a receipt for demo purposes
63
+ if (tamperId) {
64
+ const tamperFile = path.join(AUDIT_DIR, `${tamperId}.json`);
65
+ if (fs.existsSync(tamperFile)) {
66
+ const record = JSON.parse(fs.readFileSync(tamperFile, "utf-8"));
67
+ record.receipt.exec.exit_code = 999; // Tamper!
68
+ fs.writeFileSync(tamperFile, JSON.stringify(record, null, 2));
69
+ console.log(`🔧 TAMPERED with receipt ${tamperId} (changed exit_code to 999)\n`);
70
+ }
71
+ else {
72
+ console.error(`⚠ Receipt ${tamperId} not found for tampering`);
73
+ }
74
+ }
75
+ let passed = 0;
76
+ let failed = 0;
77
+ let skipped = 0;
78
+ for (const file of files) {
79
+ const filePath = path.join(AUDIT_DIR, file);
80
+ let record;
81
+ try {
82
+ record = JSON.parse(fs.readFileSync(filePath, "utf-8"));
83
+ }
84
+ catch {
85
+ console.log(` ⚠ ${file}: invalid JSON`);
86
+ skipped++;
87
+ continue;
88
+ }
89
+ const receipt = record.receipt;
90
+ const jobId = record.job_id;
91
+ const decision = record.decision;
92
+ const tool = record.tool;
93
+ // Restore Buffer types for signature
94
+ if (receipt.broker_signature && typeof receipt.broker_signature === "string") {
95
+ receipt.broker_signature = Buffer.from(receipt.broker_signature, "base64");
96
+ }
97
+ let verified = false;
98
+ let sigStatus = "⏭ no key";
99
+ if (serverVaultysId) {
100
+ verified = ExecutionManager.verifyReceipt(receipt, serverVaultysId);
101
+ sigStatus = verified ? "✅ VALID" : "❌ INVALID";
102
+ if (verified)
103
+ passed++;
104
+ else
105
+ failed++;
106
+ }
107
+ else {
108
+ skipped++;
109
+ }
110
+ const decisionIcon = decision === "allow" ? "🟢" : "🔴";
111
+ console.log(` ${sigStatus} ${decisionIcon} ${tool.padEnd(16)} ${jobId} ${record.timestamp}`);
112
+ if (!verified && serverVaultysId) {
113
+ console.log(` ⚠ Signature verification FAILED — possible tampering!`);
114
+ }
115
+ }
116
+ console.log();
117
+ console.log("─".repeat(52));
118
+ console.log(` Results: ${passed} verified, ${failed} FAILED, ${skipped} skipped`);
119
+ if (failed > 0) {
120
+ console.log();
121
+ console.log(" ⚠ TAMPERING DETECTED in one or more receipts!");
122
+ console.log(" ⚠ The audit trail has been compromised.");
123
+ }
124
+ else if (passed > 0) {
125
+ console.log();
126
+ console.log(" ✅ All receipts verified — audit trail is intact.");
127
+ }
128
+ console.log();
129
+ }
130
+ main().catch((err) => {
131
+ console.error("Error:", err);
132
+ process.exit(1);
133
+ });
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Auto-initialization for zero-config startup.
3
+ *
4
+ * When the server starts without a signed policy, this module generates
5
+ * a self-signed default policy so `npx @vaultys/mcp-agent` works immediately.
6
+ *
7
+ * Config directory defaults to ~/.vaultys-mcp/ (overridable via VAULTYS_CONFIG_DIR).
8
+ */
9
+ import { IdManager } from "@vaultys/id";
10
+ import type { PolicyBundle } from "@vaultys/id";
11
+ export declare function getConfigDir(): string;
12
+ export declare function ensureConfigDir(): string;
13
+ export declare function configPath(filename: string): string;
14
+ /**
15
+ * Build a policy with resolved workspace path and time bounds.
16
+ */
17
+ export declare function buildPolicy(workspaceRoot: string, hours?: number): Omit<PolicyBundle, "signature">;
18
+ /**
19
+ * Load or create a persisted IdManager from a file.
20
+ */
21
+ export declare function loadOrCreateIdentity(filePath: string): Promise<{
22
+ idm: IdManager;
23
+ created: boolean;
24
+ }>;
25
+ /**
26
+ * Sign and persist a policy. Returns the signed policy and authority IdManager.
27
+ */
28
+ export declare function signAndPersistPolicy(workspaceRoot: string, hours?: number): Promise<{
29
+ signedPolicy: PolicyBundle;
30
+ authorityIdm: IdManager;
31
+ }>;
32
+ /**
33
+ * Auto-initialize: load existing policy or generate a default one.
34
+ * Returns all the artifacts needed to start the server.
35
+ */
36
+ export declare function autoInit(workspaceRoot: string): Promise<{
37
+ serverIdm: IdManager;
38
+ signedPolicy: PolicyBundle;
39
+ authorityDid: string;
40
+ }>;
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Auto-initialization for zero-config startup.
3
+ *
4
+ * When the server starts without a signed policy, this module generates
5
+ * a self-signed default policy so `npx @vaultys/mcp-agent` works immediately.
6
+ *
7
+ * Config directory defaults to ~/.vaultys-mcp/ (overridable via VAULTYS_CONFIG_DIR).
8
+ */
9
+ import * as fs from "node:fs";
10
+ import * as path from "node:path";
11
+ import * as os from "node:os";
12
+ import { ExecutionManager, MemoryStorage, IdManager } from "@vaultys/id";
13
+ // ── Config directory ──
14
+ export function getConfigDir() {
15
+ return process.env.VAULTYS_CONFIG_DIR ?? path.join(os.homedir(), ".vaultys-mcp");
16
+ }
17
+ export function ensureConfigDir() {
18
+ const dir = getConfigDir();
19
+ if (!fs.existsSync(dir)) {
20
+ fs.mkdirSync(dir, { recursive: true });
21
+ }
22
+ return dir;
23
+ }
24
+ // ── Paths ──
25
+ export function configPath(filename) {
26
+ return path.join(getConfigDir(), filename);
27
+ }
28
+ // ── Default policy ──
29
+ const DEFAULT_POLICY_TEMPLATE = {
30
+ version: "1.0",
31
+ scopes: {
32
+ "fs.read": ["{{WORKSPACE}}/**"],
33
+ "fs.write": ["{{WORKSPACE}}/**"],
34
+ "fs.list": ["{{WORKSPACE}}/**"],
35
+ "proc.exec": ["ls", "cat", "echo", "wc", "head", "tail", "grep", "find", "pwd", "date", "whoami"],
36
+ "net.egress.http": ["*"],
37
+ },
38
+ denied: ["secrets.*", "pkg.system", "proc.privilege"],
39
+ constraints: {
40
+ max_runtime: 300,
41
+ no_shell_features: true,
42
+ },
43
+ };
44
+ /**
45
+ * Build a policy with resolved workspace path and time bounds.
46
+ */
47
+ export function buildPolicy(workspaceRoot, hours = 24 * 365) {
48
+ const now = Date.now();
49
+ // Deep-clone and substitute workspace
50
+ const scopes = {};
51
+ for (const [key, patterns] of Object.entries(DEFAULT_POLICY_TEMPLATE.scopes)) {
52
+ scopes[key] = patterns.map((p) => p.replace(/\{\{WORKSPACE\}\}/g, workspaceRoot));
53
+ }
54
+ return {
55
+ ...DEFAULT_POLICY_TEMPLATE,
56
+ scopes,
57
+ not_before: now,
58
+ not_after: now + hours * 3600_000,
59
+ };
60
+ }
61
+ /**
62
+ * Load or create a persisted IdManager from a file.
63
+ */
64
+ export async function loadOrCreateIdentity(filePath) {
65
+ if (fs.existsSync(filePath)) {
66
+ const data = fs.readFileSync(filePath, "utf-8");
67
+ const store = MemoryStorage().fromString(data);
68
+ const idm = await IdManager.fromStore(store);
69
+ idm.setProtocolVersion(1);
70
+ return { idm, created: false };
71
+ }
72
+ const store = MemoryStorage();
73
+ const idm = await IdManager.fromStore(store);
74
+ idm.setProtocolVersion(1);
75
+ fs.writeFileSync(filePath, store.toString());
76
+ return { idm, created: true };
77
+ }
78
+ /**
79
+ * Sign and persist a policy. Returns the signed policy and authority IdManager.
80
+ */
81
+ export async function signAndPersistPolicy(workspaceRoot, hours) {
82
+ const dir = ensureConfigDir();
83
+ // Load or create authority
84
+ const { idm: authorityIdm, created } = await loadOrCreateIdentity(configPath("authority.secret.json"));
85
+ if (created) {
86
+ console.error(`[VaultysID] Generated authority identity: ${authorityIdm.vaultysId.did}`);
87
+ }
88
+ // Export public key
89
+ const vid = authorityIdm.vaultysId;
90
+ fs.writeFileSync(configPath("authority.identity.json"), JSON.stringify({
91
+ did: vid.did,
92
+ id: Buffer.from(vid.id).toString("base64"),
93
+ fingerprint: vid.fingerprint,
94
+ }, null, 2));
95
+ // Build and sign
96
+ const policy = buildPolicy(workspaceRoot, hours);
97
+ const em = new ExecutionManager(authorityIdm);
98
+ const signedPolicy = await em.signPolicy(policy);
99
+ // Persist (serialize Buffer signature to base64 for JSON)
100
+ const serializable = {
101
+ ...signedPolicy,
102
+ signature: signedPolicy.signature
103
+ ? Buffer.from(signedPolicy.signature).toString("base64")
104
+ : undefined,
105
+ };
106
+ fs.writeFileSync(configPath("policy.signed.json"), JSON.stringify(serializable, null, 2));
107
+ return { signedPolicy, authorityIdm };
108
+ }
109
+ /**
110
+ * Auto-initialize: load existing policy or generate a default one.
111
+ * Returns all the artifacts needed to start the server.
112
+ */
113
+ export async function autoInit(workspaceRoot) {
114
+ const dir = ensureConfigDir();
115
+ // 1. Server identity
116
+ const { idm: serverIdm, created: serverCreated } = await loadOrCreateIdentity(configPath("server.identity.json"));
117
+ if (serverCreated) {
118
+ console.error(`[VaultysID] Generated server identity: ${serverIdm.vaultysId.did}`);
119
+ }
120
+ else {
121
+ console.error(`[VaultysID] Loaded server identity: ${serverIdm.vaultysId.did}`);
122
+ }
123
+ // 2. Policy — try to load existing, otherwise auto-generate
124
+ const policyPath = process.env.POLICY_FILE ?? configPath("policy.signed.json");
125
+ const authorityPath = process.env.AUTHORITY_FILE ?? configPath("authority.identity.json");
126
+ let signedPolicy;
127
+ let authorityDid = "";
128
+ if (fs.existsSync(policyPath)) {
129
+ const raw = JSON.parse(fs.readFileSync(policyPath, "utf-8"));
130
+ if (raw.signature && typeof raw.signature === "string") {
131
+ raw.signature = Buffer.from(raw.signature, "base64");
132
+ }
133
+ signedPolicy = raw;
134
+ console.error(`[VaultysID] Loaded policy (version ${signedPolicy.version})`);
135
+ // Verify if authority key is available
136
+ if (fs.existsSync(authorityPath)) {
137
+ const authRaw = JSON.parse(fs.readFileSync(authorityPath, "utf-8"));
138
+ const { VaultysId } = await import("@vaultys/id");
139
+ const authVid = VaultysId.fromId(Buffer.from(authRaw.id, "base64"));
140
+ const valid = ExecutionManager.verifyPolicy(signedPolicy, authVid);
141
+ if (!valid) {
142
+ console.error(`[VaultysID] WARNING: Policy signature verification FAILED`);
143
+ }
144
+ else {
145
+ console.error(`[VaultysID] Policy signature verified ✓`);
146
+ }
147
+ authorityDid = authVid.did;
148
+ }
149
+ // Check expiry
150
+ const now = Date.now();
151
+ if (signedPolicy.not_after && now > signedPolicy.not_after) {
152
+ console.error(`[VaultysID] Policy expired — regenerating...`);
153
+ const result = await signAndPersistPolicy(workspaceRoot);
154
+ signedPolicy = result.signedPolicy;
155
+ authorityDid = result.authorityIdm.vaultysId.did;
156
+ console.error(`[VaultysID] New policy generated (expires in 1 year)`);
157
+ }
158
+ }
159
+ else {
160
+ // Auto-generate default policy
161
+ console.error(`[VaultysID] No policy found — generating default policy...`);
162
+ const result = await signAndPersistPolicy(workspaceRoot);
163
+ signedPolicy = result.signedPolicy;
164
+ authorityDid = result.authorityIdm.vaultysId.did;
165
+ console.error(`[VaultysID] Default policy generated at ${policyPath}`);
166
+ console.error(`[VaultysID] Run 'vaultys-mcp-init' to customize`);
167
+ }
168
+ return { serverIdm, signedPolicy, authorityDid };
169
+ }
@@ -0,0 +1,55 @@
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 { ExecutionManager } from "@vaultys/id";
14
+ import type { PolicyBundle, SignedReceipt } from "@vaultys/id";
15
+ import type { IdManager } from "@vaultys/id";
16
+ export interface CapabilityMapping {
17
+ /** Taxonomy prefix, e.g. "fs.read" */
18
+ capability: string;
19
+ /** Extract the scope string from the tool arguments. workspaceRoot is provided for path resolution. */
20
+ extractScope: (args: Record<string, unknown>, workspaceRoot?: string) => string;
21
+ }
22
+ /**
23
+ * Default mappings from MCP tool names to taxonomy capabilities.
24
+ *
25
+ * Scopes must match the format used in the policy's scope patterns.
26
+ * For filesystem tools: full resolved paths (e.g. "/workspace/src/file.ts")
27
+ * For process tools: the binary name (e.g. "ls", "npm")
28
+ * For network tools: the hostname (e.g. "api.example.com")
29
+ *
30
+ * The `workspaceRoot` parameter is passed to `extractScope` so that filesystem
31
+ * tools can resolve relative paths to absolute paths matching the policy globs.
32
+ */
33
+ export declare const DEFAULT_TOOL_MAPPINGS: Record<string, CapabilityMapping>;
34
+ export interface PolicyMiddlewareOptions {
35
+ serverIdManager: IdManager;
36
+ signedPolicy: PolicyBundle;
37
+ toolMappings?: Record<string, CapabilityMapping>;
38
+ workspaceRoot?: string;
39
+ }
40
+ export interface ToolCallResult {
41
+ decision: "allow" | "deny";
42
+ content: Array<{
43
+ type: "text";
44
+ text: string;
45
+ }>;
46
+ receipt?: SignedReceipt;
47
+ jobId?: string;
48
+ }
49
+ /**
50
+ * Create policy-enforced middleware that wraps MCP tool handlers.
51
+ */
52
+ export declare function createPolicyMiddleware(options: PolicyMiddlewareOptions): {
53
+ enforce: (toolName: string, args: Record<string, unknown>, handler: () => Promise<string>) => Promise<ToolCallResult>;
54
+ em: ExecutionManager;
55
+ };