@azeth/mcp-server 0.2.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 +141 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +48 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/account.d.ts +4 -0
- package/dist/tools/account.d.ts.map +1 -0
- package/dist/tools/account.js +640 -0
- package/dist/tools/account.js.map +1 -0
- package/dist/tools/agreements.d.ts +4 -0
- package/dist/tools/agreements.d.ts.map +1 -0
- package/dist/tools/agreements.js +865 -0
- package/dist/tools/agreements.js.map +1 -0
- package/dist/tools/guardian-approval.d.ts +4 -0
- package/dist/tools/guardian-approval.d.ts.map +1 -0
- package/dist/tools/guardian-approval.js +319 -0
- package/dist/tools/guardian-approval.js.map +1 -0
- package/dist/tools/guardian.d.ts +4 -0
- package/dist/tools/guardian.d.ts.map +1 -0
- package/dist/tools/guardian.js +267 -0
- package/dist/tools/guardian.js.map +1 -0
- package/dist/tools/messaging.d.ts +4 -0
- package/dist/tools/messaging.d.ts.map +1 -0
- package/dist/tools/messaging.js +353 -0
- package/dist/tools/messaging.js.map +1 -0
- package/dist/tools/payments.d.ts +14 -0
- package/dist/tools/payments.d.ts.map +1 -0
- package/dist/tools/payments.js +723 -0
- package/dist/tools/payments.js.map +1 -0
- package/dist/tools/registry.d.ts +4 -0
- package/dist/tools/registry.d.ts.map +1 -0
- package/dist/tools/registry.js +608 -0
- package/dist/tools/registry.js.map +1 -0
- package/dist/tools/reputation.d.ts +4 -0
- package/dist/tools/reputation.d.ts.map +1 -0
- package/dist/tools/reputation.js +433 -0
- package/dist/tools/reputation.js.map +1 -0
- package/dist/tools/transfer.d.ts +4 -0
- package/dist/tools/transfer.d.ts.map +1 -0
- package/dist/tools/transfer.js +181 -0
- package/dist/tools/transfer.js.map +1 -0
- package/dist/utils/client.d.ts +25 -0
- package/dist/utils/client.d.ts.map +1 -0
- package/dist/utils/client.js +100 -0
- package/dist/utils/client.js.map +1 -0
- package/dist/utils/error-selectors.d.ts +23 -0
- package/dist/utils/error-selectors.d.ts.map +1 -0
- package/dist/utils/error-selectors.js +159 -0
- package/dist/utils/error-selectors.js.map +1 -0
- package/dist/utils/rate-limit.d.ts +17 -0
- package/dist/utils/rate-limit.d.ts.map +1 -0
- package/dist/utils/rate-limit.js +75 -0
- package/dist/utils/rate-limit.js.map +1 -0
- package/dist/utils/resolve.d.ts +38 -0
- package/dist/utils/resolve.d.ts.map +1 -0
- package/dist/utils/resolve.js +308 -0
- package/dist/utils/resolve.js.map +1 -0
- package/dist/utils/response.d.ts +42 -0
- package/dist/utils/response.d.ts.map +1 -0
- package/dist/utils/response.js +257 -0
- package/dist/utils/response.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { parseEther, parseUnits, formatEther, erc20Abi } from 'viem';
|
|
3
|
+
import { createClient, validateAddress } from '../utils/client.js';
|
|
4
|
+
import { resolveAddress, resolveSmartAccount } from '../utils/resolve.js';
|
|
5
|
+
import { success, error, handleError, guardianRequiredError } from '../utils/response.js';
|
|
6
|
+
/** Register the azeth_transfer MCP tool */
|
|
7
|
+
export function registerTransferTools(server) {
|
|
8
|
+
server.registerTool('azeth_transfer', {
|
|
9
|
+
description: [
|
|
10
|
+
'Send ETH or ERC-20 tokens FROM your Azeth smart account to another address.',
|
|
11
|
+
'',
|
|
12
|
+
'Use this when: You need to pay another participant, fund an account, or move tokens between addresses.',
|
|
13
|
+
'',
|
|
14
|
+
'The "to" field accepts: an Ethereum address, a participant name (resolved via trust registry),',
|
|
15
|
+
'"me" (your first smart account), or "#N" (Nth account index from azeth_accounts).',
|
|
16
|
+
'',
|
|
17
|
+
'IMPORTANT: This sends FROM your smart account, not your EOA. Ensure your smart account is funded.',
|
|
18
|
+
'Use azeth_deposit first to fund your smart account if needed.',
|
|
19
|
+
'One EOA can own multiple smart accounts — specify which one, or defaults to first.',
|
|
20
|
+
'',
|
|
21
|
+
'Returns: Transaction hash, sender smart account address, recipient address (with resolution info), and amount sent.',
|
|
22
|
+
'',
|
|
23
|
+
'Note: This is a state-changing operation. The tool shows the resolved address before executing.',
|
|
24
|
+
'For ETH transfers, omit the token parameter. For ERC-20 tokens, provide the token contract address AND decimals.',
|
|
25
|
+
'The amount is in human-readable units (e.g., "1.5" for 1.5 ETH or "100" for 100 USDC).',
|
|
26
|
+
'The sender account is determined by the AZETH_PRIVATE_KEY environment variable.',
|
|
27
|
+
'',
|
|
28
|
+
'Example: { "to": "Alice", "amount": "0.001" } or { "to": "0x1234...abcd", "amount": "10", "token": "0x036C...CF7e", "decimals": 6 }',
|
|
29
|
+
].join('\n'),
|
|
30
|
+
inputSchema: z.object({
|
|
31
|
+
chain: z.string().optional().describe('Target chain. Defaults to AZETH_CHAIN env var or "baseSepolia". Accepts "base", "baseSepolia", "ethereumSepolia", "ethereum" (and aliases like "base-sepolia", "eth-sepolia", "sepolia", "eth", "mainnet").'),
|
|
32
|
+
to: z.string().describe('Recipient: Ethereum address, participant name, "me", or "#N" (account index).'),
|
|
33
|
+
amount: z.string().describe('Amount to send in human-readable units (e.g., "1.5" for 1.5 ETH, "100" for 100 USDC).'),
|
|
34
|
+
token: z.string().optional().describe('ERC-20 token contract address. Omit for native ETH transfer.'),
|
|
35
|
+
decimals: z.coerce.number().int().min(0).max(18).optional().describe('Token decimals for ERC-20 transfers. REQUIRED when token is specified. Use 6 for USDC, 18 for WETH.'),
|
|
36
|
+
smartAccount: z.string().optional().describe('Smart account to transfer from: address, name, or "#N". If omitted, uses your first smart account.'),
|
|
37
|
+
}),
|
|
38
|
+
}, async (args) => {
|
|
39
|
+
if (args.token && !validateAddress(args.token)) {
|
|
40
|
+
return error('INVALID_INPUT', `Invalid token address: "${args.token}".`, 'Must be 0x-prefixed followed by 40 hex characters.');
|
|
41
|
+
}
|
|
42
|
+
if (args.token && args.decimals === undefined) {
|
|
43
|
+
return error('INVALID_INPUT', 'decimals is required when token address is provided.', 'Use 6 for USDC, 18 for WETH.');
|
|
44
|
+
}
|
|
45
|
+
let client;
|
|
46
|
+
try {
|
|
47
|
+
client = await createClient(args.chain);
|
|
48
|
+
// Resolve "to": address, name, "me", "#N"
|
|
49
|
+
let toResolved;
|
|
50
|
+
try {
|
|
51
|
+
toResolved = await resolveAddress(args.to, client, 'account');
|
|
52
|
+
}
|
|
53
|
+
catch (resolveErr) {
|
|
54
|
+
return handleError(resolveErr);
|
|
55
|
+
}
|
|
56
|
+
// Resolve smartAccount: address, name, "#N"
|
|
57
|
+
let fromAccount;
|
|
58
|
+
if (args.smartAccount) {
|
|
59
|
+
try {
|
|
60
|
+
fromAccount = await resolveSmartAccount(args.smartAccount, client);
|
|
61
|
+
}
|
|
62
|
+
catch (resolveErr) {
|
|
63
|
+
return handleError(resolveErr);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const tokenAddress = args.token;
|
|
67
|
+
const decimals = args.decimals ?? 18;
|
|
68
|
+
let amount;
|
|
69
|
+
try {
|
|
70
|
+
amount = tokenAddress
|
|
71
|
+
? parseUnits(args.amount, decimals)
|
|
72
|
+
: parseEther(args.amount);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return error('INVALID_INPUT', 'Invalid amount format — must be a valid decimal number');
|
|
76
|
+
}
|
|
77
|
+
// Pre-flight: check balance before submitting UserOp
|
|
78
|
+
try {
|
|
79
|
+
const senderAccount = fromAccount ?? await client.resolveSmartAccount();
|
|
80
|
+
if (tokenAddress) {
|
|
81
|
+
// Direct ERC-20 balanceOf call — getBalance() keys by symbol, not address
|
|
82
|
+
const available = await client.publicClient.readContract({
|
|
83
|
+
address: tokenAddress,
|
|
84
|
+
abi: erc20Abi,
|
|
85
|
+
functionName: 'balanceOf',
|
|
86
|
+
args: [senderAccount],
|
|
87
|
+
});
|
|
88
|
+
if (available < amount) {
|
|
89
|
+
const { formatUnits } = await import('viem');
|
|
90
|
+
return error('INSUFFICIENT_BALANCE', `Insufficient token balance: have ${formatUnits(available, decimals)}, need ${args.amount}.`, `Fund your smart account (${senderAccount}) before retrying.`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
const balance = await client.getBalance(senderAccount);
|
|
95
|
+
if (balance.eth < amount) {
|
|
96
|
+
return error('INSUFFICIENT_BALANCE', `Insufficient ETH balance: have ${formatEther(balance.eth)} ETH, need ${args.amount} ETH.`, `Fund your smart account (${fromAccount ?? senderAccount}) before retrying.`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Balance check is best-effort; proceed and let the bundler return details on failure
|
|
102
|
+
}
|
|
103
|
+
const result = await client.transfer({ to: toResolved.address, amount, token: tokenAddress }, fromAccount);
|
|
104
|
+
// Enrich response with transaction receipt data (gas, events)
|
|
105
|
+
let receiptData = {};
|
|
106
|
+
try {
|
|
107
|
+
const receipt = await client.publicClient.getTransactionReceipt({ hash: result.txHash });
|
|
108
|
+
const gasUsed = receipt.gasUsed;
|
|
109
|
+
const effectiveGasPrice = receipt.effectiveGasPrice;
|
|
110
|
+
const gasCostWei = gasUsed * effectiveGasPrice;
|
|
111
|
+
const { formatTokenAmount } = await import('@azeth/common');
|
|
112
|
+
// Decode known events from logs
|
|
113
|
+
const events = [];
|
|
114
|
+
try {
|
|
115
|
+
const { decodeEventLog } = await import('viem');
|
|
116
|
+
const { GuardianModuleAbi, ReputationModuleAbi: RepAbi } = await import('@azeth/common/abis');
|
|
117
|
+
const knownAbis = [GuardianModuleAbi, RepAbi];
|
|
118
|
+
for (const log of receipt.logs) {
|
|
119
|
+
for (const abi of knownAbis) {
|
|
120
|
+
try {
|
|
121
|
+
const decoded = decodeEventLog({ abi, data: log.data, topics: log.topics });
|
|
122
|
+
const stringArgs = {};
|
|
123
|
+
for (const [k, v] of Object.entries(decoded.args)) {
|
|
124
|
+
stringArgs[k] = typeof v === 'bigint' ? v.toString() : String(v);
|
|
125
|
+
}
|
|
126
|
+
events.push({ name: decoded.eventName, args: stringArgs });
|
|
127
|
+
break; // matched this log
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
// This ABI doesn't match this log — try next
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
// Event decoding failure is non-fatal
|
|
137
|
+
}
|
|
138
|
+
receiptData = {
|
|
139
|
+
gasUsed: gasUsed.toString(),
|
|
140
|
+
gasCostETH: formatTokenAmount(gasCostWei, 18, 8),
|
|
141
|
+
...(events.length > 0 ? { events } : {}),
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// Receipt fetch failure is non-fatal
|
|
146
|
+
}
|
|
147
|
+
return success({
|
|
148
|
+
txHash: result.txHash,
|
|
149
|
+
from: result.from,
|
|
150
|
+
to: result.to,
|
|
151
|
+
amount: args.amount,
|
|
152
|
+
token: result.token,
|
|
153
|
+
...(toResolved.resolvedFrom ? {
|
|
154
|
+
resolvedTo: `"${toResolved.resolvedFrom}" → ${toResolved.address}`,
|
|
155
|
+
...(toResolved.name ? { resolvedName: toResolved.name } : {}),
|
|
156
|
+
...(toResolved.tokenId ? { resolvedTokenId: toResolved.tokenId } : {}),
|
|
157
|
+
} : {}),
|
|
158
|
+
...receiptData,
|
|
159
|
+
}, { txHash: result.txHash });
|
|
160
|
+
}
|
|
161
|
+
catch (err) {
|
|
162
|
+
if (err instanceof Error && /AA24/.test(err.message)) {
|
|
163
|
+
return guardianRequiredError('Transfer amount exceeds your standard spending limit.', {
|
|
164
|
+
operation: 'transfer',
|
|
165
|
+
amount: `${args.amount} ${args.token ? 'tokens' : 'ETH'}`,
|
|
166
|
+
limit: 'Check with azeth_get_guardrails',
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
return handleError(err);
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
try {
|
|
173
|
+
await client?.destroy();
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
process.stderr.write(`[azeth-mcp] destroy error: ${e instanceof Error ? e.message : String(e)}\n`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
//# sourceMappingURL=transfer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transfer.js","sourceRoot":"","sources":["../../src/tools/transfer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { AzethKit } from '@azeth/sdk';
|
|
2
|
+
import { resolveViemChain, type SupportedChainName } from '@azeth/common';
|
|
3
|
+
/** Resolve a chain argument to a canonical SupportedChainName.
|
|
4
|
+
* Resolution order: explicit arg > AZETH_CHAIN env > 'baseSepolia' default.
|
|
5
|
+
* Accepts aliases like "base-sepolia", "eth-sepolia", "sepolia", "eth", "mainnet".
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveChain(argChain?: string): SupportedChainName;
|
|
8
|
+
export { resolveViemChain };
|
|
9
|
+
/** Create an AzethKit instance using the private key from the AZETH_PRIVATE_KEY
|
|
10
|
+
* environment variable. The private key is NEVER accepted as a tool parameter.
|
|
11
|
+
*
|
|
12
|
+
* After creation, attempts to resolve existing smart account(s) from the factory.
|
|
13
|
+
* If no accounts exist yet, the smart account will be null (createAccount() sets it).
|
|
14
|
+
*
|
|
15
|
+
* LOW-6 (Audit): A new AzethKit instance is created per MCP tool call. This is intentionally
|
|
16
|
+
* stateless — each call gets a fresh client with no shared mutable state, which prevents
|
|
17
|
+
* cross-request contamination and simplifies error recovery. For production optimization,
|
|
18
|
+
* a singleton pattern with health-check reconnection could reduce RPC setup overhead per call.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createClient(chain?: SupportedChainName | string): Promise<AzethKit>;
|
|
21
|
+
/** Validate that a string looks like a hex private key */
|
|
22
|
+
export declare function validatePrivateKey(key: string): key is `0x${string}`;
|
|
23
|
+
/** Validate that a string looks like an Ethereum address */
|
|
24
|
+
export declare function validateAddress(addr: string): addr is `0x${string}`;
|
|
25
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/utils/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAuB,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAiC,gBAAgB,EAAmB,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAE1H;;;GAGG;AACH,wBAAgB,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAYlE;AAGD,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAE5B;;;;;;;;;;GAUG;AACH,wBAAsB,YAAY,CAChC,KAAK,CAAC,EAAE,kBAAkB,GAAG,MAAM,GAClC,OAAO,CAAC,QAAQ,CAAC,CAgFnB;AAED,0DAA0D;AAC1D,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,IAAI,KAAK,MAAM,EAAE,CAEpE;AAED,4DAA4D;AAC5D,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,IAAI,KAAK,MAAM,EAAE,CAEnE"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { AzethKit } from '@azeth/sdk';
|
|
2
|
+
import { AzethError, resolveChainAlias, resolveViemChain } from '@azeth/common';
|
|
3
|
+
/** Resolve a chain argument to a canonical SupportedChainName.
|
|
4
|
+
* Resolution order: explicit arg > AZETH_CHAIN env > 'baseSepolia' default.
|
|
5
|
+
* Accepts aliases like "base-sepolia", "eth-sepolia", "sepolia", "eth", "mainnet".
|
|
6
|
+
*/
|
|
7
|
+
export function resolveChain(argChain) {
|
|
8
|
+
const raw = argChain ?? process.env['AZETH_CHAIN'];
|
|
9
|
+
if (!raw)
|
|
10
|
+
return 'baseSepolia';
|
|
11
|
+
const canonical = resolveChainAlias(raw);
|
|
12
|
+
if (!canonical) {
|
|
13
|
+
throw new AzethError(`Unknown chain "${raw}". Supported: "base", "baseSepolia", "ethereumSepolia", "ethereum" (and aliases like "base-sepolia", "eth-sepolia", "sepolia", "eth", "mainnet").`, 'INVALID_INPUT');
|
|
14
|
+
}
|
|
15
|
+
return canonical;
|
|
16
|
+
}
|
|
17
|
+
// Re-export for consumers within mcp-server that may still import from here
|
|
18
|
+
export { resolveViemChain };
|
|
19
|
+
/** Create an AzethKit instance using the private key from the AZETH_PRIVATE_KEY
|
|
20
|
+
* environment variable. The private key is NEVER accepted as a tool parameter.
|
|
21
|
+
*
|
|
22
|
+
* After creation, attempts to resolve existing smart account(s) from the factory.
|
|
23
|
+
* If no accounts exist yet, the smart account will be null (createAccount() sets it).
|
|
24
|
+
*
|
|
25
|
+
* LOW-6 (Audit): A new AzethKit instance is created per MCP tool call. This is intentionally
|
|
26
|
+
* stateless — each call gets a fresh client with no shared mutable state, which prevents
|
|
27
|
+
* cross-request contamination and simplifies error recovery. For production optimization,
|
|
28
|
+
* a singleton pattern with health-check reconnection could reduce RPC setup overhead per call.
|
|
29
|
+
*/
|
|
30
|
+
export async function createClient(chain) {
|
|
31
|
+
const privateKey = process.env['AZETH_PRIVATE_KEY'];
|
|
32
|
+
if (!privateKey) {
|
|
33
|
+
throw new AzethError('AZETH_PRIVATE_KEY environment variable is required. Set it before using Azeth tools.', 'UNAUTHORIZED');
|
|
34
|
+
}
|
|
35
|
+
if (!validatePrivateKey(privateKey)) {
|
|
36
|
+
throw new AzethError('AZETH_PRIVATE_KEY is malformed. Must be 0x-prefixed followed by 64 hex characters.', 'UNAUTHORIZED');
|
|
37
|
+
}
|
|
38
|
+
// Guardian co-signing key (optional — enables auto-approval for operations exceeding spending limits)
|
|
39
|
+
const guardianKey = process.env['AZETH_GUARDIAN_KEY'];
|
|
40
|
+
if (guardianKey && !validatePrivateKey(guardianKey)) {
|
|
41
|
+
throw new AzethError('AZETH_GUARDIAN_KEY is malformed. Must be 0x-prefixed followed by 64 hex characters.', 'UNAUTHORIZED');
|
|
42
|
+
}
|
|
43
|
+
const resolvedChain = resolveChain(chain);
|
|
44
|
+
// Guardian auto-sign: must be explicitly set to "true" to enable.
|
|
45
|
+
// When false (default), operations exceeding limits require interactive guardian approval.
|
|
46
|
+
const guardianAutoSign = process.env['AZETH_GUARDIAN_AUTO_SIGN']?.toLowerCase() === 'true';
|
|
47
|
+
const config = {
|
|
48
|
+
privateKey: privateKey,
|
|
49
|
+
chain: resolvedChain,
|
|
50
|
+
serverUrl: process.env['AZETH_SERVER_URL'],
|
|
51
|
+
guardianKey: guardianKey,
|
|
52
|
+
guardianAutoSign,
|
|
53
|
+
};
|
|
54
|
+
const rpcUrl = process.env['AZETH_RPC_URL'];
|
|
55
|
+
if (rpcUrl) {
|
|
56
|
+
config.rpcUrl = rpcUrl;
|
|
57
|
+
}
|
|
58
|
+
const bundlerUrl = process.env['AZETH_BUNDLER_URL'];
|
|
59
|
+
if (bundlerUrl) {
|
|
60
|
+
config.bundlerUrl = bundlerUrl;
|
|
61
|
+
}
|
|
62
|
+
const paymasterUrl = process.env['AZETH_PAYMASTER_URL'];
|
|
63
|
+
if (paymasterUrl) {
|
|
64
|
+
config.paymasterUrl = paymasterUrl;
|
|
65
|
+
}
|
|
66
|
+
// Wire XMTP config from env vars for persistent installations.
|
|
67
|
+
// Without a persistent encryption key, each MCP call creates a new XMTP installation
|
|
68
|
+
// which quickly exhausts the 10-installation-per-inbox limit.
|
|
69
|
+
const xmtpEncryptionKey = process.env['XMTP_ENCRYPTION_KEY'];
|
|
70
|
+
if (xmtpEncryptionKey) {
|
|
71
|
+
const xmtpConfig = {
|
|
72
|
+
dbEncryptionKey: xmtpEncryptionKey,
|
|
73
|
+
env: process.env['XMTP_ENV'] ?? 'production',
|
|
74
|
+
};
|
|
75
|
+
const xmtpDbPath = process.env['XMTP_DB_PATH'];
|
|
76
|
+
if (xmtpDbPath) {
|
|
77
|
+
xmtpConfig.dbPath = xmtpDbPath;
|
|
78
|
+
}
|
|
79
|
+
config.xmtp = xmtpConfig;
|
|
80
|
+
}
|
|
81
|
+
const client = await AzethKit.create(config);
|
|
82
|
+
// Auto-resolve smart account(s) if any exist on-chain.
|
|
83
|
+
// Non-fatal: if no account exists yet, callers will get null from .smartAccount
|
|
84
|
+
try {
|
|
85
|
+
await client.getSmartAccounts();
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
// No smart accounts deployed yet — fine, createAccount() will set them
|
|
89
|
+
}
|
|
90
|
+
return client;
|
|
91
|
+
}
|
|
92
|
+
/** Validate that a string looks like a hex private key */
|
|
93
|
+
export function validatePrivateKey(key) {
|
|
94
|
+
return /^0x[0-9a-fA-F]{64}$/.test(key.trim());
|
|
95
|
+
}
|
|
96
|
+
/** Validate that a string looks like an Ethereum address */
|
|
97
|
+
export function validateAddress(addr) {
|
|
98
|
+
return /^0x[0-9a-fA-F]{40}$/.test(addr.trim());
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/utils/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAuB,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,gBAAgB,EAA4C,MAAM,eAAe,CAAC;AAE1H;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,QAAiB;IAC5C,MAAM,GAAG,GAAG,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IACnD,IAAI,CAAC,GAAG;QAAE,OAAO,aAAa,CAAC;IAE/B,MAAM,SAAS,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC;IACzC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,UAAU,CAClB,kBAAkB,GAAG,mJAAmJ,EACxK,eAAe,CAChB,CAAC;IACJ,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,4EAA4E;AAC5E,OAAO,EAAE,gBAAgB,EAAE,CAAC;AAE5B;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,KAAmC;IAEnC,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IACpD,IAAI,CAAC,UAAU,EAAE,CAAC;QAChB,MAAM,IAAI,UAAU,CAClB,sFAAsF,EACtF,cAAc,CACf,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,UAAU,CAClB,oFAAoF,EACpF,cAAc,CACf,CAAC;IACJ,CAAC;IAED,sGAAsG;IACtG,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC;IACtD,IAAI,WAAW,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,UAAU,CAClB,qFAAqF,EACrF,cAAc,CACf,CAAC;IACJ,CAAC;IAED,MAAM,aAAa,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IAE1C,kEAAkE;IAClE,2FAA2F;IAC3F,MAAM,gBAAgB,GAAG,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,EAAE,WAAW,EAAE,KAAK,MAAM,CAAC;IAE3F,MAAM,MAAM,GAAmB;QAC7B,UAAU,EAAE,UAA2B;QACvC,KAAK,EAAE,aAAa;QACpB,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC;QAC1C,WAAW,EAAE,WAAwC;QACrD,gBAAgB;KACjB,CAAC;IAEF,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IAC5C,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,CAAC,MAAM,GAAG,MAAM,CAAC;IACzB,CAAC;IAED,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAC;IACpD,IAAI,UAAU,EAAE,CAAC;QACf,MAAM,CAAC,UAAU,GAAG,UAAU,CAAC;IACjC,CAAC;IAED,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IACxD,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,CAAC,YAAY,GAAG,YAAY,CAAC;IACrC,CAAC;IAED,+DAA+D;IAC/D,qFAAqF;IACrF,8DAA8D;IAC9D,MAAM,iBAAiB,GAAG,OAAO,CAAC,GAAG,CAAC,qBAAqB,CAAC,CAAC;IAC7D,IAAI,iBAAiB,EAAE,CAAC;QACtB,MAAM,UAAU,GAAe;YAC7B,eAAe,EAAE,iBAAiB;YAClC,GAAG,EAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAA0B,IAAI,YAAY;SACvE,CAAC;QACF,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;QAC/C,IAAI,UAAU,EAAE,CAAC;YACf,UAAU,CAAC,MAAM,GAAG,UAAU,CAAC;QACjC,CAAC;QACD,MAAM,CAAC,IAAI,GAAG,UAAU,CAAC;IAC3B,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IAE7C,uDAAuD;IACvD,gFAAgF;IAChF,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,gBAAgB,EAAE,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,uEAAuE;IACzE,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,0DAA0D;AAC1D,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,OAAO,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;AAChD,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,OAAO,qBAAqB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;AACjD,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-computed 4-byte error selectors for Azeth contracts.
|
|
3
|
+
* Maps selector hex (0x + 8 chars) → human-readable name + description.
|
|
4
|
+
*
|
|
5
|
+
* Selectors are derived from keccak256 of each custom error signature.
|
|
6
|
+
* Pre-computed to avoid runtime dependency on viem's keccak256 (which
|
|
7
|
+
* breaks in test environments where viem is mocked).
|
|
8
|
+
*
|
|
9
|
+
* To regenerate: `pnpm exec tsx scripts/compute-selectors.ts`
|
|
10
|
+
*/
|
|
11
|
+
/** Attempt to decode a 4-byte error selector from an error message string.
|
|
12
|
+
* Returns a human-readable description if a known selector is found.
|
|
13
|
+
*
|
|
14
|
+
* Handles three cases:
|
|
15
|
+
* 1. Standalone selectors: 0x1f8f95a0
|
|
16
|
+
* 2. Outer selector of long hex: first 8 chars of 0x1f8f95a0000000...
|
|
17
|
+
* 3. Inner selectors in ABI-encoded revert data: EntryPoint wraps module errors
|
|
18
|
+
* in FailedOpWithRevert(uint256,string,bytes), so the actual Azeth error
|
|
19
|
+
* selector is buried inside the hex at a 32-byte ABI boundary.
|
|
20
|
+
* 4. Error(string) decoding: extracts the revert string from standard Solidity reverts
|
|
21
|
+
*/
|
|
22
|
+
export declare function decodeErrorSelector(message: string): string | undefined;
|
|
23
|
+
//# sourceMappingURL=error-selectors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-selectors.d.ts","sourceRoot":"","sources":["../../src/utils/error-selectors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AA2EH;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CA4BvE"}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-computed 4-byte error selectors for Azeth contracts.
|
|
3
|
+
* Maps selector hex (0x + 8 chars) → human-readable name + description.
|
|
4
|
+
*
|
|
5
|
+
* Selectors are derived from keccak256 of each custom error signature.
|
|
6
|
+
* Pre-computed to avoid runtime dependency on viem's keccak256 (which
|
|
7
|
+
* breaks in test environments where viem is mocked).
|
|
8
|
+
*
|
|
9
|
+
* To regenerate: `pnpm exec tsx scripts/compute-selectors.ts`
|
|
10
|
+
*/
|
|
11
|
+
/** Pre-computed selector → error info */
|
|
12
|
+
const SELECTOR_MAP = {
|
|
13
|
+
// AzethAccount
|
|
14
|
+
'0xacfdb444': { name: 'ExecutionFailed', description: 'Smart account execution failed.' },
|
|
15
|
+
'0x5fc483c5': { name: 'OnlyOwner', description: 'Only the account owner can perform this action.' },
|
|
16
|
+
'0xbd07c551': { name: 'OnlyEntryPoint', description: 'Only the EntryPoint contract can call this function.' },
|
|
17
|
+
'0x7fb6be02': { name: 'OnlyExecutor', description: 'Only an installed executor module can call this function.' },
|
|
18
|
+
'0xea8e4eb5': { name: 'NotAuthorized', description: 'The caller is not authorized for this operation.' },
|
|
19
|
+
'0x49e27cff': { name: 'InvalidOwner', description: 'Invalid owner address.' },
|
|
20
|
+
'0x17e37b5c': { name: 'BatchLengthMismatch', description: 'Batch call arrays have mismatched lengths.' },
|
|
21
|
+
'0xcfc917be': { name: 'MaxHooksReached', description: 'Maximum number of hooks already installed.' },
|
|
22
|
+
'0xd393448a': { name: 'MismatchModuleTypeId', description: 'Module type ID does not match the expected type.' },
|
|
23
|
+
'0x172c3c6a': { name: 'ModuleAlreadyInstalled', description: 'This module is already installed on the account.' },
|
|
24
|
+
'0xbe601672': { name: 'ModuleNotInstalled', description: 'This module is not installed on the account.' },
|
|
25
|
+
// GuardianModule
|
|
26
|
+
'0xaf9aa1e0': { name: 'NotSmartAccount', description: 'The caller is not a recognized Azeth smart account.' },
|
|
27
|
+
'0xef6d0f02': { name: 'NotGuardian', description: 'Only the guardian can perform this operation.' },
|
|
28
|
+
'0x5684d698': { name: 'InvalidGuardrails', description: 'The guardrail parameters are invalid.' },
|
|
29
|
+
'0xa3fef2f8': { name: 'NoPendingChange', description: 'No guardrail change is pending.' },
|
|
30
|
+
'0x621e25c3': { name: 'TimelockNotExpired', description: 'The timelock period has not elapsed yet.' },
|
|
31
|
+
'0x0bbfcdcc': { name: 'NotTightening', description: 'Only tightening (reducing limits) can bypass the timelock.' },
|
|
32
|
+
'0x6320ab2b': { name: 'NoPendingEmergency', description: 'No emergency withdrawal is pending.' },
|
|
33
|
+
'0x4806710a': { name: 'ChangeAlreadyPending', description: 'A guardrail change is already pending.' },
|
|
34
|
+
'0x24831e77': { name: 'ExecutorSpendExceedsLimit', description: 'Payment amount exceeds the guardian daily spend limit.' },
|
|
35
|
+
// PaymentAgreementModule
|
|
36
|
+
'0x95a68634': { name: 'SelfAgreement', description: 'Cannot create a payment agreement with yourself.' },
|
|
37
|
+
'0xf84835a0': { name: 'TokenNotWhitelisted', description: 'The token is not in the guardian whitelist. Add it via setTokenWhitelist.' },
|
|
38
|
+
'0xb5c6c3ab': { name: 'AgreementNotExists', description: 'The specified agreement does not exist.' },
|
|
39
|
+
'0xfe1da89a': { name: 'InvalidAgreement', description: 'The agreement is invalid (already cancelled or completed).' },
|
|
40
|
+
'0x9563bcf0': { name: 'GuardianLimitExceeded', description: 'The payment exceeds guardian spending limits.' },
|
|
41
|
+
'0x90b8ec18': { name: 'TransferFailed', description: 'The token transfer failed.' },
|
|
42
|
+
// ReputationModule
|
|
43
|
+
'0xae525b83': { name: 'InsufficientPaymentUSD', description: 'You must pay at least $1 USD to the target before rating. Use azeth_get_net_paid to check your payment history.' },
|
|
44
|
+
'0xcb02f599': { name: 'SelfRatingNotAllowed', description: 'You cannot rate yourself.' },
|
|
45
|
+
'0x645c0b06': { name: 'SiblingRatingNotAllowed', description: 'You cannot rate accounts owned by the same EOA.' },
|
|
46
|
+
'0x565c8a5a': { name: 'InvalidValueDecimals', description: 'Value decimals must be between 0 and 18.' },
|
|
47
|
+
'0xe6394a77': { name: 'InvalidAgentId', description: 'The agent ID is not registered in the trust registry.' },
|
|
48
|
+
'0x15bebd27': { name: 'NotAzethAccount', description: 'The caller is not an Azeth smart account deployed by the factory.' },
|
|
49
|
+
// TrustRegistryModule
|
|
50
|
+
'0x3a81d6fc': { name: 'AlreadyRegistered', description: 'This account is already registered in the trust registry.' },
|
|
51
|
+
'0xaba47339': { name: 'NotRegistered', description: 'This account is not registered in the trust registry.' },
|
|
52
|
+
'0x3ba01911': { name: 'InvalidURI', description: 'The metadata URI is invalid.' },
|
|
53
|
+
// Factory
|
|
54
|
+
'0x30116425': { name: 'DeploymentFailed', description: 'Smart account deployment failed.' },
|
|
55
|
+
'0x367e9639': { name: 'AccountAlreadyDeployed', description: 'A smart account is already deployed at this address.' },
|
|
56
|
+
'0x99d0d56b': { name: 'MaxAccountsPerOwnerReached', description: 'Maximum accounts per owner has been reached.' },
|
|
57
|
+
// Oracle
|
|
58
|
+
'0x1f8f95a0': { name: 'InvalidOraclePrice', description: 'The oracle returned an invalid price.' },
|
|
59
|
+
'0xbf16aab6': { name: 'UnsupportedToken', description: 'The oracle does not support this token.' },
|
|
60
|
+
'0xcf479181': { name: 'InsufficientBalance', description: 'The smart account has insufficient token balance for this operation.' },
|
|
61
|
+
// Common (shared across multiple contracts)
|
|
62
|
+
'0x0dc149f0': { name: 'AlreadyInitialized', description: 'This module is already initialized for the account.' },
|
|
63
|
+
'0x87138d5c': { name: 'NotInitialized', description: 'The account has not been initialized on this module.' },
|
|
64
|
+
'0xe6c4247b': { name: 'InvalidAddress', description: 'An invalid address was provided.' },
|
|
65
|
+
'0x0c6d42ae': { name: 'OnlyFactory', description: 'Only the AzethFactory can call this function.' },
|
|
66
|
+
};
|
|
67
|
+
/** Well-known Solidity revert selectors (not Azeth-specific) */
|
|
68
|
+
const ERROR_STRING_SELECTOR = '08c379a0'; // Error(string)
|
|
69
|
+
const PANIC_SELECTOR = '4e487b71'; // Panic(uint256)
|
|
70
|
+
/** Attempt to decode a 4-byte error selector from an error message string.
|
|
71
|
+
* Returns a human-readable description if a known selector is found.
|
|
72
|
+
*
|
|
73
|
+
* Handles three cases:
|
|
74
|
+
* 1. Standalone selectors: 0x1f8f95a0
|
|
75
|
+
* 2. Outer selector of long hex: first 8 chars of 0x1f8f95a0000000...
|
|
76
|
+
* 3. Inner selectors in ABI-encoded revert data: EntryPoint wraps module errors
|
|
77
|
+
* in FailedOpWithRevert(uint256,string,bytes), so the actual Azeth error
|
|
78
|
+
* selector is buried inside the hex at a 32-byte ABI boundary.
|
|
79
|
+
* 4. Error(string) decoding: extracts the revert string from standard Solidity reverts
|
|
80
|
+
*/
|
|
81
|
+
export function decodeErrorSelector(message) {
|
|
82
|
+
const hexMatches = message.matchAll(/0x([0-9a-fA-F]{8,})/g);
|
|
83
|
+
for (const match of hexMatches) {
|
|
84
|
+
const hexData = match[1].toLowerCase();
|
|
85
|
+
// Pass 1: Check the outer selector (first 8 chars)
|
|
86
|
+
const outerSelector = `0x${hexData.slice(0, 8)}`;
|
|
87
|
+
const outerKnown = SELECTOR_MAP[outerSelector];
|
|
88
|
+
if (outerKnown)
|
|
89
|
+
return `${outerKnown.name}: ${outerKnown.description}`;
|
|
90
|
+
// Pass 1b: Try to decode Error(string) at the outer level
|
|
91
|
+
const outerString = tryDecodeErrorString(hexData);
|
|
92
|
+
if (outerString)
|
|
93
|
+
return outerString;
|
|
94
|
+
// Pass 2: Scan interior at 64-char (32-byte) ABI word boundaries for known
|
|
95
|
+
// selectors. ERC-4337 EntryPoint wraps inner reverts in FailedOpWithRevert
|
|
96
|
+
// where the actual error selector is ABI-encoded at a word boundary.
|
|
97
|
+
for (let i = 64; i + 8 <= hexData.length; i += 64) {
|
|
98
|
+
const innerSelector = `0x${hexData.slice(i, i + 8)}`;
|
|
99
|
+
const innerKnown = SELECTOR_MAP[innerSelector];
|
|
100
|
+
if (innerKnown)
|
|
101
|
+
return `${innerKnown.name}: ${innerKnown.description}`;
|
|
102
|
+
// Try to decode inner Error(string) — common in EntryPoint FailedOpWithRevert
|
|
103
|
+
const innerString = tryDecodeErrorString(hexData.slice(i));
|
|
104
|
+
if (innerString)
|
|
105
|
+
return innerString;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
/** Try to ABI-decode an Error(string) or Panic(uint256) from raw hex data.
|
|
111
|
+
* Returns a human-readable string if successful, undefined otherwise.
|
|
112
|
+
*
|
|
113
|
+
* Error(string) layout: 08c379a0 + offset(32B) + length(32B) + utf8 data
|
|
114
|
+
* Panic(uint256) layout: 4e487b71 + code(32B)
|
|
115
|
+
*/
|
|
116
|
+
function tryDecodeErrorString(hexData) {
|
|
117
|
+
const selector = hexData.slice(0, 8);
|
|
118
|
+
if (selector === ERROR_STRING_SELECTOR && hexData.length >= 8 + 128) {
|
|
119
|
+
// Error(string): skip selector(8) + offset(64), read length(64), then string data
|
|
120
|
+
try {
|
|
121
|
+
const lengthHex = hexData.slice(72, 136);
|
|
122
|
+
const length = parseInt(lengthHex, 16);
|
|
123
|
+
if (length > 0 && length <= 256 && hexData.length >= 136 + length * 2) {
|
|
124
|
+
const strHex = hexData.slice(136, 136 + length * 2);
|
|
125
|
+
const bytes = new Uint8Array(length);
|
|
126
|
+
for (let i = 0; i < length; i++) {
|
|
127
|
+
bytes[i] = parseInt(strHex.slice(i * 2, i * 2 + 2), 16);
|
|
128
|
+
}
|
|
129
|
+
const decoded = new TextDecoder().decode(bytes);
|
|
130
|
+
// Only return if it's printable ASCII (not garbage)
|
|
131
|
+
if (/^[\x20-\x7E]+$/.test(decoded)) {
|
|
132
|
+
return `Revert: ${decoded}`;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
// Malformed — fall through
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (selector === PANIC_SELECTOR && hexData.length >= 72) {
|
|
141
|
+
const codeHex = hexData.slice(8, 72);
|
|
142
|
+
const code = parseInt(codeHex, 16);
|
|
143
|
+
const panicReasons = {
|
|
144
|
+
0x00: 'generic compiler panic',
|
|
145
|
+
0x01: 'assertion failed',
|
|
146
|
+
0x11: 'arithmetic overflow/underflow',
|
|
147
|
+
0x12: 'division by zero',
|
|
148
|
+
0x21: 'invalid enum value',
|
|
149
|
+
0x22: 'invalid storage encoding',
|
|
150
|
+
0x31: 'empty array pop',
|
|
151
|
+
0x32: 'array index out of bounds',
|
|
152
|
+
0x41: 'out of memory',
|
|
153
|
+
0x51: 'invalid internal function call',
|
|
154
|
+
};
|
|
155
|
+
return `Panic(${code}): ${panicReasons[code] ?? 'unknown panic code'}`;
|
|
156
|
+
}
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
//# sourceMappingURL=error-selectors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-selectors.js","sourceRoot":"","sources":["../../src/utils/error-selectors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
export interface RateLimiter {
|
|
3
|
+
/** Returns true if the call is allowed, false if rate-limited */
|
|
4
|
+
check(toolName: string): boolean;
|
|
5
|
+
/** Reset all state (for testing) */
|
|
6
|
+
reset(): void;
|
|
7
|
+
/** Stop the periodic cleanup timer (for graceful shutdown / tests) */
|
|
8
|
+
destroy(): void;
|
|
9
|
+
}
|
|
10
|
+
/** Create an in-memory per-tool rate limiter with periodic cleanup */
|
|
11
|
+
export declare function createRateLimiter(maxCalls?: number, windowMs?: number): RateLimiter;
|
|
12
|
+
/**
|
|
13
|
+
* Wrap an McpServer so that each tool registered through the wrapper
|
|
14
|
+
* has per-tool rate limiting applied before the handler executes.
|
|
15
|
+
*/
|
|
16
|
+
export declare function wrapServerWithRateLimit(server: McpServer, limiter: RateLimiter): McpServer;
|
|
17
|
+
//# sourceMappingURL=rate-limit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/utils/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AASzE,MAAM,WAAW,WAAW;IAC1B,iEAAiE;IACjE,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IACjC,oCAAoC;IACpC,KAAK,IAAI,IAAI,CAAC;IACd,sEAAsE;IACtE,OAAO,IAAI,IAAI,CAAC;CACjB;AAKD,sEAAsE;AACtE,wBAAgB,iBAAiB,CAC/B,QAAQ,SAAuB,EAC/B,QAAQ,SAAY,GACnB,WAAW,CAyCb;AAED;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,SAAS,EACjB,OAAO,EAAE,WAAW,GACnB,SAAS,CA8BX"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { error } from './response.js';
|
|
2
|
+
/** Maximum tool invocations per tool per window */
|
|
3
|
+
const MAX_CALLS_PER_MINUTE = 30;
|
|
4
|
+
/** Sliding window duration in milliseconds */
|
|
5
|
+
const WINDOW_MS = 60_000;
|
|
6
|
+
/** Periodic cleanup interval for expired timestamps (60s) */
|
|
7
|
+
const CLEANUP_INTERVAL_MS = 60_000;
|
|
8
|
+
/** Create an in-memory per-tool rate limiter with periodic cleanup */
|
|
9
|
+
export function createRateLimiter(maxCalls = MAX_CALLS_PER_MINUTE, windowMs = WINDOW_MS) {
|
|
10
|
+
const timestamps = new Map();
|
|
11
|
+
// Periodic cleanup to prevent memory leak from stale tool entries
|
|
12
|
+
const cleanupTimer = setInterval(() => {
|
|
13
|
+
const now = Date.now();
|
|
14
|
+
for (const [tool, times] of timestamps) {
|
|
15
|
+
const recent = times.filter(t => now - t < windowMs);
|
|
16
|
+
if (recent.length === 0) {
|
|
17
|
+
timestamps.delete(tool);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
timestamps.set(tool, recent);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}, CLEANUP_INTERVAL_MS);
|
|
24
|
+
// Allow the process to exit without waiting for this timer
|
|
25
|
+
if (cleanupTimer && typeof cleanupTimer === 'object' && 'unref' in cleanupTimer) {
|
|
26
|
+
cleanupTimer.unref();
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
check(toolName) {
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
const existing = timestamps.get(toolName) ?? [];
|
|
32
|
+
const recent = existing.filter(t => now - t < windowMs);
|
|
33
|
+
if (recent.length >= maxCalls) {
|
|
34
|
+
timestamps.set(toolName, recent);
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
recent.push(now);
|
|
38
|
+
timestamps.set(toolName, recent);
|
|
39
|
+
return true;
|
|
40
|
+
},
|
|
41
|
+
reset() {
|
|
42
|
+
timestamps.clear();
|
|
43
|
+
},
|
|
44
|
+
destroy() {
|
|
45
|
+
clearInterval(cleanupTimer);
|
|
46
|
+
timestamps.clear();
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Wrap an McpServer so that each tool registered through the wrapper
|
|
52
|
+
* has per-tool rate limiting applied before the handler executes.
|
|
53
|
+
*/
|
|
54
|
+
export function wrapServerWithRateLimit(server, limiter) {
|
|
55
|
+
const originalRegisterTool = server.registerTool.bind(server);
|
|
56
|
+
// Create a proxy that intercepts registerTool calls
|
|
57
|
+
const proxy = new Proxy(server, {
|
|
58
|
+
get(target, prop, receiver) {
|
|
59
|
+
if (prop === 'registerTool') {
|
|
60
|
+
return function registerToolWithRateLimit(name, config, handler) {
|
|
61
|
+
const wrappedHandler = async (...handlerArgs) => {
|
|
62
|
+
if (!limiter.check(name)) {
|
|
63
|
+
return error('RATE_LIMITED', `Too many requests for tool "${name}" — please wait before retrying.`, 'Wait 60 seconds before calling this tool again.');
|
|
64
|
+
}
|
|
65
|
+
return handler(...handlerArgs);
|
|
66
|
+
};
|
|
67
|
+
return originalRegisterTool(name, config, wrappedHandler);
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return Reflect.get(target, prop, receiver);
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
return proxy;
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=rate-limit.js.map
|