@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.js CHANGED
@@ -6,13 +6,55 @@ var mcp_js = require('@modelcontextprotocol/sdk/server/mcp.js');
6
6
  var sdk = require('@aura-payments/sdk');
7
7
  var zod = require('zod');
8
8
 
9
+ // src/api-key.ts
10
+ var API_KEY_PREFIXES = [
11
+ "sk_test_",
12
+ "sk_live_",
13
+ "pk_test_",
14
+ "pk_live_"
15
+ ];
16
+ function maskApiKey(apiKey) {
17
+ const revealed = Math.min(8, Math.floor(apiKey.length / 2));
18
+ return `${apiKey.slice(0, revealed)}\u2026(${apiKey.length} chars)`;
19
+ }
20
+ function parseApiKey(apiKey) {
21
+ if (!apiKey || apiKey.trim() === "") {
22
+ throw new MissingAuraApiKeyError();
23
+ }
24
+ const prefix = API_KEY_PREFIXES.find((p) => apiKey.startsWith(p));
25
+ if (!prefix) {
26
+ throw new MalformedAuraApiKeyError(apiKey);
27
+ }
28
+ return { prefix, mode: prefix.includes("_test_") ? "test" : "live" };
29
+ }
30
+ function resolveKeyMode(apiKey) {
31
+ try {
32
+ return parseApiKey(apiKey).mode;
33
+ } catch (err) {
34
+ return err instanceof MissingAuraApiKeyError ? "unconfigured" : "invalid";
35
+ }
36
+ }
37
+ var MissingAuraApiKeyError = class extends Error {
38
+ constructor() {
39
+ super(
40
+ '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.'
41
+ );
42
+ this.name = "MissingAuraApiKeyError";
43
+ }
44
+ };
45
+ var MalformedAuraApiKeyError = class extends Error {
46
+ constructor(apiKey) {
47
+ super(
48
+ `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.`
49
+ );
50
+ this.name = "MalformedAuraApiKeyError";
51
+ }
52
+ };
9
53
  var cached = null;
