@payments-ai/mcp 1.1.3 → 1.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/main.http.js +1761 -0
- package/dist/main.js +219 -104
- package/package.json +14 -6
|
@@ -0,0 +1,1761 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
8
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
9
|
+
}) : x)(function(x) {
|
|
10
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
11
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
12
|
+
});
|
|
13
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
14
|
+
try {
|
|
15
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
16
|
+
} catch (e) {
|
|
17
|
+
throw mod = 0, e;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
var __copyProps = (to, from, except, desc) => {
|
|
21
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
22
|
+
for (let key of __getOwnPropNames(from))
|
|
23
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
24
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
25
|
+
}
|
|
26
|
+
return to;
|
|
27
|
+
};
|
|
28
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
29
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
30
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
31
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
32
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
33
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
34
|
+
mod
|
|
35
|
+
));
|
|
36
|
+
|
|
37
|
+
// ../../packages/mcp-contracts/dist/tool-names.js
|
|
38
|
+
var require_tool_names = __commonJS({
|
|
39
|
+
"../../packages/mcp-contracts/dist/tool-names.js"(exports) {
|
|
40
|
+
"use strict";
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.MCP_TOOL_NAMES = void 0;
|
|
43
|
+
exports.MCP_TOOL_NAMES = {
|
|
44
|
+
PAYMENTS_AI_HEALTH: "payments_ai_health",
|
|
45
|
+
CREATE_MERCHANT_ACCOUNT: "create_merchant_account",
|
|
46
|
+
GET_MERCHANT_ACTIVATION_STATUS: "get_merchant_activation_status",
|
|
47
|
+
SEARCH_PAYMENTS_AI_DOCS: "search_payments_ai_docs",
|
|
48
|
+
CREATE_PRODUCT: "create_product",
|
|
49
|
+
GET_PRODUCT_IMAGE_UPLOAD_URL: "get_product_image_upload_url",
|
|
50
|
+
CONFIRM_PRODUCT_IMAGE: "confirm_product_image",
|
|
51
|
+
GET_CHECKOUT_CUSTOMIZATION: "get_checkout_customization",
|
|
52
|
+
UPDATE_CHECKOUT_CUSTOMIZATION: "update_checkout_customization"
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// ../../packages/mcp-contracts/dist/create-product.js
|
|
58
|
+
var require_create_product = __commonJS({
|
|
59
|
+
"../../packages/mcp-contracts/dist/create-product.js"(exports) {
|
|
60
|
+
"use strict";
|
|
61
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
62
|
+
exports.createMcpProductOutputSchema = exports.createMcpProductInputSchema = exports.createMcpProductBodySchema = void 0;
|
|
63
|
+
var zod_1 = __require("zod");
|
|
64
|
+
var PLAN_TYPES = ["free-access", "one-time", "recurring"];
|
|
65
|
+
var BILLING_PERIODS = ["day", "week", "month", "year"];
|
|
66
|
+
var PRODUCT_STATUSES = ["draft", "active", "archived"];
|
|
67
|
+
var createMcpPlanSchema = zod_1.z.object({
|
|
68
|
+
name: zod_1.z.string().trim().min(1).max(30),
|
|
69
|
+
description: zod_1.z.string().trim().max(80).optional(),
|
|
70
|
+
richDescription: zod_1.z.string().trim().max(65535).optional(),
|
|
71
|
+
currency: zod_1.z.string().trim().length(3).transform((v) => v.toLowerCase()),
|
|
72
|
+
amount: zod_1.z.number().nonnegative(),
|
|
73
|
+
type: zod_1.z.enum(PLAN_TYPES),
|
|
74
|
+
billingPeriod: zod_1.z.enum(BILLING_PERIODS),
|
|
75
|
+
periodLength: zod_1.z.number().int().positive(),
|
|
76
|
+
freeTrial: zod_1.z.number().int().min(0).optional()
|
|
77
|
+
}).superRefine((plan, ctx) => {
|
|
78
|
+
if (plan.type === "free-access")
|
|
79
|
+
return;
|
|
80
|
+
if (plan.amount <= 0) {
|
|
81
|
+
ctx.addIssue({
|
|
82
|
+
code: "custom",
|
|
83
|
+
message: "Amount must be greater than 0 for paid plans",
|
|
84
|
+
path: ["amount"]
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
exports.createMcpProductBodySchema = zod_1.z.object({
|
|
89
|
+
name: zod_1.z.string().trim().min(1).max(30),
|
|
90
|
+
description: zod_1.z.string().trim().min(1).max(80).optional(),
|
|
91
|
+
taxCodeId: zod_1.z.string().trim().min(1).max(255).optional(),
|
|
92
|
+
status: zod_1.z.enum(PRODUCT_STATUSES).optional().default("draft"),
|
|
93
|
+
discountCode: zod_1.z.string().trim().min(1).max(255).optional(),
|
|
94
|
+
plans: zod_1.z.array(createMcpPlanSchema).min(1)
|
|
95
|
+
}).strict();
|
|
96
|
+
exports.createMcpProductInputSchema = exports.createMcpProductBodySchema.extend({
|
|
97
|
+
merchantId: zod_1.z.uuid()
|
|
98
|
+
});
|
|
99
|
+
exports.createMcpProductOutputSchema = zod_1.z.object({
|
|
100
|
+
id: zod_1.z.string(),
|
|
101
|
+
planIds: zod_1.z.array(zod_1.z.string())
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// ../../packages/mcp-contracts/dist/product-image-presigned-upload.js
|
|
107
|
+
var require_product_image_presigned_upload = __commonJS({
|
|
108
|
+
"../../packages/mcp-contracts/dist/product-image-presigned-upload.js"(exports) {
|
|
109
|
+
"use strict";
|
|
110
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
111
|
+
exports.PRODUCT_IMAGE_MIME_TYPES = exports.MAX_PRODUCT_IMAGE_UPLOAD_BYTES = exports.confirmProductImageOutputSchema = exports.confirmProductImageInputSchema = exports.confirmProductImageBodySchema = exports.productImageUploadUrlOutputSchema = exports.productImageUploadUrlInputSchema = exports.productImageUploadUrlBodySchema = void 0;
|
|
112
|
+
var zod_1 = __require("zod");
|
|
113
|
+
var PRODUCT_IMAGE_MIME_TYPES = ["image/png", "image/jpeg", "image/jpg", "image/webp"];
|
|
114
|
+
exports.PRODUCT_IMAGE_MIME_TYPES = PRODUCT_IMAGE_MIME_TYPES;
|
|
115
|
+
var MAX_PRODUCT_IMAGE_UPLOAD_BYTES = 5242880;
|
|
116
|
+
exports.MAX_PRODUCT_IMAGE_UPLOAD_BYTES = MAX_PRODUCT_IMAGE_UPLOAD_BYTES;
|
|
117
|
+
var mimeTypeSchema = zod_1.z.enum(PRODUCT_IMAGE_MIME_TYPES, {
|
|
118
|
+
message: "mimeType must be image/png, image/jpeg, image/jpg, or image/webp. Recovery: convert the file to a supported format."
|
|
119
|
+
});
|
|
120
|
+
var fileNameSchema = zod_1.z.string().trim().min(1, "fileName is required.").max(255, "fileName must be at most 255 characters.");
|
|
121
|
+
exports.productImageUploadUrlBodySchema = zod_1.z.object({
|
|
122
|
+
fileName: fileNameSchema,
|
|
123
|
+
mimeType: mimeTypeSchema
|
|
124
|
+
}).strict();
|
|
125
|
+
exports.productImageUploadUrlInputSchema = exports.productImageUploadUrlBodySchema.extend({
|
|
126
|
+
merchantId: zod_1.z.uuid(),
|
|
127
|
+
productId: zod_1.z.uuid()
|
|
128
|
+
});
|
|
129
|
+
exports.productImageUploadUrlOutputSchema = zod_1.z.object({
|
|
130
|
+
// S3 POST endpoint the client submits the multipart/form-data upload to.
|
|
131
|
+
url: zod_1.z.string(),
|
|
132
|
+
// Form fields the client must include in the multipart body (policy, signature,
|
|
133
|
+
// key, Content-Type, ...). The `file` field must be added last per S3's spec.
|
|
134
|
+
fields: zod_1.z.record(zod_1.z.string(), zod_1.z.string()),
|
|
135
|
+
// Opaque storage key the client must echo back to confirmProductImage. Bound to the
|
|
136
|
+
// merchant; confirm rejects keys outside the caller's own prefix.
|
|
137
|
+
key: zod_1.z.string(),
|
|
138
|
+
expiresIn: zod_1.z.number().int().positive()
|
|
139
|
+
});
|
|
140
|
+
exports.confirmProductImageBodySchema = zod_1.z.object({
|
|
141
|
+
key: zod_1.z.string().trim().min(1, "key is required. Recovery: use the key returned by the upload-url step.")
|
|
142
|
+
}).strict();
|
|
143
|
+
exports.confirmProductImageInputSchema = exports.confirmProductImageBodySchema.extend({
|
|
144
|
+
merchantId: zod_1.z.uuid(),
|
|
145
|
+
productId: zod_1.z.uuid()
|
|
146
|
+
});
|
|
147
|
+
exports.confirmProductImageOutputSchema = zod_1.z.object({
|
|
148
|
+
imageUrl: zod_1.z.string()
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// ../../packages/mcp-contracts/dist/create-merchant-account.js
|
|
154
|
+
var require_create_merchant_account = __commonJS({
|
|
155
|
+
"../../packages/mcp-contracts/dist/create-merchant-account.js"(exports) {
|
|
156
|
+
"use strict";
|
|
157
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
158
|
+
exports.createMerchantAccountOutputSchema = exports.createMerchantAccountInputSchema = void 0;
|
|
159
|
+
var zod_1 = __require("zod");
|
|
160
|
+
exports.createMerchantAccountInputSchema = zod_1.z.object({
|
|
161
|
+
name: zod_1.z.string().trim().min(1, {
|
|
162
|
+
message: "Provide a non-empty business or display name. Recovery: re-send the tool call with a visible label for the merchant account."
|
|
163
|
+
}),
|
|
164
|
+
email: zod_1.z.email({
|
|
165
|
+
error: "Provide a valid email. Recovery: check spelling, domain, and that the value is a single RFC-style address."
|
|
166
|
+
})
|
|
167
|
+
});
|
|
168
|
+
exports.createMerchantAccountOutputSchema = zod_1.z.object({
|
|
169
|
+
merchantId: zod_1.z.uuid()
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// ../../packages/mcp-contracts/dist/activation-status.js
|
|
175
|
+
var require_activation_status = __commonJS({
|
|
176
|
+
"../../packages/mcp-contracts/dist/activation-status.js"(exports) {
|
|
177
|
+
"use strict";
|
|
178
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
179
|
+
exports.activationStatusOutputSchema = exports.activationStatusEnumSchema = exports.activationStatusInputSchema = void 0;
|
|
180
|
+
var zod_1 = __require("zod");
|
|
181
|
+
exports.activationStatusInputSchema = zod_1.z.object({
|
|
182
|
+
merchantId: zod_1.z.string().trim().min(1, {
|
|
183
|
+
message: "Provide the merchant account identifier returned from create_merchant_account. Recovery: copy the merchantId from a prior successful create_merchant_account response."
|
|
184
|
+
})
|
|
185
|
+
});
|
|
186
|
+
exports.activationStatusEnumSchema = zod_1.z.enum(["pending", "not_provisioned", "active"]);
|
|
187
|
+
exports.activationStatusOutputSchema = zod_1.z.object({
|
|
188
|
+
status: exports.activationStatusEnumSchema
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// ../../packages/mcp-contracts/dist/payments-ai-health.js
|
|
194
|
+
var require_payments_ai_health = __commonJS({
|
|
195
|
+
"../../packages/mcp-contracts/dist/payments-ai-health.js"(exports) {
|
|
196
|
+
"use strict";
|
|
197
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
198
|
+
exports.paymentsAiHealthOutputSchema = exports.paymentsAiHealthBackendSchema = exports.paymentsAiHealthBackendProbeSchema = void 0;
|
|
199
|
+
var zod_1 = __require("zod");
|
|
200
|
+
exports.paymentsAiHealthBackendProbeSchema = zod_1.z.object({
|
|
201
|
+
path: zod_1.z.string(),
|
|
202
|
+
ok: zod_1.z.boolean(),
|
|
203
|
+
httpStatus: zod_1.z.number().optional(),
|
|
204
|
+
message: zod_1.z.string()
|
|
205
|
+
});
|
|
206
|
+
exports.paymentsAiHealthBackendSchema = zod_1.z.object({
|
|
207
|
+
baseUrlConfigured: zod_1.z.boolean(),
|
|
208
|
+
probe: exports.paymentsAiHealthBackendProbeSchema.optional()
|
|
209
|
+
});
|
|
210
|
+
exports.paymentsAiHealthOutputSchema = zod_1.z.object({
|
|
211
|
+
serverName: zod_1.z.string(),
|
|
212
|
+
version: zod_1.z.string(),
|
|
213
|
+
/** Always true for this PoC slice — tool remains illustrative / non-mutating. */
|
|
214
|
+
stub: zod_1.z.literal(true),
|
|
215
|
+
backend: exports.paymentsAiHealthBackendSchema
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// ../../packages/mcp-contracts/dist/search-payments-ai-docs.js
|
|
221
|
+
var require_search_payments_ai_docs = __commonJS({
|
|
222
|
+
"../../packages/mcp-contracts/dist/search-payments-ai-docs.js"(exports) {
|
|
223
|
+
"use strict";
|
|
224
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
225
|
+
exports.searchPaymentsAiDocsOutputSchema = exports.searchPaymentsAiDocsResultSchema = exports.searchPaymentsAiDocsInputSchema = void 0;
|
|
226
|
+
var zod_1 = __require("zod");
|
|
227
|
+
exports.searchPaymentsAiDocsInputSchema = zod_1.z.object({
|
|
228
|
+
query: zod_1.z.string().min(1).max(500).describe("Search query")
|
|
229
|
+
});
|
|
230
|
+
exports.searchPaymentsAiDocsResultSchema = zod_1.z.object({
|
|
231
|
+
title: zod_1.z.string().describe("Section title"),
|
|
232
|
+
path: zod_1.z.string().describe("Documentation path/anchor"),
|
|
233
|
+
content: zod_1.z.string().describe("Section content")
|
|
234
|
+
});
|
|
235
|
+
exports.searchPaymentsAiDocsOutputSchema = zod_1.z.object({
|
|
236
|
+
results: zod_1.z.array(exports.searchPaymentsAiDocsResultSchema).describe("Matching documentation sections with titles and links")
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// ../../packages/mcp-contracts/dist/checkout-customization.js
|
|
242
|
+
var require_checkout_customization = __commonJS({
|
|
243
|
+
"../../packages/mcp-contracts/dist/checkout-customization.js"(exports) {
|
|
244
|
+
"use strict";
|
|
245
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
246
|
+
exports.checkoutCustomizationOutputSchema = exports.updateCheckoutCustomizationInputSchema = exports.getCheckoutCustomizationInputSchema = exports.checkoutCustomizationFieldsSchema = void 0;
|
|
247
|
+
var zod_1 = __require("zod");
|
|
248
|
+
var HEX_COLOR_REGEX = /^#(?:[0-9a-fA-F]{6})$/;
|
|
249
|
+
var CHECKOUT_THEME_MODES = ["light", "dark"];
|
|
250
|
+
var CHECKOUT_FONT_FAMILIES = ["Inter", "Roboto"];
|
|
251
|
+
var CHECKOUT_INPUT_STYLES = ["rounded", "square"];
|
|
252
|
+
var hexColor = zod_1.z.string().regex(HEX_COLOR_REGEX, {
|
|
253
|
+
message: 'Provide a 6-digit hex color like "#1A2B3C". Recovery: include the leading "#".'
|
|
254
|
+
});
|
|
255
|
+
exports.checkoutCustomizationFieldsSchema = zod_1.z.object({
|
|
256
|
+
themeMode: zod_1.z.enum(CHECKOUT_THEME_MODES).optional(),
|
|
257
|
+
backgroundColor: hexColor.optional(),
|
|
258
|
+
buttonColor: hexColor.optional(),
|
|
259
|
+
buttonTextColor: hexColor.optional(),
|
|
260
|
+
fontColor: hexColor.optional(),
|
|
261
|
+
fontFamily: zod_1.z.enum(CHECKOUT_FONT_FAMILIES).optional(),
|
|
262
|
+
inputStyle: zod_1.z.enum(CHECKOUT_INPUT_STYLES).optional()
|
|
263
|
+
}).strict();
|
|
264
|
+
exports.getCheckoutCustomizationInputSchema = zod_1.z.object({
|
|
265
|
+
merchantId: zod_1.z.uuid()
|
|
266
|
+
});
|
|
267
|
+
exports.updateCheckoutCustomizationInputSchema = zod_1.z.object({
|
|
268
|
+
merchantId: zod_1.z.uuid(),
|
|
269
|
+
// At least one field must be present — an empty update is a no-op that would still bump
|
|
270
|
+
// updated_at, so reject it with a clear message rather than writing nothing meaningful.
|
|
271
|
+
customization: exports.checkoutCustomizationFieldsSchema.refine((c) => Object.keys(c).length > 0, 'Provide at least one field to change. Recovery: include e.g. "buttonColor" or "themeMode".')
|
|
272
|
+
});
|
|
273
|
+
exports.checkoutCustomizationOutputSchema = exports.checkoutCustomizationFieldsSchema.extend({
|
|
274
|
+
logoUrl: zod_1.z.string().optional()
|
|
275
|
+
}).passthrough();
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// ../../packages/mcp-contracts/dist/index.js
|
|
280
|
+
var require_dist = __commonJS({
|
|
281
|
+
"../../packages/mcp-contracts/dist/index.js"(exports) {
|
|
282
|
+
"use strict";
|
|
283
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
284
|
+
exports.checkoutCustomizationOutputSchema = exports.updateCheckoutCustomizationInputSchema = exports.getCheckoutCustomizationInputSchema = exports.checkoutCustomizationFieldsSchema = exports.searchPaymentsAiDocsOutputSchema = exports.searchPaymentsAiDocsResultSchema = exports.searchPaymentsAiDocsInputSchema = exports.paymentsAiHealthOutputSchema = exports.paymentsAiHealthBackendSchema = exports.paymentsAiHealthBackendProbeSchema = exports.activationStatusOutputSchema = exports.activationStatusEnumSchema = exports.activationStatusInputSchema = exports.createMerchantAccountOutputSchema = exports.createMerchantAccountInputSchema = exports.PRODUCT_IMAGE_MIME_TYPES = exports.MAX_PRODUCT_IMAGE_UPLOAD_BYTES = exports.confirmProductImageOutputSchema = exports.confirmProductImageInputSchema = exports.confirmProductImageBodySchema = exports.productImageUploadUrlOutputSchema = exports.productImageUploadUrlInputSchema = exports.productImageUploadUrlBodySchema = exports.createMcpProductOutputSchema = exports.createMcpProductInputSchema = exports.createMcpProductBodySchema = exports.MCP_TOOL_NAMES = void 0;
|
|
285
|
+
var tool_names_js_1 = require_tool_names();
|
|
286
|
+
Object.defineProperty(exports, "MCP_TOOL_NAMES", { enumerable: true, get: function() {
|
|
287
|
+
return tool_names_js_1.MCP_TOOL_NAMES;
|
|
288
|
+
} });
|
|
289
|
+
var create_product_js_1 = require_create_product();
|
|
290
|
+
Object.defineProperty(exports, "createMcpProductBodySchema", { enumerable: true, get: function() {
|
|
291
|
+
return create_product_js_1.createMcpProductBodySchema;
|
|
292
|
+
} });
|
|
293
|
+
Object.defineProperty(exports, "createMcpProductInputSchema", { enumerable: true, get: function() {
|
|
294
|
+
return create_product_js_1.createMcpProductInputSchema;
|
|
295
|
+
} });
|
|
296
|
+
Object.defineProperty(exports, "createMcpProductOutputSchema", { enumerable: true, get: function() {
|
|
297
|
+
return create_product_js_1.createMcpProductOutputSchema;
|
|
298
|
+
} });
|
|
299
|
+
var product_image_presigned_upload_js_1 = require_product_image_presigned_upload();
|
|
300
|
+
Object.defineProperty(exports, "productImageUploadUrlBodySchema", { enumerable: true, get: function() {
|
|
301
|
+
return product_image_presigned_upload_js_1.productImageUploadUrlBodySchema;
|
|
302
|
+
} });
|
|
303
|
+
Object.defineProperty(exports, "productImageUploadUrlInputSchema", { enumerable: true, get: function() {
|
|
304
|
+
return product_image_presigned_upload_js_1.productImageUploadUrlInputSchema;
|
|
305
|
+
} });
|
|
306
|
+
Object.defineProperty(exports, "productImageUploadUrlOutputSchema", { enumerable: true, get: function() {
|
|
307
|
+
return product_image_presigned_upload_js_1.productImageUploadUrlOutputSchema;
|
|
308
|
+
} });
|
|
309
|
+
Object.defineProperty(exports, "confirmProductImageBodySchema", { enumerable: true, get: function() {
|
|
310
|
+
return product_image_presigned_upload_js_1.confirmProductImageBodySchema;
|
|
311
|
+
} });
|
|
312
|
+
Object.defineProperty(exports, "confirmProductImageInputSchema", { enumerable: true, get: function() {
|
|
313
|
+
return product_image_presigned_upload_js_1.confirmProductImageInputSchema;
|
|
314
|
+
} });
|
|
315
|
+
Object.defineProperty(exports, "confirmProductImageOutputSchema", { enumerable: true, get: function() {
|
|
316
|
+
return product_image_presigned_upload_js_1.confirmProductImageOutputSchema;
|
|
317
|
+
} });
|
|
318
|
+
Object.defineProperty(exports, "MAX_PRODUCT_IMAGE_UPLOAD_BYTES", { enumerable: true, get: function() {
|
|
319
|
+
return product_image_presigned_upload_js_1.MAX_PRODUCT_IMAGE_UPLOAD_BYTES;
|
|
320
|
+
} });
|
|
321
|
+
Object.defineProperty(exports, "PRODUCT_IMAGE_MIME_TYPES", { enumerable: true, get: function() {
|
|
322
|
+
return product_image_presigned_upload_js_1.PRODUCT_IMAGE_MIME_TYPES;
|
|
323
|
+
} });
|
|
324
|
+
var create_merchant_account_js_1 = require_create_merchant_account();
|
|
325
|
+
Object.defineProperty(exports, "createMerchantAccountInputSchema", { enumerable: true, get: function() {
|
|
326
|
+
return create_merchant_account_js_1.createMerchantAccountInputSchema;
|
|
327
|
+
} });
|
|
328
|
+
Object.defineProperty(exports, "createMerchantAccountOutputSchema", { enumerable: true, get: function() {
|
|
329
|
+
return create_merchant_account_js_1.createMerchantAccountOutputSchema;
|
|
330
|
+
} });
|
|
331
|
+
var activation_status_js_1 = require_activation_status();
|
|
332
|
+
Object.defineProperty(exports, "activationStatusInputSchema", { enumerable: true, get: function() {
|
|
333
|
+
return activation_status_js_1.activationStatusInputSchema;
|
|
334
|
+
} });
|
|
335
|
+
Object.defineProperty(exports, "activationStatusEnumSchema", { enumerable: true, get: function() {
|
|
336
|
+
return activation_status_js_1.activationStatusEnumSchema;
|
|
337
|
+
} });
|
|
338
|
+
Object.defineProperty(exports, "activationStatusOutputSchema", { enumerable: true, get: function() {
|
|
339
|
+
return activation_status_js_1.activationStatusOutputSchema;
|
|
340
|
+
} });
|
|
341
|
+
var payments_ai_health_js_1 = require_payments_ai_health();
|
|
342
|
+
Object.defineProperty(exports, "paymentsAiHealthBackendProbeSchema", { enumerable: true, get: function() {
|
|
343
|
+
return payments_ai_health_js_1.paymentsAiHealthBackendProbeSchema;
|
|
344
|
+
} });
|
|
345
|
+
Object.defineProperty(exports, "paymentsAiHealthBackendSchema", { enumerable: true, get: function() {
|
|
346
|
+
return payments_ai_health_js_1.paymentsAiHealthBackendSchema;
|
|
347
|
+
} });
|
|
348
|
+
Object.defineProperty(exports, "paymentsAiHealthOutputSchema", { enumerable: true, get: function() {
|
|
349
|
+
return payments_ai_health_js_1.paymentsAiHealthOutputSchema;
|
|
350
|
+
} });
|
|
351
|
+
var search_payments_ai_docs_js_1 = require_search_payments_ai_docs();
|
|
352
|
+
Object.defineProperty(exports, "searchPaymentsAiDocsInputSchema", { enumerable: true, get: function() {
|
|
353
|
+
return search_payments_ai_docs_js_1.searchPaymentsAiDocsInputSchema;
|
|
354
|
+
} });
|
|
355
|
+
Object.defineProperty(exports, "searchPaymentsAiDocsResultSchema", { enumerable: true, get: function() {
|
|
356
|
+
return search_payments_ai_docs_js_1.searchPaymentsAiDocsResultSchema;
|
|
357
|
+
} });
|
|
358
|
+
Object.defineProperty(exports, "searchPaymentsAiDocsOutputSchema", { enumerable: true, get: function() {
|
|
359
|
+
return search_payments_ai_docs_js_1.searchPaymentsAiDocsOutputSchema;
|
|
360
|
+
} });
|
|
361
|
+
var checkout_customization_js_1 = require_checkout_customization();
|
|
362
|
+
Object.defineProperty(exports, "checkoutCustomizationFieldsSchema", { enumerable: true, get: function() {
|
|
363
|
+
return checkout_customization_js_1.checkoutCustomizationFieldsSchema;
|
|
364
|
+
} });
|
|
365
|
+
Object.defineProperty(exports, "getCheckoutCustomizationInputSchema", { enumerable: true, get: function() {
|
|
366
|
+
return checkout_customization_js_1.getCheckoutCustomizationInputSchema;
|
|
367
|
+
} });
|
|
368
|
+
Object.defineProperty(exports, "updateCheckoutCustomizationInputSchema", { enumerable: true, get: function() {
|
|
369
|
+
return checkout_customization_js_1.updateCheckoutCustomizationInputSchema;
|
|
370
|
+
} });
|
|
371
|
+
Object.defineProperty(exports, "checkoutCustomizationOutputSchema", { enumerable: true, get: function() {
|
|
372
|
+
return checkout_customization_js_1.checkoutCustomizationOutputSchema;
|
|
373
|
+
} });
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// src/load-env.ts
|
|
378
|
+
import path from "node:path";
|
|
379
|
+
import dotenv from "dotenv";
|
|
380
|
+
var packageRoot = path.resolve(import.meta.dirname, "..");
|
|
381
|
+
dotenv.config({ path: path.join(packageRoot, ".env"), quiet: true });
|
|
382
|
+
dotenv.config({ path: path.join(process.cwd(), ".env"), quiet: true });
|
|
383
|
+
|
|
384
|
+
// src/observability/instrument-sentry.ts
|
|
385
|
+
import * as Sentry from "@sentry/node";
|
|
386
|
+
|
|
387
|
+
// src/config/env.ts
|
|
388
|
+
var APP_ENV_VALUES = ["local", "development", "production", "stage", "test"];
|
|
389
|
+
function readAppEnv(env = process.env) {
|
|
390
|
+
const raw = env.APP_ENV?.trim();
|
|
391
|
+
if (!raw) {
|
|
392
|
+
return void 0;
|
|
393
|
+
}
|
|
394
|
+
if (!APP_ENV_VALUES.includes(raw)) {
|
|
395
|
+
throw new Error(`APP_ENV must be one of: ${APP_ENV_VALUES.join(", ")}`);
|
|
396
|
+
}
|
|
397
|
+
return raw;
|
|
398
|
+
}
|
|
399
|
+
function parsePort(raw, fallback) {
|
|
400
|
+
if (!raw?.trim()) {
|
|
401
|
+
return fallback;
|
|
402
|
+
}
|
|
403
|
+
const n = Number(raw);
|
|
404
|
+
return Number.isInteger(n) && n > 0 && n < 65536 ? n : fallback;
|
|
405
|
+
}
|
|
406
|
+
function parseAllowedHosts(raw) {
|
|
407
|
+
if (!raw?.trim()) {
|
|
408
|
+
return void 0;
|
|
409
|
+
}
|
|
410
|
+
const list = raw.split(",").map((h) => h.trim()).filter(Boolean);
|
|
411
|
+
return list.length > 0 ? list : void 0;
|
|
412
|
+
}
|
|
413
|
+
function resolveCorsOrigin() {
|
|
414
|
+
const raw = process.env.MCP_CORS_ORIGIN?.trim();
|
|
415
|
+
if (!raw || raw === "reflect") {
|
|
416
|
+
return true;
|
|
417
|
+
}
|
|
418
|
+
if (raw === "*") {
|
|
419
|
+
return "*";
|
|
420
|
+
}
|
|
421
|
+
const origins = raw.split(",").map((o) => o.trim()).filter(Boolean);
|
|
422
|
+
if (origins.length === 0) {
|
|
423
|
+
return true;
|
|
424
|
+
}
|
|
425
|
+
if (origins.length === 1) {
|
|
426
|
+
return origins[0];
|
|
427
|
+
}
|
|
428
|
+
return [...origins];
|
|
429
|
+
}
|
|
430
|
+
function readMcpHttpListenConfig() {
|
|
431
|
+
const host2 = process.env.HOST?.trim() || "0.0.0.0";
|
|
432
|
+
const port2 = parsePort(process.env.PORT, 3333);
|
|
433
|
+
const mcpPath2 = process.env.MCP_HTTP_PATH?.trim() || "/mcp";
|
|
434
|
+
const allowedHosts2 = parseAllowedHosts(process.env.MCP_ALLOWED_HOSTS);
|
|
435
|
+
const mcpPublicUrl2 = readRequiredHttpBaseUrl("MCP_PUBLIC_URL");
|
|
436
|
+
const oauthIssuerUrl2 = readRequiredHttpBaseUrl("OAUTH_ISSUER_URL");
|
|
437
|
+
return { host: host2, port: port2, mcpPath: mcpPath2, allowedHosts: allowedHosts2, mcpPublicUrl: mcpPublicUrl2, oauthIssuerUrl: oauthIssuerUrl2 };
|
|
438
|
+
}
|
|
439
|
+
function readRequiredHttpBaseUrl(envName) {
|
|
440
|
+
const raw = process.env[envName];
|
|
441
|
+
const trimmed = raw?.trim();
|
|
442
|
+
if (!trimmed) {
|
|
443
|
+
throw new Error(`${envName} is required`);
|
|
444
|
+
}
|
|
445
|
+
let parsed;
|
|
446
|
+
try {
|
|
447
|
+
parsed = new URL(trimmed);
|
|
448
|
+
} catch {
|
|
449
|
+
throw new Error(`${envName} must be a valid absolute URL`);
|
|
450
|
+
}
|
|
451
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
452
|
+
throw new Error(`${envName} must use http: or https:`);
|
|
453
|
+
}
|
|
454
|
+
const pathname = parsed.pathname.replace(/\/+$/, "");
|
|
455
|
+
return `${parsed.origin}${pathname}`;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// src/observability/sentry.config.ts
|
|
459
|
+
function parseTracesSampleRate(value, defaultValue) {
|
|
460
|
+
if (!value?.trim()) {
|
|
461
|
+
return defaultValue;
|
|
462
|
+
}
|
|
463
|
+
const parsed = Number(value);
|
|
464
|
+
if (!Number.isFinite(parsed) || parsed < 0 || parsed > 1) {
|
|
465
|
+
return defaultValue;
|
|
466
|
+
}
|
|
467
|
+
return parsed;
|
|
468
|
+
}
|
|
469
|
+
function defaultTracesSampleRateForAppEnv(appEnv) {
|
|
470
|
+
if (appEnv === "stage" || appEnv === "production") {
|
|
471
|
+
return 0.2;
|
|
472
|
+
}
|
|
473
|
+
return 1;
|
|
474
|
+
}
|
|
475
|
+
function readSentryDsn(env = process.env) {
|
|
476
|
+
const dsn = env.SENTRY_DSN?.trim();
|
|
477
|
+
return dsn || void 0;
|
|
478
|
+
}
|
|
479
|
+
function resolveSentryEnvironment(env = process.env) {
|
|
480
|
+
const fromEnv = env.SENTRY_ENVIRONMENT?.trim();
|
|
481
|
+
if (fromEnv) {
|
|
482
|
+
return fromEnv;
|
|
483
|
+
}
|
|
484
|
+
return readAppEnv(env) ?? "local";
|
|
485
|
+
}
|
|
486
|
+
function readSentryTracesSampleRate(env = process.env) {
|
|
487
|
+
const appEnv = readAppEnv(env);
|
|
488
|
+
return parseTracesSampleRate(
|
|
489
|
+
env.SENTRY_TRACES_SAMPLE_RATE,
|
|
490
|
+
defaultTracesSampleRateForAppEnv(appEnv)
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
function scrubAuthorizationHeader(headers) {
|
|
494
|
+
if (!headers) {
|
|
495
|
+
return headers;
|
|
496
|
+
}
|
|
497
|
+
const scrubbed = { ...headers };
|
|
498
|
+
for (const key of Object.keys(scrubbed)) {
|
|
499
|
+
if (key.toLowerCase() === "authorization") {
|
|
500
|
+
scrubbed[key] = "[Filtered]";
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return scrubbed;
|
|
504
|
+
}
|
|
505
|
+
function scrubEventRequestHeaders(event) {
|
|
506
|
+
const request = event.request;
|
|
507
|
+
if (!request?.headers) {
|
|
508
|
+
return event;
|
|
509
|
+
}
|
|
510
|
+
return {
|
|
511
|
+
...event,
|
|
512
|
+
request: {
|
|
513
|
+
...request,
|
|
514
|
+
headers: scrubAuthorizationHeader(request.headers)
|
|
515
|
+
}
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
function buildSentryInitOptions(env = process.env) {
|
|
519
|
+
const dsn = readSentryDsn(env);
|
|
520
|
+
if (!dsn) {
|
|
521
|
+
return void 0;
|
|
522
|
+
}
|
|
523
|
+
return {
|
|
524
|
+
dsn,
|
|
525
|
+
environment: resolveSentryEnvironment(env),
|
|
526
|
+
tracesSampleRate: readSentryTracesSampleRate(env),
|
|
527
|
+
sendDefaultPii: false,
|
|
528
|
+
beforeSend(event) {
|
|
529
|
+
return scrubEventRequestHeaders(event);
|
|
530
|
+
},
|
|
531
|
+
beforeSendTransaction(event) {
|
|
532
|
+
return scrubEventRequestHeaders(event);
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// src/observability/instrument-sentry.ts
|
|
538
|
+
var options = buildSentryInitOptions();
|
|
539
|
+
if (options) {
|
|
540
|
+
Sentry.init(options);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// src/backend/backend-client.ts
|
|
544
|
+
var BACKEND_HTTP_HEALTH_PATH = "/api/v1/health-checks/http";
|
|
545
|
+
var BACKEND_CREATE_MERCHANT_ACCOUNT_PATH = "/api/v1/mcp/provision";
|
|
546
|
+
function backendCreateProductPath(merchantId) {
|
|
547
|
+
return `/api/v1/mcp/merchants/${merchantId}/products`;
|
|
548
|
+
}
|
|
549
|
+
function backendActivationStatusPath(merchantId) {
|
|
550
|
+
return `/api/v1/mcp/merchants/${merchantId}/activation-status`;
|
|
551
|
+
}
|
|
552
|
+
function backendGetCheckoutCustomizationPath(merchantId) {
|
|
553
|
+
return `/api/v1/mcp/merchants/${merchantId}/checkout/customization`;
|
|
554
|
+
}
|
|
555
|
+
function backendProductImageUploadUrlPath(merchantId, productId) {
|
|
556
|
+
return `/api/v1/mcp/merchants/${merchantId}/products/${productId}/image/upload-url`;
|
|
557
|
+
}
|
|
558
|
+
function backendConfirmProductImagePath(merchantId, productId) {
|
|
559
|
+
return `/api/v1/mcp/merchants/${merchantId}/products/${productId}/image/confirm`;
|
|
560
|
+
}
|
|
561
|
+
function backendUpdateCheckoutCustomizationPath(merchantId) {
|
|
562
|
+
return `/api/v1/mcp/merchants/${merchantId}/checkout/customization`;
|
|
563
|
+
}
|
|
564
|
+
var DEFAULT_TIMEOUT_MS = 1e4;
|
|
565
|
+
function normalizeBaseUrl(baseUrl) {
|
|
566
|
+
return baseUrl.replace(/\/+$/, "");
|
|
567
|
+
}
|
|
568
|
+
function joinUrl(baseUrl, path2) {
|
|
569
|
+
const p = path2.startsWith("/") ? path2 : `/${path2}`;
|
|
570
|
+
return `${normalizeBaseUrl(baseUrl)}${p}`;
|
|
571
|
+
}
|
|
572
|
+
var BackendClient = class {
|
|
573
|
+
baseUrl;
|
|
574
|
+
timeoutMs;
|
|
575
|
+
fetchImpl;
|
|
576
|
+
authorizationHeader;
|
|
577
|
+
constructor(config) {
|
|
578
|
+
this.baseUrl = normalizeBaseUrl(config.baseUrl);
|
|
579
|
+
this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
580
|
+
this.fetchImpl = config.fetchImpl ?? globalThis.fetch;
|
|
581
|
+
this.authorizationHeader = config.authorizationHeader;
|
|
582
|
+
}
|
|
583
|
+
async get(path2) {
|
|
584
|
+
const url = joinUrl(this.baseUrl, path2);
|
|
585
|
+
let response;
|
|
586
|
+
try {
|
|
587
|
+
response = await this.fetchImpl(url, {
|
|
588
|
+
method: "GET",
|
|
589
|
+
headers: { Accept: "application/json", ...this.buildAuthHeader() },
|
|
590
|
+
signal: AbortSignal.timeout(this.timeoutMs)
|
|
591
|
+
});
|
|
592
|
+
} catch (error) {
|
|
593
|
+
const isAbort = error instanceof Error && error.name === "TimeoutError";
|
|
594
|
+
return {
|
|
595
|
+
ok: false,
|
|
596
|
+
kind: "network",
|
|
597
|
+
message: isAbort ? `Backend request timed out after ${this.timeoutMs}ms. Check BACKEND_BASE_URL, network reachability, and firewall rules.` : `Could not reach backend at ${this.baseUrl}. Verify BACKEND_BASE_URL and that the backend process is running. (${error instanceof Error ? error.message : String(error)})`
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
const bodyText = await response.text();
|
|
601
|
+
if (!response.ok) {
|
|
602
|
+
return {
|
|
603
|
+
ok: false,
|
|
604
|
+
kind: "http_error",
|
|
605
|
+
status: response.status,
|
|
606
|
+
message: `Backend returned HTTP ${response.status}. ${bodyText ? `Body: ${bodyText.slice(0, 500)}` : "Empty response body."} If this persists, confirm the route exists and ingress targets the API service.`
|
|
607
|
+
};
|
|
608
|
+
}
|
|
609
|
+
return { ok: true, status: response.status, bodyText };
|
|
610
|
+
}
|
|
611
|
+
post(path2, jsonBody) {
|
|
612
|
+
return this.sendJson("POST", path2, jsonBody);
|
|
613
|
+
}
|
|
614
|
+
patch(path2, jsonBody) {
|
|
615
|
+
return this.sendJson("PATCH", path2, jsonBody);
|
|
616
|
+
}
|
|
617
|
+
postNoContent(path2, jsonBody) {
|
|
618
|
+
return this.sendJson("POST", path2, jsonBody);
|
|
619
|
+
}
|
|
620
|
+
async sendJson(method, path2, jsonBody) {
|
|
621
|
+
const url = joinUrl(this.baseUrl, path2);
|
|
622
|
+
let response;
|
|
623
|
+
try {
|
|
624
|
+
response = await this.fetchImpl(url, {
|
|
625
|
+
method,
|
|
626
|
+
headers: {
|
|
627
|
+
Accept: "application/json",
|
|
628
|
+
"Content-Type": "application/json",
|
|
629
|
+
...this.buildAuthHeader()
|
|
630
|
+
},
|
|
631
|
+
body: JSON.stringify(jsonBody),
|
|
632
|
+
signal: AbortSignal.timeout(this.timeoutMs)
|
|
633
|
+
});
|
|
634
|
+
} catch (error) {
|
|
635
|
+
const isAbort = error instanceof Error && error.name === "TimeoutError";
|
|
636
|
+
return {
|
|
637
|
+
ok: false,
|
|
638
|
+
kind: "network",
|
|
639
|
+
message: isAbort ? `Backend request timed out after ${this.timeoutMs}ms. Check BACKEND_BASE_URL, network reachability, and firewall rules.` : `Could not reach backend at ${this.baseUrl}. Verify BACKEND_BASE_URL and that the backend process is running. (${error instanceof Error ? error.message : String(error)})`
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
const bodyText = await response.text();
|
|
643
|
+
if (!response.ok) {
|
|
644
|
+
return {
|
|
645
|
+
ok: false,
|
|
646
|
+
kind: "http_error",
|
|
647
|
+
status: response.status,
|
|
648
|
+
message: `Backend returned HTTP ${response.status}. ${bodyText ? `Body: ${bodyText.slice(0, 500)}` : "Empty response body."} If this persists, confirm the route exists and ingress targets the API service.`
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
return { ok: true, status: response.status, bodyText };
|
|
652
|
+
}
|
|
653
|
+
buildAuthHeader() {
|
|
654
|
+
return this.authorizationHeader ? { Authorization: this.authorizationHeader } : {};
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
function createBackendClientFromEnv(explicitBaseUrl) {
|
|
658
|
+
const raw = explicitBaseUrl?.trim() || process.env.BACKEND_BASE_URL?.trim();
|
|
659
|
+
if (!raw) {
|
|
660
|
+
console.error(
|
|
661
|
+
"BACKEND_BASE_URL is not set; MCP server runs in stub-only mode for backend-backed probes (set it to enable HTTP checks from tools)."
|
|
662
|
+
);
|
|
663
|
+
return void 0;
|
|
664
|
+
}
|
|
665
|
+
const timeoutRaw = process.env.BACKEND_HTTP_TIMEOUT_MS?.trim();
|
|
666
|
+
const timeoutMs = timeoutRaw ? Number(timeoutRaw) : DEFAULT_TIMEOUT_MS;
|
|
667
|
+
const parsedTimeout = Number.isFinite(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_TIMEOUT_MS;
|
|
668
|
+
const serviceToken = process.env.BACKEND_SERVICE_TOKEN?.trim();
|
|
669
|
+
const authorizationHeader = serviceToken ? `Bearer ${serviceToken}` : void 0;
|
|
670
|
+
return new BackendClient({ baseUrl: raw, timeoutMs: parsedTimeout, authorizationHeader });
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// src/http/app.ts
|
|
674
|
+
import cors from "cors";
|
|
675
|
+
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
|
|
676
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
677
|
+
import {
|
|
678
|
+
mcpAuthMetadataRouter,
|
|
679
|
+
getOAuthProtectedResourceMetadataUrl
|
|
680
|
+
} from "@modelcontextprotocol/sdk/server/auth/router.js";
|
|
681
|
+
|
|
682
|
+
// src/load-package-meta.ts
|
|
683
|
+
import { readFileSync } from "node:fs";
|
|
684
|
+
import { dirname, join } from "node:path";
|
|
685
|
+
import { fileURLToPath } from "node:url";
|
|
686
|
+
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
687
|
+
function readMcpServerVersion() {
|
|
688
|
+
const path2 = join(__dirname, "..", "package.json");
|
|
689
|
+
const raw = readFileSync(path2, "utf8");
|
|
690
|
+
const pkg = JSON.parse(raw);
|
|
691
|
+
return pkg.version ?? "0.0.0";
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// src/observability/create-instrumented-mcp-server.ts
|
|
695
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
696
|
+
var MCP_SERVER_NAME = "pai-managed-mcp-server";
|
|
697
|
+
function createInstrumentedMcpServer(options2) {
|
|
698
|
+
const server = new McpServer({
|
|
699
|
+
name: MCP_SERVER_NAME,
|
|
700
|
+
version: options2.version
|
|
701
|
+
});
|
|
702
|
+
return Sentry.wrapMcpServerWithSentry(server, {
|
|
703
|
+
recordInputs: false,
|
|
704
|
+
recordOutputs: false
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// src/tools/register-tools.ts
|
|
709
|
+
var import_mcp_contracts9 = __toESM(require_dist(), 1);
|
|
710
|
+
|
|
711
|
+
// src/tools/merchant/activation-status.tool.ts
|
|
712
|
+
var import_mcp_contracts = __toESM(require_dist(), 1);
|
|
713
|
+
|
|
714
|
+
// src/shared/zod-format.ts
|
|
715
|
+
function formatZodIssuesForTool(issues) {
|
|
716
|
+
return issues.map((issue) => {
|
|
717
|
+
const path2 = issue.path.length > 0 ? issue.path.join(".") : "arguments";
|
|
718
|
+
return `[${path2}] ${issue.message}`;
|
|
719
|
+
}).join("\n");
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
// src/tools/merchant/activation-status.tool.ts
|
|
723
|
+
function stubStatusForAccount(merchantId) {
|
|
724
|
+
return merchantId.toLowerCase().includes("pending") ? "pending" : "not_provisioned";
|
|
725
|
+
}
|
|
726
|
+
function buildActivationStatusStub(input) {
|
|
727
|
+
return import_mcp_contracts.activationStatusOutputSchema.parse({
|
|
728
|
+
status: stubStatusForAccount(input.merchantId)
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
function runActivationStatusHandler(raw) {
|
|
732
|
+
const parsed = import_mcp_contracts.activationStatusInputSchema.safeParse(raw);
|
|
733
|
+
if (!parsed.success) {
|
|
734
|
+
return {
|
|
735
|
+
ok: false,
|
|
736
|
+
errorText: `Invalid arguments for get_merchant_activation_status.
|
|
737
|
+
${formatZodIssuesForTool(parsed.error.issues)}`
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
return { ok: true, data: buildActivationStatusStub(parsed.data) };
|
|
741
|
+
}
|
|
742
|
+
async function runActivationStatusHandlerWithBackend(backendClient2, input) {
|
|
743
|
+
const result = await backendClient2.get(backendActivationStatusPath(input.merchantId));
|
|
744
|
+
if (!result.ok) {
|
|
745
|
+
return {
|
|
746
|
+
ok: false,
|
|
747
|
+
errorText: `Backend activation status request failed.
|
|
748
|
+
${result.message}
|
|
749
|
+
Recovery: verify BACKEND_BASE_URL and that GET ${backendActivationStatusPath(input.merchantId)} is reachable from the MCP container or host.`
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
let parsedJson;
|
|
753
|
+
try {
|
|
754
|
+
parsedJson = JSON.parse(result.bodyText);
|
|
755
|
+
} catch {
|
|
756
|
+
return {
|
|
757
|
+
ok: false,
|
|
758
|
+
errorText: "Backend returned non-JSON for get_merchant_activation_status. Recovery: confirm the API matches this MCP server version."
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
const out = import_mcp_contracts.activationStatusOutputSchema.safeParse(parsedJson);
|
|
762
|
+
if (!out.success) {
|
|
763
|
+
return {
|
|
764
|
+
ok: false,
|
|
765
|
+
errorText: "Backend response did not match the expected activation status output schema. Recovery: inspect backend deployment and shared @pai-managed/mcp-contracts version."
|
|
766
|
+
};
|
|
767
|
+
}
|
|
768
|
+
return { ok: true, data: out.data };
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
// src/tools/product/create-product.tool.ts
|
|
772
|
+
var import_mcp_contracts2 = __toESM(require_dist(), 1);
|
|
773
|
+
async function runCreateProductHandler(backendClient2, input) {
|
|
774
|
+
const { merchantId, ...body } = input;
|
|
775
|
+
const result = await backendClient2.post(backendCreateProductPath(merchantId), body);
|
|
776
|
+
if (!result.ok) {
|
|
777
|
+
return {
|
|
778
|
+
ok: false,
|
|
779
|
+
errorText: `Backend create product request failed.
|
|
780
|
+
${result.message}
|
|
781
|
+
Recovery: verify BACKEND_BASE_URL and that POST /api/v1/mcp/merchants/:merchantId/products is reachable.`
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
let parsedJson;
|
|
785
|
+
try {
|
|
786
|
+
parsedJson = JSON.parse(result.bodyText);
|
|
787
|
+
} catch {
|
|
788
|
+
return {
|
|
789
|
+
ok: false,
|
|
790
|
+
errorText: "Backend returned non-JSON for create product. Recovery: confirm the API matches this MCP server version."
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
const out = import_mcp_contracts2.createMcpProductOutputSchema.safeParse(parsedJson);
|
|
794
|
+
if (!out.success) {
|
|
795
|
+
return {
|
|
796
|
+
ok: false,
|
|
797
|
+
errorText: "Backend response did not match expected create product output schema. Recovery: inspect backend deployment and shared @pai-managed/mcp-contracts version."
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
return { ok: true, data: out.data };
|
|
801
|
+
}
|
|
802
|
+
function runCreateProductHandlerValidation(raw) {
|
|
803
|
+
const parsed = import_mcp_contracts2.createMcpProductInputSchema.safeParse(raw);
|
|
804
|
+
if (!parsed.success) {
|
|
805
|
+
return {
|
|
806
|
+
ok: false,
|
|
807
|
+
errorText: `Invalid arguments for create_product.
|
|
808
|
+
${formatZodIssuesForTool(parsed.error.issues)}`
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
return { ok: true, data: parsed.data };
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// src/tools/product/get-product-image-upload-url.tool.ts
|
|
815
|
+
var import_mcp_contracts3 = __toESM(require_dist(), 1);
|
|
816
|
+
function runGetProductImageUploadUrlValidation(raw) {
|
|
817
|
+
const parsed = import_mcp_contracts3.productImageUploadUrlInputSchema.safeParse(raw);
|
|
818
|
+
if (!parsed.success) {
|
|
819
|
+
return {
|
|
820
|
+
ok: false,
|
|
821
|
+
errorText: `Invalid arguments for get_product_image_upload_url.
|
|
822
|
+
${formatZodIssuesForTool(parsed.error.issues)}`
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
return { ok: true, data: parsed.data };
|
|
826
|
+
}
|
|
827
|
+
function mapHttpError(status, message) {
|
|
828
|
+
if (status === 404) {
|
|
829
|
+
return `Product not found for this merchant.
|
|
830
|
+
${message}
|
|
831
|
+
Recovery: call create_product first and pass the returned id as productId.`;
|
|
832
|
+
}
|
|
833
|
+
if (status === 422) {
|
|
834
|
+
return `Cannot upload an image for this product.
|
|
835
|
+
${message}
|
|
836
|
+
Recovery: archived products cannot receive images.`;
|
|
837
|
+
}
|
|
838
|
+
return `Backend get product image upload URL request failed.
|
|
839
|
+
${message}
|
|
840
|
+
Recovery: verify BACKEND_BASE_URL and that POST /api/v1/mcp/merchants/:merchantId/products/:productId/image/upload-url is reachable.`;
|
|
841
|
+
}
|
|
842
|
+
async function runGetProductImageUploadUrlHandler(backendClient2, input) {
|
|
843
|
+
const { merchantId, productId, fileName, mimeType } = input;
|
|
844
|
+
const result = await backendClient2.post(backendProductImageUploadUrlPath(merchantId, productId), {
|
|
845
|
+
fileName,
|
|
846
|
+
mimeType
|
|
847
|
+
});
|
|
848
|
+
if (!result.ok) {
|
|
849
|
+
return { ok: false, errorText: mapHttpError(result.status, result.message) };
|
|
850
|
+
}
|
|
851
|
+
let parsedJson;
|
|
852
|
+
try {
|
|
853
|
+
parsedJson = JSON.parse(result.bodyText);
|
|
854
|
+
} catch {
|
|
855
|
+
return {
|
|
856
|
+
ok: false,
|
|
857
|
+
errorText: "Backend returned non-JSON for get product image upload URL. Recovery: confirm the API matches this MCP server version."
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
const out = import_mcp_contracts3.productImageUploadUrlOutputSchema.safeParse(parsedJson);
|
|
861
|
+
if (!out.success) {
|
|
862
|
+
return {
|
|
863
|
+
ok: false,
|
|
864
|
+
errorText: "Backend response did not match expected upload URL output schema. Recovery: inspect backend deployment and shared @pai-managed/mcp-contracts version."
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
return { ok: true, data: out.data };
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// src/tools/product/confirm-product-image.tool.ts
|
|
871
|
+
var import_mcp_contracts4 = __toESM(require_dist(), 1);
|
|
872
|
+
function runConfirmProductImageValidation(raw) {
|
|
873
|
+
const parsed = import_mcp_contracts4.confirmProductImageInputSchema.safeParse(raw);
|
|
874
|
+
if (!parsed.success) {
|
|
875
|
+
return {
|
|
876
|
+
ok: false,
|
|
877
|
+
errorText: `Invalid arguments for confirm_product_image.
|
|
878
|
+
${formatZodIssuesForTool(parsed.error.issues)}`
|
|
879
|
+
};
|
|
880
|
+
}
|
|
881
|
+
return { ok: true, data: parsed.data };
|
|
882
|
+
}
|
|
883
|
+
function mapHttpError2(status, message) {
|
|
884
|
+
if (status === 404) {
|
|
885
|
+
return `Product not found for this merchant.
|
|
886
|
+
${message}
|
|
887
|
+
Recovery: pass the same productId used with get_product_image_upload_url.`;
|
|
888
|
+
}
|
|
889
|
+
if (status === 422) {
|
|
890
|
+
return `Cannot upload an image for this product.
|
|
891
|
+
${message}
|
|
892
|
+
Recovery: archived products cannot receive images.`;
|
|
893
|
+
}
|
|
894
|
+
if (status === 400) {
|
|
895
|
+
return `Uploaded image rejected by the backend.
|
|
896
|
+
${message}
|
|
897
|
+
Recovery: POST the bytes (multipart/form-data with all returned fields, then the file field last) to the upload URL first, use PNG/JPEG/WebP under 5 MB, and pass the exact key returned by get_product_image_upload_url.`;
|
|
898
|
+
}
|
|
899
|
+
return `Backend confirm product image request failed.
|
|
900
|
+
${message}
|
|
901
|
+
Recovery: verify BACKEND_BASE_URL and that POST /api/v1/mcp/merchants/:merchantId/products/:productId/image/confirm is reachable.`;
|
|
902
|
+
}
|
|
903
|
+
async function runConfirmProductImageHandler(backendClient2, input) {
|
|
904
|
+
const { merchantId, productId, key } = input;
|
|
905
|
+
const result = await backendClient2.post(backendConfirmProductImagePath(merchantId, productId), {
|
|
906
|
+
key
|
|
907
|
+
});
|
|
908
|
+
if (!result.ok) {
|
|
909
|
+
return { ok: false, errorText: mapHttpError2(result.status, result.message) };
|
|
910
|
+
}
|
|
911
|
+
let parsedJson;
|
|
912
|
+
try {
|
|
913
|
+
parsedJson = JSON.parse(result.bodyText);
|
|
914
|
+
} catch {
|
|
915
|
+
return {
|
|
916
|
+
ok: false,
|
|
917
|
+
errorText: "Backend returned non-JSON for confirm product image. Recovery: confirm the API matches this MCP server version."
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
const out = import_mcp_contracts4.confirmProductImageOutputSchema.safeParse(parsedJson);
|
|
921
|
+
if (!out.success) {
|
|
922
|
+
return {
|
|
923
|
+
ok: false,
|
|
924
|
+
errorText: "Backend response did not match expected confirm product image output schema. Recovery: inspect backend deployment and shared @pai-managed/mcp-contracts version."
|
|
925
|
+
};
|
|
926
|
+
}
|
|
927
|
+
return { ok: true, data: out.data };
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// src/tools/docs/knowledge/products-plans.ts
|
|
931
|
+
var DOC_SECTIONS = [
|
|
932
|
+
{
|
|
933
|
+
title: "Navigation \u2014 Products & Plans",
|
|
934
|
+
path: "/docs/products-plans#navigation",
|
|
935
|
+
content: `To access Products & Plans, find **Products** in the left sidebar under the "Product" group (Package icon). Click **Products** to navigate to /products.`
|
|
936
|
+
},
|
|
937
|
+
{
|
|
938
|
+
title: "Create a Product",
|
|
939
|
+
path: "/docs/products-plans#create-a-product",
|
|
940
|
+
content: `1. Call **create_product** with merchantId, name, and at least one plan.
|
|
941
|
+
2. Optionally attach an image via the presigned upload flow: call **get_product_image_upload_url** with the returned product id, POST the raw bytes (multipart/form-data with the returned fields + file last) to the returned URL, then call **confirm_product_image** with the returned key.
|
|
942
|
+
3. Use a returned planId to build a checkout URL: {checkout-base-url}/payment/{planId}`
|
|
943
|
+
},
|
|
944
|
+
{
|
|
945
|
+
title: "Upload a Product Image (MCP)",
|
|
946
|
+
path: "/docs/products-plans#upload-product-image-mcp",
|
|
947
|
+
content: `After create_product returns an id:
|
|
948
|
+
1. Call **get_product_image_upload_url** with merchantId, productId, fileName, and mimeType. It returns { url, fields, key, expiresIn }.
|
|
949
|
+
2. HTTP POST the raw file bytes to \`url\` as \`multipart/form-data\`: add every entry from \`fields\` as a form field FIRST, then add the \`file\` field last (S3 requires the file field last). Example: \`curl -X POST -F "key=<fields.key>" -F "Content-Type=<mimeType>" -F "policy=<fields.policy>" -F "x-amz-signature=<fields['x-amz-signature']>" ... -F "file=@./logo.png" "<url>"\`.
|
|
950
|
+
3. Call **confirm_product_image** with merchantId, productId, and the returned key to validate and attach the image.
|
|
951
|
+
4. Supported formats: PNG, JPEG, WebP. Maximum size: 5 MB (enforced by S3 at upload time via a \`content-length-range\` policy condition \u2014 oversized bodies return a 403 from S3, before the backend ever sees them).
|
|
952
|
+
5. If confirm fails, retry get_product_image_upload_url + POST + confirm_product_image with the same productId \u2014 the product row is kept; the previous rejected upload was cleaned up by the backend.`
|
|
953
|
+
},
|
|
954
|
+
{
|
|
955
|
+
title: "Plan Fields",
|
|
956
|
+
path: "/docs/products-plans#plan-fields",
|
|
957
|
+
content: `Each plan has the following fields:
|
|
958
|
+
- **Name** \u2014 display name for the plan (max 30 characters)
|
|
959
|
+
- **Amount** \u2014 price in USD as a decimal dollar amount (e.g. 9.99 = $9.99)
|
|
960
|
+
- **Type** \u2014 one of: \`one-time\`, \`recurring\`, \`free-access\`
|
|
961
|
+
- **Billing Period** \u2014 one of: \`day\`, \`week\`, \`month\`, \`year\`
|
|
962
|
+
- **Period Length** \u2014 number of billing periods per cycle
|
|
963
|
+
- **Free Trial** \u2014 number of free trial days (optional)`
|
|
964
|
+
},
|
|
965
|
+
{
|
|
966
|
+
title: "Edit a Product",
|
|
967
|
+
path: "/docs/products-plans#edit-a-product",
|
|
968
|
+
content: `1. Go to **Products** in the left sidebar.
|
|
969
|
+
2. Click on the product row you want to edit.
|
|
970
|
+
3. Click **Edit** to modify name, description, status, or plans.`
|
|
971
|
+
},
|
|
972
|
+
{
|
|
973
|
+
title: "Checkout URL",
|
|
974
|
+
path: "/docs/products-plans#checkout-url",
|
|
975
|
+
content: `Each plan has a unique checkout URL. The format is:
|
|
976
|
+
{checkout-base-url}/payment/{planId}
|
|
977
|
+
|
|
978
|
+
Embed it as: \`<a href="{checkout-base-url}/payment/{planId}">Pay Now!</a>\`
|
|
979
|
+
|
|
980
|
+
Customers land on the payment page when they click the link. The \`planId\` is returned when you create a product via the \`create_product\` MCP tool.`
|
|
981
|
+
}
|
|
982
|
+
];
|
|
983
|
+
|
|
984
|
+
// src/tools/docs/knowledge/checkout-customization.ts
|
|
985
|
+
var CHECKOUT_CUSTOMIZATION_DOC_SECTIONS = [
|
|
986
|
+
{
|
|
987
|
+
title: "Checkout Customization \u2014 Overview",
|
|
988
|
+
path: "/docs/checkout-customization#overview",
|
|
989
|
+
content: `Checkout customization controls the branding of a merchant's hosted checkout (the customer-facing payment page at /payment/{planId}). Configurable fields:
|
|
990
|
+
- **themeMode** \u2014 \`light\` or \`dark\`
|
|
991
|
+
- **backgroundColor**, **buttonColor**, **buttonTextColor**, **fontColor** \u2014 6-digit hex like \`#1A2B3C\`
|
|
992
|
+
- **fontFamily** \u2014 \`Inter\` or \`Roboto\`
|
|
993
|
+
- **inputStyle** \u2014 \`rounded\` or \`square\`
|
|
994
|
+
- **logo** \u2014 set via the dashboard (not yet settable over MCP)
|
|
995
|
+
|
|
996
|
+
Read with the \`get_checkout_customization\` MCP tool; change with \`update_checkout_customization\`.`
|
|
997
|
+
},
|
|
998
|
+
{
|
|
999
|
+
title: "Checkout Customization \u2014 Updating branding via MCP",
|
|
1000
|
+
path: "/docs/checkout-customization#update",
|
|
1001
|
+
content: `The \`update_checkout_customization\` tool MERGES the fields you send: only the fields in the call are changed, and any field you omit keeps its current value. So you can send just \`{ "buttonColor": "#0A84FF" }\` to recolor the button without touching the rest of the branding.
|
|
1002
|
+
|
|
1003
|
+
To remove or reset a field back to default, use the dashboard \u2014 clearing a field is not supported over MCP. Colors must be 6-digit hex (include the leading \`#\`). The tool returns the full customization after the merge, including the current \`logoUrl\` if a logo was set via the dashboard.`
|
|
1004
|
+
},
|
|
1005
|
+
{
|
|
1006
|
+
title: "Checkout Customization \u2014 Logo",
|
|
1007
|
+
path: "/docs/checkout-customization#logo",
|
|
1008
|
+
content: `The checkout logo is currently set through the PAI Managed dashboard (Checkout settings). It is returned as a read-only \`logoUrl\` by \`get_checkout_customization\`. Setting the logo directly over MCP is planned but not yet available.`
|
|
1009
|
+
}
|
|
1010
|
+
];
|
|
1011
|
+
|
|
1012
|
+
// src/tools/docs/search-payments-ai-docs.tool.ts
|
|
1013
|
+
var ALL_DOC_SECTIONS = [...DOC_SECTIONS, ...CHECKOUT_CUSTOMIZATION_DOC_SECTIONS];
|
|
1014
|
+
function runSearchPaymentsAiDocsHandler(query) {
|
|
1015
|
+
const terms = query.toLowerCase().split(/\s+/).filter((t) => t.length > 1);
|
|
1016
|
+
if (terms.length === 0) {
|
|
1017
|
+
return { results: ALL_DOC_SECTIONS };
|
|
1018
|
+
}
|
|
1019
|
+
const scored = ALL_DOC_SECTIONS.map((section) => {
|
|
1020
|
+
const haystack = `${section.title} ${section.content}`.toLowerCase();
|
|
1021
|
+
const hits = terms.filter((t) => haystack.includes(t)).length;
|
|
1022
|
+
return { section, hits };
|
|
1023
|
+
});
|
|
1024
|
+
const matches = scored.filter((s) => s.hits > 0).sort((a, b) => b.hits - a.hits);
|
|
1025
|
+
return {
|
|
1026
|
+
results: matches.length > 0 ? matches.map((s) => s.section) : ALL_DOC_SECTIONS
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
// src/tools/health/payments-ai-health.tool.ts
|
|
1031
|
+
var import_mcp_contracts5 = __toESM(require_dist(), 1);
|
|
1032
|
+
async function buildPaymentsAiHealthOutput(backendClient2) {
|
|
1033
|
+
const base = {
|
|
1034
|
+
serverName: "pai-managed-mcp-server",
|
|
1035
|
+
version: readMcpServerVersion(),
|
|
1036
|
+
stub: true,
|
|
1037
|
+
backend: { baseUrlConfigured: false }
|
|
1038
|
+
};
|
|
1039
|
+
if (!backendClient2) {
|
|
1040
|
+
return import_mcp_contracts5.paymentsAiHealthOutputSchema.parse(base);
|
|
1041
|
+
}
|
|
1042
|
+
const result = await backendClient2.get(BACKEND_HTTP_HEALTH_PATH);
|
|
1043
|
+
if (!result.ok) {
|
|
1044
|
+
return import_mcp_contracts5.paymentsAiHealthOutputSchema.parse({
|
|
1045
|
+
...base,
|
|
1046
|
+
backend: {
|
|
1047
|
+
baseUrlConfigured: true,
|
|
1048
|
+
probe: {
|
|
1049
|
+
path: BACKEND_HTTP_HEALTH_PATH,
|
|
1050
|
+
ok: false,
|
|
1051
|
+
httpStatus: result.status,
|
|
1052
|
+
message: result.message
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
let message = "Backend responded successfully.";
|
|
1058
|
+
try {
|
|
1059
|
+
const json = JSON.parse(result.bodyText);
|
|
1060
|
+
if (typeof json.message === "string" && json.message.length > 0) {
|
|
1061
|
+
message = json.message;
|
|
1062
|
+
}
|
|
1063
|
+
} catch {
|
|
1064
|
+
message = result.bodyText.slice(0, 500) || message;
|
|
1065
|
+
}
|
|
1066
|
+
return import_mcp_contracts5.paymentsAiHealthOutputSchema.parse({
|
|
1067
|
+
...base,
|
|
1068
|
+
backend: {
|
|
1069
|
+
baseUrlConfigured: true,
|
|
1070
|
+
probe: {
|
|
1071
|
+
path: BACKEND_HTTP_HEALTH_PATH,
|
|
1072
|
+
ok: true,
|
|
1073
|
+
httpStatus: result.status,
|
|
1074
|
+
message
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
// src/tools/merchant/create-merchant-account.tool.ts
|
|
1081
|
+
var import_mcp_contracts6 = __toESM(require_dist(), 1);
|
|
1082
|
+
var STUB_MERCHANT = {
|
|
1083
|
+
merchantId: "01930000-0000-7000-8000-000000000001"
|
|
1084
|
+
};
|
|
1085
|
+
function buildCreateMerchantAccountStub(_input) {
|
|
1086
|
+
return import_mcp_contracts6.createMerchantAccountOutputSchema.parse(STUB_MERCHANT);
|
|
1087
|
+
}
|
|
1088
|
+
function runCreateMerchantAccountHandler(raw) {
|
|
1089
|
+
const parsed = import_mcp_contracts6.createMerchantAccountInputSchema.safeParse(raw);
|
|
1090
|
+
if (!parsed.success) {
|
|
1091
|
+
return {
|
|
1092
|
+
ok: false,
|
|
1093
|
+
errorText: `Invalid arguments for create_merchant_account.
|
|
1094
|
+
${formatZodIssuesForTool(parsed.error.issues)}`
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
return { ok: true, data: buildCreateMerchantAccountStub(parsed.data) };
|
|
1098
|
+
}
|
|
1099
|
+
async function runCreateMerchantAccountHandlerWithBackend(backendClient2, input, userId) {
|
|
1100
|
+
const result = await backendClient2.post(BACKEND_CREATE_MERCHANT_ACCOUNT_PATH, {
|
|
1101
|
+
name: input.name,
|
|
1102
|
+
email: input.email,
|
|
1103
|
+
userId
|
|
1104
|
+
});
|
|
1105
|
+
if (!result.ok) {
|
|
1106
|
+
return {
|
|
1107
|
+
ok: false,
|
|
1108
|
+
errorText: `Backend create merchant account request failed.
|
|
1109
|
+
${result.message}
|
|
1110
|
+
Recovery: verify BACKEND_BASE_URL and that POST ${BACKEND_CREATE_MERCHANT_ACCOUNT_PATH} is reachable from the MCP container or host.`
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
let parsedJson;
|
|
1114
|
+
try {
|
|
1115
|
+
parsedJson = JSON.parse(result.bodyText);
|
|
1116
|
+
} catch {
|
|
1117
|
+
return {
|
|
1118
|
+
ok: false,
|
|
1119
|
+
errorText: "Backend returned non-JSON for create_merchant_account. Recovery: confirm the API matches this MCP server version."
|
|
1120
|
+
};
|
|
1121
|
+
}
|
|
1122
|
+
const out = import_mcp_contracts6.createMerchantAccountOutputSchema.safeParse(parsedJson);
|
|
1123
|
+
if (!out.success) {
|
|
1124
|
+
return {
|
|
1125
|
+
ok: false,
|
|
1126
|
+
errorText: "Backend response did not match the expected merchant account output schema. Recovery: inspect backend deployment and shared @pai-managed/mcp-contracts version."
|
|
1127
|
+
};
|
|
1128
|
+
}
|
|
1129
|
+
return { ok: true, data: out.data };
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// src/tools/checkout/get-checkout-customization.tool.ts
|
|
1133
|
+
var import_mcp_contracts7 = __toESM(require_dist(), 1);
|
|
1134
|
+
function runGetCheckoutCustomizationValidation(raw) {
|
|
1135
|
+
const parsed = import_mcp_contracts7.getCheckoutCustomizationInputSchema.safeParse(raw);
|
|
1136
|
+
if (!parsed.success) {
|
|
1137
|
+
return {
|
|
1138
|
+
ok: false,
|
|
1139
|
+
errorText: `Invalid arguments for get_checkout_customization.
|
|
1140
|
+
${formatZodIssuesForTool(parsed.error.issues)}`
|
|
1141
|
+
};
|
|
1142
|
+
}
|
|
1143
|
+
return { ok: true, data: parsed.data };
|
|
1144
|
+
}
|
|
1145
|
+
async function runGetCheckoutCustomizationHandler(backendClient2, input) {
|
|
1146
|
+
const result = await backendClient2.get(backendGetCheckoutCustomizationPath(input.merchantId));
|
|
1147
|
+
if (!result.ok) {
|
|
1148
|
+
return {
|
|
1149
|
+
ok: false,
|
|
1150
|
+
errorText: `Backend get checkout customization request failed.
|
|
1151
|
+
${result.message}
|
|
1152
|
+
Recovery: verify BACKEND_BASE_URL and that GET /api/v1/mcp/merchants/:merchantId/checkout/customization is reachable.`
|
|
1153
|
+
};
|
|
1154
|
+
}
|
|
1155
|
+
let parsedJson;
|
|
1156
|
+
try {
|
|
1157
|
+
parsedJson = JSON.parse(result.bodyText);
|
|
1158
|
+
} catch {
|
|
1159
|
+
return {
|
|
1160
|
+
ok: false,
|
|
1161
|
+
errorText: "Backend returned non-JSON for get checkout customization. Recovery: confirm the API matches this MCP server version."
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
const customization = parsedJson?.customization;
|
|
1165
|
+
const out = import_mcp_contracts7.checkoutCustomizationOutputSchema.safeParse(customization);
|
|
1166
|
+
if (!out.success) {
|
|
1167
|
+
return {
|
|
1168
|
+
ok: false,
|
|
1169
|
+
errorText: "Backend response did not match expected checkout customization output schema. Recovery: inspect backend deployment and shared @pai-managed/mcp-contracts version."
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
return { ok: true, data: out.data };
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// src/tools/checkout/update-checkout-customization.tool.ts
|
|
1176
|
+
var import_mcp_contracts8 = __toESM(require_dist(), 1);
|
|
1177
|
+
function runUpdateCheckoutCustomizationValidation(raw) {
|
|
1178
|
+
const parsed = import_mcp_contracts8.updateCheckoutCustomizationInputSchema.safeParse(raw);
|
|
1179
|
+
if (!parsed.success) {
|
|
1180
|
+
return {
|
|
1181
|
+
ok: false,
|
|
1182
|
+
errorText: `Invalid arguments for update_checkout_customization.
|
|
1183
|
+
${formatZodIssuesForTool(parsed.error.issues)}`
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
return { ok: true, data: parsed.data };
|
|
1187
|
+
}
|
|
1188
|
+
async function runUpdateCheckoutCustomizationHandler(backendClient2, input) {
|
|
1189
|
+
const { merchantId, customization } = input;
|
|
1190
|
+
const result = await backendClient2.patch(backendUpdateCheckoutCustomizationPath(merchantId), {
|
|
1191
|
+
customization
|
|
1192
|
+
});
|
|
1193
|
+
if (!result.ok) {
|
|
1194
|
+
return {
|
|
1195
|
+
ok: false,
|
|
1196
|
+
errorText: `Backend update checkout customization request failed.
|
|
1197
|
+
${result.message}
|
|
1198
|
+
Recovery: verify BACKEND_BASE_URL and that PATCH /api/v1/mcp/merchants/:merchantId/checkout/customization is reachable.`
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
let parsedJson;
|
|
1202
|
+
try {
|
|
1203
|
+
parsedJson = JSON.parse(result.bodyText);
|
|
1204
|
+
} catch {
|
|
1205
|
+
return {
|
|
1206
|
+
ok: false,
|
|
1207
|
+
errorText: "Backend returned non-JSON for update checkout customization. Recovery: confirm the API matches this MCP server version."
|
|
1208
|
+
};
|
|
1209
|
+
}
|
|
1210
|
+
const updated = parsedJson?.customization;
|
|
1211
|
+
const out = import_mcp_contracts8.checkoutCustomizationOutputSchema.safeParse(updated);
|
|
1212
|
+
if (!out.success) {
|
|
1213
|
+
return {
|
|
1214
|
+
ok: false,
|
|
1215
|
+
errorText: "Backend response did not match expected checkout customization output schema. Recovery: inspect backend deployment and shared @pai-managed/mcp-contracts version."
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
return { ok: true, data: out.data };
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
// src/tools/register-tools.ts
|
|
1222
|
+
function toolError(text) {
|
|
1223
|
+
return {
|
|
1224
|
+
isError: true,
|
|
1225
|
+
content: [{ type: "text", text }]
|
|
1226
|
+
};
|
|
1227
|
+
}
|
|
1228
|
+
function isMerchantAllowed(merchantId, payload) {
|
|
1229
|
+
return !payload || payload.merchantIds.includes(merchantId);
|
|
1230
|
+
}
|
|
1231
|
+
function isScopeAllowed(required, payload) {
|
|
1232
|
+
if (!payload) return true;
|
|
1233
|
+
if (payload.scopes === null) return true;
|
|
1234
|
+
return payload.scopes.includes(required);
|
|
1235
|
+
}
|
|
1236
|
+
function registerTools(server, options2) {
|
|
1237
|
+
const backendClient2 = options2?.backendClient ?? createBackendClientFromEnv();
|
|
1238
|
+
const mcpTokenPayload = options2?.mcpTokenPayload;
|
|
1239
|
+
server.registerTool(
|
|
1240
|
+
import_mcp_contracts9.MCP_TOOL_NAMES.PAYMENTS_AI_HEALTH,
|
|
1241
|
+
{
|
|
1242
|
+
description: "Check whether the PAI Managed MCP server is healthy. Returns server version and connectivity status.",
|
|
1243
|
+
outputSchema: import_mcp_contracts9.paymentsAiHealthOutputSchema
|
|
1244
|
+
},
|
|
1245
|
+
async () => {
|
|
1246
|
+
if (!isScopeAllowed("health:read", mcpTokenPayload)) {
|
|
1247
|
+
return toolError("Insufficient scope.");
|
|
1248
|
+
}
|
|
1249
|
+
const structuredContent = await buildPaymentsAiHealthOutput(backendClient2);
|
|
1250
|
+
return {
|
|
1251
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent) }],
|
|
1252
|
+
structuredContent
|
|
1253
|
+
};
|
|
1254
|
+
}
|
|
1255
|
+
);
|
|
1256
|
+
server.registerTool(
|
|
1257
|
+
import_mcp_contracts9.MCP_TOOL_NAMES.CREATE_MERCHANT_ACCOUNT,
|
|
1258
|
+
{
|
|
1259
|
+
description: "Create a merchant account on PAI Managed. Provide a business name and email; returns a merchant ID you can use with other tools such as create_product.",
|
|
1260
|
+
inputSchema: import_mcp_contracts9.createMerchantAccountInputSchema,
|
|
1261
|
+
outputSchema: import_mcp_contracts9.createMerchantAccountOutputSchema
|
|
1262
|
+
},
|
|
1263
|
+
async (args) => {
|
|
1264
|
+
if (!isScopeAllowed("merchant:create", mcpTokenPayload)) {
|
|
1265
|
+
return toolError("Insufficient scope.");
|
|
1266
|
+
}
|
|
1267
|
+
const result = backendClient2 ? await runCreateMerchantAccountHandlerWithBackend(
|
|
1268
|
+
backendClient2,
|
|
1269
|
+
args,
|
|
1270
|
+
mcpTokenPayload?.userId
|
|
1271
|
+
) : runCreateMerchantAccountHandler(args);
|
|
1272
|
+
if (!result.ok) {
|
|
1273
|
+
return toolError(result.errorText);
|
|
1274
|
+
}
|
|
1275
|
+
return {
|
|
1276
|
+
content: [{ type: "text", text: JSON.stringify(result.data) }],
|
|
1277
|
+
structuredContent: result.data
|
|
1278
|
+
};
|
|
1279
|
+
}
|
|
1280
|
+
);
|
|
1281
|
+
server.registerTool(
|
|
1282
|
+
import_mcp_contracts9.MCP_TOOL_NAMES.GET_MERCHANT_ACTIVATION_STATUS,
|
|
1283
|
+
{
|
|
1284
|
+
description: 'Get the production activation status of a merchant account on PAI Managed. Returns "pending" (account exists, activation in progress) or "not_provisioned" (not yet set up).',
|
|
1285
|
+
inputSchema: import_mcp_contracts9.activationStatusInputSchema,
|
|
1286
|
+
outputSchema: import_mcp_contracts9.activationStatusOutputSchema
|
|
1287
|
+
},
|
|
1288
|
+
async (args) => {
|
|
1289
|
+
if (!isScopeAllowed("merchant:read", mcpTokenPayload)) {
|
|
1290
|
+
return toolError("Insufficient scope.");
|
|
1291
|
+
}
|
|
1292
|
+
if (!isMerchantAllowed(args.merchantId, mcpTokenPayload)) {
|
|
1293
|
+
return toolError("Merchant not authorized for this MCP key.");
|
|
1294
|
+
}
|
|
1295
|
+
const result = backendClient2 ? await runActivationStatusHandlerWithBackend(backendClient2, args) : runActivationStatusHandler(args);
|
|
1296
|
+
if (!result.ok) {
|
|
1297
|
+
return toolError(result.errorText);
|
|
1298
|
+
}
|
|
1299
|
+
return {
|
|
1300
|
+
content: [{ type: "text", text: JSON.stringify(result.data) }],
|
|
1301
|
+
structuredContent: result.data
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
);
|
|
1305
|
+
server.registerTool(
|
|
1306
|
+
import_mcp_contracts9.MCP_TOOL_NAMES.SEARCH_PAYMENTS_AI_DOCS,
|
|
1307
|
+
{
|
|
1308
|
+
description: "Search the PAI Managed documentation knowledge base to find guides, API references, UI walkthroughs, and feature explanations. Returns matching sections with titles and direct documentation links. Use this tool to understand how PAI Managed works, find step-by-step instructions for dashboard tasks, or locate specific features such as creating products, configuring plans, or generating checkout links.",
|
|
1309
|
+
inputSchema: import_mcp_contracts9.searchPaymentsAiDocsInputSchema,
|
|
1310
|
+
outputSchema: import_mcp_contracts9.searchPaymentsAiDocsOutputSchema
|
|
1311
|
+
},
|
|
1312
|
+
async (args) => {
|
|
1313
|
+
if (!isScopeAllowed("docs:read", mcpTokenPayload)) {
|
|
1314
|
+
return toolError("Insufficient scope.");
|
|
1315
|
+
}
|
|
1316
|
+
const structuredContent = runSearchPaymentsAiDocsHandler(args.query);
|
|
1317
|
+
return {
|
|
1318
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent) }],
|
|
1319
|
+
structuredContent
|
|
1320
|
+
};
|
|
1321
|
+
}
|
|
1322
|
+
);
|
|
1323
|
+
server.registerTool(
|
|
1324
|
+
import_mcp_contracts9.MCP_TOOL_NAMES.CREATE_PRODUCT,
|
|
1325
|
+
{
|
|
1326
|
+
description: "Create a product with one or more pricing plans for a merchant on the PAI Managed platform. Products represent digital offerings (software, subscriptions, services) that customers can purchase via a checkout link. Each plan defines the price (in USD cents), billing type (one-time, recurring, or free-access), and billing period. Returns the product ID and the plan IDs; use a plan ID to build a checkout URL for your customers. Product images are not supported on create \u2014 after success, call get_product_image_upload_url with the returned id as productId, POST the file bytes (multipart/form-data) to the returned URL, then call confirm_product_image with the returned key.",
|
|
1327
|
+
inputSchema: import_mcp_contracts9.createMcpProductInputSchema,
|
|
1328
|
+
outputSchema: import_mcp_contracts9.createMcpProductOutputSchema
|
|
1329
|
+
},
|
|
1330
|
+
async (args) => {
|
|
1331
|
+
if (!isScopeAllowed("product:create", mcpTokenPayload)) {
|
|
1332
|
+
return toolError("Insufficient scope.");
|
|
1333
|
+
}
|
|
1334
|
+
const validation = runCreateProductHandlerValidation(args);
|
|
1335
|
+
if (!validation.ok) {
|
|
1336
|
+
return toolError(validation.errorText);
|
|
1337
|
+
}
|
|
1338
|
+
if (!isMerchantAllowed(validation.data.merchantId, mcpTokenPayload)) {
|
|
1339
|
+
return toolError("Merchant not authorized for this MCP key.");
|
|
1340
|
+
}
|
|
1341
|
+
if (!backendClient2) {
|
|
1342
|
+
return toolError(
|
|
1343
|
+
"The PAI Managed backend is not reachable. Contact your platform administrator to configure backend connectivity."
|
|
1344
|
+
);
|
|
1345
|
+
}
|
|
1346
|
+
const result = await runCreateProductHandler(backendClient2, validation.data);
|
|
1347
|
+
if (!result.ok) {
|
|
1348
|
+
return toolError(result.errorText);
|
|
1349
|
+
}
|
|
1350
|
+
return {
|
|
1351
|
+
content: [{ type: "text", text: JSON.stringify(result.data) }],
|
|
1352
|
+
structuredContent: result.data
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
);
|
|
1356
|
+
server.registerTool(
|
|
1357
|
+
import_mcp_contracts9.MCP_TOOL_NAMES.GET_PRODUCT_IMAGE_UPLOAD_URL,
|
|
1358
|
+
{
|
|
1359
|
+
description: 'Get a presigned POST policy to upload a product image directly to storage \u2014 the image bytes never pass through the tool call, and S3 enforces the 5 MB cap at upload time via `content-length-range` (oversized bodies get a 403 from S3, not the backend). Use this on any surface that can issue an HTTP multipart/form-data POST (CLI, agent, dashboard). The product must already exist (call create_product first). Provide merchantId, productId, fileName, and mimeType (image/png, image/jpeg, image/jpg, or image/webp). Returns { url, fields, key, expiresIn }: issue an HTTP POST to `url` with Content-Type: multipart/form-data. Add every entry from `fields` as a form field FIRST, then add the `file` field last (S3 requires the file field last), e.g. `curl -X POST <all -F fields...> -F "file=@./logo.png" "<url>"`. Then call confirm_product_image with the returned key to validate and attach the image.',
|
|
1360
|
+
inputSchema: import_mcp_contracts9.productImageUploadUrlInputSchema,
|
|
1361
|
+
outputSchema: import_mcp_contracts9.productImageUploadUrlOutputSchema
|
|
1362
|
+
},
|
|
1363
|
+
async (args) => {
|
|
1364
|
+
if (!isScopeAllowed("product:create", mcpTokenPayload)) {
|
|
1365
|
+
return toolError("Insufficient scope.");
|
|
1366
|
+
}
|
|
1367
|
+
const validation = runGetProductImageUploadUrlValidation(args);
|
|
1368
|
+
if (!validation.ok) {
|
|
1369
|
+
return toolError(validation.errorText);
|
|
1370
|
+
}
|
|
1371
|
+
if (!isMerchantAllowed(validation.data.merchantId, mcpTokenPayload)) {
|
|
1372
|
+
return toolError("Merchant not authorized for this MCP key.");
|
|
1373
|
+
}
|
|
1374
|
+
if (!backendClient2) {
|
|
1375
|
+
return toolError(
|
|
1376
|
+
"The PAI Managed backend is not reachable. Contact your platform administrator to configure backend connectivity."
|
|
1377
|
+
);
|
|
1378
|
+
}
|
|
1379
|
+
const result = await runGetProductImageUploadUrlHandler(backendClient2, validation.data);
|
|
1380
|
+
if (!result.ok) {
|
|
1381
|
+
return toolError(result.errorText);
|
|
1382
|
+
}
|
|
1383
|
+
return {
|
|
1384
|
+
content: [{ type: "text", text: JSON.stringify(result.data) }],
|
|
1385
|
+
structuredContent: result.data
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
);
|
|
1389
|
+
server.registerTool(
|
|
1390
|
+
import_mcp_contracts9.MCP_TOOL_NAMES.CONFIRM_PRODUCT_IMAGE,
|
|
1391
|
+
{
|
|
1392
|
+
description: "Confirm a product image uploaded via a presigned POST policy from get_product_image_upload_url. Call this after the multipart/form-data POST succeeds. Provide merchantId, productId, and the key returned by get_product_image_upload_url. The backend validates the stored object (format, size, magic bytes) and attaches it to the product. Returns { imageUrl } for verification. Replaces any existing product image.",
|
|
1393
|
+
inputSchema: import_mcp_contracts9.confirmProductImageInputSchema,
|
|
1394
|
+
outputSchema: import_mcp_contracts9.confirmProductImageOutputSchema
|
|
1395
|
+
},
|
|
1396
|
+
async (args) => {
|
|
1397
|
+
if (!isScopeAllowed("product:create", mcpTokenPayload)) {
|
|
1398
|
+
return toolError("Insufficient scope.");
|
|
1399
|
+
}
|
|
1400
|
+
const validation = runConfirmProductImageValidation(args);
|
|
1401
|
+
if (!validation.ok) {
|
|
1402
|
+
return toolError(validation.errorText);
|
|
1403
|
+
}
|
|
1404
|
+
if (!isMerchantAllowed(validation.data.merchantId, mcpTokenPayload)) {
|
|
1405
|
+
return toolError("Merchant not authorized for this MCP key.");
|
|
1406
|
+
}
|
|
1407
|
+
if (!backendClient2) {
|
|
1408
|
+
return toolError(
|
|
1409
|
+
"The PAI Managed backend is not reachable. Contact your platform administrator to configure backend connectivity."
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
const result = await runConfirmProductImageHandler(backendClient2, validation.data);
|
|
1413
|
+
if (!result.ok) {
|
|
1414
|
+
return toolError(result.errorText);
|
|
1415
|
+
}
|
|
1416
|
+
return {
|
|
1417
|
+
content: [{ type: "text", text: JSON.stringify(result.data) }],
|
|
1418
|
+
structuredContent: result.data
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1421
|
+
);
|
|
1422
|
+
server.registerTool(
|
|
1423
|
+
import_mcp_contracts9.MCP_TOOL_NAMES.GET_CHECKOUT_CUSTOMIZATION,
|
|
1424
|
+
{
|
|
1425
|
+
description: "Get a merchant's checkout customization on PAI Managed \u2014 theme mode, colors (background, button, button text, font), font family, input style, and the current logo URL (read-only). Use this to inspect current branding before changing it.",
|
|
1426
|
+
inputSchema: import_mcp_contracts9.getCheckoutCustomizationInputSchema,
|
|
1427
|
+
outputSchema: import_mcp_contracts9.checkoutCustomizationOutputSchema
|
|
1428
|
+
},
|
|
1429
|
+
async (args) => {
|
|
1430
|
+
if (!isScopeAllowed("checkout:read", mcpTokenPayload)) {
|
|
1431
|
+
return toolError("Insufficient scope.");
|
|
1432
|
+
}
|
|
1433
|
+
const validation = runGetCheckoutCustomizationValidation(args);
|
|
1434
|
+
if (!validation.ok) {
|
|
1435
|
+
return toolError(validation.errorText);
|
|
1436
|
+
}
|
|
1437
|
+
if (!isMerchantAllowed(validation.data.merchantId, mcpTokenPayload)) {
|
|
1438
|
+
return toolError("Merchant not authorized for this MCP key.");
|
|
1439
|
+
}
|
|
1440
|
+
if (!backendClient2) {
|
|
1441
|
+
return toolError(
|
|
1442
|
+
"The PAI Managed backend is not reachable. Contact your platform administrator to configure backend connectivity."
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1445
|
+
const result = await runGetCheckoutCustomizationHandler(backendClient2, validation.data);
|
|
1446
|
+
if (!result.ok) {
|
|
1447
|
+
return toolError(result.errorText);
|
|
1448
|
+
}
|
|
1449
|
+
return {
|
|
1450
|
+
content: [{ type: "text", text: JSON.stringify(result.data) }],
|
|
1451
|
+
structuredContent: result.data
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
);
|
|
1455
|
+
server.registerTool(
|
|
1456
|
+
import_mcp_contracts9.MCP_TOOL_NAMES.UPDATE_CHECKOUT_CUSTOMIZATION,
|
|
1457
|
+
{
|
|
1458
|
+
description: "Update a merchant's checkout customization on PAI Managed. Fields are MERGED: send only the fields you want to change \u2014 any field you omit is left unchanged. To remove/reset a field, use the dashboard (clearing is not supported over MCP). Colors are 6-digit hex like '#1A2B3C'; fontFamily is 'Inter' or 'Roboto'; themeMode is 'light' or 'dark'; inputStyle is 'rounded' or 'square'. The logo is set via the dashboard. Returns the full customization after the merge.",
|
|
1459
|
+
inputSchema: import_mcp_contracts9.updateCheckoutCustomizationInputSchema,
|
|
1460
|
+
outputSchema: import_mcp_contracts9.checkoutCustomizationOutputSchema
|
|
1461
|
+
},
|
|
1462
|
+
async (args) => {
|
|
1463
|
+
if (!isScopeAllowed("checkout:write", mcpTokenPayload)) {
|
|
1464
|
+
return toolError("Insufficient scope.");
|
|
1465
|
+
}
|
|
1466
|
+
const validation = runUpdateCheckoutCustomizationValidation(args);
|
|
1467
|
+
if (!validation.ok) {
|
|
1468
|
+
return toolError(validation.errorText);
|
|
1469
|
+
}
|
|
1470
|
+
if (!isMerchantAllowed(validation.data.merchantId, mcpTokenPayload)) {
|
|
1471
|
+
return toolError("Merchant not authorized for this MCP key.");
|
|
1472
|
+
}
|
|
1473
|
+
if (!backendClient2) {
|
|
1474
|
+
return toolError(
|
|
1475
|
+
"The PAI Managed backend is not reachable. Contact your platform administrator to configure backend connectivity."
|
|
1476
|
+
);
|
|
1477
|
+
}
|
|
1478
|
+
const result = await runUpdateCheckoutCustomizationHandler(backendClient2, validation.data);
|
|
1479
|
+
if (!result.ok) {
|
|
1480
|
+
return toolError(result.errorText);
|
|
1481
|
+
}
|
|
1482
|
+
return {
|
|
1483
|
+
content: [{ type: "text", text: JSON.stringify(result.data) }],
|
|
1484
|
+
structuredContent: result.data
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
);
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
// src/http/logger.ts
|
|
1491
|
+
function summarizeJsonRpcBody(body) {
|
|
1492
|
+
if (body === void 0 || body === null) {
|
|
1493
|
+
return "";
|
|
1494
|
+
}
|
|
1495
|
+
if (Array.isArray(body)) {
|
|
1496
|
+
const methods = body.map(
|
|
1497
|
+
(item) => item && typeof item === "object" && "method" in item && typeof item.method === "string" ? item.method : "?"
|
|
1498
|
+
).slice(0, 5);
|
|
1499
|
+
const suffix = body.length > 5 ? ",\u2026" : "";
|
|
1500
|
+
return `jsonrpc batch[${body.length}] ${methods.join(",")}${suffix}`;
|
|
1501
|
+
}
|
|
1502
|
+
if (typeof body === "object" && "method" in body && typeof body.method === "string") {
|
|
1503
|
+
const { method } = body;
|
|
1504
|
+
const id = "id" in body ? JSON.stringify(body.id) : "none";
|
|
1505
|
+
return `jsonrpc method=${method} id=${id}`;
|
|
1506
|
+
}
|
|
1507
|
+
return `body type=${typeof body}`;
|
|
1508
|
+
}
|
|
1509
|
+
function mcpHttpRequestLogger(req, res, next) {
|
|
1510
|
+
const path2 = req.path;
|
|
1511
|
+
const sessionHeader = req.headers["mcp-session-id"];
|
|
1512
|
+
const session = typeof sessionHeader === "string" ? sessionHeader : Array.isArray(sessionHeader) ? sessionHeader[0] : void 0;
|
|
1513
|
+
const bodyHint = summarizeJsonRpcBody(req.body);
|
|
1514
|
+
const parts = [
|
|
1515
|
+
`[mcp-http] --> ${req.method} ${path2}`,
|
|
1516
|
+
session ? `session=${session}` : void 0,
|
|
1517
|
+
bodyHint || void 0
|
|
1518
|
+
].filter(Boolean);
|
|
1519
|
+
console.error(parts.join(" | "));
|
|
1520
|
+
const started = Date.now();
|
|
1521
|
+
res.on("finish", () => {
|
|
1522
|
+
const ms = Date.now() - started;
|
|
1523
|
+
console.error(`[mcp-http] <-- ${req.method} ${path2} status=${res.statusCode} ${ms}ms`);
|
|
1524
|
+
});
|
|
1525
|
+
next();
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
// src/http/mcp-server-card.ts
|
|
1529
|
+
var import_mcp_contracts10 = __toESM(require_dist(), 1);
|
|
1530
|
+
var TOOL_DESCRIPTIONS = {
|
|
1531
|
+
[import_mcp_contracts10.MCP_TOOL_NAMES.PAYMENTS_AI_HEALTH]: "Check whether the PAI Managed MCP server is healthy. Returns server version and connectivity status.",
|
|
1532
|
+
[import_mcp_contracts10.MCP_TOOL_NAMES.CREATE_MERCHANT_ACCOUNT]: "Create a merchant account on PAI Managed. Provide a business name and email; returns a merchant ID.",
|
|
1533
|
+
[import_mcp_contracts10.MCP_TOOL_NAMES.GET_MERCHANT_ACTIVATION_STATUS]: "Get the production activation status of a merchant account on PAI Managed.",
|
|
1534
|
+
[import_mcp_contracts10.MCP_TOOL_NAMES.SEARCH_PAYMENTS_AI_DOCS]: "Search the PAI Managed documentation knowledge base to find guides, API references, and feature explanations.",
|
|
1535
|
+
[import_mcp_contracts10.MCP_TOOL_NAMES.CREATE_PRODUCT]: "Create a product with pricing plans for a merchant. Images are added separately via the presigned flow (get_product_image_upload_url + confirm_product_image) after create.",
|
|
1536
|
+
[import_mcp_contracts10.MCP_TOOL_NAMES.GET_CHECKOUT_CUSTOMIZATION]: "Get a merchant's checkout customization (theme, colors, font, input style, logo URL).",
|
|
1537
|
+
[import_mcp_contracts10.MCP_TOOL_NAMES.UPDATE_CHECKOUT_CUSTOMIZATION]: "Update a merchant's checkout customization. Fields are merged \u2014 omitted fields are left unchanged."
|
|
1538
|
+
};
|
|
1539
|
+
function buildMcpServerCard(opts) {
|
|
1540
|
+
return {
|
|
1541
|
+
name: opts.name,
|
|
1542
|
+
version: opts.version,
|
|
1543
|
+
tools: Object.values(import_mcp_contracts10.MCP_TOOL_NAMES).map((toolName) => ({
|
|
1544
|
+
name: toolName,
|
|
1545
|
+
description: TOOL_DESCRIPTIONS[toolName] ?? toolName
|
|
1546
|
+
})),
|
|
1547
|
+
authorizationUrl: `${opts.oauthIssuerUrl}/api/v1/oauth/authorize`,
|
|
1548
|
+
tokenUrl: `${opts.oauthIssuerUrl}/api/v1/oauth/token`,
|
|
1549
|
+
registrationUrl: `${opts.oauthIssuerUrl}/api/v1/oauth/register`
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
// src/auth/mcp-token-verifier.ts
|
|
1554
|
+
var BACKEND_VALIDATE_PATH = "/api/v1/mcp/tokens/validate";
|
|
1555
|
+
async function validateMcpToken(token, backendClient2) {
|
|
1556
|
+
const result = await backendClient2.post(BACKEND_VALIDATE_PATH, { key: token });
|
|
1557
|
+
if (!result.ok) {
|
|
1558
|
+
console.error(
|
|
1559
|
+
`[mcp-token-verifier] backend validation failed kind=${result.kind} status=${result.status ?? "n/a"} message=${result.message}`
|
|
1560
|
+
);
|
|
1561
|
+
throw new McpTokenValidationError();
|
|
1562
|
+
}
|
|
1563
|
+
let parsed;
|
|
1564
|
+
try {
|
|
1565
|
+
parsed = JSON.parse(result.bodyText);
|
|
1566
|
+
} catch {
|
|
1567
|
+
throw new McpTokenValidationError();
|
|
1568
|
+
}
|
|
1569
|
+
if (typeof parsed !== "object" || parsed === null || typeof parsed.userId !== "string" || !Array.isArray(parsed.merchantIds)) {
|
|
1570
|
+
throw new McpTokenValidationError();
|
|
1571
|
+
}
|
|
1572
|
+
const { userId, merchantIds, scopes } = parsed;
|
|
1573
|
+
if (!merchantIds.every((id) => typeof id === "string")) {
|
|
1574
|
+
throw new McpTokenValidationError();
|
|
1575
|
+
}
|
|
1576
|
+
return {
|
|
1577
|
+
userId,
|
|
1578
|
+
merchantIds,
|
|
1579
|
+
scopes: Array.isArray(scopes) ? scopes : null
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
1582
|
+
var McpTokenValidationError = class extends Error {
|
|
1583
|
+
constructor() {
|
|
1584
|
+
super("MCP token validation failed");
|
|
1585
|
+
}
|
|
1586
|
+
};
|
|
1587
|
+
|
|
1588
|
+
// src/http/app.ts
|
|
1589
|
+
var SUPPORTED_SCOPES = [
|
|
1590
|
+
"health:read",
|
|
1591
|
+
"merchant:create",
|
|
1592
|
+
"merchant:read",
|
|
1593
|
+
"product:create",
|
|
1594
|
+
"docs:read"
|
|
1595
|
+
];
|
|
1596
|
+
function createExpressApp(options2) {
|
|
1597
|
+
const { host: host2, mcpPath: mcpPath2, allowedHosts: allowedHosts2, corsOrigin: corsOrigin2, backendClient: backendClient2, mcpPublicUrl: mcpPublicUrl2, oauthIssuerUrl: oauthIssuerUrl2 } = options2;
|
|
1598
|
+
const expressOptions = allowedHosts2 !== void 0 ? { host: host2, allowedHosts: allowedHosts2 } : { host: host2 };
|
|
1599
|
+
const app2 = createMcpExpressApp(expressOptions);
|
|
1600
|
+
app2.use(
|
|
1601
|
+
cors({
|
|
1602
|
+
origin: corsOrigin2,
|
|
1603
|
+
methods: ["GET", "POST", "DELETE", "OPTIONS"],
|
|
1604
|
+
allowedHeaders: ["Content-Type", "mcp-session-id", "Accept", "Authorization"]
|
|
1605
|
+
})
|
|
1606
|
+
);
|
|
1607
|
+
app2.use(mcpHttpRequestLogger);
|
|
1608
|
+
if (mcpPublicUrl2 && oauthIssuerUrl2) {
|
|
1609
|
+
const oauthMetadata = {
|
|
1610
|
+
issuer: oauthIssuerUrl2,
|
|
1611
|
+
authorization_endpoint: `${oauthIssuerUrl2}/api/v1/oauth/authorize`,
|
|
1612
|
+
token_endpoint: `${oauthIssuerUrl2}/api/v1/oauth/token`,
|
|
1613
|
+
registration_endpoint: `${oauthIssuerUrl2}/api/v1/oauth/register`,
|
|
1614
|
+
response_types_supported: ["code"],
|
|
1615
|
+
scopes_supported: SUPPORTED_SCOPES
|
|
1616
|
+
};
|
|
1617
|
+
app2.use(
|
|
1618
|
+
mcpAuthMetadataRouter({
|
|
1619
|
+
oauthMetadata,
|
|
1620
|
+
resourceServerUrl: new URL(mcpPublicUrl2),
|
|
1621
|
+
scopesSupported: SUPPORTED_SCOPES
|
|
1622
|
+
})
|
|
1623
|
+
);
|
|
1624
|
+
}
|
|
1625
|
+
if (oauthIssuerUrl2) {
|
|
1626
|
+
const version = readMcpServerVersion();
|
|
1627
|
+
app2.get("/.well-known/mcp.json", (_req, res) => {
|
|
1628
|
+
res.status(200).type("application/json").send(
|
|
1629
|
+
JSON.stringify(
|
|
1630
|
+
buildMcpServerCard({
|
|
1631
|
+
name: "pai-managed-mcp-server",
|
|
1632
|
+
version,
|
|
1633
|
+
oauthIssuerUrl: oauthIssuerUrl2
|
|
1634
|
+
})
|
|
1635
|
+
)
|
|
1636
|
+
);
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
app2.get("/health", async (_req, res) => {
|
|
1640
|
+
if (!backendClient2) {
|
|
1641
|
+
res.status(200).type("application/json").send(JSON.stringify({ status: "ok" }));
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
const result = await backendClient2.get(BACKEND_HTTP_HEALTH_PATH);
|
|
1645
|
+
if (result.ok) {
|
|
1646
|
+
res.status(200).type("application/json").send(JSON.stringify({ status: "ok", backend: { status: "ok" } }));
|
|
1647
|
+
} else {
|
|
1648
|
+
console.error(
|
|
1649
|
+
`[health] backend unreachable kind=${result.kind} status=${result.status ?? "n/a"} message=${result.message}`
|
|
1650
|
+
);
|
|
1651
|
+
res.status(503).type("application/json").send(
|
|
1652
|
+
JSON.stringify({
|
|
1653
|
+
status: "degraded",
|
|
1654
|
+
backend: { status: "error", kind: result.kind, message: result.message }
|
|
1655
|
+
})
|
|
1656
|
+
);
|
|
1657
|
+
}
|
|
1658
|
+
});
|
|
1659
|
+
const resourceMetadataUrl = mcpPublicUrl2 ? getOAuthProtectedResourceMetadataUrl(new URL(mcpPublicUrl2)) : void 0;
|
|
1660
|
+
const mcpAuthMiddleware = backendClient2 ? async (req, res, next) => {
|
|
1661
|
+
const authHeader = req.headers.authorization;
|
|
1662
|
+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
|
|
1663
|
+
if (resourceMetadataUrl) {
|
|
1664
|
+
res.set(
|
|
1665
|
+
"WWW-Authenticate",
|
|
1666
|
+
`Bearer realm="PAI Managed MCP", resource_metadata="${resourceMetadataUrl}"`
|
|
1667
|
+
);
|
|
1668
|
+
}
|
|
1669
|
+
res.status(401).json({ error: "Unauthorized" });
|
|
1670
|
+
return;
|
|
1671
|
+
}
|
|
1672
|
+
const token = authHeader.slice("Bearer ".length);
|
|
1673
|
+
if (!token.startsWith("pai_mcp_")) {
|
|
1674
|
+
if (resourceMetadataUrl) {
|
|
1675
|
+
res.set(
|
|
1676
|
+
"WWW-Authenticate",
|
|
1677
|
+
`Bearer realm="PAI Managed MCP", resource_metadata="${resourceMetadataUrl}"`
|
|
1678
|
+
);
|
|
1679
|
+
}
|
|
1680
|
+
res.status(401).json({ error: "Unauthorized" });
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
try {
|
|
1684
|
+
req.mcpTokenPayload = await validateMcpToken(token, backendClient2);
|
|
1685
|
+
next();
|
|
1686
|
+
} catch (error) {
|
|
1687
|
+
if (error instanceof McpTokenValidationError) {
|
|
1688
|
+
res.status(401).json({ error: "Unauthorized" });
|
|
1689
|
+
} else {
|
|
1690
|
+
console.error("MCP token validation: unexpected error", error);
|
|
1691
|
+
next(error);
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
} : (_req, _res, next) => next();
|
|
1695
|
+
app2.post(mcpPath2, mcpAuthMiddleware, async (req, res) => {
|
|
1696
|
+
const version = readMcpServerVersion();
|
|
1697
|
+
const server = createInstrumentedMcpServer({ version });
|
|
1698
|
+
registerTools(server, { backendClient: backendClient2, mcpTokenPayload: req.mcpTokenPayload });
|
|
1699
|
+
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
|
|
1700
|
+
res.on("close", () => {
|
|
1701
|
+
void transport.close();
|
|
1702
|
+
void server.close();
|
|
1703
|
+
});
|
|
1704
|
+
try {
|
|
1705
|
+
await server.connect(transport);
|
|
1706
|
+
await transport.handleRequest(req, res, req.body);
|
|
1707
|
+
} catch (error) {
|
|
1708
|
+
Sentry.captureException(error);
|
|
1709
|
+
console.error("Error handling MCP request:", error);
|
|
1710
|
+
void transport.close();
|
|
1711
|
+
void server.close();
|
|
1712
|
+
if (!res.headersSent) {
|
|
1713
|
+
res.status(500).json({
|
|
1714
|
+
jsonrpc: "2.0",
|
|
1715
|
+
error: { code: -32603, message: "Internal server error" },
|
|
1716
|
+
id: null
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
});
|
|
1721
|
+
const methodNotAllowed = (_req, res) => {
|
|
1722
|
+
res.status(405).set("Allow", "POST").type("application/json").send(
|
|
1723
|
+
JSON.stringify({
|
|
1724
|
+
jsonrpc: "2.0",
|
|
1725
|
+
error: { code: -32e3, message: "Method not allowed." },
|
|
1726
|
+
id: null
|
|
1727
|
+
})
|
|
1728
|
+
);
|
|
1729
|
+
};
|
|
1730
|
+
app2.get(mcpPath2, methodNotAllowed);
|
|
1731
|
+
app2.delete(mcpPath2, methodNotAllowed);
|
|
1732
|
+
Sentry.setupExpressErrorHandler(app2);
|
|
1733
|
+
return app2;
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
// src/main.http.ts
|
|
1737
|
+
var { host, port, mcpPath, allowedHosts, mcpPublicUrl, oauthIssuerUrl } = readMcpHttpListenConfig();
|
|
1738
|
+
var corsOrigin = resolveCorsOrigin();
|
|
1739
|
+
var backendBaseUrl = readRequiredHttpBaseUrl("BACKEND_BASE_URL");
|
|
1740
|
+
var backendClient = createBackendClientFromEnv(backendBaseUrl);
|
|
1741
|
+
var app = createExpressApp({
|
|
1742
|
+
host,
|
|
1743
|
+
mcpPath,
|
|
1744
|
+
allowedHosts,
|
|
1745
|
+
corsOrigin,
|
|
1746
|
+
backendClient,
|
|
1747
|
+
mcpPublicUrl,
|
|
1748
|
+
oauthIssuerUrl
|
|
1749
|
+
});
|
|
1750
|
+
var httpServer = app.listen(port, host, () => {
|
|
1751
|
+
console.error(`[mcp-http] listening host=${host} port=${port} mcpPath=${mcpPath}`);
|
|
1752
|
+
console.error(
|
|
1753
|
+
`[mcp-http] base URL http://${host}:${port} (GET /health; MCP POST ${mcpPath}). CORS from MCP_CORS_ORIGIN; HOST/PORT env.`
|
|
1754
|
+
);
|
|
1755
|
+
});
|
|
1756
|
+
httpServer.on("error", async (error) => {
|
|
1757
|
+
Sentry.captureException(error);
|
|
1758
|
+
console.error("Failed to start MCP HTTP server:", error);
|
|
1759
|
+
await Sentry.flush(2e3);
|
|
1760
|
+
process.exit(1);
|
|
1761
|
+
});
|