@rickydata/agent0-mcp 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.
Files changed (70) hide show
  1. package/.env.example +17 -0
  2. package/Dockerfile +25 -0
  3. package/README.md +85 -0
  4. package/dist/auth/sdk-client.d.ts +47 -0
  5. package/dist/auth/sdk-client.js +142 -0
  6. package/dist/auth/sdk-client.js.map +1 -0
  7. package/dist/auth/token.d.ts +5 -0
  8. package/dist/auth/token.js +6 -0
  9. package/dist/auth/token.js.map +1 -0
  10. package/dist/auth/wallet-derivation.d.ts +26 -0
  11. package/dist/auth/wallet-derivation.js +76 -0
  12. package/dist/auth/wallet-derivation.js.map +1 -0
  13. package/dist/index.d.ts +1 -0
  14. package/dist/index.js +140 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/tools/a2a.d.ts +3 -0
  17. package/dist/tools/a2a.js +170 -0
  18. package/dist/tools/a2a.js.map +1 -0
  19. package/dist/tools/discovery.d.ts +3 -0
  20. package/dist/tools/discovery.js +465 -0
  21. package/dist/tools/discovery.js.map +1 -0
  22. package/dist/tools/index.d.ts +3 -0
  23. package/dist/tools/index.js +38 -0
  24. package/dist/tools/index.js.map +1 -0
  25. package/dist/tools/payments.d.ts +3 -0
  26. package/dist/tools/payments.js +124 -0
  27. package/dist/tools/payments.js.map +1 -0
  28. package/dist/tools/registration.d.ts +3 -0
  29. package/dist/tools/registration.js +324 -0
  30. package/dist/tools/registration.js.map +1 -0
  31. package/dist/tools/reputation.d.ts +3 -0
  32. package/dist/tools/reputation.js +147 -0
  33. package/dist/tools/reputation.js.map +1 -0
  34. package/dist/utils/chains.d.ts +10 -0
  35. package/dist/utils/chains.js +33 -0
  36. package/dist/utils/chains.js.map +1 -0
  37. package/dist/utils/trust-labels.d.ts +10 -0
  38. package/dist/utils/trust-labels.js +48 -0
  39. package/dist/utils/trust-labels.js.map +1 -0
  40. package/dist/utils/validation.d.ts +12 -0
  41. package/dist/utils/validation.js +19 -0
  42. package/dist/utils/validation.js.map +1 -0
  43. package/package.json +32 -0
  44. package/src/auth/sdk-client.ts +171 -0
  45. package/src/auth/token.ts +19 -0
  46. package/src/auth/wallet-derivation.ts +91 -0
  47. package/src/index.ts +184 -0
  48. package/src/tools/a2a.ts +205 -0
  49. package/src/tools/discovery.ts +517 -0
  50. package/src/tools/index.ts +45 -0
  51. package/src/tools/payments.ts +146 -0
  52. package/src/tools/registration.ts +389 -0
  53. package/src/tools/reputation.ts +183 -0
  54. package/src/utils/chains.ts +42 -0
  55. package/src/utils/trust-labels.ts +53 -0
  56. package/src/utils/validation.ts +20 -0
  57. package/tests/a2a.test.ts +234 -0
  58. package/tests/chains.test.ts +57 -0
  59. package/tests/discovery.test.ts +455 -0
  60. package/tests/e2e.test.ts +234 -0
  61. package/tests/payments.test.ts +148 -0
  62. package/tests/registration.test.ts +313 -0
  63. package/tests/reputation.test.ts +231 -0
  64. package/tests/sdk-client.test.ts +143 -0
  65. package/tests/tool-router.test.ts +28 -0
  66. package/tests/trust-labels.test.ts +229 -0
  67. package/tests/validation.test.ts +132 -0
  68. package/tests/wallet-derivation.test.ts +109 -0
  69. package/tsconfig.json +8 -0
  70. package/vitest.config.ts +8 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reputation.js","sourceRoot":"","sources":["../../src/tools/reputation.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /** Known chain configurations for ERC-8004 agent registry lookups. */
