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