shabaaspay-mcp-server 1.0.1 → 1.0.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.
Files changed (59) hide show
  1. package/LICENSE +21 -21
  2. package/dist/api/client.d.ts +25 -25
  3. package/dist/api/client.js +106 -82
  4. package/dist/api/mcp-auth.d.ts +18 -0
  5. package/dist/api/mcp-auth.js +64 -0
  6. package/dist/config/index.d.ts +8 -8
  7. package/dist/config/index.js +15 -18
  8. package/dist/constants/backend-urls.d.ts +8 -0
  9. package/dist/constants/backend-urls.js +11 -0
  10. package/dist/enricher/index.d.ts +2 -0
  11. package/dist/enricher/index.js +67 -0
  12. package/dist/index.js +12 -0
  13. package/dist/mcp-handler/auth.d.ts +15 -0
  14. package/dist/mcp-handler/auth.js +33 -0
  15. package/dist/mcp-handler/discovery.d.ts +19 -0
  16. package/dist/mcp-handler/discovery.js +96 -0
  17. package/dist/mcp-handler/mcp.d.ts +13 -0
  18. package/dist/mcp-handler/mcp.js +146 -0
  19. package/dist/mcp-handler/oauth-origin.d.ts +8 -0
  20. package/dist/mcp-handler/oauth-origin.js +24 -0
  21. package/dist/mcp-handler/oauth-page-assets.d.ts +3 -0
  22. package/dist/mcp-handler/oauth-page-assets.js +163 -0
  23. package/dist/mcp-handler/proxy.d.ts +9 -0
  24. package/dist/mcp-handler/proxy.js +28 -0
  25. package/dist/mcp-handler/server-card.d.ts +2 -0
  26. package/dist/mcp-handler/server-card.js +112 -0
  27. package/dist/mcp-handler/shabaas-api-client.d.ts +28 -0
  28. package/dist/mcp-handler/shabaas-api-client.js +109 -0
  29. package/dist/mcp-handler/tools/implementations.d.ts +17 -0
  30. package/dist/mcp-handler/tools/implementations.js +306 -0
  31. package/dist/mcp-handler/tools/index.d.ts +17 -0
  32. package/dist/mcp-handler/tools/index.js +103 -0
  33. package/dist/mcp-handler/types.d.ts +15 -0
  34. package/dist/mcp-handler/types.js +8 -0
  35. package/dist/mcp-handler/utils.d.ts +46 -0
  36. package/dist/mcp-handler/utils.js +126 -0
  37. package/dist/mcp-handler/worker.d.ts +11 -0
  38. package/dist/mcp-handler/worker.js +577 -0
  39. package/dist/security/auth.js +5 -4
  40. package/dist/security/policy.d.ts +0 -10
  41. package/dist/security/policy.js +0 -29
  42. package/dist/server/http-server.js +40 -27
  43. package/dist/server/stdio-server.d.ts +0 -2
  44. package/dist/server/stdio-server.js +14 -12
  45. package/dist/tools/auth.d.ts +2 -1
  46. package/dist/tools/auth.js +3 -3
  47. package/dist/tools/index.d.ts +43 -11
  48. package/dist/tools/payment-agreements.d.ts +34 -5
  49. package/dist/tools/payment-agreements.js +10 -5
  50. package/dist/tools/payment-initiations.d.ts +9 -6
  51. package/dist/tools/payment-initiations.js +15 -74
  52. package/dist/tools/response-helpers.d.ts +4 -0
  53. package/dist/types/index.d.ts +39 -6
  54. package/dist/types/index.js +27 -6
  55. package/package.json +15 -7
  56. package/readme.md +149 -105
  57. package/server.json +31 -0
  58. package/dist/worker.d.ts +0 -15
  59. package/dist/worker.js +0 -767
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 ShaBaas
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ShaBaas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,29 +1,26 @@
1
1
  import { Config } from '../config/index.js';
2
2
  import { ApiResponse } from '../types/index.js';
