@aspect-warden/mcp-server 0.3.1 → 0.4.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.
Files changed (3) hide show
  1. package/README.md +152 -0
  2. package/dist/index.js +105 -0
  3. package/package.json +1 -1
package/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # @aspect-warden/mcp-server
2
+
3
+ MCP (Model Context Protocol) server that gives AI agents policy-enforced wallet capabilities on Sepolia. Works with Claude Desktop, OpenClaw, and any MCP-compatible client.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @aspect-warden/mcp-server
9
+ ```
10
+
11
+ Or run directly:
12
+
13
+ ```bash
14
+ npx @aspect-warden/mcp-server
15
+ ```
16
+
17
+ ## Setup with Claude Desktop
18
+
19
+ Add to your `claude_desktop_config.json`:
20
+
21
+ ```json
22
+ {
23
+ "mcpServers": {
24
+ "warden": {
25
+ "command": "npx",
26
+ "args": ["@aspect-warden/mcp-server"],
27
+ "env": {
28
+ "RPC_URL": "https://eth-sepolia.g.alchemy.com/v2/YOUR_KEY",
29
+ "SEPOLIA_USDT_ADDRESS": "0x7169D38820dfd117C3FA1f22a697dBA58d90BA06"
30
+ }
31
+ }
32
+ }
33
+ }
34
+ ```
35
+
36
+ Restart Claude Desktop. The agent now has access to all Warden wallet tools.
37
+
38
+ ## Setup with OpenClaw
39
+
40
+ Add the same config to your OpenClaw MCP settings, or install the warden-wallet skill:
41
+
42
+ ```bash
43
+ npx skills add tetherto/wdk-agent-skills
44
+ ```
45
+
46
+ ## Environment Variables
47
+
48
+ | Variable | Required | Default | Description |
49
+ |----------|----------|---------|-------------|
50
+ | `RPC_URL` | No | `https://rpc.sepolia.org` | Sepolia RPC endpoint |
51
+ | `POLICY_DELEGATE_ADDRESS` | No | — | Deployed PolicyDelegate contract for EIP-7702 |
52
+ | `SEPOLIA_USDT_ADDRESS` | No | `0x7169...BA06` | USDT token address on Sepolia |
53
+ | `ERC8004_IDENTITY_REGISTRY` | No | — | ERC-8004 identity registry address |
54
+ | `WARDEN_STORAGE_KEY` | No | machine-scoped | Passphrase for encrypting persisted wallet state |
55
+
56
+ ## MCP Tools
57
+
58
+ Once connected, the agent can call these tools:
59
+
60
+ ### Wallet Management
61
+
62
+ | Tool | Description |
63
+ |------|-------------|
64
+ | `warden_create_wallet` | Create a new agent wallet with policy enforcement |
65
+ | `warden_get_balance` | Check ETH and token balances |
66
+ | `warden_transfer` | Send ERC-20 tokens (validated against policy) |
67
+ | `warden_get_audit_log` | Fetch transaction decision history |
68
+
69
+ ### Policy Management
70
+
71
+ | Tool | Description |
72
+ |------|-------------|
73
+ | `warden_setup_policy` | Configure spending policy from natural language |
74
+ | `warden_get_policy_status` | View current spending status and limits |
75
+ | `warden_update_policy` | Modify policy at runtime |
76
+
77
+ ### On-Chain Enforcement (EIP-7702)
78
+
79
+ | Tool | Description |
80
+ |------|-------------|
81
+ | `warden_delegate_to_policy` | Delegate EOA to PolicyDelegate contract |
82
+ | `warden_create_session_key` | Create scoped sub-agent permissions |
83
+ | `warden_revoke_session_key` | Revoke sub-agent access |
84
+
85
+ ### Safety Controls
86
+
87
+ | Tool | Description |
88
+ |------|-------------|
89
+ | `warden_freeze` | Emergency halt all operations |
90
+ | `warden_unfreeze` | Resume after freeze |
91
+ | `warden_register_identity` | Register agent on ERC-8004 identity registry |
92
+
93
+ ### Permissions (ERC-7715)
94
+
95
+ | Tool | Description |
96
+ |------|-------------|
97
+ | `warden_grant_permissions` | Grant permissions to an agent |
98
+ | `warden_revoke_permissions` | Revoke agent permissions |
99
+ | `warden_get_permissions` | View agent permission grants |
100
+
101
+ ## Example Conversation
102
+
103
+ ```
104
+ User: Create a conservative wallet. Max $50/day, USDT only.
105
+
106
+ Agent: [calls warden_create_wallet with maxPerTx=10, dailyLimit=50]
107
+ Created wallet 0xABC...123 with conservative policy.
108
+ - Max per transaction: 10 USDT
109
+ - Daily limit: 50 USDT
110
+ - Anomaly detection: enabled
111
+
112
+ User: Send 5 USDT to 0xDEF...456
113
+
114
+ Agent: [calls warden_transfer with amount=5, recipient=0xDEF...456]
115
+ Transfer approved and sent.
116
+ - Risk score: 12/100
117
+ - Daily remaining: 45 USDT
118
+ - Tx hash: 0x789...
119
+
120
+ User: Send 200 USDT to 0xDEF...456
121
+
122
+ Agent: [calls warden_transfer — PolicyEngine blocks it]
123
+ Transfer blocked: exceeds daily limit of 50 USDT.
124
+ Current daily spend: 5 USDT.
125
+ ```
126
+
127
+ ## Wallet Persistence
128
+
129
+ Wallet private keys and policy configuration are automatically saved to `~/.warden/wallet-state.enc` using AES-256-CBC encryption. When the MCP server restarts (e.g., Claude Desktop relaunch), it restores the previous wallet — no need to call `warden_create_wallet` again.
130
+
131
+ - **Storage**: `~/.warden/wallet-state.enc` (encrypted, file permissions `0600`)
132
+ - **Encryption**: AES-256-CBC with key derived from `WARDEN_STORAGE_KEY` env var (or machine-scoped default)
133
+ - **What's persisted**: private key, agent ID, policy configuration
134
+ - **What's NOT persisted**: spending counters, audit logs, session keys (reset on restart)
135
+
136
+ To use a different wallet, simply call `warden_create_wallet` again — it overwrites the saved state.
137
+
138
+ ## Architecture
139
+
140
+ ```
141
+ AI Agent (Claude / OpenClaw)
142
+ ↓ MCP Protocol (stdio)
143
+ @aspect-warden/mcp-server
144
+ ↓ evaluates every tx
145
+ @aspect-warden/policy-engine (19 rules + anomaly detection)
146
+ ↓ on-chain enforcement
147
+ PolicyDelegate.sol (EIP-7702 / Sepolia)
148
+ ```
149
+
150
+ ## License
151
+
152
+ MIT
package/dist/index.js CHANGED
@@ -5,6 +5,10 @@ import { z } from 'zod';
5
5
  import { createPublicClient, createWalletClient, http, parseAbi, encodeFunctionData, formatEther, formatUnits, parseUnits, } from 'viem';
