shabaaspay-mcp-server 1.0.1

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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/dist/api/client.d.ts +60 -0
  3. package/dist/api/client.js +214 -0
  4. package/dist/config/index.d.ts +54 -0
  5. package/dist/config/index.js +79 -0
  6. package/dist/enricher/action-suggester.d.ts +2 -0
  7. package/dist/enricher/action-suggester.js +26 -0
  8. package/dist/enricher/index.d.ts +2 -0
  9. package/dist/enricher/index.js +166 -0
  10. package/dist/enricher/status-analyzer.d.ts +6 -0
  11. package/dist/enricher/status-analyzer.js +71 -0
  12. package/dist/enricher/summary-generator.d.ts +8 -0
  13. package/dist/enricher/summary-generator.js +45 -0
  14. package/dist/index.d.ts +2 -0
  15. package/dist/index.js +28 -0
  16. package/dist/security/auth.d.ts +4 -0
  17. package/dist/security/auth.js +30 -0
  18. package/dist/security/policy.d.ts +18 -0
  19. package/dist/security/policy.js +35 -0
  20. package/dist/security/rate-limiter.d.ts +13 -0
  21. package/dist/security/rate-limiter.js +55 -0
  22. package/dist/security/validator.d.ts +6 -0
  23. package/dist/security/validator.js +22 -0
  24. package/dist/server/http-server.d.ts +28 -0
  25. package/dist/server/http-server.js +524 -0
  26. package/dist/server/stdio-server.d.ts +13 -0
  27. package/dist/server/stdio-server.js +114 -0
  28. package/dist/server-http.d.ts +2 -0
  29. package/dist/server-http.js +27 -0
  30. package/dist/tools/auth.d.ts +17 -0
  31. package/dist/tools/auth.js +51 -0
  32. package/dist/tools/index.d.ts +159 -0
  33. package/dist/tools/index.js +14 -0
  34. package/dist/tools/payment-agreements.d.ts +68 -0
  35. package/dist/tools/payment-agreements.js +92 -0
  36. package/dist/tools/payment-initiations.d.ts +84 -0
  37. package/dist/tools/payment-initiations.js +162 -0
  38. package/dist/tools/response-helpers.d.ts +4 -0
  39. package/dist/tools/response-helpers.js +32 -0
  40. package/dist/types/index.d.ts +184 -0
  41. package/dist/types/index.js +80 -0
  42. package/dist/worker.d.ts +15 -0
  43. package/dist/worker.js +767 -0
  44. package/package.json +64 -0
  45. package/readme.md +113 -0
