@aura-payments/mcp-server 1.0.1 → 1.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/dist/index.mjs CHANGED
@@ -3,13 +3,58 @@ import { AuraClient, AuraAuthenticationError, AuraNotFoundError, AuraValidationE
3
3
  import { z } from 'zod';
4
4
 
5
5
  // src/index.ts
6
+
7
+ // src/api-key.ts
8
+ var API_KEY_PREFIXES = [
9
+ "sk_test_",
10
+ "sk_live_",
11
+ "pk_test_",
12
+ "pk_live_"
13
+ ];
14
+ function maskApiKey(apiKey) {
15
+ const revealed = Math.min(8, Math.floor(apiKey.length / 2));
16
+ return `${apiKey.slice(0, revealed)}\u2026(${apiKey.length} chars)`;
17
+ }
18
+ function parseApiKey(apiKey) {
19
+ if (!apiKey || apiKey.trim() === "") {
20
+ throw new MissingAuraApiKeyError();
21
+ }
22
+ const prefix = API_KEY_PREFIXES.find((p) => apiKey.startsWith(p));
23
+ if (!prefix) {
24
+ throw new MalformedAuraApiKeyError(apiKey);
25
+ }
26
+ return { prefix, mode: prefix.includes("_test_") ? "test" : "live" };
27
+ }
28
+ function resolveKeyMode(apiKey) {
29
+ try {
30
+ return parseApiKey(apiKey).mode;
31
+ } catch (err) {
32
+ return err instanceof MissingAuraApiKeyError ? "unconfigured" : "invalid";
33
+ }
34
+ }
35
+ var MissingAuraApiKeyError = class extends Error {
36
+ constructor() {
37
+ super(
38
+ '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.'
39
+ );
40
+ this.name = "MissingAuraApiKeyError";
41
+ }
42
+ };
43
+ var MalformedAuraApiKeyError = class extends Error {
44
+ constructor(apiKey) {
45
+ super(
46
+ `AURA_API_KEY is malformed (got ${maskApiKey(apiKey)}). An Aura API key must start with one of: ${API_KEY_PREFIXES.join(", ")}. Check for a truncated copy/paste, a shell-quoting problem, or a key from a different service. Issue a new key at https://getaura.sh.`
47
+ );
48
+ this.name = "MalformedAuraApiKeyError";
49
+ }
50
+ };
51
+
52
+ // src/client.ts
6
53
  var cached = null;
7
54
  function getClient() {
8
55
  if (cached) return cached;
9
56
  const apiKey = process.env.AURA_API_KEY;
10
- if (!apiKey) {
11
- throw new MissingAuraApiKeyError();
12
- }
57
+ parseApiKey(apiKey);
13
58
  cached = new AuraClient({
14
59
  apiKey,
15
60
  baseUrl: process.env.AURA_BASE_URL,
@@ -18,14 +63,6 @@ function getClient() {
18
63
  });
19
64
  return cached;
20
65
  }
21
- var MissingAuraApiKeyError = class extends Error {
22
- constructor() {
23
- super(
24
- '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.'
25
- );
26
- this.name = "MissingAuraApiKeyError";
27
- }
28
- };
29
66
 
30
67
  // src/resources/agents.ts
31
68
  function registerAgentResources(server) {
@@ -64,6 +101,55 @@ function registerAgentResources(server) {
64
101
  }
65
102
  );
66
103
  }
104
+ var COLLECTION_LIMIT = 50;
105
+ function registerEscrowResources(server) {
106
+ server.resource("escrows", "aura://escrows", async (uri) => {
107
+ const client = getClient();
108
+ const page = await client.escrows.list({ limit: COLLECTION_LIMIT });
109
+ return {
110
+ contents: [
111
+ {
112
+ uri: uri.href,
113
+ mimeType: "application/json",
114
+ text: JSON.stringify(page, null, 2)
115
+ }
116
+ ]
117
+ };
118
+ });
119
+ server.resource(
120
+ "escrow",
121
+ new ResourceTemplate("aura://escrows/{id}", { list: void 0 }),
122
+ async (uri, { id }) => {
123
+ const client = getClient();
124
+ const escrow = await client.escrows.get(String(id));
125
+ return {
126
+ contents: [
127
+ {
128
+ uri: uri.href,
129
+ mimeType: "application/json",
130
+ text: JSON.stringify(escrow, null, 2)
131
+ }
132
+ ]
133
+ };
134
+ }
135
+ );
136
+ }
137
+
138
+ // src/resources/insights.ts
139
+ function registerInsightsResources(server, clientFactory = getClient) {
140
+ server.resource("insights-overview", "aura://insights/overview", async (uri) => {
141
+ const overview = await clientFactory().insights.overview();
142
+ return {
143
+ contents: [
144
+ {
145
+ uri: uri.href,
146
+ mimeType: "application/json",
147
+ text: JSON.stringify(overview, null, 2)
148
+ }
149
+ ]
150
+ };
151
+ });
152
+ }
67
153
  function registerMandateResources(server) {
68
154
  server.resource(
69
155
  "mandates-pending",
@@ -100,6 +186,108 @@ function registerMandateResources(server) {
100
186
  }
101
187
  );
102
188
  }
189
+ var COLLECTION_LIMIT2 = 50;
190
+ function registerWalletResources(server) {
191
+ server.resource("wallets", "aura://wallets", async (uri) => {
192
+ const client = getClient();
193
+ const page = await client.wallets.list({ limit: COLLECTION_LIMIT2 });
194
+ return {
195
+ contents: [
196
+ {
197
+ uri: uri.href,
198
+ mimeType: "application/json",
199
+ text: JSON.stringify(page, null, 2)
200
+ }
201
+ ]
202
+ };
203
+ });
204
+ server.resource(
205
+ "wallet",
206
+ new ResourceTemplate("aura://wallets/{id}", { list: void 0 }),
207
+ async (uri, { id }) => {
208
+ const client = getClient();
209
+ const wallet = await client.wallets.get(String(id));
210
+ return {
211
+ contents: [
212
+ {
213
+ uri: uri.href,
214
+ mimeType: "application/json",
215
+ text: JSON.stringify(wallet, null, 2)
216
+ }
217
+ ]
218
+ };
219
+ }
220
+ );
221
+ server.resource(
222
+ "wallet-balance",
223
+ new ResourceTemplate("aura://wallets/{id}/balance", { list: void 0 }),
224
+ async (uri, { id }) => {
225
+ const client = getClient();
226
+ const balance = await client.wallets.getBalance(String(id));
227
+ return {
228
+ contents: [
229
+ {
230
+ uri: uri.href,
231
+ mimeType: "application/json",
232
+ text: JSON.stringify(balance, null, 2)
233
+ }
234
+ ]
235
+ };
236
+ }
237
+ );
238
+ }
239
+ var COLLECTION_LIMIT3 = 50;
240
+ var WithdrawalIdSchema = z.string().uuid();
241
+ function registerWithdrawalResources(server, clientFactory = getClient) {
242
+ server.resource("withdrawals", "aura://withdrawals", async (uri) => {
243
+ const page = await clientFactory().withdrawals.list({ limit: COLLECTION_LIMIT3 });
244
+ return {
245
+ contents: [
246
+ {
247
+ uri: uri.href,
248
+ mimeType: "application/json",
249
+ text: JSON.stringify(page, null, 2)
250
+ }
251
+ ]
252
+ };
253
+ });
254
+ server.resource(
255
+ "withdrawal",
256
+ new ResourceTemplate("aura://withdrawals/{id}", { list: void 0 }),
257
+ async (uri, { id }) => {
258
+ const parsed = WithdrawalIdSchema.safeParse(Array.isArray(id) ? id[0] : id);
259
+ if (!parsed.success) {
260
+ throw new Error(`Invalid withdrawal id in ${uri.href}: expected a UUID`);
261
+ }
262
+ const withdrawal = await clientFactory().withdrawals.get(parsed.data);
263
+ return {
264
+ contents: [
265
+ {
266
+ uri: uri.href,
267
+ mimeType: "application/json",
268
+ text: JSON.stringify(withdrawal, null, 2)
269
+ }
270
+ ]
271
+ };
272
+ }
273
+ );
274
+ }
275
+
276
+ // src/annotations.ts
277
+ function readOnly(title) {
278
+ return { title, readOnlyHint: true };
279
+ }
280
+ function write(title, idempotent = false) {
281
+ return {
282
+ title,
283
+ readOnlyHint: false,
284
+ destructiveHint: false,
285
+ ...idempotent ? { idempotentHint: true } : {}
286
+ };
287
+ }
288
+ function destructive(title) {
289
+ return { title, readOnlyHint: false, destructiveHint: true };
290
+ }
103
291
  async function withToolErrorHandling(body, successFormatter = defaultJsonFormatter) {
104
292
  try {
105
293
  const result = await body();
@@ -114,7 +302,7 @@ function defaultJsonFormatter(data) {
114
302
  return JSON.stringify(data, null, 2);
115
303
  }
116
304
  function formatError(err) {
117
- if (err instanceof MissingAuraApiKeyError) {
305
+ if (err instanceof MissingAuraApiKeyError || err instanceof MalformedAuraApiKeyError) {
118
306
  return errorResult("CONFIG_ERROR", err.message);
119
307
  }
120
308
  if (err instanceof AuraAuthenticationError) {
@@ -202,6 +390,8 @@ function registerAgentTools(server) {
202
390
  autoApproveBelow: z.string().optional()
203
391
  }).optional()
204
392
  },
393
+ // Additive: provisions a new agent and its wallet. No funds move.
394
+ write("Create Agent"),
205
395
  async (args) => withToolErrorHandling(async () => {
206
396
  const client = getClient();
207
397
  return client.agents.create(args);
@@ -216,6 +406,7 @@ function registerAgentTools(server) {
216
406
  limit: z.number().int().min(1).max(200).optional(),
217
407
  offset: z.number().int().min(0).optional()
218
408
  },
409
+ readOnly("List Agents"),
219
410
  async (args) => withToolErrorHandling(async () => {
220
411
  const client = getClient();
221
412
  return client.agents.list(args);
@@ -227,6 +418,7 @@ function registerAgentTools(server) {
227
418
  {
228
419
  agentId: z.string().uuid()
229
420
  },
421
+ readOnly("Get Agent"),
230
422
  async ({ agentId }) => withToolErrorHandling(async () => {
231
423
  const client = getClient();
232
424
  return client.agents.get(agentId);
@@ -238,6 +430,7 @@ function registerAgentTools(server) {
238
430
  {
239
431
  agentId: z.string().uuid()
240
432
  },
433
+ readOnly("Get Agent Balance"),
241
434
  async ({ agentId }) => withToolErrorHandling(async () => {
242
435
  const client = getClient();
243
436
  return client.agents.getBalance(agentId);
@@ -250,6 +443,8 @@ function registerAgentTools(server) {
250
443
  agentId: z.string().uuid(),
251
444
  reason: z.string().max(500).optional().describe("Surfaced in activity event metadata for audit trail")
252
445
  },
446
+ // Halts an existing agent AND cascade-rejects its in-flight mandates.
447
+ destructive("Freeze Agent (kill switch)"),
253
448
  async (args) => withToolErrorHandling(async () => {
254
449
  const client = getClient();
255
450
  return client.agents.freeze(args);
@@ -261,6 +456,8 @@ function registerAgentTools(server) {
261
456
  {
262
457
  agentId: z.string().uuid()
263
458
  },
459
+ // Re-enables autonomous spending on an existing agent.
460
+ destructive("Unfreeze Agent (resumes autonomous spending)"),
264
461
  async (args) => withToolErrorHandling(async () => {
265
462
  const client = getClient();
266
463
  return client.agents.unfreeze(args);
@@ -277,11 +474,24 @@ function registerAgentTools(server) {
277
474
  reasoning: z.string().optional(),
278
475
  metadata: z.record(z.unknown()).optional()
279
476
  },
477
+ // NOT read-only despite being a dry run: when the decision requires human
478
+ // approval the engine persists a new mandate row. Additive, never
479
+ // destructive — it cannot execute the transaction it evaluates.
480
+ write("Evaluate Policy (dry run, may create a mandate)"),
280
481
  async (args) => withToolErrorHandling(async () => {
281
482
  const client = getClient();
282
483
  return client.agents.evaluate(args);
283
484
  })
284
485
  );
486
+ return [
487
+ "aura_create_agent",
488
+ "aura_list_agents",
489
+ "aura_get_agent",
490
+ "aura_get_agent_balance",
491
+ "aura_freeze_agent",
492
+ "aura_unfreeze_agent",
493
+ "aura_evaluate_policy"
494
+ ];
285
495
  }
286
496
  var ChainSchema2 = z.enum(["ARC", "ARB", "BASE", "ETH", "MATIC"]);
287
497
  var SplitInputSchema = z.object({
@@ -318,6 +528,8 @@ function registerEscrowTools(server) {
318
528
  chain: ChainSchema2.optional().describe("Target chain. Defaults to ARC"),
319
529
  splits: z.array(SplitInputSchema).min(1).describe("Payment splits. Percentages must sum to 100.")
320
530
  },
531
+ // Additive: creates a new escrow record. No funds move until `fund`.
532
+ write("Create Escrow"),
321
533
  async (args) => withToolErrorHandling(async () => {
322
534
  const client = getClient();
323
535
  return client.escrows.create(args);
@@ -329,6 +541,7 @@ function registerEscrowTools(server) {
329
541
  {
330
542
  escrowId: z.string().uuid()
331
543
  },
544
+ readOnly("Get Escrow"),
332
545
  async ({ escrowId }) => withToolErrorHandling(async () => {
333
546
  const client = getClient();
334
547
  return client.escrows.get(escrowId);
@@ -353,6 +566,7 @@ function registerEscrowTools(server) {
353
566
  limit: z.number().int().min(1).max(200).optional(),
354
567
  offset: z.number().int().min(0).optional()
355
568
  },
569
+ readOnly("List Escrows"),
356
570
  async (args) => withToolErrorHandling(async () => {
357
571
  const client = getClient();
358
572
  return client.escrows.list(args);
@@ -367,6 +581,8 @@ function registerEscrowTools(server) {
367
581
  amount: z.string().describe("USDC amount to fund (whole-number string)"),
368
582
  correlationId: z.string().optional()
369
583
  },
584
+ // Moves USDC out of the buyer wallet.
585
+ destructive("Fund Escrow (moves USDC)"),
370
586
  async (args) => withToolErrorHandling(async () => {
371
587
  const client = getClient();
372
588
  return client.escrows.fund(args);
@@ -381,6 +597,8 @@ function registerEscrowTools(server) {
381
597
  reason: z.string().optional(),
382
598
  correlationId: z.string().optional()
383
599
  },
600
+ // Irreversible payout to the split recipients.
601
+ destructive("Release Escrow (pays out USDC)"),
384
602
  async (args) => withToolErrorHandling(async () => {
385
603
  const client = getClient();
386
604
  return client.escrows.release(args);
@@ -394,6 +612,8 @@ function registerEscrowTools(server) {
394
612
  reason: z.string().min(10),
395
613
  actorId: z.string().uuid()
396
614
  },
615
+ // Irreversible return of funds to the buyer.
616
+ destructive("Refund Escrow (returns USDC to buyer)"),
397
617
  async (args) => withToolErrorHandling(async () => {
398
618
  const client = getClient();
399
619
  return client.escrows.refund(args);
@@ -413,11 +633,134 @@ function registerEscrowTools(server) {
413
633
  waitForDeploymentMs: z.number().int().min(0).optional().describe("Max ms to wait for on-chain deployment. Default 60000."),
414
634
  waitForFundingMs: z.number().int().min(0).optional().describe("Max ms to wait for funds to settle (funded/locked) before releasing. Default 60000.")
415
635
  },
636
+ // Composes create -> fund -> release: money moves and is paid out.
637
+ destructive("Receive and Split (moves and pays out USDC)"),
416
638
  async (args) => withToolErrorHandling(async () => {
417
639
  const client = getClient();
418
640
  return client.escrows.receiveAndSplit(args);
419
641
  })
420
642
  );
643
+ return [
644
+ "aura_create_escrow",
645
+ "aura_get_escrow",
646
+ "aura_list_escrows",
647
+ "aura_fund_escrow",
648
+ "aura_release_escrow",
649
+ "aura_refund_escrow",
650
+ "aura_receive_and_split"
651
+ ];
652
+ }
653
+ function validationError(error) {
654
+ const detail = error.issues.map((issue) => `${issue.path.join(".") || "(input)"}: ${issue.message}`).join("; ");
655
+ return toolError("VALIDATION_ERROR", detail);
656
+ }
657
+ function addCustomRangeIssues(ctx, field, selector, start, end) {
658
+ if (selector === "custom" && (start === void 0 || end === void 0)) {
659
+ ctx.addIssue({
660
+ code: z.ZodIssueCode.custom,
661
+ path: [start === void 0 ? "start" : "end"],
662
+ message: `start and end are both required when ${field} is 'custom'`
663
+ });
664
+ }
665
+ if (start !== void 0 && end !== void 0 && Date.parse(start) > Date.parse(end)) {
666
+ ctx.addIssue({
667
+ code: z.ZodIssueCode.custom,
668
+ path: ["end"],
669
+ message: "end must be at or after start"
670
+ });
671
+ }
672
+ }
673
+
674
+ // src/tools/insights.ts
675
+ var PeriodSchema = z.enum(["today", "7d", "30d", "90d", "custom"]);
676
+ var DateTimeSchema = z.string().datetime();
677
+ var overviewShape = {
678
+ period: PeriodSchema.optional().describe(
679
+ "Window to report on (default 7d). 'custom' requires start and end."
680
+ ),
681
+ start: DateTimeSchema.optional().describe("ISO 8601 datetime; required when period is custom"),
682
+ end: DateTimeSchema.optional().describe("ISO 8601 datetime; required when period is custom")
683
+ };
684
+ var InsightsOverviewInput = z.object(overviewShape).superRefine(
685
+ (value, ctx) => addCustomRangeIssues(ctx, "period", value.period, value.start, value.end)
686
+ );
687
+ var auditShape = {
688
+ action: z.string().min(1).optional().describe("Audit action name, e.g. escrow.released"),
689
+ actor: z.enum(["user", "system", "api", "cron", "webhook"]).optional(),
690
+ resourceType: z.string().min(1).optional().describe("Resource kind, e.g. escrow"),
691
+ // `.min(1)`, not `.uuid()`: the route accepts any string here
692
+ // (`resourceId: z.string().optional()`, insights/audit/route.ts:26) and the
693
+ // audit log records ids from several subsystems, not all of them UUIDs.
694
+ resourceId: z.string().min(1).optional().describe("Id of the audited resource"),
695
+ range: PeriodSchema.optional().describe(
696
+ "Window to query (default 30d). 'custom' requires start and end."
697
+ ),
698
+ start: DateTimeSchema.optional().describe("ISO 8601 datetime; required when range is custom"),
699
+ end: DateTimeSchema.optional().describe("ISO 8601 datetime; required when range is custom"),
700
+ page: z.number().int().min(1).optional().describe("1-based page number (default 1)"),
701
+ limit: z.number().int().min(1).max(100).optional().describe("Page size, 1-100 (default 50)")
702
+ };
703
+ var InsightsAuditInput = z.object(auditShape).superRefine(
704
+ (value, ctx) => addCustomRangeIssues(ctx, "range", value.range, value.start, value.end)
705
+ );
706
+ function registerInsightsTools(server, clientFactory = getClient) {
707
+ server.tool(
708
+ "aura_insights_overview",
709
+ "Revenue, transaction count, active escrows and commissions for a period, each with its change against the previous period, plus sparkline and success-rate charts.",
710
+ overviewShape,
711
+ readOnly("Insights Overview"),
712
+ async (args) => {
713
+ const parsed = InsightsOverviewInput.safeParse(args);
714
+ if (!parsed.success) return validationError(parsed.error);
715
+ const { period, start, end } = parsed.data;
716
+ return withToolErrorHandling(
717
+ async () => clientFactory().insights.overview(period, start, end)
718
+ );
719
+ }
720
+ );
721
+ server.tool(
722
+ "aura_insights_forecast",
723
+ "Project a metric forward over a horizon using the requested algorithm. Requires a Growth or higher plan \u2014 lower tiers return feature_not_available.",
724
+ {
725
+ metric: z.enum([
726
+ "revenue",
727
+ "transaction_count",
728
+ "escrow_count",
729
+ "commission",
730
+ "avg_transaction_value"
731
+ ]),
732
+ algorithm: z.enum([
733
+ "linear_regression",
734
+ "moving_average",
735
+ "exponential_smoothing",
736
+ "arima",
737
+ "prophet",
738
+ "ensemble"
739
+ ]).optional(),
740
+ period: z.enum(["daily", "weekly", "monthly"]).optional().describe("Granularity of each projected point"),
741
+ horizon: z.number().int().min(1).max(365).optional().describe("Number of periods to project"),
742
+ // Known platform quirk: the route parses this with `z.coerce.boolean()`
743
+ // on a query string, where any non-empty value — including "false" —
744
+ // coerces to true. Passing false therefore does not currently suppress
745
+ // history; that is a platform-side fix, not one to paper over here.
746
+ includeHistory: z.boolean().optional().describe("Include the historical series alongside the projection"),
747
+ historyPeriods: z.number().int().min(1).max(365).optional()
748
+ },
749
+ readOnly("Insights Forecast"),
750
+ async (args) => withToolErrorHandling(async () => clientFactory().insights.forecast(args))
751
+ );
752
+ server.tool(
753
+ "aura_insights_audit",
754
+ "Query the account audit log by action, actor, resource and date range. Paginated, most recent first.",
755
+ auditShape,
756
+ readOnly("Insights Audit"),
757
+ async (args) => {
758
+ const parsed = InsightsAuditInput.safeParse(args);
759
+ if (!parsed.success) return validationError(parsed.error);
760
+ return withToolErrorHandling(async () => clientFactory().insights.audit(parsed.data));
761
+ }
762
+ );
763
+ return ["aura_insights_overview", "aura_insights_forecast", "aura_insights_audit"];
421
764
  }
422
765
  function registerMandateTools(server) {
423
766
  server.tool(
@@ -429,6 +772,7 @@ function registerMandateTools(server) {
429
772
  limit: z.number().int().min(1).max(200).optional(),
430
773
  cursor: z.string().uuid().optional().describe("Last mandate id from the previous page")
431
774
  },
775
+ readOnly("List Mandates (approval inbox)"),
432
776
  async (args) => withToolErrorHandling(async () => {
433
777
  const client = getClient();
434
778
  return client.mandates.list(args);
@@ -440,6 +784,7 @@ function registerMandateTools(server) {
440
784
  {
441
785
  mandateId: z.string().uuid()
442
786
  },
787
+ readOnly("Get Mandate"),
443
788
  async ({ mandateId }) => withToolErrorHandling(async () => {
444
789
  const client = getClient();
445
790
  return client.mandates.get(mandateId);
@@ -454,6 +799,8 @@ function registerMandateTools(server) {
454
799
  decisionSignature: z.string().min(16).describe("HMAC-SHA256 hex digest, computed by the caller"),
455
800
  decidedAt: z.string().datetime().optional().describe('ISO-8601 timestamp the signature commits to. Defaults to "now" server-side.')
456
801
  },
802
+ // Authorizes the agent's intended payment — the money-moving decision.
803
+ destructive("Approve Mandate (authorizes payment)"),
457
804
  async (args) => withToolErrorHandling(async () => {
458
805
  const client = getClient();
459
806
  return client.mandates.approve(args);
@@ -469,11 +816,31 @@ function registerMandateTools(server) {
469
816
  decidedAt: z.string().datetime().optional(),
470
817
  reason: z.string().optional().describe("Human-readable reason logged to the activity feed")
471
818
  },
819
+ // Terminal decision on an existing mandate — the agent cannot resume it.
820
+ destructive("Reject Mandate"),
472
821
  async (args) => withToolErrorHandling(async () => {
473
822
  const client = getClient();
474
823
  return client.mandates.reject(args);
475
824
  })
476
825
  );
826
+ return [
827
+ "aura_list_mandates",
828
+ "aura_get_mandate",
829
+ "aura_approve_mandate",
830
+ "aura_reject_mandate"
831
+ ];
832
+ }
833
+
834
+ // src/tools/treasury.ts
835
+ function registerTreasuryTools(server, clientFactory = getClient) {
836
+ server.tool(
837
+ "aura_treasury_optimize",
838
+ "Analyze treasury balances, active escrows and pending payouts, and return an AI recommendation on how to rebalance. Read-only: it recommends, it does not move funds. Requires a Growth or higher plan \u2014 lower tiers return feature_not_available.",
839
+ {},
840
+ readOnly("Treasury Optimize"),
841
+ async () => withToolErrorHandling(async () => clientFactory().treasury.optimize())
842
+ );
843
+ return ["aura_treasury_optimize"];
477
844
  }
478
845
  var TestnetChainSchema = z.enum(["ARC", "ARB", "BASE"]);
479
846
  function walletCreateEnabled() {
@@ -487,6 +854,7 @@ function registerWalletTools(server) {
487
854
  {
488
855
  walletId: z.string().uuid()
489
856
  },
857
+ readOnly("Get Wallet Balance"),
490
858
  async ({ walletId }) => withToolErrorHandling(async () => {
491
859
  const client = getClient();
492
860
  return client.wallets.getBalance(walletId);
@@ -510,6 +878,8 @@ function registerWalletTools(server) {
510
878
  "tax"
511
879
  ]).optional().describe("Wallet owner type. Defaults to BUSINESS.")
512
880
  },
881
+ // Additive and create-or-get: provisions a receive address, moves no money.
882
+ write("Create Wallet (receive address)", true),
513
883
  async ({ entityId, chain, type }) => {
514
884
  if (!walletCreateEnabled()) {
515
885
  return toolError(
@@ -531,26 +901,216 @@ function registerWalletTools(server) {
531
901
  token: z.string().optional().describe("Token to drip. Defaults to 'USDC'."),
532
902
  chain: TestnetChainSchema.optional().describe("Override the wallet's chain.")
533
903
  },
904
+ // Additive: testnet-only drip that adds play funds, destroys nothing.
905
+ write("Request Testnet Funds"),
534
906
  async ({ walletId, token, chain }) => withToolErrorHandling(async () => {
535
907
  const client = getClient();
536
908
  return client.wallets.requestTestnetFunds(walletId, { token, chain });
537
909
  })
538
910
  );
911
+ return [
912
+ "aura_get_wallet_balance",
913
+ "aura_create_wallet",
914
+ "aura_request_testnet_funds"
915
+ ];
916
+ }
917
+ var WebhookEventTypeSchema = z.enum([
918
+ "*",
919
+ "escrow.created",
920
+ "escrow.funded",
921
+ "escrow.locked",
922
+ "escrow.released",
923
+ "escrow.refunded",
924
+ "escrow.disputed",
925
+ "transfer.completed",
926
+ "transfer.failed",
927
+ "withdrawal.created",
928
+ "withdrawal.processing",
929
+ "withdrawal.completed",
930
+ "withdrawal.failed",
931
+ "account.kyc_approved",
932
+ "account.kyc_rejected",
933
+ "account.tier_upgraded",
934
+ "security.api_key_created",
935
+ "security.api_key_revoked"
936
+ ]);
937
+ function registerWebhookTools(server) {
938
+ server.tool(
939
+ "aura_get_webhook_config",
940
+ "Get the current webhook configuration for the authenticated account: delivery URL, subscribed event types, and status. The signing secret is never returned by this endpoint.",
941
+ {},
942
+ readOnly("Get Webhook Config"),
943
+ async () => withToolErrorHandling(async () => {
944
+ const client = getClient();
945
+ return client.webhooks.getConfig();
946
+ })
947
+ );
948
+ server.tool(
949
+ "aura_set_webhook_config",
950
+ "Set the webhook delivery endpoint for the account. An account has ONE configuration, so this REPLACES any existing one \u2014 events stop going to the previous URL. Must be an https:// URL you control. Returns the signing secret; store it, it is shown only on initial configuration.",
951
+ {
952
+ url: z.string().url().describe("HTTPS endpoint that will receive event POSTs"),
953
+ events: z.array(WebhookEventTypeSchema).min(1).optional().describe(
954
+ "Event types to subscribe to. Omit to subscribe to all events, or pass ['*'] explicitly."
955
+ ),
956
+ description: z.string().optional().describe("Optional human-readable label")
957
+ },
958
+ destructive("Set Webhook Config (replaces existing)"),
959
+ async (args) => withToolErrorHandling(async () => {
960
+ const client = getClient();
961
+ return client.webhooks.configure(args);
962
+ })
963
+ );
964
+ server.tool(
965
+ "aura_delete_webhook_config",
966
+ "Delete the account webhook configuration. Event delivery stops immediately and the signing secret is invalidated \u2014 reconfiguring issues a new secret.",
967
+ {},
968
+ destructive("Delete Webhook Config"),
969
+ async () => withToolErrorHandling(async () => {
970
+ const client = getClient();
971
+ return client.webhooks.delete();
972
+ })
973
+ );
974
+ return [
975
+ "aura_get_webhook_config",
976
+ "aura_set_webhook_config",
977
+ "aura_delete_webhook_config"
978
+ ];
979
+ }
980
+ var ChainSchema3 = z.enum(["ARC", "ETH", "BASE", "ARB", "MATIC"]);
981
+ var AmountSchema = z.string().regex(
982
+ /^\d+(\.\d{1,6})?$/,
983
+ 'Amount must be a number string with up to 6 decimal places, e.g. "100.00"'
984
+ );
985
+ var EvmAddressSchema = z.string().regex(
986
+ /^0x[a-fA-F0-9]{40}$/,
987
+ "Must be an EVM address: 0x followed by 40 hexadecimal characters"
988
+ );
989
+ var ExchangeSchema = z.enum(["binance", "coinbase", "kraken", "other"]);
990
+ var FeeConfigSchema = z.enum(["FREE", "GAS_ONLY", "STANDARD"]);
991
+ var StatusSchema = z.enum(["pending", "submitted", "confirmed", "failed", "cancelled"]);
992
+ var WithdrawalIdSchema2 = z.string().uuid("Withdrawal id must be a UUID");
993
+ var estimateShape = {
994
+ // `.min(1)`, not `.uuid()`: the route accepts any non-empty string here
995
+ // (`walletId: z.string().min(1)`, estimate/route.ts:56). Requiring a UUID
996
+ // would make this tool reject wallet ids the platform itself accepts.
997
+ walletId: z.string().min(1).describe("Source wallet id to debit"),
998
+ amount: AmountSchema.describe('USDC amount as a string, e.g. "100.00"'),
999
+ destinationType: z.enum(["exchange", "wallet"]).describe("Where the funds land. 'exchange' also requires `exchange`."),
1000
+ destinationAddress: EvmAddressSchema.describe("Destination EVM address"),
1001
+ chain: ChainSchema3.describe("Chain the funds are delivered on"),
1002
+ exchange: ExchangeSchema.optional().describe(
1003
+ "Required when destinationType is 'exchange'"
1004
+ ),
1005
+ sourceChain: ChainSchema3.optional().describe(
1006
+ "Chain the funds start on. Differs from `chain` for a bridged withdrawal; defaults to ARC."
1007
+ ),
1008
+ feeConfig: FeeConfigSchema.optional()
1009
+ };
1010
+ var EstimateWithdrawalInput = z.object(estimateShape).superRefine((value, ctx) => {
1011
+ if (value.destinationType === "exchange" && value.exchange === void 0) {
1012
+ ctx.addIssue({
1013
+ code: z.ZodIssueCode.custom,
1014
+ path: ["exchange"],
1015
+ message: "exchange is required when destinationType is 'exchange'"
1016
+ });
1017
+ }
1018
+ });
1019
+ function registerWithdrawalTools(server, clientFactory = getClient) {
1020
+ server.tool(
1021
+ "aura_get_withdrawal",
1022
+ "Get a single withdrawal by id: status, amount, fees, destination, chain and transaction hash.",
1023
+ { withdrawalId: WithdrawalIdSchema2.describe("The withdrawal id (UUID)") },
1024
+ readOnly("Get Withdrawal"),
1025
+ async ({ withdrawalId }) => withToolErrorHandling(async () => clientFactory().withdrawals.get(withdrawalId))
1026
+ );
1027
+ server.tool(
1028
+ "aura_list_withdrawals",
1029
+ "List withdrawals for the account, most recent first. Filter by status, chain and date range.",
1030
+ {
1031
+ limit: z.number().int().min(1).max(100).optional().describe("Page size, 1-100 (default 50)"),
1032
+ offset: z.number().int().min(0).optional().describe("Rows to skip (default 0)"),
1033
+ status: StatusSchema.optional(),
1034
+ chain: ChainSchema3.optional(),
1035
+ fromDate: z.string().datetime().optional().describe("ISO 8601 datetime, inclusive lower bound"),
1036
+ toDate: z.string().datetime().optional().describe("ISO 8601 datetime, inclusive upper bound")
1037
+ },
1038
+ readOnly("List Withdrawals"),
1039
+ async (args) => withToolErrorHandling(async () => clientFactory().withdrawals.list(args))
1040
+ );
1041
+ server.tool(
1042
+ "aura_estimate_withdrawal",
1043
+ "Preview the fees, net amount and confirmation time for a withdrawal WITHOUT creating it. Moves no funds. Use this before quoting a payout to a user.",
1044
+ estimateShape,
1045
+ readOnly("Estimate Withdrawal"),
1046
+ async (args) => {
1047
+ const parsed = EstimateWithdrawalInput.safeParse(args);
1048
+ if (!parsed.success) return validationError(parsed.error);
1049
+ return withToolErrorHandling(
1050
+ async () => clientFactory().withdrawals.estimate(parsed.data)
1051
+ );
1052
+ }
1053
+ );
1054
+ server.tool(
1055
+ "aura_get_withdrawal_limits",
1056
+ "Get the account KYC-tier withdrawal limits and how much of the daily and monthly allowance is still available.",
1057
+ {},
1058
+ readOnly("Get Withdrawal Limits"),
1059
+ async () => withToolErrorHandling(async () => clientFactory().withdrawals.limits())
1060
+ );
1061
+ server.tool(
1062
+ "aura_validate_withdrawal_address",
1063
+ "Check that a destination address is well-formed and usable on a chain, and preview the fee when an amount is given. Moves no funds.",
1064
+ {
1065
+ address: EvmAddressSchema.describe("Destination EVM address to check"),
1066
+ chain: ChainSchema3,
1067
+ amount: AmountSchema.optional().describe(
1068
+ "Optional \u2014 when supplied the response includes a fee preview"
1069
+ ),
1070
+ feeConfig: FeeConfigSchema.optional()
1071
+ },
1072
+ readOnly("Validate Withdrawal Address"),
1073
+ async (args) => withToolErrorHandling(async () => clientFactory().withdrawals.validate(args))
1074
+ );
1075
+ return [
1076
+ "aura_get_withdrawal",
1077
+ "aura_list_withdrawals",
1078
+ "aura_estimate_withdrawal",
1079
+ "aura_get_withdrawal_limits",
1080
+ "aura_validate_withdrawal_address"
1081
+ ];
539
1082
  }
540
1083
 
541
1084
  // src/index.ts
1085
+ var SERVER_NAME = "aura-payments";
1086
+ var SERVER_VERSION = "1.2.0";
1087
+ var toolManifests = /* @__PURE__ */ new WeakMap();
1088
+ function getRegisteredToolNames(server) {
1089
+ return toolManifests.get(server) ?? [];
1090
+ }
542
1091
  function createServer(options = {}) {
543
1092
  const server = new McpServer({
544
- name: options.name ?? "aura-payments",
545
- version: options.version ?? "1.0.1"
1093
+ name: options.name ?? SERVER_NAME,
1094
+ version: options.version ?? SERVER_VERSION
546
1095
  });
547
- registerEscrowTools(server);
548
- registerAgentTools(server);
549
- registerMandateTools(server);
550
- registerWalletTools(server);
1096
+ const toolNames = [
1097
+ ...registerEscrowTools(server),
1098
+ ...registerAgentTools(server),
1099
+ ...registerMandateTools(server),
1100
+ ...registerWalletTools(server),
1101
+ ...registerWebhookTools(server),
1102
+ ...registerWithdrawalTools(server),
1103
+ ...registerTreasuryTools(server),
1104
+ ...registerInsightsTools(server)
1105
+ ];
1106
+ toolManifests.set(server, Object.freeze(toolNames));
551
1107
  registerAgentResources(server);
552
1108
  registerMandateResources(server);
1109
+ registerEscrowResources(server);
1110
+ registerWalletResources(server);
1111
+ registerWithdrawalResources(server);
1112
+ registerInsightsResources(server);
553
1113
  return server;
554
1114
  }
555
1115
 
556
- export { MissingAuraApiKeyError, createServer, getClient };
1116
+ export { API_KEY_PREFIXES, MalformedAuraApiKeyError, MissingAuraApiKeyError, SERVER_NAME, SERVER_VERSION, createServer, getClient, getRegisteredToolNames, maskApiKey, parseApiKey, resolveKeyMode };