6
6
  import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
7
7
  import { sepolia } from 'viem/chains';
8
+ import { createHash, createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';
9
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+ import { homedir } from 'node:os';
8
12
  // ============================================================
9
13
  // Configuration from environment
10
14
  // ============================================================
@@ -34,6 +38,60 @@ const ERC8004_ABI = parseAbi([
34
38
  ]);
35
39
  // PolicyEngine and AuditLogger imported from @aspect-warden/policy-engine
36
40
  // ============================================================
41
+ // Encrypted Wallet Persistence
42
+ // ============================================================
43
+ const STORAGE_DIR = join(homedir(), '.warden');
44
+ const STATE_FILE = join(STORAGE_DIR, 'wallet-state.enc');
45
+ // Derive a 32-byte AES key from a passphrase
46
+ function deriveKey(passphrase) {
47
+ return createHash('sha256').update(passphrase).digest();
48
+ }
49
+ function encrypt(data, passphrase) {
50
+ const key = deriveKey(passphrase);
51
+ const iv = randomBytes(16);
52
+ const cipher = createCipheriv('aes-256-cbc', key, iv);
53
+ let encrypted = cipher.update(data, 'utf8', 'hex');
54
+ encrypted += cipher.final('hex');
55
+ return iv.toString('hex') + ':' + encrypted;
56
+ }
57
+ function decrypt(payload, passphrase) {
58
+ const key = deriveKey(passphrase);
59
+ const [ivHex, encrypted] = payload.split(':');
60
+ const iv = Buffer.from(ivHex, 'hex');
61
+ const decipher = createDecipheriv('aes-256-cbc', key, iv);
62
+ let decrypted = decipher.update(encrypted, 'hex', 'utf8');
63
+ decrypted += decipher.final('utf8');
64
+ return decrypted;
65
+ }
66
+ const STORAGE_KEY = process.env.WARDEN_STORAGE_KEY || `warden-${homedir()}`;
67
+ function saveState(state) {
68
+ try {
69
+ if (!existsSync(STORAGE_DIR)) {
70
+ mkdirSync(STORAGE_DIR, { recursive: true, mode: 0o700 });
71
+ }
72
+ const json = JSON.stringify(state);
73
+ const encrypted = encrypt(json, STORAGE_KEY);
74
+ writeFileSync(STATE_FILE, encrypted, { mode: 0o600 });
75
+ console.error('[Warden MCP] Wallet state saved to ~/.warden/wallet-state.enc');
76
+ }
77
+ catch (err) {
78
+ console.error('[Warden MCP] Failed to save wallet state:', err);
79
+ }
80
+ }
81
+ function loadState() {
82
+ try {
83
+ if (!existsSync(STATE_FILE))
84
+ return null;
85
+ const encrypted = readFileSync(STATE_FILE, 'utf8');
86
+ const json = decrypt(encrypted, STORAGE_KEY);
87
+ return JSON.parse(json);
88
+ }
89
+ catch (err) {
90
+ console.error('[Warden MCP] Failed to load wallet state (may be from different key):', err);
91
+ return null;
92
+ }
93
+ }
94
+ // ============================================================
37
95
  // MCP Server State
38
96
  // ============================================================
39
97
  let policyEngine = null;
@@ -114,6 +172,22 @@ server.tool('warden_create_wallet', 'Create a new policy-enforced wallet for the
114
172
  policyEngine = new PolicyEngine(policy);
115
173
  auditLogger = new AuditLogger({ maxEntries: 10000 });
116
174
  frozen = false;
175
+ // Persist wallet state for recovery across restarts
176
+ saveState({
177
+ privateKey: storedPrivateKey,
178
+ agentId,
179
+ policy: {
180
+ maxPerTx: policy.maxPerTx.toString(),
181
+ dailyLimit: policy.dailyLimit.toString(),
182
+ requireApprovalAbove: policy.requireApprovalAbove.toString(),
183
+ cooldownMs: policy.cooldownMs,
184
+ allowedTokens: policy.allowedTokens,
185
+ blockedTokens: policy.blockedTokens,
186
+ allowedRecipients: policy.allowedRecipients,
187
+ blockedRecipients: policy.blockedRecipients,
188
+ allowedChains: policy.allowedChains,
189
+ },
190
+ });
117
191
  return {
118
192
  content: [{
119
193
  type: 'text',
@@ -1034,6 +1108,37 @@ server.tool('warden_get_permissions', 'Query current permissions granted to an A
1034
1108
  // Main -- stdio transport
1035
1109
  // ============================================================
1036
1110
  async function main() {
1111
+ // Restore wallet state from previous session if available
1112
+ const saved = loadState();
1113
+ if (saved) {
1114
+ try {
1115
+ initializeClients(saved.privateKey);
1116
+ const restoredPolicy = {
1117
+ agentId: saved.agentId,
1118
+ maxPerTx: BigInt(saved.policy.maxPerTx),
1119
+ dailyLimit: BigInt(saved.policy.dailyLimit),
1120
+ requireApprovalAbove: BigInt(saved.policy.requireApprovalAbove),
1121
+ cooldownMs: saved.policy.cooldownMs,
1122
+ allowedTokens: saved.policy.allowedTokens,
1123
+ blockedTokens: saved.policy.blockedTokens,
1124
+ allowedRecipients: saved.policy.allowedRecipients,
1125
+ blockedRecipients: saved.policy.blockedRecipients,
1126
+ allowedChains: saved.policy.allowedChains,
1127
+ anomalyDetection: {
1128
+ maxTxPerHour: 20,
1129
+ maxRecipientsPerHour: 5,
1130
+ largeTransactionPct: 50,
1131
+ },
1132
+ };
1133
+ policyEngine = new PolicyEngine(restoredPolicy);
1134
+ auditLogger = new AuditLogger({ maxEntries: 10000 });
1135
+ frozen = false;
1136
+ console.error(`[Warden MCP] Restored wallet ${walletAddress} (agent: ${saved.agentId})`);
1137
+ }
1138
+ catch (err) {
1139
+ console.error('[Warden MCP] Failed to restore wallet state:', err);
1140
+ }
1141
+ }
1037
1142
  const transport = new StdioServerTransport();
1038
1143
  await server.connect(transport);
1039
1144
  console.error('[Warden MCP] Server running on stdio');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aspect-warden/mcp-server",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "warden-mcp": "dist/index.js"