@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.
- package/LICENSE +21 -0
- package/README.md +246 -0
- package/dist/bin/audit.d.ts +12 -0
- package/dist/bin/audit.js +137 -0
- package/dist/bin/grant-policy.d.ts +10 -0
- package/dist/bin/grant-policy.js +110 -0
- package/dist/bin/init.d.ts +15 -0
- package/dist/bin/init.js +77 -0
- package/dist/bin/server.d.ts +9 -0
- package/dist/bin/server.js +9 -0
- package/dist/grant-policy.d.ts +16 -0
- package/dist/grant-policy.js +117 -0
- package/dist/src/audit.d.ts +12 -0
- package/dist/src/audit.js +133 -0
- package/dist/src/autoInit.d.ts +40 -0
- package/dist/src/autoInit.js +169 -0
- package/dist/src/policyMiddleware.d.ts +55 -0
- package/dist/src/policyMiddleware.js +194 -0
- package/dist/src/resources/index.d.ts +13 -0
- package/dist/src/resources/index.js +80 -0
- package/dist/src/server.d.ts +19 -0
- package/dist/src/server.js +87 -0
- package/dist/src/tools/filesystem.d.ts +10 -0
- package/dist/src/tools/filesystem.js +44 -0
- package/dist/src/tools/network.d.ts +10 -0
- package/dist/src/tools/network.js +25 -0
- package/dist/src/tools/shell.d.ts +11 -0
- package/dist/src/tools/shell.js +26 -0
- package/dist/srp-grant-policy.d.ts +24 -0
- package/dist/srp-grant-policy.js +177 -0
- package/package.json +55 -0
- package/policy.example.json +36 -0
|
@@ -0,0 +1,177 @@
|
|
|
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
|
+
import * as fs from "node:fs";
|
|
25
|
+
import * as path from "node:path";
|
|
26
|
+
import { IdManager, MemoryStorage, MemoryChannel, ExecutionManager, } from "@vaultys/id";
|
|
27
|
+
// ── Configuration ──
|
|
28
|
+
const args = process.argv.slice(2);
|
|
29
|
+
function getArg(name, defaultValue) {
|
|
30
|
+
const idx = args.indexOf(`--${name}`);
|
|
31
|
+
if (idx !== -1 && args[idx + 1])
|
|
32
|
+
return args[idx + 1];
|
|
33
|
+
return defaultValue;
|
|
34
|
+
}
|
|
35
|
+
const policyFile = getArg("policy", "policy.example.json");
|
|
36
|
+
const hours = parseInt(getArg("hours", "24"), 10);
|
|
37
|
+
const workspaceRoot = path.resolve(getArg("workspace", "workspace"));
|
|
38
|
+
const PRODUCER_FILE = "authority.secret.json";
|
|
39
|
+
const EXECUTOR_FILE = "server.identity.json";
|
|
40
|
+
// ── Helpers ──
|
|
41
|
+
async function loadOrCreateIdentity(filePath, label) {
|
|
42
|
+
if (fs.existsSync(filePath)) {
|
|
43
|
+
const data = fs.readFileSync(filePath, "utf-8");
|
|
44
|
+
const store = MemoryStorage().fromString(data);
|
|
45
|
+
const idm = await IdManager.fromStore(store);
|
|
46
|
+
idm.setProtocolVersion(1);
|
|
47
|
+
console.log(`✓ Loaded ${label}: ${idm.vaultysId.did}`);
|
|
48
|
+
return idm;
|
|
49
|
+
}
|
|
50
|
+
const store = MemoryStorage();
|
|
51
|
+
const idm = await IdManager.fromStore(store);
|
|
52
|
+
idm.setProtocolVersion(1);
|
|
53
|
+
fs.writeFileSync(filePath, store.toString());
|
|
54
|
+
console.log(`✓ Generated ${label}: ${idm.vaultysId.did}`);
|
|
55
|
+
console.log(` Saved to: ${filePath}`);
|
|
56
|
+
return idm;
|
|
57
|
+
}
|
|
58
|
+
// ── Main ──
|
|
59
|
+
async function main() {
|
|
60
|
+
console.log("╔══════════════════════════════════════════════════╗");
|
|
61
|
+
console.log("║ VaultysID SRP Policy Agreement ║");
|
|
62
|
+
console.log("╚══════════════════════════════════════════════════╝");
|
|
63
|
+
console.log();
|
|
64
|
+
// 1. Load or create identities
|
|
65
|
+
const producer = await loadOrCreateIdentity(PRODUCER_FILE, "producer (authority)");
|
|
66
|
+
const executor = await loadOrCreateIdentity(EXECUTOR_FILE, "executor (MCP server)");
|
|
67
|
+
// 2. Load and prepare policy
|
|
68
|
+
if (!fs.existsSync(policyFile)) {
|
|
69
|
+
console.error(`✗ Policy file not found: ${policyFile}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
const policyTemplate = JSON.parse(fs.readFileSync(policyFile, "utf-8"));
|
|
73
|
+
// Substitute {{WORKSPACE}} placeholders
|
|
74
|
+
if (policyTemplate.scopes) {
|
|
75
|
+
for (const [key, patterns] of Object.entries(policyTemplate.scopes)) {
|
|
76
|
+
if (Array.isArray(patterns)) {
|
|
77
|
+
policyTemplate.scopes[key] = patterns.map((p) => p.replace(/\{\{WORKSPACE\}\}/g, workspaceRoot));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Add time bounds
|
|
82
|
+
const now = Date.now();
|
|
83
|
+
const policy = {
|
|
84
|
+
...policyTemplate,
|
|
85
|
+
not_before: now,
|
|
86
|
+
not_after: now + hours * 3600_000,
|
|
87
|
+
};
|
|
88
|
+
console.log();
|
|
89
|
+
console.log(`✓ Policy loaded: ${policyFile}`);
|
|
90
|
+
console.log(` Workspace root: ${workspaceRoot}`);
|
|
91
|
+
console.log(` Valid from: ${new Date(policy.not_before).toISOString()}`);
|
|
92
|
+
console.log(` Expires: ${new Date(policy.not_after).toISOString()}`);
|
|
93
|
+
// 3. Create ExecutionManagers
|
|
94
|
+
const producerEm = new ExecutionManager(producer);
|
|
95
|
+
const executorEm = new ExecutionManager(executor);
|
|
96
|
+
// 4. Create bidirectional channel (in-process demo)
|
|
97
|
+
const channel = MemoryChannel.createBidirectionnal();
|
|
98
|
+
if (!channel.otherend) {
|
|
99
|
+
throw new Error("Failed to create bidirectional channel");
|
|
100
|
+
}
|
|
101
|
+
console.log();
|
|
102
|
+
console.log("⟳ Starting SRP handshake...");
|
|
103
|
+
// 5. Run grantPolicy / acceptPolicy concurrently
|
|
104
|
+
const [counterpart, result] = await Promise.all([
|
|
105
|
+
producerEm.grantPolicy(channel, policy),
|
|
106
|
+
executorEm.acceptPolicy(channel.otherend),
|
|
107
|
+
]);
|
|
108
|
+
// 6. Verify
|
|
109
|
+
console.log();
|
|
110
|
+
console.log("✓ SRP handshake complete!");
|
|
111
|
+
console.log(` Producer sees executor: ${counterpart.did}`);
|
|
112
|
+
console.log(` Executor sees producer: ${result.counterpart.did}`);
|
|
113
|
+
// Verify the stored policy certificates
|
|
114
|
+
const producerStored = producerEm.getPolicy(executor.vaultysId.did);
|
|
115
|
+
const executorStored = executorEm.getPolicy(producer.vaultysId.did);
|
|
116
|
+
if (producerStored) {
|
|
117
|
+
const valid = await ExecutionManager.verifyStoredPolicy(producerStored);
|
|
118
|
+
console.log(` Producer certificate: ${valid ? "✓ valid" : "✗ INVALID"}`);
|
|
119
|
+
}
|
|
120
|
+
if (executorStored) {
|
|
121
|
+
const valid = await ExecutionManager.verifyStoredPolicy(executorStored);
|
|
122
|
+
console.log(` Executor certificate: ${valid ? "✓ valid" : "✗ INVALID"}`);
|
|
123
|
+
}
|
|
124
|
+
// 7. Persist updated identities (with policy substore)
|
|
125
|
+
// The stores were already modified by grantPolicy/acceptPolicy
|
|
126
|
+
fs.writeFileSync(PRODUCER_FILE, producer.store.toString());
|
|
127
|
+
fs.writeFileSync(EXECUTOR_FILE, executor.store.toString());
|
|
128
|
+
// 8. Also export files for backward compatibility with file-based server.ts
|
|
129
|
+
// Export the signed policy (from the SRP-stored version)
|
|
130
|
+
if (executorStored) {
|
|
131
|
+
const signedPolicy = executorStored.policy;
|
|
132
|
+
// For file-based usage, we also need the authority's signed version
|
|
133
|
+
// The SRP approach stores the policy without a separate signature field;
|
|
134
|
+
// instead the SRP certificate binds the policy cryptographically.
|
|
135
|
+
// For the file-based server flow, we sign the policy explicitly too.
|
|
136
|
+
const explicitSigned = await producerEm.signPolicy(policy);
|
|
137
|
+
const serializable = {
|
|
138
|
+
...explicitSigned,
|
|
139
|
+
signature: explicitSigned.signature
|
|
140
|
+
? Buffer.from(explicitSigned.signature).toString("base64")
|
|
141
|
+
: undefined,
|
|
142
|
+
};
|
|
143
|
+
fs.writeFileSync("policy.signed.json", JSON.stringify(serializable, null, 2));
|
|
144
|
+
console.log(` policy.signed.json: written (for file-based server mode)`);
|
|
145
|
+
}
|
|
146
|
+
// Export authority public identity
|
|
147
|
+
const vid = producer.vaultysId;
|
|
148
|
+
fs.writeFileSync("authority.identity.json", JSON.stringify({
|
|
149
|
+
did: vid.did,
|
|
150
|
+
id: Buffer.from(vid.id).toString("base64"),
|
|
151
|
+
fingerprint: vid.fingerprint,
|
|
152
|
+
}, null, 2));
|
|
153
|
+
// 9. Summary
|
|
154
|
+
console.log();
|
|
155
|
+
console.log("┌──────────────────────────────────────────────┐");
|
|
156
|
+
console.log("│ SRP Policy Agreement Complete! │");
|
|
157
|
+
console.log("├──────────────────────────────────────────────┤");
|
|
158
|
+
console.log(`│ Producer DID: ${producer.vaultysId.did.slice(0, 28)}...│`);
|
|
159
|
+
console.log(`│ Executor DID: ${executor.vaultysId.did.slice(0, 28)}...│`);
|
|
160
|
+
console.log("│ │");
|
|
161
|
+
console.log("│ Policy stored in both identity stores │");
|
|
162
|
+
console.log("│ SRP certificates bind the agreement │");
|
|
163
|
+
console.log("│ │");
|
|
164
|
+
console.log("│ To start the MCP server: │");
|
|
165
|
+
console.log("│ pnpm start │");
|
|
166
|
+
console.log("│ │");
|
|
167
|
+
console.log("│ The server will load the policy from its │");
|
|
168
|
+
console.log("│ identity store (SRP) or from signed file. │");
|
|
169
|
+
console.log("└──────────────────────────────────────────────┘");
|
|
170
|
+
// Show stored policies
|
|
171
|
+
const allPolicies = executorEm.listPolicies();
|
|
172
|
+
console.log(`\nExecutor has ${allPolicies.length} stored policy agreement(s).`);
|
|
173
|
+
}
|
|
174
|
+
main().catch((err) => {
|
|
175
|
+
console.error("Error:", err);
|
|
176
|
+
process.exit(1);
|
|
177
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vaultys/mcp-agent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Policy-enforced MCP server powered by VaultysID — cryptographic authorization and audit trail for every AI tool call",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/nicmusic/vaultysid",
|
|
10
|
+
"directory": "typescript/demos/mcp-agent"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"mcp",
|
|
14
|
+
"model-context-protocol",
|
|
15
|
+
"vaultysid",
|
|
16
|
+
"policy",
|
|
17
|
+
"audit",
|
|
18
|
+
"ai-safety",
|
|
19
|
+
"cryptographic",
|
|
20
|
+
"tool-use"
|
|
21
|
+
],
|
|
22
|
+
"main": "./dist/src/server.js",
|
|
23
|
+
"bin": {
|
|
24
|
+
"vaultys-mcp-agent": "./dist/bin/server.js",
|
|
25
|
+
"vaultys-mcp-init": "./dist/bin/init.js",
|
|
26
|
+
"vaultys-mcp-grant": "./dist/bin/grant-policy.js",
|
|
27
|
+
"vaultys-mcp-audit": "./dist/bin/audit.js"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"dist/",
|
|
31
|
+
"policy.example.json",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE"
|
|
34
|
+
],
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
37
|
+
"@vaultys/id": "^3.0.0-beta7",
|
|
38
|
+
"buffer": "^6.0.3",
|
|
39
|
+
"zod": "^3.25.0"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^22.0.0",
|
|
43
|
+
"tsx": "^4.21.0",
|
|
44
|
+
"typescript": "^5.9.0"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"start": "tsx src/server.ts",
|
|
48
|
+
"init": "tsx bin/init.ts",
|
|
49
|
+
"grant-policy": "tsx grant-policy.ts",
|
|
50
|
+
"srp-grant": "tsx srp-grant-policy.ts",
|
|
51
|
+
"audit": "tsx src/audit.ts",
|
|
52
|
+
"test": "tsx test-e2e.ts",
|
|
53
|
+
"build": "tsc && chmod +x dist/bin/*.js"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0",
|
|
3
|
+
"scopes": {
|
|
4
|
+
"fs.read": [
|
|
5
|
+
"{{WORKSPACE}}/**"
|
|
6
|
+
],
|
|
7
|
+
"fs.write": [
|
|
8
|
+
"{{WORKSPACE}}/**"
|
|
9
|
+
],
|
|
10
|
+
"fs.list": [
|
|
11
|
+
"{{WORKSPACE}}/**"
|
|
12
|
+
],
|
|
13
|
+
"proc.exec": [
|
|
14
|
+
"ls",
|
|
15
|
+
"cat",
|
|
16
|
+
"echo",
|
|
17
|
+
"wc",
|
|
18
|
+
"head",
|
|
19
|
+
"tail",
|
|
20
|
+
"grep",
|
|
21
|
+
"find"
|
|
22
|
+
],
|
|
23
|
+
"net.egress.http": [
|
|
24
|
+
"*"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"denied": [
|
|
28
|
+
"secrets.*",
|
|
29
|
+
"pkg.system",
|
|
30
|
+
"proc.privilege"
|
|
31
|
+
],
|
|
32
|
+
"constraints": {
|
|
33
|
+
"max_runtime": 300,
|
|
34
|
+
"no_shell_features": true
|
|
35
|
+
}
|
|
36
|
+
}
|