3
+ /** Options for API calls that need backend auth; requestApiKey comes from the request (no .env). */
4
+ export type RequestAuthOptions = {
5
+ requestUuid?: string;
6
+ };
3
7
  export declare class ShabaasApiClient {
4
8
  private client;
5
9
  private config;
6
- private bearerToken;
7
- private bearerTokenFetchedAtMs;
8
10
  constructor(config: Config);
9
11
  private normalizeBearer;
10
12
  /**
11
- * Exchanges the configured UUID for a Bearer token.
12
- * UUID must be sent only to /api/public/authorization in the Authorization header.
13
- * The returned token is used for all subsequent API calls.
13
+ * Returns a bearer token for backend API calls. Use the API key from the request (Authorization header).
14
+ * No .env or global API key; requestUuid must be provided by the caller (value is the API key from the authenticated request).
15
+ * The backend accepts API key only.
14
16
  */
15
- authorize(): Promise<{
16
- token: string;
17
- raw: any;
18
- }>;
19
- private tokenLooksFresh;
20
- ensureAuthenticated(): Promise<void>;
17
+ getTokenForRequest(requestUuid?: string): Promise<string>;
21
18
  private withAuthRetry;
22
- getAuthToken(): Promise<{
19
+ getAuthToken(options?: RequestAuthOptions): Promise<{
23
20
  token: string;
24
21
  fetchedAt: string;
25
22
  }>;
26
- getPaymentAgreement(id: string): Promise<ApiResponse>;
23
+ getPaymentAgreement(id: string, options?: RequestAuthOptions): Promise<ApiResponse>;
27
24
  createPaymentAgreement(data: {
28
25
  name: string;
29
26
  type: string;
@@ -31,30 +28,33 @@ export declare class ShabaasApiClient {
31
28
  frequency: string;
32
29
  number_of_transactions_permitted: number;
33
30
  pay_id: string;
34
- idempotency_key: string;
35
- }): Promise<ApiResponse>;
36
- cancelPaymentAgreement(id: string): Promise<ApiResponse>;
37
- resendPaymentAgreement(id: string): Promise<ApiResponse>;
31
+ phone_number?: string;
32
+ bsb?: number;
33
+ account_number?: number;
34
+ agreement_type?: string;
35
+ start_date?: string;
36
+ end_date?: string;
37
+ }, options?: RequestAuthOptions): Promise<ApiResponse>;
38
+ cancelPaymentAgreement(id: string, options?: RequestAuthOptions): Promise<ApiResponse>;
39
+ resendPaymentAgreement(id: string, options?: RequestAuthOptions): Promise<ApiResponse>;
38
40
  initiatePayment(data: {
39
41
  payment_agreement_id: string;
40
42
  amount: string | number;
41
43
  description?: string;
42
- idempotency_key: string;
43
- }): Promise<ApiResponse>;
44
- getPaymentInitiation(id: string): Promise<ApiResponse>;
44
+ }, options?: RequestAuthOptions): Promise<ApiResponse>;
45
+ getPaymentInitiation(id: string, options?: RequestAuthOptions): Promise<ApiResponse>;
45
46
  initiateDirectDebit(data: {
46
47
  payment_agreement_id: string;
47
48
  amount: string | number;
48
49
  description?: string;
49
- idempotency_key: string;
50
- }): Promise<ApiResponse>;
50
+ }, options?: RequestAuthOptions): Promise<ApiResponse>;
51
51
  getPaymentLink(data: {
52
52
  amount: string;
53
53
  reference?: string;
54
- }): Promise<ApiResponse>;
54
+ }, options?: RequestAuthOptions): Promise<ApiResponse>;
55
55
  registerWebhook(data: {
56
56
  url: string;
57
57
  events: string[];
58
- }): Promise<ApiResponse>;
59
- listWebhooks(): Promise<ApiResponse>;
58
+ }, options?: RequestAuthOptions): Promise<ApiResponse>;
59
+ listWebhooks(options?: RequestAuthOptions): Promise<ApiResponse>;
60
60
  }
@@ -6,11 +6,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.ShabaasApiClient = void 0;
7
7
  const axios_1 = __importDefault(require("axios"));
8
8
  const index_js_1 = require("../config/index.js");
