@naturalpay/sdk 0.0.2

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.
@@ -0,0 +1,1044 @@
1
+ import { FastMCP } from 'fastmcp';
2
+ import { z } from 'zod';
3
+ import { AsyncLocalStorage } from 'async_hooks';
4
+ import { createHash } from 'crypto';
5
+
6
+ // src/mcp/server.ts
7
+
8
+ // src/errors.ts
9
+ var NaturalError = class extends Error {
10
+ statusCode;
11
+ code;
12
+ constructor(message, options) {
13
+ super(message);
14
+ this.name = "NaturalError";
15
+ this.statusCode = options?.statusCode;
16
+ this.code = options?.code;
17
+ if (Error.captureStackTrace) {
18
+ Error.captureStackTrace(this, this.constructor);
19
+ }
20
+ }
21
+ };
22
+ var AuthenticationError = class extends NaturalError {
23
+ constructor(message = "Invalid or missing API key") {
24
+ super(message, { statusCode: 401, code: "authentication_error" });
25
+ this.name = "AuthenticationError";
26
+ }
27
+ };
28
+ var InvalidRequestError = class extends NaturalError {
29
+ constructor(message, code = "invalid_request") {
30
+ super(message, { statusCode: 400, code });
31
+ this.name = "InvalidRequestError";
32
+ }
33
+ };
34
+ var RateLimitError = class extends NaturalError {
35
+ retryAfter;
36
+ constructor(message = "Rate limit exceeded", retryAfter) {
37
+ super(message, { statusCode: 429, code: "rate_limit_exceeded" });
38
+ this.name = "RateLimitError";
39
+ this.retryAfter = retryAfter;
40
+ }
41
+ };
42
+ var ServerError = class extends NaturalError {
43
+ constructor(message = "Internal server error") {
44
+ super(message, { statusCode: 500, code: "server_error" });
45
+ this.name = "ServerError";
46
+ }
47
+ };
48
+ var SDK_VERSION = "0.0.1";
49
+ var LOG_LEVEL_VALUES = {
50
+ debug: 10,
51
+ info: 20,
52
+ warning: 30,
53
+ error: 40
54
+ };
55
+ var asyncContext = new AsyncLocalStorage();
56
+ var globalContext = {};
57
+ function getContext() {
58
+ const asyncStore = asyncContext.getStore();
59
+ if (asyncStore) {
60
+ return { ...asyncStore };
61
+ }
62
+ return { ...globalContext };
63
+ }
64
+ var loggingConfig = {
65
+ level: process.env["NATURAL_LOG_LEVEL"]?.toLowerCase() || "info",
66
+ jsonFormat: process.env["NATURAL_LOG_FORMAT"]?.toLowerCase() === "json",
67
+ serviceName: "naturalpay-sdk",
68
+ environment: process.env["NATURAL_ENV"] || process.env["DD_ENV"] || "development"
69
+ };
70
+ function getSourceInfo() {
71
+ const stack = new Error().stack;
72
+ if (!stack) {
73
+ return { file: "unknown", line: 0, function: "unknown" };
74
+ }
75
+ const lines = stack.split("\n");
76
+ const callerLine = lines[4] || lines[3] || "";
77
+ const match = callerLine.match(/at\s+(?:(.+?)\s+\()?(.*?):(\d+):\d+\)?/);
78
+ if (match) {
79
+ return {
80
+ function: match[1] || "anonymous",
81
+ file: match[2] || "unknown",
82
+ line: parseInt(match[3] || "0", 10)
83
+ };
84
+ }
85
+ return { file: "unknown", line: 0, function: "unknown" };
86
+ }
87
+ function formatJsonLog(level, loggerName, message, extra) {
88
+ const record = {
89
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
90
+ level: level.toUpperCase(),
91
+ logger: loggerName,
92
+ message,
93
+ source: getSourceInfo(),
94
+ service: loggingConfig.serviceName,
95
+ environment: loggingConfig.environment,
96
+ version: SDK_VERSION,
97
+ ...getContext(),
98
+ ...extra
99
+ };
100
+ const ddTraceId = process.env["DD_TRACE_ID"];
101
+ const ddSpanId = process.env["DD_SPAN_ID"];
102
+ if (ddTraceId) {
103
+ record["dd.trace_id"] = ddTraceId;
104
+ record["dd.span_id"] = ddSpanId;
105
+ record["dd.service"] = loggingConfig.serviceName;
106
+ record["dd.version"] = SDK_VERSION;
107
+ record["dd.env"] = loggingConfig.environment;
108
+ }
109
+ return JSON.stringify(record);
110
+ }
111
+ function formatTextLog(level, loggerName, message, extra) {
112
+ const contextEntries = Object.entries({ ...getContext(), ...extra });
113
+ const contextStr = contextEntries.length > 0 ? ` [${contextEntries.map(([k, v]) => `${k}=${v}`).join(", ")}]` : "";
114
+ return `${level.toUpperCase().padEnd(8)} ${loggerName}: ${message}${contextStr}`;
115
+ }
116
+ var Logger = class {
117
+ constructor(name) {
118
+ this.name = name;
119
+ }
120
+ log(level, message, extra) {
121
+ if (LOG_LEVEL_VALUES[level] < LOG_LEVEL_VALUES[loggingConfig.level]) {
122
+ return;
123
+ }
124
+ const formatted = loggingConfig.jsonFormat ? formatJsonLog(level, this.name, message, extra) : formatTextLog(level, this.name, message, extra);
125
+ if (level === "error") {
126
+ console.error(formatted);
127
+ } else if (level === "warning") {
128
+ console.warn(formatted);
129
+ } else {
130
+ console.error(formatted);
131
+ }
132
+ }
133
+ debug(message, extra) {
134
+ this.log("debug", message, extra);
135
+ }
136
+ info(message, extra) {
137
+ this.log("info", message, extra);
138
+ }
139
+ warning(message, extra) {
140
+ this.log("warning", message, extra);
141
+ }
142
+ warn(message, extra) {
143
+ this.warning(message, extra);
144
+ }
145
+ error(message, extra) {
146
+ this.log("error", message, extra);
147
+ }
148
+ };
149
+ function getLogger(name) {
150
+ if (!name.startsWith("naturalpay")) {
151
+ name = `naturalpay.${name}`;
152
+ }
153
+ return new Logger(name);
154
+ }
155
+ function logError(logger3, message, options) {
156
+ const extra = { ...options };
157
+ if (options?.error) {
158
+ extra["errorType"] = options.error.name;
159
+ extra["errorMessage"] = options.error.message;
160
+ if (options.error.stack) {
161
+ extra["errorStack"] = options.error.stack.split("\n").slice(0, 5).join("\n");
162
+ }
163
+ delete extra["error"];
164
+ }
165
+ logger3.error(message, extra);
166
+ }
167
+ function logApiCall(logger3, method, path, options) {
168
+ const extra = {
169
+ method,
170
+ path,
171
+ ...options
172
+ };
173
+ if (options?.statusCode) {
174
+ extra["statusCode"] = options.statusCode;
175
+ }
176
+ if (options?.durationMs !== void 0) {
177
+ extra["durationMs"] = Math.round(options.durationMs * 100) / 100;
178
+ }
179
+ if (options?.error) {
180
+ logError(logger3, `API call failed: ${method} ${path}`, extra);
181
+ } else if (options?.statusCode && options.statusCode >= 400) {
182
+ logger3.warning(`API call error: ${method} ${path} -> ${options.statusCode}`, extra);
183
+ } else {
184
+ logger3.info(`API call: ${method} ${path} -> ${options?.statusCode}`, extra);
185
+ }
186
+ }
187
+ function logToolCall(logger3, toolName, options) {
188
+ const extra = {
189
+ toolName,
190
+ ...options
191
+ };
192
+ if (options?.durationMs !== void 0) {
193
+ extra["durationMs"] = Math.round(options.durationMs * 100) / 100;
194
+ }
195
+ if (options?.error) {
196
+ logError(logger3, `Tool call failed: ${toolName}`, extra);
197
+ } else if (options?.success === false) {
198
+ logger3.warning(`Tool call returned error: ${toolName}`, extra);
199
+ } else {
200
+ logger3.info(`Tool call: ${toolName}`, extra);
201
+ }
202
+ }
203
+
204
+ // src/http.ts
205
+ var logger = getLogger("http");
206
+ var DEFAULT_BASE_URL = "https://api.natural.co";
207
+ var DEFAULT_TIMEOUT = 3e4;
208
+ var SDK_VERSION2 = "0.0.1";
209
+ function hashString(str) {
210
+ let hash = 0;
211
+ for (let i = 0; i < str.length; i++) {
212
+ const char = str.charCodeAt(i);
213
+ hash = (hash << 5) - hash + char;
214
+ hash = hash & hash;
215
+ }
216
+ return Math.abs(hash).toString(16).slice(0, 16);
217
+ }
218
+ function snakeToCamel(obj) {
219
+ if (obj === null || obj === void 0) {
220
+ return obj;
221
+ }
222
+ if (Array.isArray(obj)) {
223
+ return obj.map(snakeToCamel);
224
+ }
225
+ if (typeof obj === "object") {
226
+ const result = {};
227
+ for (const [key, value] of Object.entries(obj)) {
228
+ const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
229
+ result[camelKey] = snakeToCamel(value);
230
+ }
231
+ return result;
232
+ }
233
+ return obj;
234
+ }
235
+ function camelToSnake(obj) {
236
+ if (obj === null || obj === void 0) {
237
+ return obj;
238
+ }
239
+ if (Array.isArray(obj)) {
240
+ return obj.map(camelToSnake);
241
+ }
242
+ if (typeof obj === "object") {
243
+ const result = {};
244
+ for (const [key, value] of Object.entries(obj)) {
245
+ const snakeKey = key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
246
+ result[snakeKey] = camelToSnake(value);
247
+ }
248
+ return result;
249
+ }
250
+ return obj;
251
+ }
252
+ var HTTPClient = class {
253
+ apiKey;
254
+ baseUrl;
255
+ timeout;
256
+ jwtCache = /* @__PURE__ */ new Map();
257
+ constructor(options = {}) {
258
+ this.apiKey = options.apiKey ?? process.env["NATURAL_API_KEY"] ?? "";
259
+ this.baseUrl = (options.baseUrl ?? process.env["NATURAL_SERVER_URL"] ?? DEFAULT_BASE_URL).replace(/\/$/, "");
260
+ this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
261
+ }
262
+ /**
263
+ * Get a cached JWT or exchange API key for a new one.
264
+ */
265
+ async getJwt() {
266
+ if (!this.apiKey) {
267
+ throw new AuthenticationError();
268
+ }
269
+ if (!this.apiKey.startsWith("pk_")) {
270
+ return this.apiKey;
271
+ }
272
+ const cacheKey = hashString(this.apiKey);
273
+ const cached = this.jwtCache.get(cacheKey);
274
+ if (cached && Date.now() < cached.expiresAt) {
275
+ return cached.token;
276
+ }
277
+ const controller = new AbortController();
278
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
279
+ logger.debug("Exchanging API key for JWT", { path: "/auth/partner/token" });
280
+ try {
281
+ const response = await fetch(`${this.baseUrl}/auth/partner/token`, {
282
+ method: "POST",
283
+ headers: {
284
+ Authorization: `Bearer ${this.apiKey}`,
285
+ "Content-Type": "application/json"
286
+ },
287
+ signal: controller.signal
288
+ });
289
+ clearTimeout(timeoutId);
290
+ if (!response.ok) {
291
+ const authError = new AuthenticationError(
292
+ `Authentication failed (status=${response.status})`
293
+ );
294
+ logError(logger, "JWT exchange failed", {
295
+ error: authError,
296
+ statusCode: response.status,
297
+ path: "/auth/partner/token"
298
+ });
299
+ throw authError;
300
+ }
301
+ const data = await response.json();
302
+ const expiresIn = data.expires_in ?? 900;
303
+ const expiresAt = Date.now() + (expiresIn - 30) * 1e3;
304
+ this.jwtCache.set(cacheKey, { token: data.access_token, expiresAt });
305
+ return data.access_token;
306
+ } catch (error) {
307
+ clearTimeout(timeoutId);
308
+ if (error instanceof AuthenticationError) {
309
+ throw error;
310
+ }
311
+ if (error instanceof Error && error.name === "AbortError") {
312
+ const networkError2 = new NaturalError("Request timed out during authentication");
313
+ logError(logger, "JWT exchange network error", {
314
+ error: networkError2,
315
+ path: "/auth/partner/token"
316
+ });
317
+ throw networkError2;
318
+ }
319
+ const networkError = new NaturalError(
320
+ `Network error during authentication: ${error instanceof Error ? error.message : "Unknown error"}`
321
+ );
322
+ logError(logger, "JWT exchange network error", {
323
+ error: networkError,
324
+ path: "/auth/partner/token"
325
+ });
326
+ throw networkError;
327
+ }
328
+ }
329
+ /**
330
+ * Build URL with query parameters.
331
+ */
332
+ buildUrl(path, params) {
333
+ const url = new URL(path, this.baseUrl);
334
+ if (params) {
335
+ for (const [key, value] of Object.entries(params)) {
336
+ if (value !== void 0 && value !== null) {
337
+ const snakeKey = key.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
338
+ url.searchParams.set(snakeKey, String(value));
339
+ }
340
+ }
341
+ }
342
+ return url.toString();
343
+ }
344
+ /**
345
+ * Handle API response and throw appropriate errors.
346
+ */
347
+ async handleResponse(response, method, path, durationMs) {
348
+ if (response.status === 401) {
349
+ const authError = new AuthenticationError();
350
+ logApiCall(logger, method, path, {
351
+ statusCode: response.status,
352
+ durationMs,
353
+ error: authError
354
+ });
355
+ throw authError;
356
+ }
357
+ if (response.status === 429) {
358
+ const retryAfter = response.headers.get("Retry-After");
359
+ const rateError = new RateLimitError(
360
+ "Rate limit exceeded",
361
+ retryAfter ? parseInt(retryAfter, 10) : void 0
362
+ );
363
+ logger.warning(`Rate limited: ${method} ${path}`, {
364
+ method,
365
+ path,
366
+ statusCode: response.status,
367
+ retryAfter,
368
+ durationMs: Math.round(durationMs * 100) / 100
369
+ });
370
+ throw rateError;
371
+ }
372
+ if (response.status >= 500) {
373
+ const serverError = new ServerError(`Server error: ${response.status}`);
374
+ logApiCall(logger, method, path, {
375
+ statusCode: response.status,
376
+ durationMs,
377
+ error: serverError
378
+ });
379
+ throw serverError;
380
+ }
381
+ let data;
382
+ try {
383
+ const text = await response.text();
384
+ data = text ? JSON.parse(text) : {};
385
+ } catch {
386
+ if (response.status >= 400) {
387
+ const parseError = new NaturalError(`Request failed: ${response.status}`, {
388
+ statusCode: response.status
389
+ });
390
+ logApiCall(logger, method, path, {
391
+ statusCode: response.status,
392
+ durationMs,
393
+ error: parseError
394
+ });
395
+ throw parseError;
396
+ }
397
+ logApiCall(logger, method, path, { statusCode: response.status, durationMs });
398
+ return {};
399
+ }
400
+ if (response.status >= 400) {
401
+ const errorData = data;
402
+ const errorMessage = errorData.detail ?? errorData.message ?? errorData.error ?? "Request failed";
403
+ const errorCode = errorData.code ?? "unknown_error";
404
+ const requestError = new InvalidRequestError(
405
+ `${errorMessage} (status=${response.status})`,
406
+ errorCode
407
+ );
408
+ logApiCall(logger, method, path, {
409
+ statusCode: response.status,
410
+ durationMs,
411
+ error: requestError
412
+ });
413
+ throw requestError;
414
+ }
415
+ logApiCall(logger, method, path, { statusCode: response.status, durationMs });
416
+ return snakeToCamel(data);
417
+ }
418
+ /**
419
+ * Make an authenticated request.
420
+ */
421
+ async request(method, path, options) {
422
+ const jwt = await this.getJwt();
423
+ const url = this.buildUrl(path, options?.params);
424
+ const controller = new AbortController();
425
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
426
+ logger.debug(`API request: ${method} ${path}`, {
427
+ method,
428
+ path,
429
+ hasBody: !!options?.body
430
+ });
431
+ const startTime = Date.now();
432
+ try {
433
+ const headers = {
434
+ Authorization: `Bearer ${jwt}`,
435
+ "Content-Type": "application/json",
436
+ "User-Agent": `naturalpay-ts/${SDK_VERSION2}`,
437
+ ...options?.headers
438
+ };
439
+ const response = await fetch(url, {
440
+ method,
441
+ headers,
442
+ body: options?.body ? JSON.stringify(camelToSnake(options.body)) : void 0,
443
+ signal: controller.signal
444
+ });
445
+ clearTimeout(timeoutId);
446
+ const durationMs = Date.now() - startTime;
447
+ return this.handleResponse(response, method, path, durationMs);
448
+ } catch (error) {
449
+ clearTimeout(timeoutId);
450
+ const durationMs = Date.now() - startTime;
451
+ if (error instanceof NaturalError || error instanceof AuthenticationError || error instanceof InvalidRequestError || error instanceof RateLimitError || error instanceof ServerError) {
452
+ throw error;
453
+ }
454
+ if (error instanceof Error && error.name === "AbortError") {
455
+ const networkError2 = new NaturalError("Request timed out");
456
+ logApiCall(logger, method, path, { durationMs, error: networkError2 });
457
+ throw networkError2;
458
+ }
459
+ const networkError = new NaturalError(
460
+ `Network error: ${error instanceof Error ? error.message : "Unknown error"}`
461
+ );
462
+ logApiCall(logger, method, path, { durationMs, error: networkError });
463
+ throw networkError;
464
+ }
465
+ }
466
+ async get(path, options) {
467
+ return this.request("GET", path, options);
468
+ }
469
+ async post(path, options) {
470
+ return this.request("POST", path, options);
471
+ }
472
+ async put(path, options) {
473
+ return this.request("PUT", path, options);
474
+ }
475
+ async delete(path) {
476
+ return this.request("DELETE", path);
477
+ }
478
+ };
479
+
480
+ // src/resources/base.ts
481
+ var BaseResource = class {
482
+ http;
483
+ constructor(http) {
484
+ this.http = http;
485
+ }
486
+ };
487
+
488
+ // src/resources/payments.ts
489
+ var IDEMPOTENCY_WINDOW_SECONDS = 300;
490
+ var PaymentsResource = class extends BaseResource {
491
+ /**
492
+ * Generate idempotency key based on payment details + time window.
493
+ */
494
+ generateIdempotencyKey(recipient, amount, memo) {
495
+ const timeWindow = Math.floor(Date.now() / 1e3 / IDEMPOTENCY_WINDOW_SECONDS);
496
+ const data = [recipient, String(amount), memo ?? "", String(timeWindow)].join(":");
497
+ return createHash("sha256").update(data).digest("hex").slice(0, 32);
498
+ }
499
+ /**
500
+ * Create a payment.
501
+ *
502
+ * Must provide exactly one of: recipientEmail, recipientPhone, or recipientPartyId.
503
+ *
504
+ * @param params - Payment creation parameters
505
+ * @returns Payment object with transfer_id, status, etc.
506
+ */
507
+ async create(params) {
508
+ const recipientCount = [
509
+ params.recipientEmail,
510
+ params.recipientPhone,
511
+ params.recipientPartyId
512
+ ].filter((r) => r !== void 0).length;
513
+ if (recipientCount !== 1) {
514
+ throw new Error(
515
+ "Must provide exactly one of: recipientEmail, recipientPhone, or recipientPartyId"
516
+ );
517
+ }
518
+ const recipient = params.recipientEmail ?? params.recipientPhone ?? params.recipientPartyId;
519
+ const idempotencyKey = params.idempotencyKey ?? this.generateIdempotencyKey(recipient, params.amount, params.memo);
520
+ const body = {
521
+ amount: params.amount
522
+ };
523
+ if (params.recipientEmail) body["recipientEmail"] = params.recipientEmail;
524
+ if (params.recipientPhone) body["recipientPhone"] = params.recipientPhone;
525
+ if (params.recipientPartyId) body["recipientPartyId"] = params.recipientPartyId;
526
+ if (params.memo) body["memo"] = params.memo;
527
+ if (params.agentId) body["agentId"] = params.agentId;
528
+ if (params.customerPartyId) body["customerPartyId"] = params.customerPartyId;
529
+ if (params.instanceId) body["instanceId"] = params.instanceId;
530
+ return this.http.post("/payments/initiate", {
531
+ body,
532
+ headers: { "Idempotency-Key": idempotencyKey }
533
+ });
534
+ }
535
+ /**
536
+ * Get payment status by transfer ID.
537
+ *
538
+ * @param transferId - The transfer ID to look up
539
+ * @returns Payment object with current status
540
+ */
541
+ async retrieve(transferId) {
542
+ return this.http.get(`/payments/${transferId}`);
543
+ }
544
+ /**
545
+ * Cancel a pending payment.
546
+ *
547
+ * @param transferId - The transfer ID to cancel
548
+ * @returns Cancellation result with status and message
549
+ */
550
+ async cancel(transferId) {
551
+ return this.http.post(`/payments/${transferId}/cancel`);
552
+ }
553
+ };
554
+
555
+ // src/resources/wallet.ts
556
+ var WalletResource = class extends BaseResource {
557
+ /**
558
+ * Get current wallet balance.
559
+ *
560
+ * @returns AccountBalance with available, current, pending amounts
561
+ */
562
+ async balance() {
563
+ return this.http.get("/wallet/balance");
564
+ }
565
+ /**
566
+ * Initiate a deposit from a linked bank account.
567
+ *
568
+ * @param params - Deposit parameters
569
+ * @returns DepositResponse with transfer status
570
+ */
571
+ async deposit(params) {
572
+ const body = {
573
+ amount: params.amount,
574
+ currency: params.currency ?? "USD",
575
+ paymentInstrumentId: params.paymentInstrumentId
576
+ };
577
+ if (params.description) {
578
+ body["description"] = params.description;
579
+ }
580
+ return this.http.post("/wallet/deposit", {
581
+ body,
582
+ headers: { "Idempotency-Key": params.idempotencyKey }
583
+ });
584
+ }
585
+ /**
586
+ * Initiate a withdrawal to a linked bank account.
587
+ *
588
+ * @param params - Withdrawal parameters
589
+ * @returns WithdrawResponse with transfer status (may require KYC/MFA)
590
+ */
591
+ async withdraw(params) {
592
+ const body = {
593
+ amount: params.amount,
594
+ currency: params.currency ?? "USD",
595
+ paymentInstrumentId: params.paymentInstrumentId
596
+ };
597
+ if (params.description) {
598
+ body["description"] = params.description;
599
+ }
600
+ return this.http.post("/wallet/withdraw", {
601
+ body,
602
+ headers: { "Idempotency-Key": params.idempotencyKey }
603
+ });
604
+ }
605
+ };
606
+
607
+ // src/resources/transactions.ts
608
+ var TransactionsResource = class extends BaseResource {
609
+ /**
610
+ * List recent transactions.
611
+ *
612
+ * @param params - List parameters including agent context
613
+ * @returns List of Transaction objects
614
+ */
615
+ async list(params) {
616
+ const headers = {};
617
+ if (params?.agentId) {
618
+ headers["X-Agent-ID"] = params.agentId;
619
+ }
620
+ if (params?.customerPartyId) {
621
+ headers["X-On-Behalf-Of"] = params.customerPartyId;
622
+ }
623
+ const data = await this.http.get("/transactions", {
624
+ params: {
625
+ limit: params?.limit ?? 10,
626
+ offset: params?.offset ?? 0,
627
+ customerFilter: params?.customerFilter,
628
+ type: params?.type
629
+ },
630
+ headers: Object.keys(headers).length > 0 ? headers : void 0
631
+ });
632
+ return data.transfers ?? data.transactions ?? [];
633
+ }
634
+ };
635
+
636
+ // src/resources/agents.ts
637
+ var AgentsResource = class extends BaseResource {
638
+ /**
639
+ * List agents for the partner.
640
+ *
641
+ * @param params - Filter and pagination parameters
642
+ * @returns AgentListResponse with list of agents
643
+ */
644
+ async list(params) {
645
+ return this.http.get("/agents", {
646
+ params: {
647
+ status: params?.status,
648
+ partyId: params?.partyId,
649
+ limit: params?.limit ?? 50,
650
+ offset: params?.offset ?? 0
651
+ }
652
+ });
653
+ }
654
+ /**
655
+ * Get agent by ID.
656
+ *
657
+ * @param agentId - The agent ID to retrieve (agt_xxx)
658
+ * @returns Agent details
659
+ */
660
+ async get(agentId) {
661
+ return this.http.get(`/agents/${agentId}`);
662
+ }
663
+ /**
664
+ * Create a new agent.
665
+ *
666
+ * @param params - Agent creation parameters
667
+ * @returns AgentCreateResponse with created agent details
668
+ */
669
+ async create(params) {
670
+ const body = {
671
+ name: params.name,
672
+ partyId: params.partyId
673
+ };
674
+ if (params.description) {
675
+ body["description"] = params.description;
676
+ }
677
+ const headers = {};
678
+ if (params.idempotencyKey) {
679
+ headers["Idempotency-Key"] = params.idempotencyKey;
680
+ }
681
+ return this.http.post("/agents", {
682
+ body,
683
+ headers: Object.keys(headers).length > 0 ? headers : void 0
684
+ });
685
+ }
686
+ /**
687
+ * Update an existing agent.
688
+ *
689
+ * @param agentId - The agent ID to update (agt_xxx)
690
+ * @param params - Update parameters
691
+ * @returns AgentUpdateResponse with updated agent details
692
+ */
693
+ async update(agentId, params) {
694
+ const body = {};
695
+ if (params.name !== void 0) body["name"] = params.name;
696
+ if (params.description !== void 0) body["description"] = params.description;
697
+ if (params.status !== void 0) body["status"] = params.status;
698
+ const headers = {};
699
+ if (params.idempotencyKey) {
700
+ headers["Idempotency-Key"] = params.idempotencyKey;
701
+ }
702
+ return this.http.put(`/agents/${agentId}`, {
703
+ body,
704
+ headers: Object.keys(headers).length > 0 ? headers : void 0
705
+ });
706
+ }
707
+ /**
708
+ * Delete an agent.
709
+ *
710
+ * @param agentId - The agent ID to delete (agt_xxx)
711
+ */
712
+ async delete(agentId) {
713
+ await this.http.delete(`/agents/${agentId}`);
714
+ }
715
+ };
716
+
717
+ // src/resources/delegations.ts
718
+ var DelegationsResource = class extends BaseResource {
719
+ /**
720
+ * List delegations with optional filters.
721
+ *
722
+ * @param params - Filter parameters
723
+ * @returns DelegationListResponse with list of delegations
724
+ */
725
+ async list(params) {
726
+ return this.http.get("/delegations", {
727
+ params: {
728
+ status: params?.status,
729
+ delegatingPartyId: params?.delegatingPartyId,
730
+ delegatedPartyId: params?.delegatedPartyId
731
+ }
732
+ });
733
+ }
734
+ /**
735
+ * Get delegation by ID.
736
+ *
737
+ * @param delegationId - The delegation handle (dlg_xxx)
738
+ * @returns Delegation details
739
+ */
740
+ async get(delegationId) {
741
+ return this.http.get(`/delegations/${delegationId}`);
742
+ }
743
+ /**
744
+ * Create a new delegation (party-to-party trust relationship).
745
+ *
746
+ * @param params - Delegation creation parameters
747
+ * @returns Created Delegation
748
+ */
749
+ async create(params) {
750
+ const body = {
751
+ delegatingPartyId: params.delegatingPartyId,
752
+ delegatedPartyId: params.delegatedPartyId,
753
+ permissions: params.permissions
754
+ };
755
+ if (params.expiresAt) {
756
+ body["expiresAt"] = params.expiresAt;
757
+ }
758
+ const headers = {};
759
+ if (params.idempotencyKey) {
760
+ headers["Idempotency-Key"] = params.idempotencyKey;
761
+ }
762
+ return this.http.post("/delegations", {
763
+ body,
764
+ headers: Object.keys(headers).length > 0 ? headers : void 0
765
+ });
766
+ }
767
+ /**
768
+ * Update an existing delegation.
769
+ *
770
+ * @param delegationId - Delegation handle (dlg_xxx)
771
+ * @param params - Update parameters
772
+ * @returns Updated Delegation
773
+ */
774
+ async update(delegationId, params) {
775
+ const body = {};
776
+ if (params.status !== void 0) body["status"] = params.status;
777
+ if (params.permissions !== void 0) body["permissions"] = params.permissions;
778
+ if (params.expiresAt !== void 0) body["expiresAt"] = params.expiresAt;
779
+ return this.http.put(`/delegations/${delegationId}`, { body });
780
+ }
781
+ /**
782
+ * Revoke a delegation (soft delete by setting status to REVOKED).
783
+ *
784
+ * @param delegationId - Delegation handle (dlg_xxx)
785
+ * @returns Revoked Delegation
786
+ */
787
+ async revoke(delegationId) {
788
+ return this.http.put(`/delegations/${delegationId}/revoke`);
789
+ }
790
+ };
791
+
792
+ // src/resources/customers.ts
793
+ var CustomersResource = class extends BaseResource {
794
+ /**
795
+ * List customers onboarded by the partner via delegation.
796
+ *
797
+ * @returns List of Customer objects with party info and delegation details
798
+ */
799
+ async list() {
800
+ return this.http.get("/customers");
801
+ }
802
+ };
803
+
804
+ // src/client.ts
805
+ var NaturalClient = class {
806
+ http;
807
+ /** Payments API resource. */
808
+ payments;
809
+ /** Wallet API resource for balance, transfers, deposits, and withdrawals. */
810
+ wallet;
811
+ /** Transactions API resource. */
812
+ transactions;
813
+ /** Agents API resource for managing agents. */
814
+ agents;
815
+ /** Delegations API resource for managing party-to-party delegations. */
816
+ delegations;
817
+ /** Customers API resource for listing customers onboarded via delegation. */
818
+ customers;
819
+ /**
820
+ * Initialize the Natural client.
821
+ *
822
+ * @param options - Client configuration options
823
+ * @param options.apiKey - API key (defaults to NATURAL_API_KEY env var)
824
+ * @param options.baseUrl - API base URL (defaults to https://api.natural.co)
825
+ * @param options.timeout - Request timeout in milliseconds (default: 30000)
826
+ */
827
+ constructor(options = {}) {
828
+ this.http = new HTTPClient(options);
829
+ this.payments = new PaymentsResource(this.http);
830
+ this.wallet = new WalletResource(this.http);
831
+ this.transactions = new TransactionsResource(this.http);
832
+ this.agents = new AgentsResource(this.http);
833
+ this.delegations = new DelegationsResource(this.http);
834
+ this.customers = new CustomersResource(this.http);
835
+ }
836
+ };
837
+
838
+ // src/mcp/server.ts
839
+ var logger2 = getLogger("mcp.server");
840
+ function createServer(apiKey) {
841
+ logger2.info("Creating Natural Payments MCP server");
842
+ const server = new FastMCP({
843
+ name: "Natural Payments",
844
+ version: "0.0.1"
845
+ });
846
+ let client = null;
847
+ const getClient = () => {
848
+ if (!client) {
849
+ client = new NaturalClient({ apiKey });
850
+ }
851
+ return client;
852
+ };
853
+ server.addTool({
854
+ name: "create_payment",
855
+ description: "Send a payment to a recipient. Must provide exactly one of: recipientEmail, recipientPhone, or recipientPartyId.",
856
+ parameters: z.object({
857
+ amount: z.number().positive().describe("Payment amount"),
858
+ agentId: z.string().describe("Agent ID making the payment (agt_xxx)"),
859
+ memo: z.string().describe("Payment memo (required)"),
860
+ customerPartyId: z.string().describe("Customer party ID on whose behalf (pty_xxx)"),
861
+ recipientEmail: z.string().email().optional().describe("Recipient email address"),
862
+ recipientPhone: z.string().optional().describe("Recipient phone number"),
863
+ recipientPartyId: z.string().optional().describe("Recipient party ID (pty_xxx)")
864
+ }),
865
+ execute: async (args) => {
866
+ const startTime = Date.now();
867
+ try {
868
+ const result = await getClient().payments.create({
869
+ recipientEmail: args.recipientEmail,
870
+ recipientPhone: args.recipientPhone,
871
+ recipientPartyId: args.recipientPartyId,
872
+ amount: args.amount,
873
+ memo: args.memo,
874
+ agentId: args.agentId,
875
+ customerPartyId: args.customerPartyId
876
+ });
877
+ const durationMs = Date.now() - startTime;
878
+ logToolCall(logger2, "create_payment", { success: true, durationMs });
879
+ return JSON.stringify(result, null, 2);
880
+ } catch (error) {
881
+ const durationMs = Date.now() - startTime;
882
+ logToolCall(logger2, "create_payment", {
883
+ success: false,
884
+ durationMs,
885
+ error: error instanceof Error ? error : new Error(String(error))
886
+ });
887
+ throw error;
888
+ }
889
+ }
890
+ });
891
+ server.addTool({
892
+ name: "get_payment_status",
893
+ description: "Check the status of a payment by transfer ID",
894
+ parameters: z.object({
895
+ transferId: z.string().describe("The transfer ID returned from create_payment")
896
+ }),
897
+ execute: async (args) => {
898
+ const startTime = Date.now();
899
+ try {
900
+ const result = await getClient().payments.retrieve(args.transferId);
901
+ const durationMs = Date.now() - startTime;
902
+ logToolCall(logger2, "get_payment_status", { success: true, durationMs });
903
+ return JSON.stringify(result, null, 2);
904
+ } catch (error) {
905
+ const durationMs = Date.now() - startTime;
906
+ logToolCall(logger2, "get_payment_status", {
907
+ success: false,
908
+ durationMs,
909
+ error: error instanceof Error ? error : new Error(String(error))
910
+ });
911
+ throw error;
912
+ }
913
+ }
914
+ });
915
+ server.addTool({
916
+ name: "cancel_payment",
917
+ description: "Cancel a pending payment. Only pending payments can be cancelled.",
918
+ parameters: z.object({
919
+ transferId: z.string().describe("The transfer ID to cancel")
920
+ }),
921
+ execute: async (args) => {
922
+ const startTime = Date.now();
923
+ try {
924
+ const result = await getClient().payments.cancel(args.transferId);
925
+ const durationMs = Date.now() - startTime;
926
+ logToolCall(logger2, "cancel_payment", { success: true, durationMs });
927
+ return JSON.stringify(result, null, 2);
928
+ } catch (error) {
929
+ const durationMs = Date.now() - startTime;
930
+ logToolCall(logger2, "cancel_payment", {
931
+ success: false,
932
+ durationMs,
933
+ error: error instanceof Error ? error : new Error(String(error))
934
+ });
935
+ throw error;
936
+ }
937
+ }
938
+ });
939
+ server.addTool({
940
+ name: "get_account_balance",
941
+ description: "Get the current wallet balance",
942
+ parameters: z.object({}),
943
+ execute: async () => {
944
+ const startTime = Date.now();
945
+ try {
946
+ const result = await getClient().wallet.balance();
947
+ const balances = result.balances.map((bal) => ({
948
+ assetCode: bal.assetCode,
949
+ available: bal.available.amountDollars,
950
+ breakdown: {
951
+ operatingFunded: bal.breakdown.operatingFunded.amountDollars,
952
+ operatingAdvanced: bal.breakdown.operatingAdvanced.amountDollars,
953
+ escrowFundedSettled: bal.breakdown.escrowFundedSettled.amountDollars,
954
+ escrowAdvanced: bal.breakdown.escrowAdvanced.amountDollars,
955
+ holdsOutbound: bal.breakdown.holdsOutbound.amountDollars
956
+ }
957
+ }));
958
+ const durationMs = Date.now() - startTime;
959
+ logToolCall(logger2, "get_account_balance", { success: true, durationMs });
960
+ return JSON.stringify(
961
+ {
962
+ walletId: result.walletId,
963
+ balances
964
+ },
965
+ null,
966
+ 2
967
+ );
968
+ } catch (error) {
969
+ const durationMs = Date.now() - startTime;
970
+ logToolCall(logger2, "get_account_balance", {
971
+ success: false,
972
+ durationMs,
973
+ error: error instanceof Error ? error : new Error(String(error))
974
+ });
975
+ throw error;
976
+ }
977
+ }
978
+ });
979
+ server.addTool({
980
+ name: "list_transactions",
981
+ description: "List recent transactions with optional agent context",
982
+ parameters: z.object({
983
+ limit: z.number().min(1).max(100).default(10).describe("Maximum number of transactions"),
984
+ customerFilter: z.string().optional().describe("Filter by customer agent_id (or '_self' for partner only)"),
985
+ agentId: z.string().optional().describe("Agent ID for agent-context authentication"),
986
+ customerPartyId: z.string().optional().describe("Customer party ID when acting on behalf of customer")
987
+ }),
988
+ execute: async (args) => {
989
+ const startTime = Date.now();
990
+ try {
991
+ const result = await getClient().transactions.list({
992
+ limit: args.limit,
993
+ customerFilter: args.customerFilter,
994
+ agentId: args.agentId,
995
+ customerPartyId: args.customerPartyId
996
+ });
997
+ const durationMs = Date.now() - startTime;
998
+ logToolCall(logger2, "list_transactions", { success: true, durationMs });
999
+ return JSON.stringify(result, null, 2);
1000
+ } catch (error) {
1001
+ const durationMs = Date.now() - startTime;
1002
+ logToolCall(logger2, "list_transactions", {
1003
+ success: false,
1004
+ durationMs,
1005
+ error: error instanceof Error ? error : new Error(String(error))
1006
+ });
1007
+ throw error;
1008
+ }
1009
+ }
1010
+ });
1011
+ server.addTool({
1012
+ name: "list_agents",
1013
+ description: "List agents for the partner",
1014
+ parameters: z.object({
1015
+ status: z.enum(["ACTIVE", "REVOKED"]).optional().describe("Filter by status"),
1016
+ limit: z.number().min(1).max(100).default(50).describe("Maximum number of agents")
1017
+ }),
1018
+ execute: async (args) => {
1019
+ const startTime = Date.now();
1020
+ try {
1021
+ const result = await getClient().agents.list({
1022
+ status: args.status,
1023
+ limit: args.limit
1024
+ });
1025
+ const durationMs = Date.now() - startTime;
1026
+ logToolCall(logger2, "list_agents", { success: true, durationMs });
1027
+ return JSON.stringify(result, null, 2);
1028
+ } catch (error) {
1029
+ const durationMs = Date.now() - startTime;
1030
+ logToolCall(logger2, "list_agents", {
1031
+ success: false,
1032
+ durationMs,
1033
+ error: error instanceof Error ? error : new Error(String(error))
1034
+ });
1035
+ throw error;
1036
+ }
1037
+ }
1038
+ });
1039
+ return server;
1040
+ }
1041
+
1042
+ export { createServer };
1043
+ //# sourceMappingURL=index.js.map
1044
+ //# sourceMappingURL=index.js.map