@solvapay/mcp 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -9
- package/dist/chunk-3VDAADZU.js +211 -0
- package/dist/express/index.cjs +504 -0
- package/dist/express/index.d.cts +65 -0
- package/dist/express/index.d.ts +65 -0
- package/dist/express/index.js +481 -0
- package/dist/fetch/index.cjs +787 -0
- package/dist/fetch/index.d.cts +238 -0
- package/dist/fetch/index.d.ts +238 -0
- package/dist/fetch/index.js +565 -0
- package/dist/index.cjs +116 -75
- package/dist/index.d.cts +46 -30
- package/dist/index.d.ts +46 -30
- package/dist/index.js +14 -171
- package/package.json +16 -7
|
@@ -0,0 +1,787 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/fetch/index.ts
|
|
21
|
+
var fetch_exports = {};
|
|
22
|
+
__export(fetch_exports, {
|
|
23
|
+
McpBearerAuthError: () => import_mcp_core6.McpBearerAuthError,
|
|
24
|
+
applyNativeCors: () => applyNativeCors,
|
|
25
|
+
authChallenge: () => authChallenge,
|
|
26
|
+
buildAuthInfoFromBearer: () => import_mcp_core6.buildAuthInfoFromBearer,
|
|
27
|
+
corsPreflight: () => corsPreflight,
|
|
28
|
+
createAuthorizationServerHandler: () => createAuthorizationServerHandler,
|
|
29
|
+
createOAuthAuthorizeHandler: () => createOAuthAuthorizeHandler,
|
|
30
|
+
createOAuthFetchRouter: () => createOAuthFetchRouter,
|
|
31
|
+
createOAuthRegisterHandler: () => createOAuthRegisterHandler,
|
|
32
|
+
createOAuthRevokeHandler: () => createOAuthRevokeHandler,
|
|
33
|
+
createOAuthTokenHandler: () => createOAuthTokenHandler,
|
|
34
|
+
createOpenidNotFoundHandler: () => createOpenidNotFoundHandler,
|
|
35
|
+
createProtectedResourceHandler: () => createProtectedResourceHandler,
|
|
36
|
+
createSolvaPayMcpFetch: () => createSolvaPayMcpFetch,
|
|
37
|
+
createSolvaPayMcpFetchHandler: () => createSolvaPayMcpFetchHandler,
|
|
38
|
+
getOAuthAuthorizationServerResponse: () => import_mcp_core6.getOAuthAuthorizationServerResponse,
|
|
39
|
+
getOAuthProtectedResourceResponse: () => import_mcp_core6.getOAuthProtectedResourceResponse,
|
|
40
|
+
isNativeClientOrigin: () => isNativeClientOrigin,
|
|
41
|
+
resolveBearer: () => resolveBearer
|
|
42
|
+
});
|
|
43
|
+
module.exports = __toCommonJS(fetch_exports);
|
|
44
|
+
|
|
45
|
+
// src/fetch/oauth-bridge.ts
|
|
46
|
+
var import_mcp_core2 = require("@solvapay/mcp-core");
|
|
47
|
+
|
|
48
|
+
// src/fetch/cors.ts
|
|
49
|
+
var import_mcp_core = require("@solvapay/mcp-core");
|
|
50
|
+
var NATIVE_CLIENT_ORIGIN_REGEX = /^(cursor|vscode|vscode-webview|claude):\/\/.+$/;
|
|
51
|
+
function isNativeClientOrigin(origin) {
|
|
52
|
+
if (!origin) return false;
|
|
53
|
+
return NATIVE_CLIENT_ORIGIN_REGEX.test(origin);
|
|
54
|
+
}
|
|
55
|
+
function applyNativeCors(reqHeaders, resHeaders) {
|
|
56
|
+
const origin = reqHeaders.get("origin");
|
|
57
|
+
if (!origin) return;
|
|
58
|
+
if (isNativeClientOrigin(origin)) {
|
|
59
|
+
resHeaders.set("Access-Control-Allow-Origin", origin);
|
|
60
|
+
resHeaders.append("Vary", "Origin");
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function corsPreflight(req) {
|
|
64
|
+
const reqHeaders = req.headers;
|
|
65
|
+
const requestedMethod = reqHeaders.get("access-control-request-method") ?? "POST";
|
|
66
|
+
const requestedHeaders = reqHeaders.get("access-control-request-headers") ?? "authorization, content-type";
|
|
67
|
+
const headers = new Headers();
|
|
68
|
+
applyNativeCors(reqHeaders, headers);
|
|
69
|
+
headers.set("Access-Control-Allow-Methods", `${requestedMethod}, OPTIONS`);
|
|
70
|
+
headers.set("Access-Control-Allow-Headers", requestedHeaders);
|
|
71
|
+
headers.set("Access-Control-Max-Age", "600");
|
|
72
|
+
return new Response(null, { status: 204, headers });
|
|
73
|
+
}
|
|
74
|
+
function authChallenge(req, options) {
|
|
75
|
+
const {
|
|
76
|
+
publicBaseUrl,
|
|
77
|
+
protectedResourcePath = "/.well-known/oauth-protected-resource",
|
|
78
|
+
jsonRpcId = null
|
|
79
|
+
} = options;
|
|
80
|
+
const headers = new Headers();
|
|
81
|
+
applyNativeCors(req.headers, headers);
|
|
82
|
+
headers.set("Access-Control-Expose-Headers", "WWW-Authenticate");
|
|
83
|
+
headers.set(
|
|
84
|
+
"WWW-Authenticate",
|
|
85
|
+
`Bearer resource_metadata="${(0, import_mcp_core.withoutTrailingSlash)(publicBaseUrl)}${protectedResourcePath}"`
|
|
86
|
+
);
|
|
87
|
+
headers.set("Content-Type", "application/json");
|
|
88
|
+
const body = {
|
|
89
|
+
jsonrpc: "2.0",
|
|
90
|
+
id: jsonRpcId,
|
|
91
|
+
error: { code: -32001, message: "Unauthorized" }
|
|
92
|
+
};
|
|
93
|
+
return new Response(JSON.stringify(body), { status: 401, headers });
|
|
94
|
+
}
|
|
95
|
+
function resolveBearer(req) {
|
|
96
|
+
const header = req.headers.get("authorization");
|
|
97
|
+
if (!header) return null;
|
|
98
|
+
const match = /^\s*Bearer\s+(.+?)\s*$/i.exec(header);
|
|
99
|
+
return match ? match[1] : null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/fetch/oauth-bridge.ts
|
|
103
|
+
function hasOAuthErrorShape(body) {
|
|
104
|
+
return body !== null && typeof body === "object" && typeof body.error === "string";
|
|
105
|
+
}
|
|
106
|
+
function extractZodErrors(body) {
|
|
107
|
+
const errs = body.errors;
|
|
108
|
+
if (!Array.isArray(errs)) return [];
|
|
109
|
+
return errs.filter((e) => !!e && typeof e === "object");
|
|
110
|
+
}
|
|
111
|
+
function deriveOAuthErrorCode(status, nestBody) {
|
|
112
|
+
if (status === 401 || status === 403) return "invalid_client";
|
|
113
|
+
if (status >= 500) return "server_error";
|
|
114
|
+
const zodErrors = extractZodErrors(nestBody);
|
|
115
|
+
const touches = (field) => zodErrors.some((e) => {
|
|
116
|
+
const path = e.path;
|
|
117
|
+
return Array.isArray(path) && path.includes(field);
|
|
118
|
+
});
|
|
119
|
+
if (touches("grant_type")) {
|
|
120
|
+
const grantTypeErr = zodErrors.find((e) => {
|
|
121
|
+
const path = e.path;
|
|
122
|
+
return Array.isArray(path) && path.includes("grant_type");
|
|
123
|
+
});
|
|
124
|
+
const received = grantTypeErr && grantTypeErr.received;
|
|
125
|
+
if (received !== "undefined" && received !== void 0 && received !== "") {
|
|
126
|
+
return "unsupported_grant_type";
|
|
127
|
+
}
|
|
128
|
+
return "invalid_request";
|
|
129
|
+
}
|
|
130
|
+
if (touches("code") || touches("refresh_token")) return "invalid_grant";
|
|
131
|
+
if (touches("scope")) return "invalid_scope";
|
|
132
|
+
if (touches("client_id") || touches("client_secret")) return "invalid_client";
|
|
133
|
+
return "invalid_request";
|
|
134
|
+
}
|
|
135
|
+
function buildErrorDescription(nestBody) {
|
|
136
|
+
const zodErrors = extractZodErrors(nestBody);
|
|
137
|
+
if (zodErrors.length > 0) {
|
|
138
|
+
const parts = zodErrors.map((e) => {
|
|
139
|
+
const path = e.path;
|
|
140
|
+
const message2 = e.message;
|
|
141
|
+
const pathStr = Array.isArray(path) ? path.filter((p) => typeof p === "string").join(".") : "";
|
|
142
|
+
const msgStr = typeof message2 === "string" ? message2 : "";
|
|
143
|
+
if (pathStr && msgStr) return `${pathStr}: ${msgStr}`;
|
|
144
|
+
return pathStr || msgStr;
|
|
145
|
+
}).filter(Boolean);
|
|
146
|
+
if (parts.length > 0) return parts.join("; ");
|
|
147
|
+
}
|
|
148
|
+
const message = nestBody.message;
|
|
149
|
+
if (typeof message === "string") return message;
|
|
150
|
+
if (Array.isArray(message)) {
|
|
151
|
+
const strings = message.filter((m) => typeof m === "string");
|
|
152
|
+
if (strings.length > 0) return strings.join("; ");
|
|
153
|
+
}
|
|
154
|
+
return void 0;
|
|
155
|
+
}
|
|
156
|
+
function toOAuthErrorBody(body, text, status) {
|
|
157
|
+
if (hasOAuthErrorShape(body)) return body;
|
|
158
|
+
if (body && typeof body === "object") {
|
|
159
|
+
const nestBody = body;
|
|
160
|
+
const error = deriveOAuthErrorCode(status, nestBody);
|
|
161
|
+
const error_description = buildErrorDescription(nestBody);
|
|
162
|
+
return error_description ? { error, error_description } : { error };
|
|
163
|
+
}
|
|
164
|
+
const fallbackError = status >= 500 ? "server_error" : "invalid_request";
|
|
165
|
+
const description = typeof text === "string" && text.length > 0 && text.length < 500 ? text : void 0;
|
|
166
|
+
return description ? { error: fallbackError, error_description: description } : { error: fallbackError };
|
|
167
|
+
}
|
|
168
|
+
async function parseUpstreamJson(response) {
|
|
169
|
+
const text = await response.text();
|
|
170
|
+
if (!text) return { body: {}, text: "" };
|
|
171
|
+
try {
|
|
172
|
+
return { body: JSON.parse(text), text };
|
|
173
|
+
} catch {
|
|
174
|
+
return { body: text, text };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
function upstreamUnreachable(req) {
|
|
178
|
+
const headers = new Headers({ "content-type": "application/json" });
|
|
179
|
+
applyNativeCors(req.headers, headers);
|
|
180
|
+
return new Response(JSON.stringify({ error: "upstream_unreachable" }), { status: 502, headers });
|
|
181
|
+
}
|
|
182
|
+
function corsResponse(req, response) {
|
|
183
|
+
const headers = new Headers(response.headers);
|
|
184
|
+
applyNativeCors(req.headers, headers);
|
|
185
|
+
return new Response(response.body, { status: response.status, headers });
|
|
186
|
+
}
|
|
187
|
+
function jsonResponse(req, status, body) {
|
|
188
|
+
const headers = new Headers({ "content-type": "application/json" });
|
|
189
|
+
applyNativeCors(req.headers, headers);
|
|
190
|
+
return new Response(JSON.stringify(body), { status, headers });
|
|
191
|
+
}
|
|
192
|
+
function emptyResponse(req, status) {
|
|
193
|
+
const headers = new Headers();
|
|
194
|
+
applyNativeCors(req.headers, headers);
|
|
195
|
+
return new Response(null, { status, headers });
|
|
196
|
+
}
|
|
197
|
+
function pathOf(req) {
|
|
198
|
+
return new URL(req.url).pathname;
|
|
199
|
+
}
|
|
200
|
+
function queryOf(req) {
|
|
201
|
+
const search = new URL(req.url).search;
|
|
202
|
+
return search || "";
|
|
203
|
+
}
|
|
204
|
+
function createProtectedResourceHandler(options) {
|
|
205
|
+
const path = options.protectedResourcePath ?? "/.well-known/oauth-protected-resource";
|
|
206
|
+
return async (req) => {
|
|
207
|
+
if (req.method !== "GET" || pathOf(req) !== path) return null;
|
|
208
|
+
return jsonResponse(req, 200, (0, import_mcp_core2.getOAuthProtectedResourceResponse)(options.publicBaseUrl));
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function createAuthorizationServerHandler(options) {
|
|
212
|
+
const path = options.authorizationServerPath ?? "/.well-known/oauth-authorization-server";
|
|
213
|
+
const resolvedPaths = (0, import_mcp_core2.resolveOAuthPaths)(options.paths);
|
|
214
|
+
return async (req) => {
|
|
215
|
+
if (req.method !== "GET" || pathOf(req) !== path) return null;
|
|
216
|
+
if (!options.productRef) {
|
|
217
|
+
return jsonResponse(req, 500, { error: "SOLVAPAY_PRODUCT_REF missing" });
|
|
218
|
+
}
|
|
219
|
+
return jsonResponse(
|
|
220
|
+
req,
|
|
221
|
+
200,
|
|
222
|
+
(0, import_mcp_core2.getOAuthAuthorizationServerResponse)({
|
|
223
|
+
publicBaseUrl: options.publicBaseUrl,
|
|
224
|
+
paths: resolvedPaths
|
|
225
|
+
})
|
|
226
|
+
);
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function createOpenidNotFoundHandler() {
|
|
230
|
+
return async (req) => {
|
|
231
|
+
if (req.method !== "GET" || pathOf(req) !== "/.well-known/openid-configuration") return null;
|
|
232
|
+
return emptyResponse(req, 404);
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
function createOAuthRegisterHandler(options) {
|
|
236
|
+
const path = options.path ?? "/oauth/register";
|
|
237
|
+
const api = (0, import_mcp_core2.withoutTrailingSlash)(options.apiBaseUrl);
|
|
238
|
+
const upstream = `${api}/v1/customer/auth/register?product_ref=${encodeURIComponent(options.productRef)}`;
|
|
239
|
+
return async (req) => {
|
|
240
|
+
if (pathOf(req) !== path) return null;
|
|
241
|
+
if (req.method === "OPTIONS") return corsPreflight(req);
|
|
242
|
+
if (req.method !== "POST") return null;
|
|
243
|
+
const body = await req.text();
|
|
244
|
+
try {
|
|
245
|
+
const upstreamResponse = await fetch(upstream, {
|
|
246
|
+
method: "POST",
|
|
247
|
+
headers: { "content-type": req.headers.get("content-type") ?? "application/json" },
|
|
248
|
+
body
|
|
249
|
+
});
|
|
250
|
+
return corsResponse(req, upstreamResponse);
|
|
251
|
+
} catch {
|
|
252
|
+
return upstreamUnreachable(req);
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
function createOAuthAuthorizeHandler(options) {
|
|
257
|
+
const path = options.path ?? "/oauth/authorize";
|
|
258
|
+
const api = (0, import_mcp_core2.withoutTrailingSlash)(options.apiBaseUrl);
|
|
259
|
+
return async (req) => {
|
|
260
|
+
if (pathOf(req) !== path) return null;
|
|
261
|
+
if (req.method === "OPTIONS") return corsPreflight(req);
|
|
262
|
+
if (req.method !== "GET") return null;
|
|
263
|
+
const query = queryOf(req);
|
|
264
|
+
const location = `${api}/v1/customer/auth/authorize${query}`;
|
|
265
|
+
const headers = new Headers({ Location: location });
|
|
266
|
+
applyNativeCors(req.headers, headers);
|
|
267
|
+
return new Response(null, { status: 302, headers });
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
async function proxyFormEndpoint(req, upstreamUrl, normalizeErrors) {
|
|
271
|
+
const rawBody = await req.text();
|
|
272
|
+
const contentType = req.headers.get("content-type") ?? "application/x-www-form-urlencoded";
|
|
273
|
+
const headers = { "content-type": contentType };
|
|
274
|
+
const authorization = req.headers.get("authorization");
|
|
275
|
+
if (authorization) headers.authorization = authorization;
|
|
276
|
+
try {
|
|
277
|
+
const upstreamResponse = await fetch(upstreamUrl, {
|
|
278
|
+
method: "POST",
|
|
279
|
+
headers,
|
|
280
|
+
body: rawBody
|
|
281
|
+
});
|
|
282
|
+
if (!normalizeErrors || upstreamResponse.ok || upstreamResponse.status === 204) {
|
|
283
|
+
return corsResponse(req, upstreamResponse);
|
|
284
|
+
}
|
|
285
|
+
const { body, text } = await parseUpstreamJson(upstreamResponse);
|
|
286
|
+
const normalized = toOAuthErrorBody(body, text, upstreamResponse.status);
|
|
287
|
+
return jsonResponse(req, upstreamResponse.status, normalized);
|
|
288
|
+
} catch {
|
|
289
|
+
return upstreamUnreachable(req);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
function createOAuthTokenHandler(options) {
|
|
293
|
+
const path = options.path ?? "/oauth/token";
|
|
294
|
+
const upstream = `${(0, import_mcp_core2.withoutTrailingSlash)(options.apiBaseUrl)}/v1/customer/auth/token`;
|
|
295
|
+
return async (req) => {
|
|
296
|
+
if (pathOf(req) !== path) return null;
|
|
297
|
+
if (req.method === "OPTIONS") return corsPreflight(req);
|
|
298
|
+
if (req.method !== "POST") return null;
|
|
299
|
+
return proxyFormEndpoint(
|
|
300
|
+
req,
|
|
301
|
+
upstream,
|
|
302
|
+
/* normalizeErrors */
|
|
303
|
+
true
|
|
304
|
+
);
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
function createOAuthRevokeHandler(options) {
|
|
308
|
+
const path = options.path ?? "/oauth/revoke";
|
|
309
|
+
const upstream = `${(0, import_mcp_core2.withoutTrailingSlash)(options.apiBaseUrl)}/v1/customer/auth/revoke`;
|
|
310
|
+
return async (req) => {
|
|
311
|
+
if (pathOf(req) !== path) return null;
|
|
312
|
+
if (req.method === "OPTIONS") return corsPreflight(req);
|
|
313
|
+
if (req.method !== "POST") return null;
|
|
314
|
+
return proxyFormEndpoint(
|
|
315
|
+
req,
|
|
316
|
+
upstream,
|
|
317
|
+
/* normalizeErrors */
|
|
318
|
+
true
|
|
319
|
+
);
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
function createOAuthFetchRouter(options) {
|
|
323
|
+
const paths = (0, import_mcp_core2.resolveOAuthPaths)(options.oauthPaths);
|
|
324
|
+
const handlers = [
|
|
325
|
+
createOpenidNotFoundHandler(),
|
|
326
|
+
createProtectedResourceHandler({
|
|
327
|
+
publicBaseUrl: options.publicBaseUrl,
|
|
328
|
+
protectedResourcePath: options.protectedResourcePath
|
|
329
|
+
}),
|
|
330
|
+
createAuthorizationServerHandler({
|
|
331
|
+
publicBaseUrl: options.publicBaseUrl,
|
|
332
|
+
authorizationServerPath: options.authorizationServerPath,
|
|
333
|
+
paths,
|
|
334
|
+
productRef: options.productRef
|
|
335
|
+
}),
|
|
336
|
+
createOAuthRegisterHandler({
|
|
337
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
338
|
+
productRef: options.productRef,
|
|
339
|
+
path: paths.register
|
|
340
|
+
}),
|
|
341
|
+
createOAuthAuthorizeHandler({ apiBaseUrl: options.apiBaseUrl, path: paths.authorize }),
|
|
342
|
+
createOAuthTokenHandler({ apiBaseUrl: options.apiBaseUrl, path: paths.token }),
|
|
343
|
+
createOAuthRevokeHandler({ apiBaseUrl: options.apiBaseUrl, path: paths.revoke })
|
|
344
|
+
];
|
|
345
|
+
return async (req) => {
|
|
346
|
+
for (const handler of handlers) {
|
|
347
|
+
const response = await handler(req);
|
|
348
|
+
if (response) return response;
|
|
349
|
+
}
|
|
350
|
+
return null;
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// src/fetch/handler.ts
|
|
355
|
+
var import_mcp_core3 = require("@solvapay/mcp-core");
|
|
356
|
+
var import_webStandardStreamableHttp = require("@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js");
|
|
357
|
+
function defaultSessionIdGenerator() {
|
|
358
|
+
const c = globalThis.crypto;
|
|
359
|
+
if (c?.randomUUID) return c.randomUUID();
|
|
360
|
+
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (ch) => {
|
|
361
|
+
const r = Math.floor(Math.random() * 16);
|
|
362
|
+
const v = ch === "x" ? r : r & 3 | 8;
|
|
363
|
+
return v.toString(16);
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
function getJsonRpcId(body) {
|
|
367
|
+
if (body && typeof body === "object" && "id" in body) {
|
|
368
|
+
const id = body.id;
|
|
369
|
+
return id ?? null;
|
|
370
|
+
}
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
async function readJsonRpcId(req) {
|
|
374
|
+
try {
|
|
375
|
+
const clone = req.clone();
|
|
376
|
+
const body = await clone.json();
|
|
377
|
+
return getJsonRpcId(body);
|
|
378
|
+
} catch {
|
|
379
|
+
return null;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
function createSolvaPayMcpFetchHandler(options) {
|
|
383
|
+
const {
|
|
384
|
+
server,
|
|
385
|
+
publicBaseUrl,
|
|
386
|
+
apiBaseUrl,
|
|
387
|
+
productRef,
|
|
388
|
+
mcpPath = "/mcp",
|
|
389
|
+
requireAuth = true,
|
|
390
|
+
authInfo,
|
|
391
|
+
protectedResourcePath,
|
|
392
|
+
authorizationServerPath,
|
|
393
|
+
oauthPaths,
|
|
394
|
+
mode = "sse-stateful",
|
|
395
|
+
buildTransport,
|
|
396
|
+
sessionIdGenerator
|
|
397
|
+
} = options;
|
|
398
|
+
const oauthRouter = createOAuthFetchRouter({
|
|
399
|
+
publicBaseUrl,
|
|
400
|
+
apiBaseUrl,
|
|
401
|
+
productRef,
|
|
402
|
+
protectedResourcePath,
|
|
403
|
+
authorizationServerPath,
|
|
404
|
+
oauthPaths
|
|
405
|
+
});
|
|
406
|
+
const makeTransport = () => {
|
|
407
|
+
if (buildTransport) return buildTransport();
|
|
408
|
+
if (mode === "json-stateless") {
|
|
409
|
+
return new import_webStandardStreamableHttp.WebStandardStreamableHTTPServerTransport({
|
|
410
|
+
sessionIdGenerator: void 0,
|
|
411
|
+
enableJsonResponse: true
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
if (mode === "sse-stateless") {
|
|
415
|
+
return new import_webStandardStreamableHttp.WebStandardStreamableHTTPServerTransport({
|
|
416
|
+
sessionIdGenerator: void 0
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
return new import_webStandardStreamableHttp.WebStandardStreamableHTTPServerTransport({
|
|
420
|
+
sessionIdGenerator: sessionIdGenerator ?? defaultSessionIdGenerator
|
|
421
|
+
});
|
|
422
|
+
};
|
|
423
|
+
let serverMutex = Promise.resolve();
|
|
424
|
+
return async (req) => {
|
|
425
|
+
const url = new URL(req.url);
|
|
426
|
+
const pathname = url.pathname;
|
|
427
|
+
if (req.method === "OPTIONS" && pathname === mcpPath) {
|
|
428
|
+
return corsPreflight(req);
|
|
429
|
+
}
|
|
430
|
+
const oauthResponse = await oauthRouter(req);
|
|
431
|
+
if (oauthResponse) return oauthResponse;
|
|
432
|
+
if (pathname !== mcpPath) {
|
|
433
|
+
return new Response("not_found", { status: 404 });
|
|
434
|
+
}
|
|
435
|
+
if (req.method && req.method !== "POST" && req.method !== "OPTIONS") {
|
|
436
|
+
const headers = new Headers({ Allow: "POST, OPTIONS" });
|
|
437
|
+
applyNativeCors(req.headers, headers);
|
|
438
|
+
return new Response(null, { status: 405, headers });
|
|
439
|
+
}
|
|
440
|
+
const authHeader = req.headers.get("authorization");
|
|
441
|
+
let resolvedAuthInfo = null;
|
|
442
|
+
if (authHeader || requireAuth) {
|
|
443
|
+
try {
|
|
444
|
+
resolvedAuthInfo = (0, import_mcp_core3.buildAuthInfoFromBearer)(authHeader, authInfo);
|
|
445
|
+
if (!resolvedAuthInfo) {
|
|
446
|
+
throw new import_mcp_core3.McpBearerAuthError("Missing bearer token");
|
|
447
|
+
}
|
|
448
|
+
} catch {
|
|
449
|
+
const jsonRpcId = await readJsonRpcId(req);
|
|
450
|
+
return authChallenge(req, {
|
|
451
|
+
publicBaseUrl,
|
|
452
|
+
protectedResourcePath,
|
|
453
|
+
jsonRpcId
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
const previous = serverMutex;
|
|
458
|
+
let releaseMutex = () => {
|
|
459
|
+
};
|
|
460
|
+
serverMutex = new Promise((resolve) => {
|
|
461
|
+
releaseMutex = resolve;
|
|
462
|
+
});
|
|
463
|
+
await previous;
|
|
464
|
+
const transport = makeTransport();
|
|
465
|
+
try {
|
|
466
|
+
await server.connect(transport);
|
|
467
|
+
const response = await transport.handleRequest(
|
|
468
|
+
req,
|
|
469
|
+
resolvedAuthInfo ? {
|
|
470
|
+
// `AuthInfo` from the SDK is structurally identical to our
|
|
471
|
+
// envelope — cast away the brand so the types line up.
|
|
472
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
473
|
+
authInfo: resolvedAuthInfo
|
|
474
|
+
} : void 0
|
|
475
|
+
);
|
|
476
|
+
const merged = new Headers(response.headers);
|
|
477
|
+
applyNativeCors(req.headers, merged);
|
|
478
|
+
return new Response(response.body, { status: response.status, headers: merged });
|
|
479
|
+
} catch (error) {
|
|
480
|
+
const headers = new Headers({ "content-type": "application/json" });
|
|
481
|
+
applyNativeCors(req.headers, headers);
|
|
482
|
+
const jsonRpcId = await readJsonRpcId(req);
|
|
483
|
+
return new Response(
|
|
484
|
+
JSON.stringify({
|
|
485
|
+
jsonrpc: "2.0",
|
|
486
|
+
id: jsonRpcId,
|
|
487
|
+
error: {
|
|
488
|
+
code: -32603,
|
|
489
|
+
message: error instanceof Error ? error.message : "internal_error"
|
|
490
|
+
}
|
|
491
|
+
}),
|
|
492
|
+
{ status: 500, headers }
|
|
493
|
+
);
|
|
494
|
+
} finally {
|
|
495
|
+
await transport.close().catch(() => {
|
|
496
|
+
});
|
|
497
|
+
releaseMutex();
|
|
498
|
+
}
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// src/internal/buildMcpServer.ts
|
|
503
|
+
var import_server = require("@modelcontextprotocol/ext-apps/server");
|
|
504
|
+
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
505
|
+
var import_mcp_core4 = require("@solvapay/mcp-core");
|
|
506
|
+
function registerDescriptor(server, tool) {
|
|
507
|
+
const baseMeta = tool.meta ?? {};
|
|
508
|
+
const baseUi = baseMeta.ui ?? {};
|
|
509
|
+
const metaWithIcons = tool.icons && tool.icons.length > 0 ? { ...baseMeta, ui: { ...baseUi, icons: tool.icons } } : baseMeta;
|
|
510
|
+
(0, import_server.registerAppTool)(
|
|
511
|
+
server,
|
|
512
|
+
tool.name,
|
|
513
|
+
{
|
|
514
|
+
...tool.title !== void 0 ? { title: tool.title } : {},
|
|
515
|
+
description: tool.description,
|
|
516
|
+
inputSchema: tool.inputSchema,
|
|
517
|
+
_meta: metaWithIcons,
|
|
518
|
+
...tool.annotations !== void 0 ? { annotations: tool.annotations } : {},
|
|
519
|
+
...tool.icons !== void 0 ? { icons: tool.icons } : {}
|
|
520
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
521
|
+
},
|
|
522
|
+
// `SolvaPayCallToolResult` is a structural subset of the official
|
|
523
|
+
// SDK's `CallToolResult`; cast to erase the extra-narrow `resource`
|
|
524
|
+
// block typing the SDK expects on `{ type: 'resource' }` content.
|
|
525
|
+
async (args, extra) => await tool.handler(
|
|
526
|
+
args,
|
|
527
|
+
extra
|
|
528
|
+
)
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
function registerPromptDescriptor(server, prompt) {
|
|
532
|
+
const config = { description: prompt.description };
|
|
533
|
+
if (prompt.title !== void 0) config.title = prompt.title;
|
|
534
|
+
if (prompt.argsSchema !== void 0) config.argsSchema = prompt.argsSchema;
|
|
535
|
+
server.registerPrompt(
|
|
536
|
+
prompt.name,
|
|
537
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
538
|
+
config,
|
|
539
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
540
|
+
async (args) => await prompt.handler(args ?? {})
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
function registerDocsResource(server, docs) {
|
|
544
|
+
server.registerResource(
|
|
545
|
+
docs.name,
|
|
546
|
+
docs.uri,
|
|
547
|
+
{
|
|
548
|
+
...docs.title !== void 0 ? { title: docs.title } : {},
|
|
549
|
+
description: docs.description,
|
|
550
|
+
mimeType: docs.mimeType
|
|
551
|
+
},
|
|
552
|
+
async () => ({
|
|
553
|
+
contents: [
|
|
554
|
+
{
|
|
555
|
+
uri: docs.uri,
|
|
556
|
+
mimeType: docs.mimeType,
|
|
557
|
+
text: await docs.readBody()
|
|
558
|
+
}
|
|
559
|
+
]
|
|
560
|
+
})
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
function buildSolvaPayMcpServer(options) {
|
|
564
|
+
const {
|
|
565
|
+
registerPrompts = true,
|
|
566
|
+
registerDocsResources = true,
|
|
567
|
+
serverName,
|
|
568
|
+
serverVersion = "1.0.0",
|
|
569
|
+
hideToolsByAudience: _hideToolsByAudience,
|
|
570
|
+
...descriptorOptions
|
|
571
|
+
} = options;
|
|
572
|
+
const descriptors = (0, import_mcp_core4.buildSolvaPayDescriptors)(descriptorOptions);
|
|
573
|
+
const effectiveServerName = serverName ?? descriptorOptions.branding?.brandName ?? "solvapay-mcp-server";
|
|
574
|
+
const serverIcons = (0, import_mcp_core4.deriveIcons)(descriptorOptions.branding);
|
|
575
|
+
const server = new import_mcp.McpServer({
|
|
576
|
+
name: effectiveServerName,
|
|
577
|
+
version: serverVersion,
|
|
578
|
+
...serverIcons ? { icons: serverIcons } : {}
|
|
579
|
+
});
|
|
580
|
+
for (const tool of descriptors.tools) {
|
|
581
|
+
registerDescriptor(server, tool);
|
|
582
|
+
}
|
|
583
|
+
if (registerPrompts) {
|
|
584
|
+
for (const prompt of descriptors.prompts) {
|
|
585
|
+
registerPromptDescriptor(server, prompt);
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
if (registerDocsResources) {
|
|
589
|
+
for (const docs of descriptors.docsResources) {
|
|
590
|
+
registerDocsResource(server, docs);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
const resource = descriptors.resource;
|
|
594
|
+
(0, import_server.registerAppResource)(
|
|
595
|
+
server,
|
|
596
|
+
resource.uri,
|
|
597
|
+
resource.uri,
|
|
598
|
+
{
|
|
599
|
+
mimeType: import_server.RESOURCE_MIME_TYPE,
|
|
600
|
+
_meta: {
|
|
601
|
+
ui: {
|
|
602
|
+
csp: resource.csp,
|
|
603
|
+
// `false` asks the host to skip painting its own outer card /
|
|
604
|
+
// border around the iframe. The widget paints its own frame
|
|
605
|
+
// via `.solvapay-mcp-card`, and `<AppHeader>` renders the
|
|
606
|
+
// merchant mark at the top; a host-painted card on top of
|
|
607
|
+
// that produced a nested-container look (visible on MCP Jam
|
|
608
|
+
// with the earlier `true` default). Hosts that honour the
|
|
609
|
+
// preference (per the MCP Apps spec) now render us flush
|
|
610
|
+
// inside their conversation surface.
|
|
611
|
+
prefersBorder: false
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
},
|
|
615
|
+
async () => ({
|
|
616
|
+
contents: [
|
|
617
|
+
{
|
|
618
|
+
uri: resource.uri,
|
|
619
|
+
mimeType: import_server.RESOURCE_MIME_TYPE,
|
|
620
|
+
text: await resource.readHtml(),
|
|
621
|
+
_meta: {
|
|
622
|
+
ui: {
|
|
623
|
+
csp: resource.csp,
|
|
624
|
+
prefersBorder: false
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
]
|
|
629
|
+
})
|
|
630
|
+
);
|
|
631
|
+
return { server, descriptors };
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/registerPayableTool.ts
|
|
635
|
+
var import_server2 = require("@modelcontextprotocol/ext-apps/server");
|
|
636
|
+
var import_mcp_core5 = require("@solvapay/mcp-core");
|
|
637
|
+
function registerPayableTool(server, name, options) {
|
|
638
|
+
const {
|
|
639
|
+
solvaPay,
|
|
640
|
+
schema,
|
|
641
|
+
product,
|
|
642
|
+
title,
|
|
643
|
+
description,
|
|
644
|
+
handler,
|
|
645
|
+
buildBootstrap,
|
|
646
|
+
getCustomerRef,
|
|
647
|
+
meta,
|
|
648
|
+
annotations,
|
|
649
|
+
icons
|
|
650
|
+
} = options;
|
|
651
|
+
const protectedHandler = (0, import_mcp_core5.buildPayableHandler)(
|
|
652
|
+
solvaPay,
|
|
653
|
+
{ product, buildBootstrap, getCustomerRef },
|
|
654
|
+
handler
|
|
655
|
+
);
|
|
656
|
+
const baseMeta = meta ?? {};
|
|
657
|
+
const baseUi = baseMeta.ui ?? {};
|
|
658
|
+
const hasIcons = icons !== void 0 && icons.length > 0;
|
|
659
|
+
const mergedUi = {
|
|
660
|
+
...baseUi,
|
|
661
|
+
...hasIcons ? { icons } : {}
|
|
662
|
+
};
|
|
663
|
+
const hasUi = Object.keys(mergedUi).length > 0;
|
|
664
|
+
const toolMeta = hasUi ? { ...baseMeta, ui: mergedUi } : { ...baseMeta };
|
|
665
|
+
const effectiveAnnotations = {
|
|
666
|
+
readOnlyHint: true,
|
|
667
|
+
openWorldHint: true,
|
|
668
|
+
...annotations
|
|
669
|
+
};
|
|
670
|
+
const hasUiResource = hasUi && typeof mergedUi.resourceUri === "string";
|
|
671
|
+
const toolConfig = {
|
|
672
|
+
...title !== void 0 ? { title } : {},
|
|
673
|
+
...description !== void 0 ? { description } : {},
|
|
674
|
+
...schema !== void 0 ? { inputSchema: schema } : {},
|
|
675
|
+
...Object.keys(toolMeta).length > 0 ? { _meta: toolMeta } : {},
|
|
676
|
+
annotations: effectiveAnnotations,
|
|
677
|
+
...icons !== void 0 && icons.length > 0 ? { icons } : {}
|
|
678
|
+
};
|
|
679
|
+
const toolCallback = async (args, extra) => await protectedHandler(args, extra);
|
|
680
|
+
if (hasUiResource) {
|
|
681
|
+
return (0, import_server2.registerAppTool)(
|
|
682
|
+
server,
|
|
683
|
+
name,
|
|
684
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
685
|
+
toolConfig,
|
|
686
|
+
toolCallback
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
return server.registerTool(
|
|
690
|
+
name,
|
|
691
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
692
|
+
toolConfig,
|
|
693
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
694
|
+
toolCallback
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// src/fetch/createSolvaPayMcpFetch.ts
|
|
699
|
+
function createSolvaPayMcpFetch(options) {
|
|
700
|
+
const {
|
|
701
|
+
// Descriptor options.
|
|
702
|
+
solvaPay,
|
|
703
|
+
productRef,
|
|
704
|
+
resourceUri,
|
|
705
|
+
htmlPath,
|
|
706
|
+
readHtml,
|
|
707
|
+
publicBaseUrl,
|
|
708
|
+
views,
|
|
709
|
+
csp,
|
|
710
|
+
getCustomerRef,
|
|
711
|
+
onToolCall,
|
|
712
|
+
onToolResult,
|
|
713
|
+
branding,
|
|
714
|
+
// Server / registration options.
|
|
715
|
+
additionalTools,
|
|
716
|
+
hideToolsByAudience,
|
|
717
|
+
registerPrompts = true,
|
|
718
|
+
registerDocsResources = true,
|
|
719
|
+
serverName,
|
|
720
|
+
serverVersion = "1.0.0",
|
|
721
|
+
// Handler options — everything in
|
|
722
|
+
// `CreateSolvaPayMcpFetchHandlerOptions` except `server`.
|
|
723
|
+
...handlerRest
|
|
724
|
+
} = options;
|
|
725
|
+
const { server, descriptors } = buildSolvaPayMcpServer({
|
|
726
|
+
solvaPay,
|
|
727
|
+
productRef,
|
|
728
|
+
resourceUri,
|
|
729
|
+
...htmlPath !== void 0 ? { htmlPath } : {},
|
|
730
|
+
...readHtml !== void 0 ? { readHtml } : {},
|
|
731
|
+
publicBaseUrl,
|
|
732
|
+
...views !== void 0 ? { views } : {},
|
|
733
|
+
...csp !== void 0 ? { csp } : {},
|
|
734
|
+
...getCustomerRef !== void 0 ? { getCustomerRef } : {},
|
|
735
|
+
...onToolCall !== void 0 ? { onToolCall } : {},
|
|
736
|
+
...onToolResult !== void 0 ? { onToolResult } : {},
|
|
737
|
+
...branding !== void 0 ? { branding } : {},
|
|
738
|
+
registerPrompts,
|
|
739
|
+
registerDocsResources,
|
|
740
|
+
...serverName !== void 0 ? { serverName } : {},
|
|
741
|
+
serverVersion
|
|
742
|
+
});
|
|
743
|
+
if (additionalTools) {
|
|
744
|
+
const registerPayable = (name, opts) => {
|
|
745
|
+
registerPayableTool(server, name, {
|
|
746
|
+
solvaPay,
|
|
747
|
+
...opts,
|
|
748
|
+
product: opts.product ?? productRef,
|
|
749
|
+
buildBootstrap: opts.buildBootstrap ?? descriptors.buildBootstrapPayload
|
|
750
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
751
|
+
});
|
|
752
|
+
};
|
|
753
|
+
additionalTools({ server, solvaPay, resourceUri, productRef, registerPayable });
|
|
754
|
+
}
|
|
755
|
+
(0, import_mcp_core4.applyHideToolsByAudience)(server, hideToolsByAudience);
|
|
756
|
+
return createSolvaPayMcpFetchHandler({
|
|
757
|
+
server,
|
|
758
|
+
publicBaseUrl,
|
|
759
|
+
productRef,
|
|
760
|
+
...handlerRest
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// src/fetch/index.ts
|
|
765
|
+
var import_mcp_core6 = require("@solvapay/mcp-core");
|
|
766
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
767
|
+
0 && (module.exports = {
|
|
768
|
+
McpBearerAuthError,
|
|
769
|
+
applyNativeCors,
|
|
770
|
+
authChallenge,
|
|
771
|
+
buildAuthInfoFromBearer,
|
|
772
|
+
corsPreflight,
|
|
773
|
+
createAuthorizationServerHandler,
|
|
774
|
+
createOAuthAuthorizeHandler,
|
|
775
|
+
createOAuthFetchRouter,
|
|
776
|
+
createOAuthRegisterHandler,
|
|
777
|
+
createOAuthRevokeHandler,
|
|
778
|
+
createOAuthTokenHandler,
|
|
779
|
+
createOpenidNotFoundHandler,
|
|
780
|
+
createProtectedResourceHandler,
|
|
781
|
+
createSolvaPayMcpFetch,
|
|
782
|
+
createSolvaPayMcpFetchHandler,
|
|
783
|
+
getOAuthAuthorizationServerResponse,
|
|
784
|
+
getOAuthProtectedResourceResponse,
|
|
785
|
+
isNativeClientOrigin,
|
|
786
|
+
resolveBearer
|
|
787
|
+
});
|