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