package/dist/worker.js ADDED
@@ -0,0 +1,767 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const policy_js_1 = require("./security/policy.js");
4
+ const webStandardStreamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js");
5
+ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
6
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
7
+ const zod_to_json_schema_1 = require("zod-to-json-schema");
8
+ const index_js_2 = require("./types/index.js");
9
+ const SHABAAS_URLS = {
10
+ sandbox: 'https://sandbox.example.com',
11
+ production: 'https://api.example.com',
12
+ };
13
+ // Cache bearer token per worker instance
14
+ let cachedBearer = null;
15
+ let cachedFetchedAt = null;
16
+ let resolvedPolicyCache = null;
17
+ // Shared MCP transport/server for Worker HTTP streamable MCP
18
+ const sharedMcpTransport = new webStandardStreamableHttp_js_1.WebStandardStreamableHTTPServerTransport();
19
+ const sharedMcpServer = new index_js_1.Server({ name: 'shabaaspay-mcp', version: '1.0.1' }, { capabilities: { tools: {} } });
20
+ let mcpHandlersInitialized = false;
21
+ let currentMcpContext = null;
22
+ exports.default = {
23
+ async fetch(request, env, ctx) {
24
+ const startTime = Date.now();
25
+ const requestId = `req_${startTime}_${Math.random().toString(36).slice(2, 8)}`;
26
+ // CORS preflight
27
+ if (request.method === 'OPTIONS') {
28
+ return corsResponse(env);
29
+ }
30
+ const url = new URL(request.url);
31
+ // Health check - no auth required
32
+ if (url.pathname === '/health') {
33
+ return jsonResponse({
34
+ status: 'healthy',
35
+ version: '1.0.0',
36
+ environment: env.SHABAAS_ENVIRONMENT,
37
+ timestamp: new Date().toISOString(),
38
+ cloudflare: true,
39
+ }, 200, env);
40
+ }
41
+ // List tools - no auth required
42
+ if (url.pathname === '/tools' && request.method === 'GET') {
43
+ return listTools(env);
44
+ }
45
+ if (url.pathname === '/mcp' && request.method === 'GET') {
46
+ return listTools(env);
47
+ }
48
+ // All other endpoints require authentication + policy
49
+ const authResult = await authenticate(request, env);
50
+ if (!authResult.success) {
51
+ return withRequestId(authResult.response, requestId);
52
+ }
53
+ const authToken = authResult.token;
54
+ const policyLookup = lookupPolicyFromEnv(authToken, env);
55
+ if (!policyLookup.policy) {
56
+ logStructured({
57
+ requestId,
58
+ clientId: undefined,
59
+ toolName: undefined,
60
+ statusCode: 403,
61
+ latencyMs: Date.now() - startTime,
62
+ rejection: policyLookup.rejection ?? 'forbidden',
63
+ path: url.pathname,
64
+ environment: env.SHABAAS_ENVIRONMENT,
65
+ });
66
+ return withRequestId(forbiddenResponse(env), requestId);
67
+ }
68
+ const policy = policyLookup.policy;
69
+ // Route request
70
+ try {
71
+ let response;
72
+ if (url.pathname === '/tools/execute' && request.method === 'POST') {
73
+ response = await handleToolsExecute(request, env, policy, authToken, requestId, startTime);
74
+ }
75
+ else if (url.pathname === '/mcp' && request.method === 'POST') {
76
+ response = await handleMcp(request, env, policy, authToken, requestId, startTime);
77
+ }
78
+ else if (url.pathname.startsWith('/api/')) {
79
+ // Direct API proxy
80
+ response = await proxyToShaBaas(request, env);
81
+ }
82
+ else {
83
+ return withRequestId(jsonResponse({
84
+ error: 'Not found',
85
+ message: `Endpoint ${request.method} ${url.pathname} does not exist`,
86
+ }, 404, env), requestId);
87
+ }
88
+ // Add headers
89
+ const headers = new Headers(response.headers);
90
+ headers.set('X-Processing-Time', `${Date.now() - startTime}ms`);
91
+ headers.set('X-Request-Id', requestId);
92
+ const wrapped = new Response(response.body, {
93
+ status: response.status,
94
+ headers,
95
+ });
96
+ logStructured({
97
+ requestId,
98
+ clientId: policy.client_id,
99
+ toolName: undefined,
100
+ statusCode: response.status,
101
+ latencyMs: Date.now() - startTime,
102
+ path: url.pathname,
103
+ environment: env.SHABAAS_ENVIRONMENT,
104
+ });
105
+ return wrapped;
106
+ }
107
+ catch (error) {
108
+ console.error('Error:', error);
109
+ logStructured({
110
+ requestId,
111
+ clientId: undefined,
112
+ toolName: undefined,
113
+ statusCode: 500,
114
+ latencyMs: Date.now() - startTime,
115
+ rejection: 'internal_error',
116
+ });
117
+ return withRequestId(errorResponse('Internal server error', env), requestId);
118
+ }
119
+ },
120
+ };
121
+ async function authenticate(request, env) {
122
+ const authHeader = request.headers.get('Authorization');
123
+ if (!authHeader) {
124
+ return {
125
+ success: false,
126
+ response: jsonResponse({
127
+ error: 'Authentication required',
128
+ message: 'Provide the ShaBaas auth UUID in the Authorization header.',
129
+ hint: 'Use: Authorization: <your_uuid>',
130
+ }, 401, env),
131
+ };
132
+ }
133
+ const token = authHeader.replace(/^Bearer\s+/i, '').trim();
134
+ if (!token) {
135
+ return {
136
+ success: false,
137
+ response: jsonResponse({
138
+ error: 'Invalid UUID',
139
+ message: 'Authorization header must contain a UUID for ShaBaas authorization.',
140
+ }, 403, env),
141
+ };
142
+ }
143
+ return { success: true, token };
144
+ }
145
+ function getClientIdentifier(request) {
146
+ return request.headers.get('CF-Connecting-IP') ||
147
+ request.headers.get('X-Forwarded-For') ||
148
+ 'unknown';
149
+ }
150
+ async function listTools(env) {
151
+ const prod = env.SHABAAS_ENVIRONMENT === 'production';
152
+ const toolsList = [
153
+ {
154
+ name: 'get_payment_agreement',
155
+ description: 'Retrieve payment agreement details with AI insights',
156
+ method: 'POST',
157
+ endpoint: '/tools/execute',
158
+ example: {
159
+ tool: 'get_payment_agreement',
160
+ arguments: { payment_agreement_id: '2882PA20251227045450257' }
161
+ }
162
+ },
163
+ {
164
+ name: 'create_payment_agreement',
165
+ description: 'Create a new payment agreement',
166
+ method: 'POST',
167
+ endpoint: '/tools/execute',
168
+ },
169
+ {
170
+ name: 'initiate_payment',
171
+ description: 'Initiate a payment against an agreement',
172
+ method: 'POST',
173
+ endpoint: '/tools/execute',
174
+ },
175
+ {
176
+ name: 'get_payment_initiation',
177
+ description: 'Retrieve payment initiation by ID',
178
+ method: 'POST',
179
+ endpoint: '/tools/execute',
180
+ },
181
+ ];
182
+ // Exclude get_auth_token in production
183
+ if (!prod) {
184
+ toolsList.push({
185
+ name: 'get_auth_token',
186
+ description: 'Get ShaBaas API authorization token',
187
+ method: 'POST',
188
+ endpoint: '/tools/execute',
189
+ example: {
190
+ tool: 'get_auth_token',
191
+ arguments: { uuid: 'your-uuid-here' }
192
+ },
193
+ });
194
+ }
195
+ return jsonResponse({
196
+ tools: toolsList,
197
+ direct_api_access: {
198
+ description: 'You can also call ShaBaas API directly through this proxy',
199
+ base_url: '/api',
200
+ example: 'POST /api/public/authorization'
201
+ }
202
+ }, 200, env);
203
+ }
204
+ async function executeToolProxy(request, env, policy, authToken, requestId) {
205
+ const body = await request.json();
206
+ const { tool, arguments: args } = body;
207
+ const outcome = await runToolInvocation(tool, args, env, policy, authToken, requestId);
208
+ return withRequestId(jsonResponse(outcome.body, outcome.status, env), requestId);
209
+ }
210
+ async function getAuthToken(args, baseUrl, env) {
211
+ try {
212
+ const uuid = args?.uuid;
213
+ if (!uuid) {
214
+ return jsonResponse({
215
+ error: 'Missing uuid',
216
+ message: 'Provide the ShaBaas auth UUID in the Authorization header.',
217
+ }, 400, env);
218
+ }
219
+ const token = await getShaBaasBearer(env, baseUrl, uuid);
220
+ return jsonResponse({
221
+ success: true,
222
+ data: { fetched_at: new Date().toISOString() },
223
+ insights: {
224
+ status: 'success',
225
+ message: 'Bearer token fetched successfully and cached inside the worker.',
226
+ },
227
+ }, 200, env);
228
+ }
229
+ catch (error) {
230
+ return jsonResponse({
231
+ error: 'Authorization failed',
232
+ message: error.message,
233
+ }, 500, env);
234
+ }
235
+ }
236
+ async function getPaymentAgreement(args, baseUrl, env, bearer) {
237
+ if (!args.payment_agreement_id) {
238
+ return jsonResponse({
239
+ error: 'Missing payment_agreement_id',
240
+ message: 'payment_agreement_id is required',
241
+ }, 400, env);
242
+ }
243
+ try {
244
+ const response = await fetch(`${baseUrl}/api/public/payment_agreement?id=${args.payment_agreement_id}`, {
245
+ headers: {
246
+ 'Authorization': bearer,
247
+ 'Content-Type': 'application/json',
248
+ },
249
+ });
250
+ const data = (await response.json());
251
+ // Enrich the response
252
+ if (response.ok && data.data) {
253
+ const agreement = data.data;
254
+ return jsonResponse({
255
+ success: true,
256
+ data: agreement,
257
+ insights: {
258
+ status: agreement.status,
259
+ canInitiatePayment: agreement.status === 'active',
260
+ nextActions: agreement.status === 'active'
261
+ ? ['initiate_payment', 'cancel_payment_agreement']
262
+ : ['resend_payment_agreement'],
263
+ summary: generateSummary(agreement),
264
+ },
265
+ }, 200, env);
266
+ }
267
+ return jsonResponse(data, response.status, env);
268
+ }
269
+ catch (error) {
270
+ return jsonResponse({
271
+ error: 'Request failed',
272
+ message: 'An error occurred',
273
+ }, 500, env);
274
+ }
275
+ }
276
+ async function createPaymentAgreement(args, baseUrl, env, bearer) {
277
+ if (!args.idempotency_key) {
278
+ return jsonResponse({
279
+ error: 'Invalid request',
280
+ message: 'idempotency_key is required',
281
+ }, 400, env);
282
+ }
283
+ try {
284
+ const response = await fetch(`${baseUrl}/api/public/payment_agreement`, {
285
+ method: 'POST',
286
+ headers: {
287
+ 'Authorization': bearer,
288
+ 'Content-Type': 'application/json',
289
+ 'Idempotency-Key': args.idempotency_key,
290
+ },
291
+ body: JSON.stringify({
292
+ maximum_amount: args.maximum_amount,
293
+ end_date: args.end_date,
294
+ customer_reference: args.customer_reference,
295
+ }),
296
+ });
297
+ const data = (await response.json());
298
+ return jsonResponse({
299
+ success: response.ok,
300
+ data: data.data,
301
+ insights: {
302
+ status: 'pending',
303
+ message: 'Payment agreement created. Customer must approve it.',
304
+ nextActions: ['get_payment_agreement', 'resend_payment_agreement'],
305
+ },
306
+ }, response.status, env);
307
+ }
308
+ catch (error) {
309
+ return jsonResponse({
310
+ error: 'Request failed',
311
+ message: 'An error occurred',
312
+ }, 500, env);
313
+ }
314
+ }
315
+ async function initiatePayment(args, baseUrl, env, bearer, requestId) {
316
+ const amountNum = Number(args.amount);
317
+ if (!Number.isFinite(amountNum) || amountNum <= 0) {
318
+ return withRequestId(jsonResponse({ error: 'Invalid amount', message: 'Amount must be greater than zero' }, 400, env), requestId);
319
+ }
320
+ if (!args.idempotency_key) {
321
+ return withRequestId(jsonResponse({ error: 'Invalid request', message: 'idempotency_key is required' }, 400, env), requestId);
322
+ }
323
+ try {
324
+ const response = await fetch(`${baseUrl}/api/public/payment_initiation`, {
325
+ method: 'POST',
326
+ headers: {
327
+ 'Authorization': bearer,
328
+ 'Content-Type': 'application/json',
329
+ 'Idempotency-Key': args.idempotency_key,
330
+ },
331
+ body: JSON.stringify({
332
+ payment_initiation: {
333
+ payment_agreement_id: args.payment_agreement_id,
334
+ amount: amountNum,
335
+ description: args.description,
336
+ },
337
+ }),
338
+ });
339
+ const data = response.ok ? (await response.json()) : undefined;
340
+ return jsonResponse({
341
+ success: response.ok,
342
+ data: data?.data,
343
+ insights: {
344
+ status: data?.data?.status || 'unknown',
345
+ message: response.ok ? 'Payment initiated successfully' : 'Payment initiation failed',
346
+ },
347
+ }, response.status, env);
348
+ }
349
+ catch (error) {
350
+ return jsonResponse({
351
+ error: 'Request failed',
352
+ message: 'An error occurred',
353
+ }, 500, env);
354
+ }
355
+ }
356
+ async function getPaymentInitiation(args, baseUrl, env, bearer) {
357
+ if (!args.payment_initiation_id) {
358
+ return jsonResponse({
359
+ error: 'Missing payment_initiation_id',
360
+ message: 'payment_initiation_id is required',
361
+ }, 400, env);
362
+ }
363
+ try {
364
+ const response = await fetch(`${baseUrl}/api/public/payment_initiation?id=${args.payment_initiation_id}`, {
365
+ headers: {
366
+ Authorization: bearer,
367
+ 'Content-Type': 'application/json',
368
+ },
369
+ });
370
+ const data = await response.json();
371
+ return jsonResponse({
372
+ success: response.ok,
373
+ data: data?.data,
374
+ insights: {
375
+ status: data?.data?.status || 'unknown',
376
+ canProceed: data?.data?.status === 'acsc',
377
+ nextActions: ['get_payment_initiation'],
378
+ },
379
+ }, response.status, env);
380
+ }
381
+ catch (error) {
382
+ return jsonResponse({
383
+ error: 'Request failed',
384
+ message: 'An error occurred',
385
+ }, 500, env);
386
+ }
387
+ }
388
+ async function proxyToShaBaas(request, env) {
389
+ const url = new URL(request.url);
390
+ const baseUrl = SHABAAS_URLS[env.SHABAAS_ENVIRONMENT];
391
+ // Forward the request to ShaBaas API
392
+ const shabaasUrl = `${baseUrl}${url.pathname}${url.search}`;
393
+ const headers = new Headers(request.headers);
394
+ headers.set('Host', new URL(baseUrl).host);
395
+ const response = await fetch(shabaasUrl, {
396
+ method: request.method,
397
+ headers,
398
+ body: request.body,
399
+ });
400
+ return new Response(response.body, {
401
+ status: response.status,
402
+ headers: corsHeaders(env),
403
+ });
404
+ }
405
+ function isWriteTool(toolName) {
406
+ return toolName === 'create_payment_agreement' || toolName === 'initiate_payment';
407
+ }
408
+ function validateWriteConstraints(toolName, args, env, requestId) {
409
+ if (!isWriteTool(toolName))
410
+ return undefined;
411
+ if (!args?.idempotency_key) {
412
+ return withRequestId(jsonResponse({ error: 'Invalid request', message: 'idempotency_key is required' }, 400, env), requestId);
413
+ }
414
+ if (toolName === 'initiate_payment') {
415
+ const amountNum = Number(args?.amount);
416
+ if (!Number.isFinite(amountNum) || amountNum <= 0) {
417
+ return withRequestId(jsonResponse({ error: 'Invalid amount', message: 'Amount must be greater than zero' }, 400, env), requestId);
418
+ }
419
+ }
420
+ return undefined;
421
+ }
422
+ async function runToolInvocation(toolName, args, env, policy, authToken, requestId) {
423
+ if (!(0, policy_js_1.isToolAllowed)(policy, toolName)) {
424
+ return {
425
+ status: 403,
426
+ body: { error: 'Forbidden', message: 'Tool not permitted' },
427
+ toolName,
428
+ };
429
+ }
430
+ const precheck = validateWriteConstraints(toolName, args, env, requestId);
431
+ if (precheck) {
432
+ return {
433
+ status: precheck.status,
434
+ body: JSON.parse(await precheck.clone().text()),
435
+ toolName,
436
+ };
437
+ }
438
+ const baseUrl = SHABAAS_URLS[env.SHABAAS_ENVIRONMENT];
439
+ const uuid = args?.uuid || authToken;
440
+ if (!uuid) {
441
+ return {
442
+ status: 401,
443
+ body: { error: 'Invalid credentials', message: 'UUID is required' },
444
+ toolName,
445
+ };
446
+ }
447
+ const bearer = await getShaBaasBearer(env, baseUrl, uuid);
448
+ let resp;
449
+ switch (toolName) {
450
+ case 'get_auth_token':
451
+ if (!(0, policy_js_1.isAdmin)(policy) || env.SHABAAS_ENVIRONMENT === 'production') {
452
+ return {
453
+ status: 403,
454
+ body: { error: 'Forbidden', message: 'Tool not permitted' },
455
+ toolName,
456
+ };
457
+ }
458
+ resp = await getAuthToken(args, baseUrl, env);
459
+ break;
460
+ case 'get_payment_agreement':
461
+ resp = await getPaymentAgreement(args, baseUrl, env, bearer);
462
+ break;
463
+ case 'create_payment_agreement':
464
+ resp = await createPaymentAgreement(args, baseUrl, env, bearer);
465
+ break;
466
+ case 'initiate_payment':
467
+ resp = await initiatePayment(args, baseUrl, env, bearer, requestId);
468
+ break;
469
+ case 'get_payment_initiation':
470
+ resp = await getPaymentInitiation(args, baseUrl, env, bearer);
471
+ break;
472
+ default:
473
+ return {
474
+ status: 404,
475
+ body: { error: 'Unknown tool', message: `Tool "${toolName}" does not exist` },
476
+ toolName,
477
+ };
478
+ }
479
+ const parsed = await parseResponse(resp);
480
+ return { status: resp.status, body: parsed, toolName };
481
+ }
482
+ async function parseResponse(resp) {
483
+ try {
484
+ const text = await resp.text();
485
+ return text ? JSON.parse(text) : {};
486
+ }
487
+ catch {
488
+ return { error: 'Invalid response payload' };
489
+ }
490
+ }
491
+ function generateSummary(agreement) {
492
+ const parts = [];
493
+ parts.push(`Payment agreement ${agreement.payment_agreement_id} is ${agreement.status}`);
494
+ parts.push(`with maximum amount $${agreement.maximum_amount}`);
495
+ if (agreement.end_date) {
496
+ parts.push(`expires on ${agreement.end_date}`);
497
+ }
498
+ return parts.join(', ') + '.';
499
+ }
500
+ function corsHeaders(env) {
501
+ const origins = env.ALLOWED_ORIGINS.split(',');
502
+ const allowOrigin = origins.includes('*') ? '*' : origins[0];
503
+ return {
504
+ 'Content-Type': 'application/json',
505
+ 'Access-Control-Allow-Origin': allowOrigin,
506
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS, DELETE, PATCH',
507
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization',
508
+ 'Access-Control-Max-Age': '86400',
509
+ };
510
+ }
511
+ function corsResponse(env) {
512
+ return new Response(null, {
513
+ status: 204,
514
+ headers: corsHeaders(env),
515
+ });
516
+ }
517
+ function jsonResponse(data, status, env) {
518
+ return new Response(JSON.stringify(data, null, 2), {
519
+ status,
520
+ headers: corsHeaders(env),
521
+ });
522
+ }
523
+ function errorResponse(message, env) {
524
+ return jsonResponse({
525
+ error: 'Internal server error',
526
+ message,
527
+ }, 500, env);
528
+ }
529
+ async function handleToolsExecute(request, env, policy, authToken, requestId, startTime) {
530
+ let body;
531
+ try {
532
+ body = await request.json();
533
+ }
534
+ catch {
535
+ return withRequestId(jsonResponse({ error: 'Invalid JSON body', message: 'Provide a valid JSON payload' }, 400, env), requestId);
536
+ }
537
+ const { tool, arguments: args } = body || {};
538
+ if (!tool) {
539
+ return withRequestId(jsonResponse({ error: 'Invalid request', message: 'tool is required' }, 400, env), requestId);
540
+ }
541
+ const outcome = await runToolInvocation(tool, args, env, policy, authToken, requestId);
542
+ logStructured({
543
+ requestId,
544
+ clientId: policy.client_id,
545
+ toolName: tool,
546
+ statusCode: outcome.status,
547
+ latencyMs: Date.now() - startTime,
548
+ path: '/tools/execute',
549
+ environment: env.SHABAAS_ENVIRONMENT,
550
+ rejection: outcome.status >= 400 ? 'tool_error' : undefined,
551
+ });
552
+ return withRequestId(jsonResponse(outcome.body, outcome.status, env), requestId);
553
+ }
554
+ async function handleMcp(request, env, policy, authToken, requestId, startTime) {
555
+ await ensureMcpHandlersInitialized();
556
+ currentMcpContext = { policy, authToken, requestId, env };
557
+ try {
558
+ const mcpResponse = await sharedMcpTransport.handleRequest(request);
559
+ const headers = new Headers(mcpResponse.headers);
560
+ const cors = corsHeaders(env);
561
+ Object.entries(cors).forEach(([k, v]) => headers.set(k, v));
562
+ headers.set('X-Request-Id', requestId);
563
+ headers.set('X-Processing-Time', `${Date.now() - startTime}ms`);
564
+ return new Response(mcpResponse.body, { status: mcpResponse.status, headers });
565
+ }
566
+ finally {
567
+ currentMcpContext = null;
568
+ }
569
+ }
570
+ function forbiddenResponse(env) {
571
+ return jsonResponse({
572
+ error: 'Forbidden',
573
+ message: 'Access denied',
574
+ }, 403, env);
575
+ }
576
+ function normalizeBearer(token) {
577
+ return token.toLowerCase().startsWith('bearer ') ? token : `Bearer ${token}`;
578
+ }
579
+ function withRequestId(resp, requestId) {
580
+ const headers = new Headers(resp.headers);
581
+ headers.set('X-Request-Id', requestId);
582
+ return new Response(resp.body, { status: resp.status, headers });
583
+ }
584
+ function logStructured(entry) {
585
+ const line = JSON.stringify(entry);
586
+ if (entry.statusCode >= 400) {
587
+ console.error(line);
588
+ }
589
+ else {
590
+ console.log(line);
591
+ }
592
+ }
593
+ async function getShaBaasBearer(env, baseUrl, uuid) {
594
+ const maxAgeMinutes = Number(env.AUTH_TOKEN_MAX_AGE_MINUTES) || 50;
595
+ if (cachedBearer && cachedFetchedAt) {
596
+ const ageMs = Date.now() - cachedFetchedAt;
597
+ if (ageMs < maxAgeMinutes * 60 * 1000) {
598
+ return cachedBearer;
599
+ }
600
+ }
601
+ if (!uuid) {
602
+ throw new Error('Missing ShaBaas auth UUID');
603
+ }
604
+ const response = await fetch(`${baseUrl}/api/public/authorization`, {
605
+ method: 'POST',
606
+ headers: {
607
+ 'Authorization': uuid,
608
+ 'accept': 'application/json',
609
+ },
610
+ });
611
+ const data = await response.json();
612
+ if (!response.ok) {
613
+ throw new Error(data?.message || 'Failed to obtain bearer token');
614
+ }
615
+ const rawToken = data?.data?.token ||
616
+ data?.data?.access_token ||
617
+ data?.token ||
618
+ data?.access_token;
619
+ if (!rawToken || typeof rawToken !== 'string') {
620
+ throw new Error('Authorization succeeded but no token was returned.');
621
+ }
622
+ cachedBearer = normalizeBearer(rawToken);
623
+ cachedFetchedAt = Date.now();
624
+ return cachedBearer;
625
+ }
626
+ function buildAllowedToolsList(policy, env) {
627
+ const allTools = [
628
+ {
629
+ name: 'get_payment_agreement',
630
+ description: 'Retrieve payment agreement details with AI insights',
631
+ inputSchema: (0, zod_to_json_schema_1.zodToJsonSchema)(index_js_2.GetPaymentAgreementInputSchema),
632
+ },
633
+ {
634
+ name: 'create_payment_agreement',
635
+ description: 'Create a new payment agreement',
636
+ inputSchema: (0, zod_to_json_schema_1.zodToJsonSchema)(index_js_2.CreatePaymentAgreementInputSchema),
637
+ },
638
+ {
639
+ name: 'initiate_payment',
640
+ description: 'Initiate a payment against an agreement',
641
+ inputSchema: (0, zod_to_json_schema_1.zodToJsonSchema)(index_js_2.InitiatePaymentInputSchema),
642
+ },
643
+ {
644
+ name: 'get_payment_initiation',
645
+ description: 'Retrieve payment initiation by ID',
646
+ inputSchema: { type: 'object', properties: { payment_initiation_id: { type: 'string' } } },
647
+ },
648
+ ...(env.SHABAAS_ENVIRONMENT === 'production'
649
+ ? []
650
+ : [{
651
+ name: 'get_auth_token',
652
+ description: 'Get ShaBaas API authorization token',
653
+ inputSchema: { type: 'object', properties: { uuid: { type: 'string' } } },
654
+ }]),
655
+ ];
656
+ return allTools.filter(t => (0, policy_js_1.isToolAllowed)(policy, t.name));
657
+ }
658
+ function normalizeEnvName(name) {
659
+ if (!name)
660
+ return undefined;
661
+ const lower = name.toLowerCase();
662
+ if (lower === 'sandbox' || lower === 'staging')
663
+ return 'sandbox';
664
+ if (lower === 'production' || lower === 'prod')
665
+ return 'production';
666
+ return undefined;
667
+ }
668
+ function resolvePolicyTable(env) {
669
+ if (resolvedPolicyCache)
670
+ return resolvedPolicyCache;
671
+ if (!env.POLICY_TABLE_JSON) {
672
+ throw new Error('POLICY_TABLE_JSON is not set');
673
+ }
674
+ let parsed;
675
+ try {
676
+ parsed = JSON.parse(env.POLICY_TABLE_JSON);
677
+ }
678
+ catch {
679
+ throw new Error('POLICY_TABLE_JSON is invalid JSON');
680
+ }
681
+ const resolved = {};
682
+ for (const [clientId, policy] of Object.entries(parsed)) {
683
+ const secretName = policy.uuid_secret;
684
+ const environment = normalizeEnvName(policy.environment);
685
+ if (!secretName) {
686
+ throw new Error(`Missing uuid_secret for client ${clientId}`);
687
+ }
688
+ if (!environment) {
689
+ throw new Error(`Invalid environment for client ${clientId}`);
690
+ }
691
+ const uuid = env[secretName];
692
+ if (!uuid) {
693
+ throw new Error(`Missing secret ${secretName} for client ${clientId}`);
694
+ }
695
+ resolved[uuid] = {
696
+ client_id: clientId,
697
+ status: policy.status === 'active' ? 'active' : 'suspended',
698
+ allowed_tools: Array.isArray(policy.allowed_tools) ? policy.allowed_tools : [],
699
+ environment,
700
+ admin: !!policy.admin,
701
+ };
702
+ }
703
+ resolvedPolicyCache = resolved;
704
+ return resolved;
705
+ }
706
+ function lookupPolicyFromEnv(token, env) {
707
+ let map;
708
+ try {
709
+ map = resolvePolicyTable(env);
710
+ }
711
+ catch (err) {
712
+ return { policy: undefined, rejection: err.message || 'policy_load_failed' };
713
+ }
714
+ const policy = map[token];
715
+ if (!policy)
716
+ return { policy: undefined, rejection: 'unknown_client' };
717
+ if (policy.status !== 'active')
718
+ return { policy: undefined, rejection: 'suspended' };
719
+ const envNorm = normalizeEnvName(env.SHABAAS_ENVIRONMENT);
720
+ if (!envNorm || policy.environment !== envNorm) {
721
+ return { policy: undefined, rejection: 'environment_mismatch' };
722
+ }
723
+ return { policy, rejection: undefined };
724
+ }
725
+ async function ensureMcpHandlersInitialized() {
726
+ if (mcpHandlersInitialized)
727
+ return;
728
+ sharedMcpServer.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
729
+ if (!currentMcpContext) {
730
+ throw new Error('Unauthorized');
731
+ }
732
+ const { policy, env } = currentMcpContext;
733
+ return { tools: buildAllowedToolsList(policy, env) };
734
+ });
735
+ sharedMcpServer.setRequestHandler(types_js_1.CallToolRequestSchema, async (reqMessage) => {
736
+ if (!currentMcpContext) {
737
+ throw new Error('Unauthorized');
738
+ }
739
+ const { policy, authToken, requestId, env } = currentMcpContext;
740
+ const toolName = reqMessage.params.name;
741
+ const args = reqMessage.params.arguments ?? {};
742
+ const outcome = await runToolInvocation(toolName, args, env, policy, authToken, requestId);
743
+ logStructured({
744
+ requestId,
745
+ clientId: policy.client_id,
746
+ toolName,
747
+ statusCode: outcome.status,
748
+ latencyMs: 0,
749
+ path: '/mcp',
750
+ environment: env.SHABAAS_ENVIRONMENT,
751
+ rejection: outcome.status >= 400 ? 'tool_error' : undefined,
752
+ });
753
+ if (outcome.status >= 400) {
754
+ throw new Error(outcome.body?.message || 'Tool execution failed');
755
+ }
756
+ return {
757
+ content: [
758
+ {
759
+ type: 'text',
760
+ text: JSON.stringify(outcome.body, null, 2),
761
+ },
762
+ ],
763
+ };
764
+ });
765
+ await sharedMcpServer.connect(sharedMcpTransport);
766
+ mcpHandlersInitialized = true;
767
+ }