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