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
@@ -0,0 +1,524 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.HttpMcpServer = void 0;
7
+ const http_1 = __importDefault(require("http"));
8
+ const url_1 = require("url");
9
+ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
10
+ const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
11
+ const zod_to_json_schema_1 = require("zod-to-json-schema");
12
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
13
+ const client_js_1 = require("../api/client.js");
14
+ const index_js_2 = require("../tools/index.js");
15
+ const rate_limiter_js_1 = require("../security/rate-limiter.js");
16
+ const auth_js_1 = require("../security/auth.js");
17
+ const policy_js_1 = require("../security/policy.js");
18
+ class HttpMcpServer {
19
+ server;
20
+ tools;
21
+ rateLimiter;
22
+ config;
23
+ mcpServer;
24
+ mcpTransport;
25
+ constructor(config) {
26
+ this.config = config;
27
+ // Initialize API client
28
+ const apiClient = new client_js_1.ShabaasApiClient(config);
29
+ // Create tools
30
+ this.tools = (0, index_js_2.createAllTools)(apiClient, config);
31
+ // Create MCP server (Streamable HTTP transport for VS Code / MCP HTTP clients)
32
+ this.mcpServer = new index_js_1.Server({
33
+ name: 'shabaaspay-mcp',
34
+ version: '1.0.0',
35
+ }, {
36
+ capabilities: {
37
+ tools: {},
38
+ },
39
+ });
40
+ this.setupMcpHandlers();
41
+ this.mcpTransport = new streamableHttp_js_1.StreamableHTTPServerTransport();
42
+ // Initialize rate limiter
43
+ this.rateLimiter = new rate_limiter_js_1.RateLimiter(config.rateLimitPerMinute, config.rateLimitPerHour);
44
+ // Create HTTP server
45
+ this.server = http_1.default.createServer(this.handleRequest.bind(this));
46
+ }
47
+ async handleRequest(req, res) {
48
+ const startTime = Date.now();
49
+ const requestId = `req_${startTime}`;
50
+ try {
51
+ // Parse request
52
+ const parsedUrl = new url_1.URL(req.url || '', `http://${req.headers.host}`);
53
+ const path = parsedUrl.pathname;
54
+ // CORS preflight for any route (including /mcp)
55
+ if ((req.method || '').toUpperCase() === 'OPTIONS') {
56
+ this.sendResponse(res, this.corsResponse());
57
+ return;
58
+ }
59
+ // Dedicated MCP HTTP transport endpoint
60
+ if (path === '/mcp') {
61
+ await this.handleMcpTransport(req, res, startTime);
62
+ return;
63
+ }
64
+ const body = await this.parseBody(req);
65
+ const request = {
66
+ method: req.method || 'GET',
67
+ path,
68
+ headers: req.headers,
69
+ body,
70
+ requestId,
71
+ };
72
+ console.log(`[HTTP] ${request.method} ${request.path}`);
73
+ // Health check does not require auth
74
+ if (request.method === 'GET' && request.path === '/health') {
75
+ this.sendResponse(res, this.healthResponse());
76
+ return;
77
+ }
78
+ // Handle CORS preflight
79
+ if (request.method === 'OPTIONS') {
80
+ this.sendResponse(res, this.corsResponse());
81
+ return;
82
+ }
83
+ // Authenticate
84
+ const authResult = this.authenticate(request);
85
+ if (!authResult.success) {
86
+ this.sendResponse(res, authResult.response);
87
+ this.logStructured({
88
+ requestId,
89
+ clientId: undefined,
90
+ toolName: request.toolName,
91
+ statusCode: authResult.response?.statusCode || 401,
92
+ latencyMs: Date.now() - startTime,
93
+ rejection: 'auth_failed',
94
+ });
95
+ return;
96
+ }
97
+ request.clientId = authResult.clientId;
98
+ // Rate limiting
99
+ const clientId = this.getClientIdentifier(request);
100
+ const minuteLimit = this.rateLimiter.checkLimit(clientId, 'minute');
101
+ const hourLimit = this.rateLimiter.checkLimit(clientId, 'hour');
102
+ if (!minuteLimit.allowed) {
103
+ this.sendResponse(res, this.rateLimitResponse(minuteLimit));
104
+ return;
105
+ }
106
+ if (!hourLimit.allowed) {
107
+ this.sendResponse(res, this.rateLimitResponse(hourLimit));
108
+ return;
109
+ }
110
+ // Route request
111
+ const response = await this.routeRequest(request);
112
+ // Add rate limit headers
113
+ response.headers['X-RateLimit-Limit-Minute'] = minuteLimit.limit.toString();
114
+ response.headers['X-RateLimit-Remaining-Minute'] = minuteLimit.remaining.toString();
115
+ response.headers['X-RateLimit-Reset-Minute'] = minuteLimit.reset.toString();
116
+ response.headers['X-RateLimit-Limit-Hour'] = hourLimit.limit.toString();
117
+ response.headers['X-RateLimit-Remaining-Hour'] = hourLimit.remaining.toString();
118
+ response.headers['X-RateLimit-Reset-Hour'] = hourLimit.reset.toString();
119
+ // Add processing time
120
+ response.headers['X-Processing-Time'] = `${Date.now() - startTime}ms`;
121
+ this.sendResponse(res, response, requestId);
122
+ this.logStructured({
123
+ requestId,
124
+ clientId: request.clientId,
125
+ toolName: request.toolName,
126
+ statusCode: response.statusCode,
127
+ latencyMs: Date.now() - startTime,
128
+ });
129
+ }
130
+ catch (error) {
131
+ console.error('[HTTP] Error:', error);
132
+ this.sendResponse(res, this.errorResponse('Internal error'), requestId);
133
+ this.logStructured({
134
+ requestId,
135
+ clientId: undefined,
136
+ toolName: undefined,
137
+ statusCode: 500,
138
+ latencyMs: Date.now() - startTime,
139
+ rejection: 'internal_error',
140
+ });
141
+ }
142
+ }
143
+ healthResponse() {
144
+ return {
145
+ statusCode: 200,
146
+ headers: this.corsHeaders(),
147
+ body: {
148
+ status: 'healthy',
149
+ version: '1.0.0',
150
+ environment: this.config.environment,
151
+ timestamp: new Date().toISOString(),
152
+ },
153
+ };
154
+ }
155
+ async handleMcpTransport(req, res, startTime) {
156
+ console.log(`[HTTP][MCP] ${req.method} ${req.url}`);
157
+ // Always set CORS headers for MCP responses
158
+ const baseCors = this.corsHeaders();
159
+ Object.entries(baseCors).forEach(([k, v]) => res.setHeader(k, v));
160
+ // Auth
161
+ const authResult = this.authenticateHeaders(req.headers);
162
+ if (!authResult.success) {
163
+ this.sendResponse(res, authResult.response);
164
+ return;
165
+ }
166
+ // Rate limiting
167
+ const clientId = this.getClientIdentifierFromHeaders(req.headers);
168
+ const minuteLimit = this.rateLimiter.checkLimit(clientId, 'minute');
169
+ const hourLimit = this.rateLimiter.checkLimit(clientId, 'hour');
170
+ if (!minuteLimit.allowed) {
171
+ this.sendResponse(res, this.rateLimitResponse(minuteLimit));
172
+ return;
173
+ }
174
+ if (!hourLimit.allowed) {
175
+ this.sendResponse(res, this.rateLimitResponse(hourLimit));
176
+ return;
177
+ }
178
+ // Pass through to MCP transport
179
+ res.setHeader('X-RateLimit-Limit-Minute', minuteLimit.limit.toString());
180
+ res.setHeader('X-RateLimit-Remaining-Minute', minuteLimit.remaining.toString());
181
+ res.setHeader('X-RateLimit-Reset-Minute', minuteLimit.reset.toString());
182
+ res.setHeader('X-RateLimit-Limit-Hour', hourLimit.limit.toString());
183
+ res.setHeader('X-RateLimit-Remaining-Hour', hourLimit.remaining.toString());
184
+ res.setHeader('X-RateLimit-Reset-Hour', hourLimit.reset.toString());
185
+ res.setHeader('X-Processing-Time', `${Date.now() - startTime}ms`);
186
+ await this.mcpTransport.handleRequest(req, res);
187
+ }
188
+ async parseBody(req) {
189
+ return new Promise((resolve, reject) => {
190
+ let body = '';
191
+ req.on('data', chunk => (body += chunk.toString()));
192
+ req.on('end', () => {
193
+ try {
194
+ resolve(body ? JSON.parse(body) : undefined);
195
+ }
196
+ catch (error) {
197
+ reject(new Error('Invalid JSON body'));
198
+ }
199
+ });
200
+ req.on('error', reject);
201
+ });
202
+ }
203
+ authenticate(request) {
204
+ const token = (0, auth_js_1.extractBearerToken)(request.headers.authorization);
205
+ if (!token) {
206
+ return {
207
+ success: false,
208
+ response: {
209
+ statusCode: 401,
210
+ headers: this.corsHeaders(),
211
+ body: {
212
+ error: 'Authentication required',
213
+ message: 'Authorization header with UUID is required',
214
+ },
215
+ },
216
+ };
217
+ }
218
+ // Optional HTTP guard
219
+ if (this.config.mcpHttpApiKey && token !== this.config.mcpHttpApiKey) {
220
+ return {
221
+ success: false,
222
+ response: {
223
+ statusCode: 403,
224
+ headers: this.corsHeaders(),
225
+ body: {
226
+ error: 'Invalid API key',
227
+ message: 'The provided API key is not valid',
228
+ },
229
+ },
230
+ };
231
+ }
232
+ const policyResult = (0, policy_js_1.lookupClientPolicy)(token, this.config.environment);
233
+ if (!policyResult.policy) {
234
+ return {
235
+ success: false,
236
+ response: {
237
+ statusCode: 403,
238
+ headers: this.corsHeaders(),
239
+ body: {
240
+ error: 'Forbidden',
241
+ message: 'Access denied',
242
+ },
243
+ },
244
+ };
245
+ }
246
+ return { success: true, clientId: policyResult.policy.client_id };
247
+ }
248
+ authenticateHeaders(headers) {
249
+ const token = (0, auth_js_1.extractBearerToken)(headers.authorization);
250
+ if (!token) {
251
+ return {
252
+ success: false,
253
+ response: {
254
+ statusCode: 401,
255
+ headers: this.corsHeaders(),
256
+ body: {
257
+ error: 'Authentication required',
258
+ message: 'Authorization header with UUID is required',
259
+ },
260
+ },
261
+ };
262
+ }
263
+ if (this.config.mcpHttpApiKey && token !== this.config.mcpHttpApiKey) {
264
+ return {
265
+ success: false,
266
+ response: {
267
+ statusCode: 403,
268
+ headers: this.corsHeaders(),
269
+ body: {
270
+ error: 'Invalid API key',
271
+ message: 'The provided API key is not valid',
272
+ },
273
+ },
274
+ };
275
+ }
276
+ const policyResult = (0, policy_js_1.lookupClientPolicy)(token, this.config.environment);
277
+ if (!policyResult.policy) {
278
+ return {
279
+ success: false,
280
+ response: {
281
+ statusCode: 403,
282
+ headers: this.corsHeaders(),
283
+ body: {
284
+ error: 'Forbidden',
285
+ message: 'Access denied',
286
+ },
287
+ },
288
+ };
289
+ }
290
+ return { success: true, clientId: policyResult.policy.client_id };
291
+ }
292
+ getClientIdentifier(request) {
293
+ return this.getClientIdentifierFromHeaders(request.headers);
294
+ }
295
+ getClientIdentifierFromHeaders(headers) {
296
+ // Use IP address for rate limiting
297
+ return headers['x-forwarded-for']?.toString() ||
298
+ headers['x-real-ip']?.toString() ||
299
+ 'unknown';
300
+ }
301
+ async routeRequest(request) {
302
+ const { method, path, body } = request;
303
+ // Health check
304
+ if (path === '/health' && method === 'GET') {
305
+ return {
306
+ statusCode: 200,
307
+ headers: this.corsHeaders(),
308
+ body: {
309
+ status: 'healthy',
310
+ version: '1.0.0',
311
+ environment: this.config.environment,
312
+ timestamp: new Date().toISOString(),
313
+ },
314
+ };
315
+ }
316
+ // List tools
317
+ if (path === '/tools' && method === 'GET') {
318
+ const toolsList = Object.values(this.tools).map(tool => ({
319
+ name: tool.name,
320
+ description: tool.description,
321
+ inputSchema: tool.inputSchema,
322
+ }));
323
+ return {
324
+ statusCode: 200,
325
+ headers: this.corsHeaders(),
326
+ body: {
327
+ tools: toolsList,
328
+ },
329
+ };
330
+ }
331
+ // Execute tool
332
+ if (path === '/tools/execute' && method === 'POST') {
333
+ if (!body || !body.tool || !body.arguments) {
334
+ throw new Error('Request must include "tool" and "arguments" fields');
335
+ }
336
+ const { tool: toolName, arguments: args } = body;
337
+ request.toolName = toolName;
338
+ const tool = this.tools[toolName];
339
+ if (!tool) {
340
+ return {
341
+ statusCode: 404,
342
+ headers: this.corsHeaders(),
343
+ body: {
344
+ error: 'Tool not found',
345
+ message: `Tool "${toolName}" does not exist`,
346
+ },
347
+ };
348
+ }
349
+ // Tool allowlist check based on policy
350
+ const token = (0, auth_js_1.extractBearerToken)(request.headers.authorization);
351
+ if (!token) {
352
+ return {
353
+ statusCode: 401,
354
+ headers: this.corsHeaders(),
355
+ body: {
356
+ error: 'Authentication required',
357
+ message: 'Authorization header with UUID is required',
358
+ },
359
+ };
360
+ }
361
+ const policyResult = (0, policy_js_1.lookupClientPolicy)(token, this.config.environment);
362
+ if (!policyResult.policy || !(0, policy_js_1.isToolAllowed)(policyResult.policy, toolName)) {
363
+ return {
364
+ statusCode: 403,
365
+ headers: this.corsHeaders(),
366
+ body: {
367
+ error: 'Forbidden',
368
+ message: 'Tool is not permitted',
369
+ },
370
+ };
371
+ }
372
+ try {
373
+ const result = await tool.execute(args);
374
+ return {
375
+ statusCode: 200,
376
+ headers: this.corsHeaders(),
377
+ body: {
378
+ success: true,
379
+ result,
380
+ },
381
+ };
382
+ }
383
+ catch (error) {
384
+ return {
385
+ statusCode: 400,
386
+ headers: this.corsHeaders(),
387
+ body: {
388
+ error: 'Tool execution failed',
389
+ message: error.message,
390
+ },
391
+ };
392
+ }
393
+ }
394
+ // 404 - Not found
395
+ return {
396
+ statusCode: 404,
397
+ headers: this.corsHeaders(),
398
+ body: {
399
+ error: 'Not found',
400
+ message: `Endpoint ${method} ${path} does not exist`,
401
+ },
402
+ };
403
+ }
404
+ corsHeaders() {
405
+ const origins = this.config.allowedOrigins;
406
+ const allowOrigin = origins.includes('*') ? '*' : origins[0];
407
+ return {
408
+ 'Content-Type': 'application/json',
409
+ 'Access-Control-Allow-Origin': allowOrigin,
410
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
411
+ 'Access-Control-Allow-Headers': 'Content-Type, Authorization, MCP-Protocol-Version, MCP-Sequence-Id, MCP-Client-Id, X-Requested-With',
412
+ 'Access-Control-Max-Age': '86400',
413
+ };
414
+ }
415
+ corsResponse() {
416
+ return {
417
+ statusCode: 204,
418
+ headers: this.corsHeaders(),
419
+ body: null,
420
+ };
421
+ }
422
+ rateLimitResponse(limitInfo) {
423
+ return {
424
+ statusCode: 429,
425
+ headers: {
426
+ ...this.corsHeaders(),
427
+ 'Retry-After': Math.ceil((limitInfo.reset - Date.now()) / 1000).toString(),
428
+ 'X-RateLimit-Limit': limitInfo.limit.toString(),
429
+ 'X-RateLimit-Remaining': limitInfo.remaining.toString(),
430
+ 'X-RateLimit-Reset': limitInfo.reset.toString(),
431
+ },
432
+ body: {
433
+ error: 'Rate limit exceeded',
434
+ message: 'Too many requests. Please try again later.',
435
+ retry_after: Math.ceil((limitInfo.reset - Date.now()) / 1000),
436
+ },
437
+ };
438
+ }
439
+ errorResponse(message) {
440
+ return {
441
+ statusCode: 500,
442
+ headers: this.corsHeaders(),
443
+ body: {
444
+ error: 'Internal server error',
445
+ message,
446
+ },
447
+ };
448
+ }
449
+ sendResponse(res, response, requestId) {
450
+ if (requestId) {
451
+ res.setHeader('X-Request-Id', requestId);
452
+ }
453
+ res.writeHead(response.statusCode, response.headers);
454
+ res.end(response.body ? JSON.stringify(response.body, null, 2) : '');
455
+ }
456
+ logStructured(entry) {
457
+ console.error(JSON.stringify(entry));
458
+ }
459
+ setupMcpHandlers() {
460
+ // List available tools
461
+ this.mcpServer.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
462
+ const toolsList = Object.values(this.tools).map(tool => ({
463
+ name: tool.name,
464
+ description: tool.description,
465
+ inputSchema: (0, zod_to_json_schema_1.zodToJsonSchema)(tool.inputSchema),
466
+ }));
467
+ console.log(`[HTTP][MCP] Listing ${toolsList.length} tools`);
468
+ return {
469
+ tools: toolsList,
470
+ };
471
+ });
472
+ // Execute tool calls
473
+ this.mcpServer.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
474
+ const { name, arguments: args } = request.params;
475
+ console.log(`[HTTP][MCP] Executing tool: ${name}`);
476
+ console.log(`[HTTP][MCP] Arguments:`, JSON.stringify(args, null, 2));
477
+ const tool = this.tools[name];
478
+ if (!tool) {
479
+ throw new Error(`Unknown tool: ${name}`);
480
+ }
481
+ try {
482
+ const result = await tool.execute(args);
483
+ console.log(`[HTTP][MCP] Tool execution successful`);
484
+ return {
485
+ content: [
486
+ {
487
+ type: 'text',
488
+ text: JSON.stringify(result, null, 2),
489
+ },
490
+ ],
491
+ };
492
+ }
493
+ catch (error) {
494
+ console.error(`[HTTP][MCP] Tool execution failed:`, error.message);
495
+ throw new Error(`Tool execution failed: ${error.message}`);
496
+ }
497
+ });
498
+ }
499
+ async start() {
500
+ await this.mcpServer.connect(this.mcpTransport);
501
+ this.server.listen(this.config.httpPort, this.config.httpHost, () => {
502
+ console.log('[HTTP] ShaBaas Pay MCP Server started');
503
+ console.log(`[HTTP] Listening on http://${this.config.httpHost}:${this.config.httpPort}`);
504
+ console.log(`[HTTP] Environment: ${this.config.environment}`);
505
+ console.log(`[HTTP] Tools available: ${Object.keys(this.tools).length}`);
506
+ console.log('[HTTP] Endpoints:');
507
+ console.log(' - GET /health - Health check');
508
+ console.log(' - GET /tools - List available tools');
509
+ console.log(' - POST /tools/execute - Execute a tool');
510
+ console.log(' - POST/GET /mcp - MCP Streamable HTTP (VS Code / MCP HTTP clients)');
511
+ });
512
+ }
513
+ async stop() {
514
+ await this.mcpTransport.close();
515
+ await new Promise((resolve) => {
516
+ this.server.close(() => {
517
+ console.log('[HTTP] Server stopped');
518
+ resolve();
519
+ });
520
+ });
521
+ await this.mcpServer.close();
522
+ }
523
+ }
524
+ exports.HttpMcpServer = HttpMcpServer;
@@ -0,0 +1,13 @@
1
+ import { Config } from '../config/index.js';
2
+ export declare class StdioMcpServer {
3
+ private server;
4
+ private tools;
5
+ private rateLimiter;
6
+ private rateLimitClientId;
7
+ private config;
8
+ private readTools;
9
+ private writeTools;
10
+ constructor(config: Config);
11
+ private setupHandlers;
12
+ start(): Promise<void>;
13
+ }
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StdioMcpServer = void 0;
4
+ const zod_to_json_schema_1 = require("zod-to-json-schema");
5
+ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
6
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
7
+ const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
8
+ const client_js_1 = require("../api/client.js");
9
+ const index_js_2 = require("../tools/index.js");
10
+ const rate_limiter_js_1 = require("../security/rate-limiter.js");
11
+ const policy_js_1 = require("../security/policy.js");
12
+ class StdioMcpServer {
13
+ server;
14
+ tools;
15
+ rateLimiter;
16
+ rateLimitClientId = 'stdio-session';
17
+ config;
18
+ readTools = new Set(['get_payment_agreement', 'get_payment_initiation', 'get_auth_token']);
19
+ writeTools = new Set(['create_payment_agreement', 'initiate_payment']);
20
+ constructor(config) {
21
+ this.config = config;
22
+ // Initialize API client
23
+ const apiClient = new client_js_1.ShabaasApiClient(config);
24
+ // Create tools
25
+ this.tools = (0, index_js_2.createAllTools)(apiClient, config);
26
+ // Rate limiter (reuse HTTP settings for consistency)
27
+ this.rateLimiter = new rate_limiter_js_1.RateLimiter(config.rateLimitPerMinute, config.rateLimitPerHour);
28
+ // Create MCP server
29
+ this.server = new index_js_1.Server({
30
+ name: 'shabaaspay-mcp',
31
+ version: '1.0.0',
32
+ }, {
33
+ capabilities: {
34
+ tools: {},
35
+ },
36
+ });
37
+ this.setupHandlers();
38
+ }
39
+ setupHandlers() {
40
+ const self = this;
41
+ // List available tools
42
+ this.server.setRequestHandler(types_js_1.ListToolsRequestSchema, async () => {
43
+ const toolsList = Object.values(self.tools).map(tool => ({
44
+ name: tool.name,
45
+ description: tool.description,
46
+ inputSchema: (0, zod_to_json_schema_1.zodToJsonSchema)(tool.inputSchema),
47
+ }));
48
+ console.error(`[STDIO] Listing ${toolsList.length} tools`);
49
+ return {
50
+ tools: toolsList,
51
+ };
52
+ });
53
+ // Execute tool calls
54
+ this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
55
+ const { name, arguments: args } = request.params;
56
+ console.error(`[STDIO] Executing tool: ${name}`);
57
+ console.error(`[STDIO] Arguments:`, JSON.stringify(args, null, 2));
58
+ // Auth guard: require authorization field in args (UUID or configured key)
59
+ const authToken = args?.authorization;
60
+ if (!authToken) {
61
+ throw new Error('STDIO authorization failed: missing authorization');
62
+ }
63
+ if (self.config.mcpStdioApiKey && authToken !== self.config.mcpStdioApiKey) {
64
+ throw new Error('STDIO authorization failed: invalid authorization');
65
+ }
66
+ // Tool allowlist via policy
67
+ const policyResult = (0, policy_js_1.lookupClientPolicy)(authToken, self.config.environment);
68
+ if (!policyResult.policy) {
69
+ throw new Error('STDIO authorization failed: access denied');
70
+ }
71
+ if (!(0, policy_js_1.isToolAllowed)(policyResult.policy, name)) {
72
+ throw new Error('STDIO authorization failed: tool not permitted');
73
+ }
74
+ // Rate limiting per stdio session (no client context available)
75
+ const minuteLimit = self.rateLimiter.checkLimit(self.rateLimitClientId, 'minute');
76
+ const hourLimit = self.rateLimiter.checkLimit(self.rateLimitClientId, 'hour');
77
+ if (!minuteLimit.allowed || !hourLimit.allowed) {
78
+ const limitInfo = !minuteLimit.allowed ? minuteLimit : hourLimit;
79
+ throw new Error(`Rate limit exceeded (${limitInfo.limit} requests per ${!minuteLimit.allowed ? 'minute' : 'hour'}). ` +
80
+ `Retry after ${Math.ceil((limitInfo.reset - Date.now()) / 1000)} seconds.`);
81
+ }
82
+ const tool = self.tools[name];
83
+ if (!tool) {
84
+ throw new Error(`Unknown tool: ${name}`);
85
+ }
86
+ try {
87
+ const result = await tool.execute(args);
88
+ console.error(`[STDIO] Tool execution successful`);
89
+ return {
90
+ content: [
91
+ {
92
+ type: 'text',
93
+ text: JSON.stringify(result, null, 2),
94
+ },
95
+ ],
96
+ };
97
+ }
98
+ catch (error) {
99
+ console.error(`[STDIO] Tool execution failed:`, error.message);
100
+ throw new Error(`Tool execution failed`);
101
+ }
102
+ });
103
+ }
104
+ async start() {
105
+ console.error('[STDIO] Starting ShaBaas Pay MCP Server...');
106
+ console.error(`[STDIO] Environment: ${process.env.SHABAAS_ENVIRONMENT || 'sandbox'}`);
107
+ console.error(`[STDIO] Tools available: ${Object.keys(this.tools).length}`);
108
+ const transport = new stdio_js_1.StdioServerTransport();
109
+ await this.server.connect(transport);
110
+ console.error('[STDIO] Server started successfully');
111
+ console.error('[STDIO] Waiting for requests...');
112
+ }
113
+ }
114
+ exports.StdioMcpServer = StdioMcpServer;
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const index_js_1 = require("./config/index.js");
5
+ const http_server_js_1 = require("./server/http-server.js");
6
+ async function main() {
7
+ try {
8
+ // Load configuration
9
+ const config = (0, index_js_1.loadConfig)();
10
+ // Create and start server
11
+ const server = new http_server_js_1.HttpMcpServer(config);
12
+ await server.start();
13
+ // Handle shutdown
14
+ const shutdown = async () => {
15
+ console.log('\n[HTTP] Shutting down gracefully...');
16
+ await server.stop();
17
+ process.exit(0);
18
+ };
19
+ process.on('SIGINT', shutdown);
20
+ process.on('SIGTERM', shutdown);
21
+ }
22
+ catch (error) {
23
+ console.error('[HTTP] Fatal error:', error);
24
+ process.exit(1);
25
+ }
26
+ }
27
+ main();