9
+ /** Cache bearer token per API key so we don't refetch every request. */
10
+ const tokenCache = new Map();
11
+ const CACHE_TTL_MS = 50 * 60 * 1000; // 50 min
9
12
  class ShabaasApiClient {
10
13
  client;
11
14
  config;
12
- bearerToken = null;
13
- bearerTokenFetchedAtMs = null;
14
15
  constructor(config) {
15
16
  this.config = config;
16
17
  const baseURL = (0, index_js_1.getApiUrl)(config);
@@ -18,12 +19,17 @@ class ShabaasApiClient {
18
19
  baseURL,
19
20
  headers: {
20
21
  'Content-Type': 'application/json',
22
+ // Identify requests as coming from the MCP server so the backend
23
+ 'X-Shabaas-Client': 'mcp',
21
24
  },
22
25
  timeout: 30000,
23
26
  });
24
27
  this.client.interceptors.request.use((cfg) => {
25
28
  // Log to stderr to keep STDIO transport stdout clean for MCP JSON
26
29
  console.error(`[API Request] ${cfg.method?.toUpperCase()} ${cfg.url}`);
30
+ if (process.env.MCP_DEBUG === '1' || process.env.SHABAAS_DEBUG_HEADERS === '1') {
31
+ console.error(`[API Request] X-Shabaas-Client: ${cfg.headers['X-Shabaas-Client'] ?? '(not set)'}`);
32
+ }
27
33
  return cfg;
28
34
  }, (error) => {
29
35
  console.error('[API Request Error]', error);
@@ -49,14 +55,24 @@ class ShabaasApiClient {
49
55
  : `Bearer ${trimmed}`;
50
56
  }
51
57
  /**
52
- * Exchanges the configured UUID for a Bearer token.
53
- * UUID must be sent only to /api/public/authorization in the Authorization header.
54
- * The returned token is used for all subsequent API calls.
58
+ * Returns a bearer token for backend API calls. Use the API key from the request (Authorization header).
59
+ * No .env or global API key; requestUuid must be provided by the caller (value is the API key from the authenticated request).
60
+ * The backend accepts API key only.
55
61
  */
56
- async authorize() {
62
+ async getTokenForRequest(requestUuid) {
63
+ const apiKey = (requestUuid ?? this.config.shabaasAuthUuid ?? '').trim();
64
+ if (!apiKey) {
65
+ throw new Error('Backend API auth requires the API key. Send it in the Authorization header (Bearer <your_api_key>) when calling the MCP server.');
66
+ }
67
+ const cacheKey = apiKey;
68
+ const cached = tokenCache.get(cacheKey);
69
+ if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
70
+ return cached.token;
71
+ }
72
+ const authHeader = apiKey.toLowerCase().startsWith('bearer ') ? apiKey : `Bearer ${apiKey}`;
57
73
  const response = await this.client.post('/api/public/authorization', null, {
58
74
  headers: {
59
- Authorization: this.config.shabaasAuthUuid,
75
+ Authorization: authHeader,
60
76
  accept: 'application/json',
61
77
  },
62
78
  });
@@ -71,81 +87,81 @@ class ShabaasApiClient {
71
87
  throw new Error('Authorization succeeded but no token was returned. Expected token in response at data.token.');
72
88
  }
73
89
  const token = this.normalizeBearer(rawToken);
74
- this.bearerToken = token;
75
- this.bearerTokenFetchedAtMs = Date.now();
76
- this.client.defaults.headers.common['Authorization'] = token;
77
- return { token, raw: payload };
78
- }
79
- tokenLooksFresh() {
80
- if (!this.bearerToken || !this.bearerTokenFetchedAtMs)
81
- return false;
82
- const ageMs = Date.now() - this.bearerTokenFetchedAtMs;
83
- const maxAgeMs = this.config.authTokenMaxAgeMinutes * 60 * 1000;
84
- return ageMs < maxAgeMs;
85
- }
86
- async ensureAuthenticated() {
87
- if (this.tokenLooksFresh())
88
- return;
89
- await this.authorize();
90
- }
91
- async withAuthRetry(fn) {
90
+ tokenCache.set(cacheKey, { token, fetchedAt: Date.now() });
91
+ return token;
92
+ }
93
+ async withAuthRetry(fn, requestUuid) {
92
94
  try {
93
- await this.ensureAuthenticated();
94
- return await fn();
95
+ const token = await this.getTokenForRequest(requestUuid);
96
+ return await fn(token);
95
97
  }
96
98
  catch (err) {
97
99
  const status = err?.response?.status;
98
- if (status === 401 || status === 403) {
99
- await this.authorize();
100
- return await fn();
100
+ if ((status === 401 || status === 403) && requestUuid) {
101
+ tokenCache.delete((requestUuid ?? '').trim());
102
+ const token = await this.getTokenForRequest(requestUuid);
103
+ return await fn(token);
101
104
  }
102
105
  throw err;
103
106
  }
104
107
  }
105
- async getAuthToken() {
106
- const { token } = await this.authorize();
108
+ async getAuthToken(options) {
109
+ const token = await this.getTokenForRequest(options?.requestUuid);
107
110
  return { token, fetchedAt: new Date().toISOString() };
108
111
  }
109
- async getPaymentAgreement(id) {
110
- return await this.withAuthRetry(async () => {
111
- const response = await this.client.get(`/api/public/payment_agreement?id=${encodeURIComponent(id)}`);
112
+ async getPaymentAgreement(id, options) {
113
+ return await this.withAuthRetry(async (token) => {
114
+ const response = await this.client.get(`/api/public/payment_agreement?id=${encodeURIComponent(id)}`, { headers: { Authorization: token } });
112
115
  return response.data;
113
- });
114
- }
115
- async createPaymentAgreement(data) {
116
- return await this.withAuthRetry(async () => {
117
- const body = {
118
- payment_agreement: {
119
- name: data.name,
120
- type: data.type,
121
- maximum_amount: data.maximum_amount,
122
- frequency: data.frequency,
123
- number_of_transactions_permitted: data.number_of_transactions_permitted,
124
- pay_id: data.pay_id,
125
- },
116
+ }, options?.requestUuid);
117
+ }
118
+ async createPaymentAgreement(data, options) {
119
+ return await this.withAuthRetry(async (token) => {
120
+ const pa = {
121
+ name: data.name,
122
+ type: data.type,
123
+ maximum_amount: data.maximum_amount,
124
+ frequency: data.frequency,
125
+ number_of_transactions_permitted: data.number_of_transactions_permitted,
126
+ pay_id: data.pay_id,
126
127
  };
128
+ if (data.phone_number != null)
129
+ pa.phone_number = data.phone_number;
130
+ if (data.bsb != null)
131
+ pa.bsb = data.bsb;
132
+ if (data.account_number != null)
133
+ pa.account_number = data.account_number;
134
+ if (data.agreement_type != null)
135
+ pa.agreement_type = data.agreement_type;
136
+ if (data.start_date != null)
137
+ pa.start_date = data.start_date;
138
+ if (data.end_date != null)
139
+ pa.end_date = data.end_date;
140
+ const body = { payment_agreement: pa };
127
141
  const response = await this.client.post('/api/public/payment_agreement', body, {
128
142
  headers: {
129
- 'Idempotency-Key': data.idempotency_key,
143
+ Authorization: token,
130
144
  },
131
145
  });
132
146
  return response.data;
133
- });
147
+ }, options?.requestUuid);
134
148
  }
135
- async cancelPaymentAgreement(id) {
136
- return await this.withAuthRetry(async () => {
137
- const response = await this.client.delete(`/api/public/payment_agreement/cancel?id=${encodeURIComponent(id)}`);
149
+ async cancelPaymentAgreement(id, options) {
150
+ return await this.withAuthRetry(async (token) => {
151
+ const response = await this.client.delete(`/api/public/payment_agreement/cancel?id=${encodeURIComponent(id)}`, { headers: { Authorization: token } });
138
152
  return response.data;
139
- });
153
+ }, options?.requestUuid);
140
154
  }
141
- async resendPaymentAgreement(id) {
142
- return await this.withAuthRetry(async () => {
143
- const response = await this.client.patch('/api/public/payment_agreement/resend', { id });
155
+ async resendPaymentAgreement(id, options) {
156
+ return await this.withAuthRetry(async (token) => {
157
+ const response = await this.client.patch('/api/public/payment_agreement/resend', { id }, {
158
+ headers: { Authorization: token },
159
+ });
144
160
  return response.data;
145
- });
161
+ }, options?.requestUuid);
146
162
  }
147
- async initiatePayment(data) {
148
- return await this.withAuthRetry(async () => {
163
+ async initiatePayment(data, options) {
164
+ return await this.withAuthRetry(async (token) => {
149
165
  const amount = normalizeAmount(data.amount);
150
166
  const response = await this.client.post('/api/public/payment_initiation', {
151
167
  payment_initiation: {
@@ -155,20 +171,22 @@ class ShabaasApiClient {
155
171
  },
156
172
  }, {
157
173
  headers: {
158
- 'Idempotency-Key': data.idempotency_key,
174
+ Authorization: token,
159
175
  },
176
+ // Backend may call Monoova and can take up to Rack timeout (60s); use 65s so we get Rails response
177
+ timeout: 65000,
160
178
  });
161
179
  return response.data;
162
- });
180
+ }, options?.requestUuid);
163
181
  }
164
- async getPaymentInitiation(id) {
165
- return await this.withAuthRetry(async () => {
166
- const response = await this.client.get(`/api/public/payment_initiation?id=${encodeURIComponent(id)}`);
182
+ async getPaymentInitiation(id, options) {
183
+ return await this.withAuthRetry(async (token) => {
184
+ const response = await this.client.get(`/api/public/payment_initiation?id=${encodeURIComponent(id)}`, { headers: { Authorization: token } });
167
185
  return response.data;
168
- });
186
+ }, options?.requestUuid);
169
187
  }
170
- async initiateDirectDebit(data) {
171
- return await this.withAuthRetry(async () => {
188
+ async initiateDirectDebit(data, options) {
189
+ return await this.withAuthRetry(async (token) => {
172
190
  const amount = normalizeAmount(data.amount);
173
191
  const response = await this.client.post('/api/public/payment_initiation/direct_debit', {
174
192
  payment_initiation: {
@@ -178,29 +196,35 @@ class ShabaasApiClient {
178
196
  },
179
197
  }, {
180
198
  headers: {
181
- 'Idempotency-Key': data.idempotency_key,
199
+ Authorization: token,
182
200
  },
183
201
  });
184
202
  return response.data;
185
- });
203
+ }, options?.requestUuid);
186
204
  }
187
- async getPaymentLink(data) {
188
- return await this.withAuthRetry(async () => {
189
- const response = await this.client.post('/api/get_url', data);
205
+ async getPaymentLink(data, options) {
206
+ return await this.withAuthRetry(async (token) => {
207
+ const response = await this.client.post('/api/get_url', data, {
208
+ headers: { Authorization: token },
209
+ });
190
210
  return response.data;
191
- });
211
+ }, options?.requestUuid);
192
212
  }
193
- async registerWebhook(data) {
194
- return await this.withAuthRetry(async () => {
195
- const response = await this.client.post('/api/public/webhooks', data);
213
+ async registerWebhook(data, options) {
214
+ return await this.withAuthRetry(async (token) => {
215
+ const response = await this.client.post('/api/public/webhooks', data, {
216
+ headers: { Authorization: token },
217
+ });
196
218
  return response.data;
197
- });
219
+ }, options?.requestUuid);
198
220
  }
199
- async listWebhooks() {
200
- return await this.withAuthRetry(async () => {
201
- const response = await this.client.get('/api/public/webhooks');
221
+ async listWebhooks(options) {
222
+ return await this.withAuthRetry(async (token) => {
223
+ const response = await this.client.get('/api/public/webhooks', {
224
+ headers: { Authorization: token },
225
+ });
202
226
  return response.data;
203
- });
227
+ }, options?.requestUuid);
204
228
  }
205
229
  }
206
230
  exports.ShabaasApiClient = ShabaasApiClient;
@@ -0,0 +1,18 @@
1
+ import type { ClientPolicy } from '../security/policy.js';
2
+ export type FetchPolicyResult = {
3
+ policy: ClientPolicy;
4
+ rejection?: undefined;
5
+ } | {
6
+ policy?: undefined;
7
+ rejection: string;
8
+ };
9
+ /**
10
+ * Returns policy for the given token, using in-memory cache when ttlMs > 0.
11
+ * On cache miss or when ttlMs <= 0, calls the backend. Only successful results are cached.
12
+ */
13
+ export declare function getPolicyWithCache(token: string, baseUrl: string, environment: 'sandbox' | 'production', ttlMs: number): Promise<FetchPolicyResult>;
14
+ /**
15
+ * Calls backend GET /api/mcp/auth with Bearer token (API key); returns policy or rejection.
16
+ * The backend looks up the user by API key only.
17
+ */
18
+ export declare function fetchPolicyFromBackend(token: string, baseUrl: string, environment: 'sandbox' | 'production'): Promise<FetchPolicyResult>;
@@ -0,0 +1,64 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getPolicyWithCache = getPolicyWithCache;
4
+ exports.fetchPolicyFromBackend = fetchPolicyFromBackend;
5
+ const policyCache = new Map();
6
+ function policyCacheKey(token, baseUrl, environment) {
7
+ return `${token.trim()}\u0000${baseUrl}\u0000${environment}`;
8
+ }
9
+ /**
10
+ * Returns policy for the given token, using in-memory cache when ttlMs > 0.
11
+ * On cache miss or when ttlMs <= 0, calls the backend. Only successful results are cached.
12
+ */
13
+ async function getPolicyWithCache(token, baseUrl, environment, ttlMs) {
14
+ if (ttlMs <= 0) {
15
+ return fetchPolicyFromBackend(token, baseUrl, environment);
16
+ }
17
+ const key = policyCacheKey(token, baseUrl, environment);
18
+ const cached = policyCache.get(key);
19
+ if (cached) {
20
+ if (cached.expiresAt > Date.now()) {
21
+ return { policy: cached.policy };
22
+ }
23
+ policyCache.delete(key);
24
+ }
25
+ const result = await fetchPolicyFromBackend(token, baseUrl, environment);
26
+ if (result.policy) {
27
+ policyCache.set(key, {
28
+ policy: result.policy,
29
+ expiresAt: Date.now() + ttlMs,
30
+ });
31
+ }
32
+ return result;
33
+ }
34
+ /**
35
+ * Calls backend GET /api/mcp/auth with Bearer token (API key); returns policy or rejection.
36
+ * The backend looks up the user by API key only.
37
+ */
38
+ async function fetchPolicyFromBackend(token, baseUrl, environment) {
39
+ const url = `${baseUrl.replace(/\/$/, '')}/api/mcp/auth`;
40
+ try {
41
+ const response = await fetch(url, {
42
+ method: 'GET',
43
+ headers: { Authorization: `Bearer ${token}` },
44
+ });
45
+ if (!response.ok) {
46
+ return { rejection: response.status === 401 ? 'unknown_client' : 'policy_load_failed' };
47
+ }
48
+ const data = (await response.json());
49
+ if (!data || !Array.isArray(data.allowed_tools)) {
50
+ return { rejection: 'invalid_response' };
51
+ }
52
+ const policy = {
53
+ client_id: token,
54
+ status: 'active',
55
+ allowed_tools: data.allowed_tools,
56
+ environment,
57
+ admin: false,
58
+ };
59
+ return { policy };
60
+ }
61
+ catch {
62
+ return { rejection: 'policy_load_failed' };
63
+ }
64
+ }
@@ -1,16 +1,13 @@
1
1
  import { z } from 'zod';
2
2
  /**
3
3
  * Configuration notes
4
- * - ShaBaas API auth uses a UUID that must ONLY be sent to /api/public/authorization to obtain a Bearer token.
5
- * - The Bearer token returned by that endpoint is used for all subsequent ShaBaas API calls.
6
- *
7
- * Env var compatibility:
8
- * - Prefer SHABAAS_AUTH_UUID
9
- * - SHABAAS_API_KEY is accepted as a backwards compatible alias for SHABAAS_AUTH_UUID
4
+ * - Backend URLs are derived from environment in code (canonical URLs); no need to set in env/config.
5
+ * - Override only when needed (e.g. local dev: SHABAAS_SANDBOX_URL=http://localhost:3000).
6
+ * - Auth uses the API key from each request (Authorization header).
10
7
  */
11
8
  declare const ConfigSchema: z.ZodObject<{
12
9
  environment: z.ZodDefault<z.ZodEnum<["sandbox", "production"]>>;
13
- shabaasAuthUuid: z.ZodString;
10
+ shabaasAuthUuid: z.ZodDefault<z.ZodOptional<z.ZodString>>;
14
11
  sandboxUrl: z.ZodDefault<z.ZodString>;
15
12
  productionUrl: z.ZodDefault<z.ZodString>;
16
13
  httpPort: z.ZodDefault<z.ZodNumber>;
@@ -21,6 +18,7 @@ declare const ConfigSchema: z.ZodObject<{
21
18
  rateLimitPerMinute: z.ZodDefault<z.ZodNumber>;
22
19
  rateLimitPerHour: z.ZodDefault<z.ZodNumber>;
23
20
  authTokenMaxAgeMinutes: z.ZodDefault<z.ZodNumber>;
21
+ policyCacheTtlMs: z.ZodDefault<z.ZodNumber>;
24
22
  }, "strip", z.ZodTypeAny, {
25
23
  environment: "sandbox" | "production";
26
24
  shabaasAuthUuid: string;
@@ -34,9 +32,10 @@ declare const ConfigSchema: z.ZodObject<{
34
32
  rateLimitPerMinute: number;
35
33
  rateLimitPerHour: number;
36
34
  authTokenMaxAgeMinutes: number;
35
+ policyCacheTtlMs: number;
37
36
  }, {
38
- shabaasAuthUuid: string;
39
37
  environment?: "sandbox" | "production" | undefined;
38
+ shabaasAuthUuid?: string | undefined;
40
39
  sandboxUrl?: string | undefined;
41
40
  productionUrl?: string | undefined;
42
41
  httpPort?: number | undefined;
@@ -47,6 +46,7 @@ declare const ConfigSchema: z.ZodObject<{
47
46
  rateLimitPerMinute?: number | undefined;
48
47
  rateLimitPerHour?: number | undefined;
49
48
  authTokenMaxAgeMinutes?: number | undefined;
49
+ policyCacheTtlMs?: number | undefined;
50
50
  }>;
51
51
  export type Config = z.infer<typeof ConfigSchema>;
52
52
  export declare function loadConfig(): Config;
@@ -1,31 +1,25 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.loadConfig = loadConfig;
7
4
  exports.getApiUrl = getApiUrl;
8
- const dotenv_1 = __importDefault(require("dotenv"));
9
5
  const zod_1 = require("zod");
10
- dotenv_1.default.config();
6
+ const backend_urls_js_1 = require("../constants/backend-urls.js");
7
+ // No .env file is loaded; use process.env (e.g. from mcp.json or shell) or defaults.
11
8
  /**
12
9
  * Configuration notes
13
- * - ShaBaas API auth uses a UUID that must ONLY be sent to /api/public/authorization to obtain a Bearer token.
14
- * - The Bearer token returned by that endpoint is used for all subsequent ShaBaas API calls.
15
- *
16
- * Env var compatibility:
17
- * - Prefer SHABAAS_AUTH_UUID
18
- * - SHABAAS_API_KEY is accepted as a backwards compatible alias for SHABAAS_AUTH_UUID
10
+ * - Backend URLs are derived from environment in code (canonical URLs); no need to set in env/config.
11
+ * - Override only when needed (e.g. local dev: SHABAAS_SANDBOX_URL=http://localhost:3000).
12
+ * - Auth uses the API key from each request (Authorization header).
19
13
  */
20
14
  const ConfigSchema = zod_1.z.object({
21
15
  environment: zod_1.z.enum(['sandbox', 'production']).default('sandbox'),
22
- // ShaBaas API authentication
23
- shabaasAuthUuid: zod_1.z.string().min(1, 'ShaBaas auth UUID is required'),
24
- // Base URLs (override via env; defaults are placeholders only)
25
- sandboxUrl: zod_1.z.string().url().default('https://sandbox.example.com'),
26
- productionUrl: zod_1.z.string().url().default('https://api.example.com'),
27
- // HTTP mode configuration (optional)
28
- httpPort: zod_1.z.coerce.number().default(3000),
16
+ // Optional fallback when request does not provide API key (e.g. legacy); normally leave empty. Value is treated as API key.
17
+ shabaasAuthUuid: zod_1.z.string().optional().default(''),
18
+ // Backend API base URLs: default to canonical URLs from code; override via env only for local/dev
19
+ sandboxUrl: zod_1.z.string().url().default(backend_urls_js_1.CANONICAL_SANDBOX_BASE_URL),
20
+ productionUrl: zod_1.z.string().url().default(backend_urls_js_1.CANONICAL_PRODUCTION_BASE_URL),
21
+ // HTTP mode: MCP server port (default 3001 so it does not conflict with backend on 3000)
22
+ httpPort: zod_1.z.coerce.number().default(3001),
29
23
  httpHost: zod_1.z.string().default('0.0.0.0'),
30
24
  // If set, HTTP mode requires: Authorization: Bearer <mcpHttpApiKey>
31
25
  mcpHttpApiKey: zod_1.z.string().optional().default(''),
@@ -38,6 +32,8 @@ const ConfigSchema = zod_1.z.object({
38
32
  // Token cache settings
39
33
  // Used only as a heuristic if the auth response does not include expiry metadata
40
34
  authTokenMaxAgeMinutes: zod_1.z.coerce.number().default(50),
35
+ // Policy cache: TTL in ms for in-memory MCP policy cache. If 0, policy is not cached and backend is called every time.
36
+ policyCacheTtlMs: zod_1.z.coerce.number().default(300_000),
41
37
  });
42
38
  function loadConfig() {
43
39
  try {
@@ -57,6 +53,7 @@ function loadConfig() {
57
53
  rateLimitPerMinute: process.env.API_RATE_LIMIT_PER_MINUTE,
58
54
  rateLimitPerHour: process.env.API_RATE_LIMIT_PER_HOUR,
59
55
  authTokenMaxAgeMinutes: process.env.AUTH_TOKEN_MAX_AGE_MINUTES,
56
+ policyCacheTtlMs: process.env.MCP_POLICY_CACHE_TTL_MS || process.env.POLICY_CACHE_TTL_MS,
60
57
  });
61
58
  }
62
59
  catch (error) {
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Canonical backend base URLs by environment (code-only; single source of truth).
3
+ * Used when SHABAAS_SANDBOX_URL / SHABAAS_PRODUCTION_URL are not set in env,
4
+ * so the backend URL does not need to be exposed in config or Cloudflare vars.
5
+ * Override via env only when needed (e.g. local dev: SHABAAS_SANDBOX_URL=http://localhost:3000).
6
+ */
7
+ export declare const CANONICAL_SANDBOX_BASE_URL = "https://dev-api.shabaas.com";
8
+ export declare const CANONICAL_PRODUCTION_BASE_URL = "https://api.shabaas.com";
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CANONICAL_PRODUCTION_BASE_URL = exports.CANONICAL_SANDBOX_BASE_URL = void 0;
4
+ /**
5
+ * Canonical backend base URLs by environment (code-only; single source of truth).
6
+ * Used when SHABAAS_SANDBOX_URL / SHABAAS_PRODUCTION_URL are not set in env,
7
+ * so the backend URL does not need to be exposed in config or Cloudflare vars.
8
+ * Override via env only when needed (e.g. local dev: SHABAAS_SANDBOX_URL=http://localhost:3000).
9
+ */
10
+ exports.CANONICAL_SANDBOX_BASE_URL = 'https://dev-api.shabaas.com';
11
+ exports.CANONICAL_PRODUCTION_BASE_URL = 'https://api.shabaas.com';
@@ -1,2 +1,4 @@
1
1
  import { PaymentAgreement, StandardResponse } from "../types/index.js";
2
2
  export declare function enrichPaymentAgreement(data: PaymentAgreement, includeRaw: boolean, rawResponse: any, environment: "sandbox" | "production"): StandardResponse<PaymentAgreement>;
3
+ /** Shared enricher for payment initiation (get_payment_initiation and initiate_payment). */
4
+ export declare function enrichPaymentInitiation(data: any, raw: any, processingTime: number, environment: "sandbox" | "production", mode: "initiate" | "retrieve"): StandardResponse<any>;