2
+ export interface ChainConfig {
3
+ chainId: number;
4
+ name: string;
5
+ rpcUrl: string;
6
+ explorerUrl: string;
7
+ }
8
+ export declare const CHAINS: Record<number, ChainConfig>;
9
+ export declare function getChain(chainId: number): ChainConfig | undefined;
10
+ export declare function getChainName(chainId: number): string;
@@ -0,0 +1,33 @@
1
+ export const CHAINS = {
2
+ 1: {
3
+ chainId: 1,
4
+ name: "Ethereum Mainnet",
5
+ rpcUrl: "https://eth.llamarpc.com",
6
+ explorerUrl: "https://etherscan.io",
7
+ },
8
+ 8453: {
9
+ chainId: 8453,
10
+ name: "Base",
11
+ rpcUrl: "https://mainnet.base.org",
12
+ explorerUrl: "https://basescan.org",
13
+ },
14
+ 42161: {
15
+ chainId: 42161,
16
+ name: "Arbitrum One",
17
+ rpcUrl: "https://arb1.arbitrum.io/rpc",
18
+ explorerUrl: "https://arbiscan.io",
19
+ },
20
+ 10: {
21
+ chainId: 10,
22
+ name: "Optimism",
23
+ rpcUrl: "https://mainnet.optimism.io",
24
+ explorerUrl: "https://optimistic.etherscan.io",
25
+ },
26
+ };
27
+ export function getChain(chainId) {
28
+ return CHAINS[chainId];
29
+ }
30
+ export function getChainName(chainId) {
31
+ return CHAINS[chainId]?.name ?? `Chain ${chainId}`;
32
+ }
33
+ //# sourceMappingURL=chains.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chains.js","sourceRoot":"","sources":["../../src/utils/chains.ts"],"names":[],"mappings":"AAQA,MAAM,CAAC,MAAM,MAAM,GAAgC;IACjD,CAAC,EAAE;QACD,OAAO,EAAE,CAAC;QACV,IAAI,EAAE,kBAAkB;QACxB,MAAM,EAAE,0BAA0B;QAClC,WAAW,EAAE,sBAAsB;KACpC;IACD,IAAI,EAAE;QACJ,OAAO,EAAE,IAAI;QACb,IAAI,EAAE,MAAM;QACZ,MAAM,EAAE,0BAA0B;QAClC,WAAW,EAAE,sBAAsB;KACpC;IACD,KAAK,EAAE;QACL,OAAO,EAAE,KAAK;QACd,IAAI,EAAE,cAAc;QACpB,MAAM,EAAE,8BAA8B;QACtC,WAAW,EAAE,qBAAqB;KACnC;IACD,EAAE,EAAE;QACF,OAAO,EAAE,EAAE;QACX,IAAI,EAAE,UAAU;QAChB,MAAM,EAAE,6BAA6B;QACrC,WAAW,EAAE,iCAAiC;KAC/C;CACF,CAAC;AAEF,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAe;IAC1C,OAAO,MAAM,CAAC,OAAO,CAAC,EAAE,IAAI,IAAI,SAAS,OAAO,EAAE,CAAC;AACrD,CAAC"}
@@ -0,0 +1,10 @@
1
+ export interface TrustLabel {
2
+ emoji: string;
3
+ label: string;
4
+ display: string;
5
+ }
6
+ /**
7
+ * Compute a human-readable trust label from review count and average score.
8
+ * First-match-wins rules (order matters).
9
+ */
10
+ export declare function computeTrustLabel(count: number, avg: number): TrustLabel;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Compute a human-readable trust label from review count and average score.
3
+ * First-match-wins rules (order matters).
4
+ */
5
+ export function computeTrustLabel(count, avg) {
6
+ if (count >= 5 && avg < -50)
7
+ return {
8
+ emoji: "\u{1F534}",
9
+ label: "Untrusted",
10
+ display: `\u{1F534} Untrusted -- ${avg}/100 (${count} reviews)`,
11
+ };
12
+ if (avg < 0)
13
+ return {
14
+ emoji: "\u{1F7E0}",
15
+ label: "Caution",
16
+ display: `\u{1F7E0} Caution -- ${avg}/100 (${count} reviews)`,
17
+ };
18
+ if (count >= 20 && avg >= 80)
19
+ return {
20
+ emoji: "\u2B50",
21
+ label: "Highly Trusted",
22
+ display: `\u2B50 Highly Trusted -- ${avg}/100 (${count} reviews)`,
23
+ };
24
+ if (count >= 10 && avg >= 70)
25
+ return {
26
+ emoji: "\u{1F7E2}",
27
+ label: "Trusted",
28
+ display: `\u{1F7E2} Trusted -- ${avg}/100 (${count} reviews)`,
29
+ };
30
+ if (count >= 5 && avg >= 50)
31
+ return {
32
+ emoji: "\u{1F7E2}",
33
+ label: "Established",
34
+ display: `\u{1F7E2} Established -- ${avg}/100 (${count} reviews)`,
35
+ };
36
+ if (count > 0)
37
+ return {
38
+ emoji: "\u{1F535}",
39
+ label: "Emerging",
40
+ display: `\u{1F535} Emerging -- ${avg}/100 (${count} reviews)`,
41
+ };
42
+ return {
43
+ emoji: "\u26AA",
44
+ label: "No Data",
45
+ display: "\u26AA No Data -- 0/100 (0 reviews)",
46
+ };
47
+ }
48
+ //# sourceMappingURL=trust-labels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"trust-labels.js","sourceRoot":"","sources":["../../src/utils/trust-labels.ts"],"names":[],"mappings":"AAMA;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAa,EAAE,GAAW;IAC1D,IAAI,KAAK,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC,EAAE;QACzB,OAAO;YACL,KAAK,EAAE,WAAW;YAClB,KAAK,EAAE,WAAW;YAClB,OAAO,EAAE,0BAA0B,GAAG,SAAS,KAAK,WAAW;SAChE,CAAC;IACJ,IAAI,GAAG,GAAG,CAAC;QACT,OAAO;YACL,KAAK,EAAE,WAAW;YAClB,KAAK,EAAE,SAAS;YAChB,OAAO,EAAE,wBAAwB,GAAG,SAAS,KAAK,WAAW;SAC9D,CAAC;IACJ,IAAI,KAAK,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE;QAC1B,OAAO;YACL,KAAK,EAAE,QAAQ;YACf,KAAK,EAAE,gBAAgB;YACvB,OAAO,EAAE,4BAA4B,GAAG,SAAS,KAAK,WAAW;SAClE,CAAC;IACJ,IAAI,KAAK,IAAI,EAAE,IAAI,GAAG,IAAI,EAAE;QAC1B,OAAO;YACL,KAAK,EAAE,WAAW;YAClB,KAAK,EAAE,SAAS;YAChB,OAAO,EAAE,wBAAwB,GAAG,SAAS,KAAK,WAAW;SAC9D,CAAC;IACJ,IAAI,KAAK,IAAI,CAAC,IAAI,GAAG,IAAI,EAAE;QACzB,OAAO;YACL,KAAK,EAAE,WAAW;YAClB,KAAK,EAAE,aAAa;YACpB,OAAO,EAAE,4BAA4B,GAAG,SAAS,KAAK,WAAW;SAClE,CAAC;IACJ,IAAI,KAAK,GAAG,CAAC;QACX,OAAO;YACL,KAAK,EAAE,WAAW;YAClB,KAAK,EAAE,UAAU;YACjB,OAAO,EAAE,yBAAyB,GAAG,SAAS,KAAK,WAAW;SAC/D,CAAC;IACJ,OAAO;QACL,KAAK,EAAE,QAAQ;QACf,KAAK,EAAE,SAAS;QAChB,OAAO,EAAE,qCAAqC;KAC/C,CAAC;AACJ,CAAC"}
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Validate an Ethereum address (basic checksum-agnostic check).
3
+ */
4
+ export declare function isValidAddress(address: string): boolean;
5
+ /**
6
+ * Validate a bytes32 hex string (e.g., agent ID).
7
+ */
8
+ export declare function isValidBytes32(hex: string): boolean;
9
+ /**
10
+ * Validate a positive integer chain ID.
11
+ */
12
+ export declare function isValidChainId(chainId: number): boolean;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Validate an Ethereum address (basic checksum-agnostic check).
3
+ */
4
+ export function isValidAddress(address) {
5
+ return /^0x[0-9a-fA-F]{40}$/.test(address);
6
+ }
7
+ /**
8
+ * Validate a bytes32 hex string (e.g., agent ID).
9
+ */
10
+ export function isValidBytes32(hex) {
11
+ return /^0x[0-9a-fA-F]{64}$/.test(hex);
12
+ }
13
+ /**
14
+ * Validate a positive integer chain ID.
15
+ */
16
+ export function isValidChainId(chainId) {
17
+ return Number.isInteger(chainId) && chainId > 0;
18
+ }
19
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/utils/validation.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,OAAO,qBAAqB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;AAC7C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,OAAO,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACzC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,OAAO,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC;AAClD,CAAC"}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@rickydata/agent0-mcp",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "description": "MCP server wrapping Agent0 SDK for ERC-8004 trustless agent operations",
6
+ "type": "module",
7
+ "main": "dist/index.js",
8
+ "scripts": {
9
+ "build": "tsc",
10
+ "dev": "tsx src/index.ts",
11
+ "start": "node dist/index.js",
12
+ "test": "vitest run",
13
+ "clean": "rm -rf dist"
14
+ },
15
+ "dependencies": {
16
+ "@modelcontextprotocol/sdk": "^1.12.1",
17
+ "agent0-sdk": "^1.7.1",
18
+ "cors": "^2.8.5",
19
+ "ethers": "^6.16.0",
20
+ "express": "^4.21.2",
21
+ "zod": "^3.24.0"
22
+ },
23
+ "devDependencies": {
24
+ "@noble/hashes": "^1.7.0",
25
+ "@types/cors": "^2.8.17",
26
+ "@types/express": "^5.0.0",
27
+ "@types/node": "^22.10.2",
28
+ "tsx": "^4.19.2",
29
+ "typescript": "^5.7.2",
30
+ "vitest": "^3.0.0"
31
+ }
32
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Agent0 SDK client management.
3
+ *
4
+ * Provides read-only and authenticated SDK instances for ERC-8004 operations.
5
+ *
6
+ * Key resolution order:
7
+ * 1. ERC8004_PRIVATE_KEY env var (explicit private key)
8
+ * 2. ERC8004_DERIVED_KEY env var (previously derived key from wallet signature)
9
+ * 3. Prompt user for wallet signature derivation (via configure_wallet tool)
10
+ */
11
+ import { SDK, type SDKConfig } from "agent0-sdk";
12
+
13
+ // ============================================================================
14
+ // CONFIGURATION
15
+ // ============================================================================
16
+
17
+ const DEFAULT_CHAIN_ID = parseInt(process.env.AGENT0_CHAIN_ID || "11155111", 10);
18
+ const RPC_URL = process.env.AGENT0_RPC_URL;
19
+ const IPFS_PROVIDER = (process.env.AGENT0_IPFS_PROVIDER || "pinata") as
20
+ | "pinata"
21
+ | "helia"
22
+ | "node"
23
+ | "filecoinPin";
24
+ const PINATA_JWT = process.env.PINATA_JWT;
25
+
26
+ // ============================================================================
27
+ // SDK CLIENT STATE
28
+ // ============================================================================
29
+
30
+ let _readOnlySDK: SDK | null = null;
31
+ let _authenticatedSDK: SDK | null = null;
32
+ let _currentPrivateKey: string | null = null;
33
+ let _currentChainId: number = DEFAULT_CHAIN_ID;
34
+
35
+ /**
36
+ * Resolve the private key from environment or stored state.
37
+ * Returns null if no key is available (read-only mode).
38
+ */
39
+ function resolvePrivateKey(): string | null {
40
+ // 1. Explicit private key
41
+ if (process.env.ERC8004_PRIVATE_KEY) {
42
+ return process.env.ERC8004_PRIVATE_KEY;
43
+ }
44
+ // 2. Previously derived key
45
+ if (process.env.ERC8004_DERIVED_KEY) {
46
+ return process.env.ERC8004_DERIVED_KEY;
47
+ }
48
+ // 3. Stored from configure_wallet
49
+ return _currentPrivateKey;
50
+ }
51
+
52
+ /**
53
+ * Build SDK config for the given chain.
54
+ */
55
+ function buildConfig(
56
+ chainId: number,
57
+ privateKey?: string | null,
58
+ ): SDKConfig {
59
+ const config: SDKConfig = { chainId };
60
+
61
+ if (RPC_URL) config.rpcUrl = RPC_URL;
62
+ if (privateKey) config.privateKey = privateKey;
63
+
64
+ // Only configure IPFS when we have a private key (write operations need IPFS)
65
+ if (privateKey && PINATA_JWT) {
66
+ config.ipfs = IPFS_PROVIDER;
67
+ config.pinataJwt = PINATA_JWT;
68
+ }
69
+
70
+ return config;
71
+ }
72
+
73
+ // ============================================================================
74
+ // PUBLIC API
75
+ // ============================================================================
76
+
77
+ /**
78
+ * Get a read-only SDK instance (no signing capabilities).
79
+ * Suitable for search, discovery, and reputation queries.
80
+ */
81
+ export function getReadOnlySDK(chainId?: number): SDK {
82
+ const targetChain = chainId ?? _currentChainId;
83
+
84
+ // Cache the read-only SDK for the current chain
85
+ if (_readOnlySDK && targetChain === _currentChainId) {
86
+ return _readOnlySDK;
87
+ }
88
+
89
+ _readOnlySDK = new SDK(buildConfig(targetChain));
90
+ if (!chainId) _currentChainId = targetChain;
91
+ return _readOnlySDK;
92
+ }
93
+
94
+ /**
95
+ * Get an authenticated SDK instance (with signing capabilities).
96
+ * Returns null if no private key is available.
97
+ */
98
+ export function getAuthenticatedSDK(chainId?: number): SDK | null {
99
+ const privateKey = resolvePrivateKey();
100
+ if (!privateKey) return null;
101
+
102
+ const targetChain = chainId ?? _currentChainId;
103
+
104
+ // Return cached if key and chain haven't changed
105
+ if (
106
+ _authenticatedSDK &&
107
+ _currentPrivateKey === privateKey &&
108
+ targetChain === _currentChainId
109
+ ) {
110
+ return _authenticatedSDK;
111
+ }
112
+
113
+ _authenticatedSDK = new SDK(buildConfig(targetChain, privateKey));
114
+ _currentPrivateKey = privateKey;
115
+ _currentChainId = targetChain;
116
+ return _authenticatedSDK;
117
+ }
118
+
119
+ /**
120
+ * Set the derived private key (from wallet signature derivation).
121
+ * Clears cached authenticated SDK so it's rebuilt on next access.
122
+ */
123
+ export function setDerivedKey(privateKey: string): void {
124
+ _currentPrivateKey = privateKey;
125
+ _authenticatedSDK = null;
126
+ }
127
+
128
+ /**
129
+ * Check if an authenticated SDK is available (has a private key).
130
+ */
131
+ export function hasAuthentication(): boolean {
132
+ return resolvePrivateKey() !== null;
133
+ }
134
+
135
+ /**
136
+ * Get the current chain ID.
137
+ */
138
+ export function getCurrentChainId(): number {
139
+ return _currentChainId;
140
+ }
141
+
142
+ /**
143
+ * Set the current chain ID (clears cached SDK instances).
144
+ */
145
+ export function setChainId(chainId: number): void {
146
+ _currentChainId = chainId;
147
+ _readOnlySDK = null;
148
+ _authenticatedSDK = null;
149
+ }
150
+
151
+ /**
152
+ * Get authentication status information.
153
+ */
154
+ export function getAuthStatus(): {
155
+ hasKey: boolean;
156
+ chainId: number;
157
+ source: "env:ERC8004_PRIVATE_KEY" | "env:ERC8004_DERIVED_KEY" | "derived" | "none";
158
+ isReadOnly: boolean;
159
+ } {
160
+ let source: "env:ERC8004_PRIVATE_KEY" | "env:ERC8004_DERIVED_KEY" | "derived" | "none" = "none";
161
+ if (process.env.ERC8004_PRIVATE_KEY) source = "env:ERC8004_PRIVATE_KEY";
162
+ else if (process.env.ERC8004_DERIVED_KEY) source = "env:ERC8004_DERIVED_KEY";
163
+ else if (_currentPrivateKey) source = "derived";
164
+
165
+ return {
166
+ hasKey: source !== "none",
167
+ chainId: _currentChainId,
168
+ source,
169
+ isReadOnly: source === "none",
170
+ };
171
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Re-exports for auth module.
3
+ */
4
+ export {
5
+ getDerivationMessage,
6
+ deriveWalletFromSignature,
7
+ verifyDerivationSignature,
8
+ type DerivedWallet,
9
+ } from "./wallet-derivation.js";
10
+
11
+ export {
12
+ getReadOnlySDK,
13
+ getAuthenticatedSDK,
14
+ setDerivedKey,
15
+ hasAuthentication,
16
+ getCurrentChainId,
17
+ setChainId,
18
+ getAuthStatus,
19
+ } from "./sdk-client.js";
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Wallet derivation for ERC-8004 agent identity.
3
+ *
4
+ * Derives a deterministic private key from a wallet signature using HKDF.
5
+ * This allows an MCP client user to derive a signing key for ERC-8004 operations
6
+ * without exposing their main wallet private key.
7
+ *
8
+ * Flow:
9
+ * 1. User signs a deterministic message with their wallet
10
+ * 2. HKDF extracts a derived key from the signature
11
+ * 3. The derived key is used for agent0-sdk write operations
12
+ */
13
+ import { hkdf } from "@noble/hashes/hkdf";
14
+ import { sha256 } from "@noble/hashes/sha2";
15
+ import { ethers } from "ethers";
16
+
17
+ export interface DerivedWallet {
18
+ address: string;
19
+ privateKey: string;
20
+ }
21
+
22
+ /**
23
+ * The deterministic signing message used to derive the key.
24
+ * CRITICAL: This MUST NOT contain nonces, timestamps, or any variable data.
25
+ * If changed, all previously derived keys become invalid.
26
+ */
27
+ const DERIVATION_MESSAGE =
28
+ "Sign this message to derive your ERC-8004 agent key.\n\n" +
29
+ "This signature will be used to create a deterministic private key " +
30
+ "for managing your on-chain agent identity. " +
31
+ "This does not grant access to your funds.";
32
+
33
+ /**
34
+ * Get the deterministic message that must be signed for key derivation.
35
+ */
36
+ export function getDerivationMessage(): string {
37
+ return DERIVATION_MESSAGE;
38
+ }
39
+
40
+ /**
41
+ * Derive an ERC-8004 agent wallet from a wallet signature.
42
+ *
43
+ * Uses HKDF-SHA256 to extract a 32-byte private key from the signature.
44
+ * The same signature always produces the same derived key.
45
+ *
46
+ * @param signature - The wallet's signature of DERIVATION_MESSAGE (hex string with 0x prefix)
47
+ * @returns DerivedWallet with address and privateKey
48
+ */
49
+ export function deriveWalletFromSignature(signature: string): DerivedWallet {
50
+ if (!signature || !signature.startsWith("0x")) {
51
+ throw new Error("Invalid signature: must be a hex string starting with 0x");
52
+ }
53
+
54
+ // Convert signature to bytes
55
+ const sigBytes = ethers.getBytes(signature);
56
+
57
+ // HKDF: extract + expand
58
+ // salt: "agent0-erc8004" (domain separation)
59
+ // info: "agent-key-v1" (versioned derivation context)
60
+ const salt = new TextEncoder().encode("agent0-erc8004");
61
+ const info = new TextEncoder().encode("agent-key-v1");
62
+ const derivedKeyBytes = hkdf(sha256, sigBytes, salt, info, 32);
63
+
64
+ // Create ethers wallet from derived key
65
+ const privateKey = ethers.hexlify(derivedKeyBytes);
66
+ const wallet = new ethers.Wallet(privateKey);
67
+
68
+ return {
69
+ address: wallet.address,
70
+ privateKey,
71
+ };
72
+ }
73
+
74
+ /**
75
+ * Verify that a signature was produced by the expected wallet address.
76
+ *
77
+ * @param signature - The signature to verify
78
+ * @param expectedAddress - The wallet address that should have signed
79
+ * @returns true if the signature was produced by expectedAddress
80
+ */
81
+ export function verifyDerivationSignature(
82
+ signature: string,
83
+ expectedAddress: string,
84
+ ): boolean {
85
+ try {
86
+ const recovered = ethers.verifyMessage(DERIVATION_MESSAGE, signature);
87
+ return recovered.toLowerCase() === expectedAddress.toLowerCase();
88
+ } catch {
89
+ return false;
90
+ }
91
+ }
package/src/index.ts ADDED
@@ -0,0 +1,184 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
5
+ import {
6
+ CallToolRequestSchema,
7
+ ListToolsRequestSchema,
8
+ } from "@modelcontextprotocol/sdk/types.js";
9
+ import express from "express";
10
+ import cors from "cors";
11
+ import { TOOLS, handleToolCall } from "./tools/index.js";
12
+
13
+ // ============================================================================
14
+ // CONFIGURATION
15
+ // ============================================================================
16
+
17
+ const RESPONSE_MAX_LENGTH = parseInt(
18
+ process.env.RESPONSE_MAX_LENGTH || "200000",
19
+ 10,
20
+ );
21
+ const SERVER_NAME = "agent0-mcp";
22
+ const SERVER_VERSION = "0.1.0";
23
+
24
+ // ============================================================================
25
+ // RESPONSE CAPPING
26
+ // ============================================================================
27
+
28
+ function truncateResponse(result: unknown): string {
29
+ const text =
30
+ typeof result === "string" ? result : JSON.stringify(result, null, 2);
31
+ if (text.length <= RESPONSE_MAX_LENGTH) return text;
32
+ return (
33
+ text.slice(0, RESPONSE_MAX_LENGTH) +
34
+ `\n\n--- Response truncated (${text.length} chars, limit ${RESPONSE_MAX_LENGTH}) ---`
35
+ );
36
+ }
37
+
38
+ // ============================================================================
39
+ // MCP HANDLER SETUP
40
+ // ============================================================================
41
+
42
+ function setupMCPHandlers(server: Server): void {
43
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
44
+ tools: TOOLS,
45
+ }));
46
+
47
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
48
+ const { name, arguments: args = {} } = request.params;
49
+ let result: unknown;
50
+
51
+ try {
52
+ result = await handleToolCall(name, args);
53
+ } catch (error: unknown) {
54
+ const message =
55
+ error instanceof Error ? error.message : String(error);
56
+ result = { success: false, error: message };
57
+ }
58
+
59
+ const content = truncateResponse(result);
60
+ return {
61
+ content: [{ type: "text" as const, text: content }],
62
+ };
63
+ });
64
+ }
65
+
66
+ // ============================================================================
67
+ // SESSION MANAGEMENT (HTTP mode)
68
+ // ============================================================================
69
+
70
+ interface MCPSession {
71
+ server: Server;
72
+ transport: StreamableHTTPServerTransport;
73
+ createdAt: number;
74
+ }
75
+
76
+ const sessions = new Map<string, MCPSession>();
77
+
78
+ // Clean up sessions older than 2 hours
79
+ setInterval(
80
+ () => {
81
+ const now = Date.now();
82
+ for (const [id, session] of sessions) {
83
+ if (now - session.createdAt > 2 * 60 * 60 * 1000) {
84
+ session.transport.close();
85
+ session.server.close();
86
+ sessions.delete(id);
87
+ }
88
+ }
89
+ },
90
+ 5 * 60 * 1000,
91
+ );
92
+
93
+ function createServer(): Server {
94
+ return new Server(
95
+ { name: SERVER_NAME, version: SERVER_VERSION },
96
+ { capabilities: { tools: { listChanged: false } } },
97
+ );
98
+ }
99
+
100
+ // ============================================================================
101
+ // TRANSPORT: HTTP (StreamableHTTP)
102
+ // ============================================================================
103
+
104
+ const app = express();
105
+ app.use(cors());
106
+ app.use(express.json());
107
+
108
+ app.get("/health", (_req, res) => {
109
+ res.json({
110
+ status: "ok",
111
+ server: SERVER_NAME,
112
+ version: SERVER_VERSION,
113
+ tools: TOOLS.length,
114
+ sessions: sessions.size,
115
+ });
116
+ });
117
+
118
+ app.post("/mcp", async (req, res) => {
119
+ const sessionId = req.headers["mcp-session-id"] as string | undefined;
120
+
121
+ if (sessionId && sessions.has(sessionId)) {
122
+ const session = sessions.get(sessionId)!;
123
+ await session.transport.handleRequest(req, res, req.body);
124
+ } else {
125
+ const newId = randomUUID();
126
+ const server = createServer();
127
+ setupMCPHandlers(server);
128
+
129
+ const transport = new StreamableHTTPServerTransport({
130
+ sessionIdGenerator: () => newId,
131
+ });
132
+ await server.connect(transport);
133
+
134
+ sessions.set(newId, { server, transport, createdAt: Date.now() });
135
+ await transport.handleRequest(req, res, req.body);
136
+ }
137
+ });
138
+
139
+ app.get("/mcp", async (req, res) => {
140
+ const sessionId = req.headers["mcp-session-id"] as string | undefined;
141
+ if (!sessionId || !sessions.has(sessionId)) {
142
+ res.status(400).json({
143
+ error: "Invalid or missing session. Send initialize first via POST.",
144
+ });
145
+ return;
146
+ }
147
+ await sessions.get(sessionId)!.transport.handleRequest(req, res);
148
+ });
149
+
150
+ app.delete("/mcp", async (req, res) => {
151
+ const sessionId = req.headers["mcp-session-id"] as string | undefined;
152
+ if (sessionId && sessions.has(sessionId)) {
153
+ const session = sessions.get(sessionId)!;
154
+ await session.transport.handleRequest(req, res);
155
+ session.transport.close();
156
+ session.server.close();
157
+ sessions.delete(sessionId);
158
+ } else {
159
+ res.status(400).json({ error: "Invalid or missing session." });
160
+ }
161
+ });
162
+
163
+ // ============================================================================
164
+ // START
165
+ // ============================================================================
166
+
167
+ const isStdio = process.argv.includes("--stdio");
168
+
169
+ if (isStdio) {
170
+ // In stdio mode, redirect console.log to stderr so it doesn't pollute the MCP JSON stream
171
+ console.log = console.error;
172
+ const server = createServer();
173
+ setupMCPHandlers(server);
174
+ const transport = new StdioServerTransport();
175
+ await server.connect(transport);
176
+ console.error(`${SERVER_NAME} running on stdio (${TOOLS.length} tools)`);
177
+ } else {
178
+ const port = parseInt(process.env.PORT || "8080", 10);
179
+ app.listen(port, () => {
180
+ console.log(`${SERVER_NAME} running on port ${port}`);
181
+ console.log(`Tools: ${TOOLS.length}`);
182
+ console.log(`Endpoints: /health /mcp`);
183
+ });
184
+ }