10
54
  function getClient() {
11
55
  if (cached) return cached;
12
56
  const apiKey = process.env.AURA_API_KEY;
13
- if (!apiKey) {
14
- throw new MissingAuraApiKeyError();
15
- }
57
+ parseApiKey(apiKey);
16
58
  cached = new sdk.AuraClient({
17
59
  apiKey,
18
60
  baseUrl: process.env.AURA_BASE_URL,
@@ -21,14 +63,6 @@ function getClient() {
21
63
  });
22
64
  return cached;
23
65
  }
24
- var MissingAuraApiKeyError = class extends Error {
25
- constructor() {
26
- super(
27
- '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.'
28
- );
29
- this.name = "MissingAuraApiKeyError";
30
- }
31
- };
32
66
 
33
67
  // src/resources/agents.ts
34
68
  function registerAgentResources(server) {
@@ -67,6 +101,55 @@ function registerAgentResources(server) {
67
101
  }
68
102
  );
69
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 mcp_js.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
+ }
70
153
  function registerMandateResources(server) {
71
154
  server.resource(
72
155
  "mandates-pending",
@@ -103,6 +186,108 @@ function registerMandateResources(server) {
103
186
  }
104
187
  );
105
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 mcp_js.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 mcp_js.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 = zod.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 mcp_js.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
+ }
106
291
  async function withToolErrorHandling(body, successFormatter = defaultJsonFormatter) {
107
292
  try {
108
293
  const result = await body();
@@ -117,7 +302,7 @@ function defaultJsonFormatter(data) {
117
302
  return JSON.stringify(data, null, 2);
118
303
  }
119
304
  function formatError(err) {
120
- if (err instanceof MissingAuraApiKeyError) {
305
+ if (err instanceof MissingAuraApiKeyError || err instanceof MalformedAuraApiKeyError) {
121
306
  return errorResult("CONFIG_ERROR", err.message);
122
307
  }
123
308
  if (err instanceof sdk.AuraAuthenticationError) {
@@ -205,6 +390,8 @@ function registerAgentTools(server) {
205
390
  autoApproveBelow: zod.z.string().optional()
206
391
  }).optional()
207
392
  },
393
+ // Additive: provisions a new agent and its wallet. No funds move.
394
+ write("Create Agent"),
208
395
  async (args) => withToolErrorHandling(async () => {
209
396
  const client = getClient();
210
397
  return client.agents.create(args);
@@ -219,6 +406,7 @@ function registerAgentTools(server) {
219
406
  limit: zod.z.number().int().min(1).max(200).optional(),
220
407
  offset: zod.z.number().int().min(0).optional()
221
408
  },
409
+ readOnly("List Agents"),
222
410
  async (args) => withToolErrorHandling(async () => {
223
411
  const client = getClient();
224
412
  return client.agents.list(args);
@@ -230,6 +418,7 @@ function registerAgentTools(server) {
230
418
  {
231
419
  agentId: zod.z.string().uuid()
232
420
  },
421
+ readOnly("Get Agent"),
233
422
  async ({ agentId }) => withToolErrorHandling(async () => {
234
423
  const client = getClient();
235
424
  return client.agents.get(agentId);
@@ -241,6 +430,7 @@ function registerAgentTools(server) {
241
430
  {
242
431
  agentId: zod.z.string().uuid()
243
432
  },
433
+ readOnly("Get Agent Balance"),
244
434
  async ({ agentId }) => withToolErrorHandling(async () => {
245
435
  const client = getClient();
246
436
  return client.agents.getBalance(agentId);
@@ -253,6 +443,8 @@ function registerAgentTools(server) {
253
443
  agentId: zod.z.string().uuid(),
254
444
  reason: zod.z.string().max(500).optional().describe("Surfaced in activity event metadata for audit trail")
255
445
  },
446
+ // Halts an existing agent AND cascade-rejects its in-flight mandates.
447
+ destructive("Freeze Agent (kill switch)"),
256
448
  async (args) => withToolErrorHandling(async () => {
257
449
  const client = getClient();
258
450
  return client.agents.freeze(args);
@@ -264,6 +456,8 @@ function registerAgentTools(server) {
264
456
  {
265
457
  agentId: zod.z.string().uuid()
266
458
  },
459
+ // Re-enables autonomous spending on an existing agent.
460
+ destructive("Unfreeze Agent (resumes autonomous spending)"),
267
461
  async (args) => withToolErrorHandling(async () => {
268
462
  const client = getClient();
269
463
  return client.agents.unfreeze(args);
@@ -280,11 +474,24 @@ function registerAgentTools(server) {
280
474
  reasoning: zod.z.string().optional(),
281
475
  metadata: zod.z.record(zod.z.unknown()).optional()
282
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)"),
283
481
  async (args) => withToolErrorHandling(async () => {
284
482
  const client = getClient();
285
483
  return client.agents.evaluate(args);
286
484
  })
287
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
+ ];
288
495
  }
289
496
  var ChainSchema2 = zod.z.enum(["ARC", "ARB", "BASE", "ETH", "MATIC"]);
290
497
  var SplitInputSchema = zod.z.object({
@@ -321,6 +528,8 @@ function registerEscrowTools(server) {
321
528
  chain: ChainSchema2.optional().describe("Target chain. Defaults to ARC"),
322
529
  splits: zod.z.array(SplitInputSchema).min(1).describe("Payment splits. Percentages must sum to 100.")
323
530
  },
531
+ // Additive: creates a new escrow record. No funds move until `fund`.
532
+ write("Create Escrow"),
324
533
  async (args) => withToolErrorHandling(async () => {
325
534
  const client = getClient();
326
535
  return client.escrows.create(args);
@@ -332,6 +541,7 @@ function registerEscrowTools(server) {
332
541
  {
333
542
  escrowId: zod.z.string().uuid()
334
543
  },
544
+ readOnly("Get Escrow"),
335
545
  async ({ escrowId }) => withToolErrorHandling(async () => {
336
546
  const client = getClient();
337
547
  return client.escrows.get(escrowId);
@@ -356,6 +566,7 @@ function registerEscrowTools(server) {
356
566
  limit: zod.z.number().int().min(1).max(200).optional(),
357
567
  offset: zod.z.number().int().min(0).optional()
358
568
  },
569
+ readOnly("List Escrows"),
359
570
  async (args) => withToolErrorHandling(async () => {
360
571
  const client = getClient();
361
572
  return client.escrows.list(args);
@@ -370,6 +581,8 @@ function registerEscrowTools(server) {
370
581
  amount: zod.z.string().describe("USDC amount to fund (whole-number string)"),
371
582
  correlationId: zod.z.string().optional()
372
583
  },
584
+ // Moves USDC out of the buyer wallet.
585
+ destructive("Fund Escrow (moves USDC)"),
373
586
  async (args) => withToolErrorHandling(async () => {
374
587
  const client = getClient();
375
588
  return client.escrows.fund(args);
@@ -384,6 +597,8 @@ function registerEscrowTools(server) {
384
597
  reason: zod.z.string().optional(),
385
598
  correlationId: zod.z.string().optional()
386
599
  },
600
+ // Irreversible payout to the split recipients.
601
+ destructive("Release Escrow (pays out USDC)"),
387
602
  async (args) => withToolErrorHandling(async () => {
388
603
  const client = getClient();
389
604
  return client.escrows.release(args);
@@ -397,6 +612,8 @@ function registerEscrowTools(server) {
397
612
  reason: zod.z.string().min(10),
398
613
  actorId: zod.z.string().uuid()
399
614
  },
615
+ // Irreversible return of funds to the buyer.
616
+ destructive("Refund Escrow (returns USDC to buyer)"),
400
617
  async (args) => withToolErrorHandling(async () => {
401
618
  const client = getClient();
402
619
  return client.escrows.refund(args);
@@ -416,11 +633,134 @@ function registerEscrowTools(server) {
416
633
  waitForDeploymentMs: zod.z.number().int().min(0).optional().describe("Max ms to wait for on-chain deployment. Default 60000."),
417
634
  waitForFundingMs: zod.z.number().int().min(0).optional().describe("Max ms to wait for funds to settle (funded/locked) before releasing. Default 60000.")
418
635
  },
636
+ // Composes create -> fund -> release: money moves and is paid out.
637
+ destructive("Receive and Split (moves and pays out USDC)"),
419
638
  async (args) => withToolErrorHandling(async () => {
420
639
  const client = getClient();
421
640
  return client.escrows.receiveAndSplit(args);
422
641
  })
423
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: zod.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: zod.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 = zod.z.enum(["today", "7d", "30d", "90d", "custom"]);
676
+ var DateTimeSchema = zod.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 = zod.z.object(overviewShape).superRefine(
685
+ (value, ctx) => addCustomRangeIssues(ctx, "period", value.period, value.start, value.end)
686
+ );
687
+ var auditShape = {
688
+ action: zod.z.string().min(1).optional().describe("Audit action name, e.g. escrow.released"),
689
+ actor: zod.z.enum(["user", "system", "api", "cron", "webhook"]).optional(),
690
+ resourceType: zod.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: zod.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: zod.z.number().int().min(1).optional().describe("1-based page number (default 1)"),
701
+ limit: zod.z.number().int().min(1).max(100).optional().describe("Page size, 1-100 (default 50)")
702
+ };
703
+ var InsightsAuditInput = zod.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: zod.z.enum([
726
+ "revenue",
727
+ "transaction_count",
728
+ "escrow_count",
729
+ "commission",
730
+ "avg_transaction_value"
731
+ ]),
732
+ algorithm: zod.z.enum([
733
+ "linear_regression",
734
+ "moving_average",
735
+ "exponential_smoothing",
736
+ "arima",
737
+ "prophet",
738
+ "ensemble"
739
+ ]).optional(),
740
+ period: zod.z.enum(["daily", "weekly", "monthly"]).optional().describe("Granularity of each projected point"),
741
+ horizon: zod.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: zod.z.boolean().optional().describe("Include the historical series alongside the projection"),
747
+ historyPeriods: zod.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"];
424
764
  }
425
765
  function registerMandateTools(server) {
426
766
  server.tool(
@@ -432,6 +772,7 @@ function registerMandateTools(server) {
432
772
  limit: zod.z.number().int().min(1).max(200).optional(),
433
773
  cursor: zod.z.string().uuid().optional().describe("Last mandate id from the previous page")
434
774
  },
775
+ readOnly("List Mandates (approval inbox)"),
435
776
  async (args) => withToolErrorHandling(async () => {
436
777
  const client = getClient();
437
778
  return client.mandates.list(args);
@@ -443,6 +784,7 @@ function registerMandateTools(server) {
443
784
  {
444
785
  mandateId: zod.z.string().uuid()
445
786
  },
787
+ readOnly("Get Mandate"),
446
788
  async ({ mandateId }) => withToolErrorHandling(async () => {
447
789
  const client = getClient();
448
790
  return client.mandates.get(mandateId);
@@ -457,6 +799,8 @@ function registerMandateTools(server) {
457
799
  decisionSignature: zod.z.string().min(16).describe("HMAC-SHA256 hex digest, computed by the caller"),
458
800
  decidedAt: zod.z.string().datetime().optional().describe('ISO-8601 timestamp the signature commits to. Defaults to "now" server-side.')
459
801
  },
802
+ // Authorizes the agent's intended payment — the money-moving decision.
803
+ destructive("Approve Mandate (authorizes payment)"),
460
804
  async (args) => withToolErrorHandling(async () => {
461
805
  const client = getClient();
462
806
  return client.mandates.approve(args);
@@ -472,11 +816,31 @@ function registerMandateTools(server) {
472
816
  decidedAt: zod.z.string().datetime().optional(),
473
817
  reason: zod.z.string().optional().describe("Human-readable reason logged to the activity feed")
474
818
  },
819
+ // Terminal decision on an existing mandate — the agent cannot resume it.
820
+ destructive("Reject Mandate"),
475
821
  async (args) => withToolErrorHandling(async () => {
476
822
  const client = getClient();
477
823
  return client.mandates.reject(args);
478
824
  })
479
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"];
480
844
  }
481
845
  var TestnetChainSchema = zod.z.enum(["ARC", "ARB", "BASE"]);
482
846
  function walletCreateEnabled() {
@@ -490,6 +854,7 @@ function registerWalletTools(server) {
490
854
  {
491
855
  walletId: zod.z.string().uuid()
492
856
  },
857
+ readOnly("Get Wallet Balance"),
493
858
  async ({ walletId }) => withToolErrorHandling(async () => {
494
859
  const client = getClient();
495
860
  return client.wallets.getBalance(walletId);
@@ -513,6 +878,8 @@ function registerWalletTools(server) {
513
878
  "tax"
514
879
  ]).optional().describe("Wallet owner type. Defaults to BUSINESS.")
515
880
  },
881
+ // Additive and create-or-get: provisions a receive address, moves no money.
882
+ write("Create Wallet (receive address)", true),
516
883
  async ({ entityId, chain, type }) => {
517
884
  if (!walletCreateEnabled()) {
518
885
  return toolError(
@@ -534,31 +901,239 @@ function registerWalletTools(server) {
534
901
  token: zod.z.string().optional().describe("Token to drip. Defaults to 'USDC'."),
535
902
  chain: TestnetChainSchema.optional().describe("Override the wallet's chain.")
536
903
  },
904
+ // Additive: testnet-only drip that adds play funds, destroys nothing.
905
+ write("Request Testnet Funds"),
537
906
  async ({ walletId, token, chain }) => withToolErrorHandling(async () => {
538
907
  const client = getClient();
539
908
  return client.wallets.requestTestnetFunds(walletId, { token, chain });
540
909
  })
541
910
  );
911
+ return [
912
+ "aura_get_wallet_balance",
913
+ "aura_create_wallet",
914
+ "aura_request_testnet_funds"
915
+ ];
916
+ }
917
+ var WebhookEventTypeSchema = zod.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: zod.z.string().url().describe("HTTPS endpoint that will receive event POSTs"),
953
+ events: zod.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: zod.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 = zod.z.enum(["ARC", "ETH", "BASE", "ARB", "MATIC"]);
981
+ var AmountSchema = zod.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 = zod.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 = zod.z.enum(["binance", "coinbase", "kraken", "other"]);
990
+ var FeeConfigSchema = zod.z.enum(["FREE", "GAS_ONLY", "STANDARD"]);
991
+ var StatusSchema = zod.z.enum(["pending", "submitted", "confirmed", "failed", "cancelled"]);
992
+ var WithdrawalIdSchema2 = zod.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: zod.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: zod.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 = zod.z.object(estimateShape).superRefine((value, ctx) => {
1011
+ if (value.destinationType === "exchange" && value.exchange === void 0) {
1012
+ ctx.addIssue({
1013
+ code: zod.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: zod.z.number().int().min(1).max(100).optional().describe("Page size, 1-100 (default 50)"),
1032
+ offset: zod.z.number().int().min(0).optional().describe("Rows to skip (default 0)"),
1033
+ status: StatusSchema.optional(),
1034
+ chain: ChainSchema3.optional(),
1035
+ fromDate: zod.z.string().datetime().optional().describe("ISO 8601 datetime, inclusive lower bound"),
1036
+ toDate: zod.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
+ ];
542
1082
  }
543
1083
 
544
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
+ }
545
1091
  function createServer(options = {}) {
546
1092
  const server = new mcp_js.McpServer({
547
- name: options.name ?? "aura-payments",
548
- version: options.version ?? "1.0.1"
1093
+ name: options.name ?? SERVER_NAME,
1094
+ version: options.version ?? SERVER_VERSION
549
1095
  });
550
- registerEscrowTools(server);
551
- registerAgentTools(server);
552
- registerMandateTools(server);
553
- 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));
554
1107
  registerAgentResources(server);
555
1108
  registerMandateResources(server);
1109
+ registerEscrowResources(server);
1110
+ registerWalletResources(server);
1111
+ registerWithdrawalResources(server);
1112
+ registerInsightsResources(server);
556
1113
  return server;
557
1114
  }
558
1115
 
559
1116
  // src/bin.ts
1117
+ var DEFAULT_BASE_URL = "https://getaura.sh/api";
1118
+ function emitBanner(toolCount) {
1119
+ const mode = resolveKeyMode(process.env.AURA_API_KEY);
1120
+ const baseUrl = process.env.AURA_BASE_URL || DEFAULT_BASE_URL;
1121
+ console.error(
1122
+ `${SERVER_NAME} v${SERVER_VERSION} | mode=${mode} | baseUrl=${baseUrl} | tools=${toolCount}`
1123
+ );
1124
+ if (mode === "unconfigured") {
1125
+ console.error(
1126
+ `${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.`
1127
+ );
1128
+ } else if (mode === "invalid") {
1129
+ console.error(
1130
+ `${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.`
1131
+ );
1132
+ }
1133
+ }
560
1134
  async function main() {
561
1135
  const server = createServer();
1136
+ emitBanner(getRegisteredToolNames(server).length);
562
1137
  const transport = new stdio_js.StdioServerTransport();
563
1138
  await server.connect(transport);
564
1139
  }