@aura-payments/mcp-server 1.0.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/dist/bin.mjs ADDED
@@ -0,0 +1,568 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ import { AuraClient, AuraAuthenticationError, AuraNotFoundError, AuraValidationError, AuraRateLimitError, AuraTimeoutError, AuraAPIError, AuraError } from '@aura-payments/sdk';
5
+ import { z } from 'zod';
6
+
7
+ var cached = null;
8
+ function getClient() {
9
+ if (cached) return cached;
10
+ const apiKey = process.env.AURA_API_KEY;
11
+ if (!apiKey) {
12
+ throw new MissingAuraApiKeyError();
13
+ }
14
+ cached = new AuraClient({
15
+ apiKey,
16
+ baseUrl: process.env.AURA_BASE_URL,
17
+ // Honor caller's preference; default to SDK default (auto on POST/PUT)
18
+ autoIdempotency: process.env.AURA_AUTO_IDEMPOTENCY !== "false"
19
+ });
20
+ return cached;
21
+ }
22
+ var MissingAuraApiKeyError = class extends Error {
23
+ constructor() {
24
+ super(
25
+ 'AURA_API_KEY not set. Configure it in your MCP client config \u2014 for example in mcp.json: { "mcpServers": { "aura": { "command": "aura-mcp", "env": { "AURA_API_KEY": "pk_live_..." } } } }. Get a key at https://getaura.sh.'
26
+ );
27
+ this.name = "MissingAuraApiKeyError";
28
+ }
29
+ };
30
+
31
+ // src/resources/agents.ts
32
+ function registerAgentResources(server) {
33
+ server.resource(
34
+ "agent",
35
+ new ResourceTemplate("aura://agents/{id}", { list: void 0 }),
36
+ async (uri, { id }) => {
37
+ const client = getClient();
38
+ const agent = await client.agents.get(String(id));
39
+ return {
40
+ contents: [
41
+ {
42
+ uri: uri.href,
43
+ mimeType: "application/json",
44
+ text: JSON.stringify(agent, null, 2)
45
+ }
46
+ ]
47
+ };
48
+ }
49
+ );
50
+ server.resource(
51
+ "agent-balance",
52
+ new ResourceTemplate("aura://agents/{id}/balance", { list: void 0 }),
53
+ async (uri, { id }) => {
54
+ const client = getClient();
55
+ const balance = await client.agents.getBalance(String(id));
56
+ return {
57
+ contents: [
58
+ {
59
+ uri: uri.href,
60
+ mimeType: "application/json",
61
+ text: JSON.stringify(balance, null, 2)
62
+ }
63
+ ]
64
+ };
65
+ }
66
+ );
67
+ }
68
+ function registerMandateResources(server) {
69
+ server.resource(
70
+ "mandates-pending",
71
+ "aura://mandates/pending",
72
+ async (uri) => {
73
+ const client = getClient();
74
+ const page = await client.mandates.listPending({ limit: 50 });
75
+ return {
76
+ contents: [
77
+ {
78
+ uri: uri.href,
79
+ mimeType: "application/json",
80
+ text: JSON.stringify(page, null, 2)
81
+ }
82
+ ]
83
+ };
84
+ }
85
+ );
86
+ server.resource(
87
+ "mandate",
88
+ new ResourceTemplate("aura://mandates/{id}", { list: void 0 }),
89
+ async (uri, { id }) => {
90
+ const client = getClient();
91
+ const mandate = await client.mandates.get(String(id));
92
+ return {
93
+ contents: [
94
+ {
95
+ uri: uri.href,
96
+ mimeType: "application/json",
97
+ text: JSON.stringify(mandate, null, 2)
98
+ }
99
+ ]
100
+ };
101
+ }
102
+ );
103
+ }
104
+ async function withToolErrorHandling(body, successFormatter = defaultJsonFormatter) {
105
+ try {
106
+ const result = await body();
107
+ return {
108
+ content: [{ type: "text", text: successFormatter(result) }]
109
+ };
110
+ } catch (err) {
111
+ return formatError(err);
112
+ }
113
+ }
114
+ function defaultJsonFormatter(data) {
115
+ return JSON.stringify(data, null, 2);
116
+ }
117
+ function formatError(err) {
118
+ if (err instanceof MissingAuraApiKeyError) {
119
+ return errorResult("CONFIG_ERROR", err.message);
120
+ }
121
+ if (err instanceof AuraAuthenticationError) {
122
+ return errorResult(
123
+ "AUTH_ERROR",
124
+ `Authentication failed: ${err.message}. Check that AURA_API_KEY is correct and not revoked.`
125
+ );
126
+ }
127
+ if (err instanceof AuraNotFoundError) {
128
+ return errorResult("NOT_FOUND", err.message);
129
+ }
130
+ if (err instanceof AuraValidationError) {
131
+ return errorResult("VALIDATION_ERROR", err.message);
132
+ }
133
+ if (err instanceof AuraRateLimitError) {
134
+ return errorResult(
135
+ "RATE_LIMITED",
136
+ `Rate limited. ${err.retryAfter ? `Retry after ${err.retryAfter}s.` : "Back off and retry."}`
137
+ );
138
+ }
139
+ if (err instanceof AuraTimeoutError) {
140
+ return errorResult("TIMEOUT", err.message);
141
+ }
142
+ if (err instanceof AuraAPIError) {
143
+ return errorResult(`API_ERROR_${err.statusCode}`, err.message);
144
+ }
145
+ if (err instanceof AuraError) {
146
+ return errorResult(err.code || "AURA_ERROR", err.message);
147
+ }
148
+ if (err instanceof Error) {
149
+ return errorResult("UNKNOWN_ERROR", err.message);
150
+ }
151
+ return errorResult("UNKNOWN_ERROR", String(err));
152
+ }
153
+ function errorResult(code, message) {
154
+ return {
155
+ isError: true,
156
+ content: [
157
+ {
158
+ type: "text",
159
+ text: `errorCode: ${code}
160
+ message: ${message}`
161
+ }
162
+ ]
163
+ };
164
+ }
165
+ function toolError(code, message) {
166
+ return errorResult(code, message);
167
+ }
168
+
169
+ // src/tools/agents.ts
170
+ var ChainSchema = z.enum(["ARC", "ARB", "BASE", "ETH", "MATIC"]);
171
+ var SpendingLimitSchema = z.object({
172
+ limitType: z.enum([
173
+ "per_transaction",
174
+ "daily",
175
+ "weekly",
176
+ "monthly",
177
+ "lifetime"
178
+ ]),
179
+ maxAmount: z.string().describe("Whole-number USDC amount as a string"),
180
+ currency: z.string().optional().default("USDC")
181
+ });
182
+ var MerchantRuleSchema = z.object({
183
+ ruleType: z.enum(["allowlist", "blocklist"]),
184
+ destinationType: z.enum(["address", "domain", "category", "entity_id"]),
185
+ destinationValue: z.string().min(1)
186
+ });
187
+ function registerAgentTools(server) {
188
+ server.tool(
189
+ "aura_create_agent",
190
+ "Create a new AI agent with a dedicated wallet and optional initial spending policy. The wallet is provisioned on-chain during this call (a few seconds latency).",
191
+ {
192
+ name: z.string().min(1).max(100),
193
+ description: z.string().max(500).optional(),
194
+ type: z.enum(["treasury", "payment", "trading", "custom"]),
195
+ chain: ChainSchema.optional().default("ARC"),
196
+ metadata: z.record(z.unknown()).optional(),
197
+ initialPolicy: z.object({
198
+ name: z.string().optional(),
199
+ description: z.string().optional(),
200
+ spendingLimits: z.array(SpendingLimitSchema).optional(),
201
+ merchantRules: z.array(MerchantRuleSchema).optional(),
202
+ requiresApprovalAbove: z.string().optional(),
203
+ autoApproveBelow: z.string().optional()
204
+ }).optional()
205
+ },
206
+ async (args) => withToolErrorHandling(async () => {
207
+ const client = getClient();
208
+ return client.agents.create(args);
209
+ })
210
+ );
211
+ server.tool(
212
+ "aura_list_agents",
213
+ "List agents for the authenticated account, paginated. Filter by status or type.",
214
+ {
215
+ status: z.enum(["active", "paused", "archived"]).optional(),
216
+ type: z.enum(["treasury", "payment", "trading", "custom"]).optional(),
217
+ limit: z.number().int().min(1).max(200).optional(),
218
+ offset: z.number().int().min(0).optional()
219
+ },
220
+ async (args) => withToolErrorHandling(async () => {
221
+ const client = getClient();
222
+ return client.agents.list(args);
223
+ })
224
+ );
225
+ server.tool(
226
+ "aura_get_agent",
227
+ "Get a single agent by id with its current status, wallet, and recent activity.",
228
+ {
229
+ agentId: z.string().uuid()
230
+ },
231
+ async ({ agentId }) => withToolErrorHandling(async () => {
232
+ const client = getClient();
233
+ return client.agents.get(agentId);
234
+ })
235
+ );
236
+ server.tool(
237
+ "aura_get_agent_balance",
238
+ "Get the current USDC balance of an agent's wallet (on-chain at request time).",
239
+ {
240
+ agentId: z.string().uuid()
241
+ },
242
+ async ({ agentId }) => withToolErrorHandling(async () => {
243
+ const client = getClient();
244
+ return client.agents.getBalance(agentId);
245
+ })
246
+ );
247
+ server.tool(
248
+ "aura_freeze_agent",
249
+ "Kill switch \u2014 halt all autonomous activity for an agent. Sets status=paused AND auto-rejects every pending mandate the agent has in flight. Idempotent.",
250
+ {
251
+ agentId: z.string().uuid(),
252
+ reason: z.string().max(500).optional().describe("Surfaced in activity event metadata for audit trail")
253
+ },
254
+ async (args) => withToolErrorHandling(async () => {
255
+ const client = getClient();
256
+ return client.agents.freeze(args);
257
+ })
258
+ );
259
+ server.tool(
260
+ "aura_unfreeze_agent",
261
+ "Resume autonomous activity for a frozen agent (status -> active). Previously cascade-rejected mandates are NOT restored \u2014 the agent re-creates them on its next loop.",
262
+ {
263
+ agentId: z.string().uuid()
264
+ },
265
+ async (args) => withToolErrorHandling(async () => {
266
+ const client = getClient();
267
+ return client.agents.unfreeze(args);
268
+ })
269
+ );
270
+ server.tool(
271
+ "aura_evaluate_policy",
272
+ "Run a hypothetical transaction through the policy engine WITHOUT executing it. Returns the decision (auto_approve / auto_deny / requires_human_approval) and which rules matched. When the decision requires human approval, a mandate row is created and its id is returned.",
273
+ {
274
+ agentId: z.string().uuid(),
275
+ amount: z.string().describe("USDC amount as decimal string"),
276
+ destination: z.string().describe("Counterparty wallet (0x...) or merchant identifier"),
277
+ destinationType: z.enum(["address", "domain", "category", "entity_id"]).optional(),
278
+ reasoning: z.string().optional(),
279
+ metadata: z.record(z.unknown()).optional()
280
+ },
281
+ async (args) => withToolErrorHandling(async () => {
282
+ const client = getClient();
283
+ return client.agents.evaluate(args);
284
+ })
285
+ );
286
+ }
287
+ var ChainSchema2 = z.enum(["ARC", "ARB", "BASE", "ETH", "MATIC"]);
288
+ var SplitInputSchema = z.object({
289
+ ownerType: z.enum([
290
+ "buyer",
291
+ "seller",
292
+ "platform",
293
+ "vendor",
294
+ "creator",
295
+ "affiliate",
296
+ "logistics",
297
+ "tax"
298
+ ]),
299
+ ownerId: z.string().uuid(),
300
+ role: z.enum([
301
+ "seller",
302
+ "platform",
303
+ "vendor",
304
+ "creator",
305
+ "affiliate",
306
+ "logistics",
307
+ "tax"
308
+ ]),
309
+ percentage: z.number().min(0).max(100)
310
+ });
311
+ function registerEscrowTools(server) {
312
+ server.tool(
313
+ "aura_create_escrow",
314
+ "Create a new escrow with payment splits across vendor/platform/seller wallets. Default chain is ARC. Async by default \u2014 returns an escrowId immediately while contract deployment continues in background.",
315
+ {
316
+ orderId: z.string().describe("Caller-provided order id, unique per account"),
317
+ buyerOwnerId: z.string().uuid().describe("UUID of the buyer in the platform"),
318
+ amount: z.string().describe('Whole-number USDC amount as a string (e.g. "100" for $100)'),
319
+ chain: ChainSchema2.optional().describe("Target chain. Defaults to ARC"),
320
+ splits: z.array(SplitInputSchema).min(1).describe("Payment splits. Percentages must sum to 100.")
321
+ },
322
+ async (args) => withToolErrorHandling(async () => {
323
+ const client = getClient();
324
+ return client.escrows.create(args);
325
+ })
326
+ );
327
+ server.tool(
328
+ "aura_get_escrow",
329
+ "Get the current state of an escrow by id, including splits and on-chain deployment status.",
330
+ {
331
+ escrowId: z.string().uuid()
332
+ },
333
+ async ({ escrowId }) => withToolErrorHandling(async () => {
334
+ const client = getClient();
335
+ return client.escrows.get(escrowId);
336
+ })
337
+ );
338
+ server.tool(
339
+ "aura_list_escrows",
340
+ "List escrows for the authenticated account, optionally filtered by state. Returns paginated results.",
341
+ {
342
+ state: z.enum([
343
+ "initiated",
344
+ "deployed",
345
+ "funded",
346
+ "locked",
347
+ "released",
348
+ "refunded",
349
+ "disputed",
350
+ "all"
351
+ ]).optional(),
352
+ sortBy: z.enum(["created_desc", "created_asc", "amount_desc", "amount_asc"]).optional(),
353
+ search: z.string().optional(),
354
+ limit: z.number().int().min(1).max(200).optional(),
355
+ offset: z.number().int().min(0).optional()
356
+ },
357
+ async (args) => withToolErrorHandling(async () => {
358
+ const client = getClient();
359
+ return client.escrows.list(args);
360
+ })
361
+ );
362
+ server.tool(
363
+ "aura_fund_escrow",
364
+ "Fund an existing escrow from the buyer wallet. Once funded, the escrow can be released (after unlock conditions) or refunded.",
365
+ {
366
+ escrowId: z.string().uuid(),
367
+ buyerWalletId: z.string().uuid(),
368
+ amount: z.string().describe("USDC amount to fund (whole-number string)"),
369
+ correlationId: z.string().optional()
370
+ },
371
+ async (args) => withToolErrorHandling(async () => {
372
+ const client = getClient();
373
+ return client.escrows.fund(args);
374
+ })
375
+ );
376
+ server.tool(
377
+ "aura_release_escrow",
378
+ "Release a funded escrow to the recipients per the original splits. Requires the unlock conditions to be met (or manual override with actorId).",
379
+ {
380
+ escrowId: z.string().uuid(),
381
+ actorId: z.string().uuid().describe("UUID of the user/agent performing the release"),
382
+ reason: z.string().optional(),
383
+ correlationId: z.string().optional()
384
+ },
385
+ async (args) => withToolErrorHandling(async () => {
386
+ const client = getClient();
387
+ return client.escrows.release(args);
388
+ })
389
+ );
390
+ server.tool(
391
+ "aura_refund_escrow",
392
+ "Refund a funded escrow back to the buyer. Requires a reason (audit trail).",
393
+ {
394
+ escrowId: z.string().uuid(),
395
+ reason: z.string().min(10),
396
+ actorId: z.string().uuid()
397
+ },
398
+ async (args) => withToolErrorHandling(async () => {
399
+ const client = getClient();
400
+ return client.escrows.refund(args);
401
+ })
402
+ );
403
+ server.tool(
404
+ "aura_receive_and_split",
405
+ 'Receive USDC and split it to recipients in ONE call \u2014 composes escrow create\u2192fund\u2192release. The payer (payerOwnerId) may be the agent itself (splitting funds it holds) or a counterparty; funds move from that party\'s Aura wallet. Returns a `stage` (created|deployed|funded|released): if it stops at "deployed" with `fundingRequired`, fund that wallet (e.g. aura_request_testnet_funds) and retry. Testnet only.',
406
+ {
407
+ orderId: z.string().describe("Caller-provided order id, unique per account"),
408
+ payerOwnerId: z.string().describe("Entity that funds the escrow (the agent itself or a counterparty)"),
409
+ amount: z.string().describe('Whole-number USDC amount as a string (e.g. "50"). Each split must net \u2265 $1 after fees.'),
410
+ splits: z.array(SplitInputSchema).min(1).describe("Payment splits. Percentages must sum to 100."),
411
+ chain: z.enum(["ARC", "ARB", "BASE"]).optional().describe("Testnet chain. Defaults to ARC."),
412
+ actorId: z.string().optional().describe("Actor recorded on release. Defaults to payerOwnerId."),
413
+ autoRelease: z.boolean().optional().describe("Release immediately after funding. Default true."),
414
+ waitForDeploymentMs: z.number().int().min(0).optional().describe("Max ms to wait for on-chain deployment. Default 60000."),
415
+ waitForFundingMs: z.number().int().min(0).optional().describe("Max ms to wait for funds to settle (funded/locked) before releasing. Default 60000.")
416
+ },
417
+ async (args) => withToolErrorHandling(async () => {
418
+ const client = getClient();
419
+ return client.escrows.receiveAndSplit(args);
420
+ })
421
+ );
422
+ }
423
+ function registerMandateTools(server) {
424
+ server.tool(
425
+ "aura_list_mandates",
426
+ "List mandates for the authenticated account. Defaults to status=pending so it acts as the operator's inbox by default. Most-urgent (soonest-expiring) first.",
427
+ {
428
+ status: z.enum(["pending", "approved", "rejected", "cancelled"]).optional().default("pending"),
429
+ agentId: z.string().uuid().optional(),
430
+ limit: z.number().int().min(1).max(200).optional(),
431
+ cursor: z.string().uuid().optional().describe("Last mandate id from the previous page")
432
+ },
433
+ async (args) => withToolErrorHandling(async () => {
434
+ const client = getClient();
435
+ return client.mandates.list(args);
436
+ })
437
+ );
438
+ server.tool(
439
+ "aura_get_mandate",
440
+ "Get a single mandate by id, including its full intent payload (the action the agent wants to take), expiry, and decision history.",
441
+ {
442
+ mandateId: z.string().uuid()
443
+ },
444
+ async ({ mandateId }) => withToolErrorHandling(async () => {
445
+ const client = getClient();
446
+ return client.mandates.get(mandateId);
447
+ })
448
+ );
449
+ server.tool(
450
+ "aura_approve_mandate",
451
+ "Approve a pending mandate. The decisionSignature MUST be precomputed by the caller (HMAC-SHA256 over `${mandateId}:approve:${decidedAt}`) \u2014 the LLM should never see the HMAC secret. Use the CLI or mobile app to compute it. Idempotent: re-approving an already-approved mandate returns the existing decision.",
452
+ {
453
+ mandateId: z.string().uuid(),
454
+ decisionMethod: z.enum(["biometric", "passcode", "manual"]),
455
+ decisionSignature: z.string().min(16).describe("HMAC-SHA256 hex digest, computed by the caller"),
456
+ decidedAt: z.string().datetime().optional().describe('ISO-8601 timestamp the signature commits to. Defaults to "now" server-side.')
457
+ },
458
+ async (args) => withToolErrorHandling(async () => {
459
+ const client = getClient();
460
+ return client.mandates.approve(args);
461
+ })
462
+ );
463
+ server.tool(
464
+ "aura_reject_mandate",
465
+ "Reject a pending mandate. Same signature scheme as approve \u2014 caller computes it, LLM forwards.",
466
+ {
467
+ mandateId: z.string().uuid(),
468
+ decisionMethod: z.enum(["biometric", "passcode", "manual"]),
469
+ decisionSignature: z.string().min(16),
470
+ decidedAt: z.string().datetime().optional(),
471
+ reason: z.string().optional().describe("Human-readable reason logged to the activity feed")
472
+ },
473
+ async (args) => withToolErrorHandling(async () => {
474
+ const client = getClient();
475
+ return client.mandates.reject(args);
476
+ })
477
+ );
478
+ }
479
+ var TestnetChainSchema = z.enum(["ARC", "ARB", "BASE"]);
480
+ function walletCreateEnabled() {
481
+ const v = (process.env.AURA_MCP_ALLOW_WALLET_CREATE ?? "").trim().toLowerCase();
482
+ return v === "1" || v === "true" || v === "yes";
483
+ }
484
+ function registerWalletTools(server) {
485
+ server.tool(
486
+ "aura_get_wallet_balance",
487
+ "Get a wallet's current USDC balance (on-chain at request time).",
488
+ {
489
+ walletId: z.string().uuid()
490
+ },
491
+ async ({ walletId }) => withToolErrorHandling(async () => {
492
+ const client = getClient();
493
+ return client.wallets.getBalance(walletId);
494
+ })
495
+ );
496
+ server.tool(
497
+ "aura_create_wallet",
498
+ "Provision (create-or-get) a wallet to RECEIVE USDC into. Idempotent and moves no money. Testnet only. Disabled unless the host sets AURA_MCP_ALLOW_WALLET_CREATE=1.",
499
+ {
500
+ entityId: z.string().min(1).describe("Your unique identifier for the wallet owner (e.g. the agent id)"),
501
+ chain: TestnetChainSchema.optional().describe("Testnet chain. Defaults to ARC."),
502
+ type: z.enum([
503
+ "BUSINESS",
504
+ "buyer",
505
+ "seller",
506
+ "platform",
507
+ "vendor",
508
+ "creator",
509
+ "affiliate",
510
+ "logistics",
511
+ "tax"
512
+ ]).optional().describe("Wallet owner type. Defaults to BUSINESS.")
513
+ },
514
+ async ({ entityId, chain, type }) => {
515
+ if (!walletCreateEnabled()) {
516
+ return toolError(
517
+ "WALLET_CREATE_DISABLED",
518
+ "Wallet creation is disabled on this MCP host. Set AURA_MCP_ALLOW_WALLET_CREATE=1 in the server env to enable it, or create the wallet via the Aura CLI (`aura wallet create`) or SDK."
519
+ );
520
+ }
521
+ return withToolErrorHandling(async () => {
522
+ const client = getClient();
523
+ return client.wallets.create({ entityId, chain, type });
524
+ });
525
+ }
526
+ );
527
+ server.tool(
528
+ "aura_request_testnet_funds",
529
+ "Request testnet USDC for a wallet from the platform faucet (testnet only). Drips are async \u2014 balance reflects after a few seconds. Returns FAUCET_UNAVAILABLE if the deployment has no faucet enabled.",
530
+ {
531
+ walletId: z.string().uuid(),
532
+ token: z.string().optional().describe("Token to drip. Defaults to 'USDC'."),
533
+ chain: TestnetChainSchema.optional().describe("Override the wallet's chain.")
534
+ },
535
+ async ({ walletId, token, chain }) => withToolErrorHandling(async () => {
536
+ const client = getClient();
537
+ return client.wallets.requestTestnetFunds(walletId, { token, chain });
538
+ })
539
+ );
540
+ }
541
+
542
+ // src/index.ts
543
+ function createServer(options = {}) {
544
+ const server = new McpServer({
545
+ name: options.name ?? "aura-payments",
546
+ version: options.version ?? "1.0.0"
547
+ });
548
+ registerEscrowTools(server);
549
+ registerAgentTools(server);
550
+ registerMandateTools(server);
551
+ registerWalletTools(server);
552
+ registerAgentResources(server);
553
+ registerMandateResources(server);
554
+ return server;
555
+ }
556
+
557
+ // src/bin.ts
558
+ async function main() {
559
+ const server = createServer();
560
+ const transport = new StdioServerTransport();
561
+ await server.connect(transport);
562
+ }
563
+ main().catch((err) => {
564
+ console.error("aura-mcp: fatal error:", err);
565
+ process.exit(1);
566
+ });
567
+ //# sourceMappingURL=bin.mjs.map
568
+ //# sourceMappingURL=bin.mjs.map