@akropolys/mcp 1.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/test.js ADDED
@@ -0,0 +1,656 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+ var __copyProps = (to, from, except, desc) => {
17
+ if (from && typeof from === "object" || typeof from === "function") {
18
+ for (let key of __getOwnPropNames(from))
19
+ if (!__hasOwnProp.call(to, key) && key !== except)
20
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
21
+ }
22
+ return to;
23
+ };
24
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
25
+ // If the importer is in node compatibility mode or this is not an ESM
26
+ // file that has been converted to a CommonJS file using a Babel-
27
+ // compatible transform (i.e. "__esModule" has not been set), then set
28
+ // "default" to the CommonJS "module.exports" for node compatibility.
29
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
30
+ mod
31
+ ));
32
+
33
+ // src/vault.ts
34
+ var import_crypto, LocalAESVault;
35
+ var init_vault = __esm({
36
+ "src/vault.ts"() {
37
+ "use strict";
38
+ import_crypto = __toESM(require("crypto"));
39
+ LocalAESVault = class {
40
+ masterKey;
41
+ constructor(masterKeyEnvVar = "AKROPOLYS_KMS_MASTER_KEY") {
42
+ const keyStr = process.env[masterKeyEnvVar];
43
+ if (!keyStr) {
44
+ throw new Error(`Master key environment variable ${masterKeyEnvVar} is not defined`);
45
+ }
46
+ this.masterKey = import_crypto.default.createHash("sha256").update(keyStr).digest();
47
+ }
48
+ async encrypt(plaintext) {
49
+ const iv = import_crypto.default.randomBytes(12);
50
+ const cipher = import_crypto.default.createCipheriv("aes-256-gcm", this.masterKey, iv);
51
+ let encrypted = cipher.update(plaintext, "utf8", "hex");
52
+ encrypted += cipher.final("hex");
53
+ const authTag = cipher.getAuthTag().toString("hex");
54
+ return `${iv.toString("hex")}:${authTag}:${encrypted}`;
55
+ }
56
+ async decrypt(ciphertext) {
57
+ const parts = ciphertext.split(":");
58
+ if (parts.length !== 3) {
59
+ throw new Error("Invalid ciphertext format");
60
+ }
61
+ const iv = Buffer.from(parts[0], "hex");
62
+ const authTag = Buffer.from(parts[1], "hex");
63
+ const encryptedData = parts[2];
64
+ const decipher = import_crypto.default.createDecipheriv("aes-256-gcm", this.masterKey, iv);
65
+ decipher.setAuthTag(authTag);
66
+ let decrypted = decipher.update(encryptedData, "hex", "utf8");
67
+ decrypted += decipher.final("utf8");
68
+ return decrypted;
69
+ }
70
+ };
71
+ }
72
+ });
73
+
74
+ // src/db.ts
75
+ function getPool() {
76
+ if (_pool) return _pool;
77
+ const dbUrl = process.env.DATABASE_URL;
78
+ if (!dbUrl) {
79
+ throw new Error("DATABASE_URL environment variable is not defined");
80
+ }
81
+ _pool = new Pool({
82
+ connectionString: dbUrl,
83
+ max: 5,
84
+ idleTimeoutMillis: 3e4,
85
+ connectionTimeoutMillis: 5e3,
86
+ ssl: {
87
+ // Enforce TLS certificate validation.
88
+ // Set PGSSLROOTCERT or NODE_EXTRA_CA_CERTS if using a self-signed cert.
89
+ rejectUnauthorized: true
90
+ }
91
+ });
92
+ _pool.on("error", (err) => {
93
+ console.error("[db] idle client error:", err.message);
94
+ });
95
+ return _pool;
96
+ }
97
+ async function fetchPropertyAndTools(propertyId) {
98
+ const pool = getPool();
99
+ const propRes = await pool.query(
100
+ "SELECT id, site_id, name, api_base, auth_type, auth_token, allow_agent_access FROM developer_properties WHERE id = $1",
101
+ [propertyId]
102
+ );
103
+ if (propRes.rows.length === 0) {
104
+ throw new Error(`Property config with ID "${propertyId}" not found in database`);
105
+ }
106
+ const toolsRes = await pool.query(
107
+ "SELECT id, property_id, name, description, method, path, parameters, response_schema, response_mapping FROM developer_tools WHERE property_id = $1",
108
+ [propertyId]
109
+ );
110
+ return {
111
+ property: propRes.rows[0],
112
+ tools: toolsRes.rows
113
+ };
114
+ }
115
+ async function closePool() {
116
+ if (_pool) {
117
+ await _pool.end();
118
+ _pool = null;
119
+ }
120
+ }
121
+ var import_pg, Pool, _pool;
122
+ var init_db = __esm({
123
+ "src/db.ts"() {
124
+ "use strict";
125
+ import_pg = __toESM(require("pg"));
126
+ ({ Pool } = import_pg.default);
127
+ _pool = null;
128
+ }
129
+ });
130
+
131
+ // src/cache.ts
132
+ async function getCachedConfig(propertyId) {
133
+ if (!redisClient) {
134
+ return null;
135
+ }
136
+ try {
137
+ const cached = await redisClient.get(`mcp:config:${propertyId}`);
138
+ if (cached) {
139
+ return JSON.parse(cached);
140
+ }
141
+ } catch (err) {
142
+ console.error("Redis cache get error:", err);
143
+ }
144
+ return null;
145
+ }
146
+ async function setCachedConfig(propertyId, config) {
147
+ if (!redisClient) {
148
+ return;
149
+ }
150
+ try {
151
+ await redisClient.set(`mcp:config:${propertyId}`, JSON.stringify(config), {
152
+ EX: 3600
153
+ // 1 hour TTL
154
+ });
155
+ } catch (err) {
156
+ console.error("Redis cache set error:", err);
157
+ }
158
+ }
159
+ var import_redis, redisUrl, redisClient;
160
+ var init_cache = __esm({
161
+ "src/cache.ts"() {
162
+ "use strict";
163
+ import_redis = require("redis");
164
+ redisUrl = process.env.UPSTASH_REDIS_URL;
165
+ redisClient = redisUrl ? (0, import_redis.createClient)({ url: redisUrl }) : null;
166
+ if (redisClient) {
167
+ redisClient.connect().catch((err) => {
168
+ console.error("Redis connection error:", err);
169
+ });
170
+ }
171
+ }
172
+ });
173
+
174
+ // src/ssrfValidator.ts
175
+ function isPrivateIp(ip) {
176
+ const ipv4MappedMatch = ip.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/i);
177
+ const normalizedIp = ipv4MappedMatch ? ipv4MappedMatch[1] : ip;
178
+ if (/^\d+\.\d+\.\d+\.\d+$/.test(normalizedIp)) {
179
+ const parts = normalizedIp.split(".").map(Number);
180
+ if (parts.some(isNaN) || parts.length !== 4) return true;
181
+ if (parts[0] === 127) return true;
182
+ if (parts[0] === 10) return true;
183
+ if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) return true;
184
+ if (parts[0] === 192 && parts[1] === 168) return true;
185
+ if (parts[0] === 169 && parts[1] === 254) return true;
186
+ if (parts[0] === 0) return true;
187
+ return false;
188
+ }
189
+ if (normalizedIp === "::1" || normalizedIp === "::") return true;
190
+ if (/^fe[89ab]/i.test(normalizedIp)) return true;
191
+ if (/^f[cd]/i.test(normalizedIp)) return true;
192
+ return false;
193
+ }
194
+ async function assertSafeUrl(urlStr) {
195
+ const url = new URL(urlStr);
196
+ const hostname = url.hostname;
197
+ const res = await lookup(hostname);
198
+ if (isPrivateIp(res.address)) {
199
+ throw new Error(`SSRF Prevention: outbound requests to private IP address ${res.address} (resolved from "${hostname}") are prohibited`);
200
+ }
201
+ const safeUrl = new URL(urlStr);
202
+ safeUrl.hostname = res.address;
203
+ return {
204
+ safeUrl: safeUrl.toString(),
205
+ hostHeader: hostname
206
+ // caller must forward this as the Host header
207
+ };
208
+ }
209
+ var import_dns, import_util, lookup;
210
+ var init_ssrfValidator = __esm({
211
+ "src/ssrfValidator.ts"() {
212
+ "use strict";
213
+ import_dns = __toESM(require("dns"));
214
+ import_util = require("util");
215
+ lookup = (0, import_util.promisify)(import_dns.default.lookup);
216
+ }
217
+ });
218
+
219
+ // src/rateLimiter.ts
220
+ var McpRateLimiter;
221
+ var init_rateLimiter = __esm({
222
+ "src/rateLimiter.ts"() {
223
+ "use strict";
224
+ McpRateLimiter = class {
225
+ client;
226
+ limit;
227
+ windowSeconds;
228
+ constructor(redisClient2, limit = 100, windowSeconds = 60) {
229
+ this.client = redisClient2;
230
+ this.limit = limit;
231
+ this.windowSeconds = windowSeconds;
232
+ }
233
+ async assertAllowed(propertyId) {
234
+ if (!this.client) {
235
+ throw new Error("Rate limiting is not configured (Redis unavailable). Request denied.");
236
+ }
237
+ const key = `mcp:ratelimit:${propertyId}`;
238
+ const current = await this.client.incr(key);
239
+ if (current === 1) {
240
+ await this.client.expire(key, this.windowSeconds);
241
+ }
242
+ if (current > this.limit) {
243
+ throw new Error(
244
+ `Rate limit exceeded: property "${propertyId}" has exceeded the limit of ${this.limit} requests per ${this.windowSeconds} seconds.`
245
+ );
246
+ }
247
+ }
248
+ };
249
+ }
250
+ });
251
+
252
+ // src/index.ts
253
+ var index_exports = {};
254
+ __export(index_exports, {
255
+ applyResponseMapping: () => applyResponseMapping,
256
+ cleanParameterSchema: () => cleanParameterSchema,
257
+ getNestedValue: () => getNestedValue,
258
+ isPrivateIp: () => isPrivateIp,
259
+ server: () => server,
260
+ validateUrlForSSRF: () => assertSafeUrl
261
+ });
262
+ async function getConfig() {
263
+ const propertyId = process.env.AKROPOLYS_PROPERTY_ID;
264
+ if (!propertyId) {
265
+ throw new Error("AKROPOLYS_PROPERTY_ID environment variable is required");
266
+ }
267
+ const cached = await getCachedConfig(propertyId);
268
+ if (cached) {
269
+ return cached;
270
+ }
271
+ const config = await fetchPropertyAndTools(propertyId);
272
+ await setCachedConfig(propertyId, config);
273
+ return config;
274
+ }
275
+ async function getDecryptedToken(token) {
276
+ if (!token) return null;
277
+ if (!vault) return token;
278
+ try {
279
+ return await vault.decrypt(token);
280
+ } catch (err) {
281
+ return token;
282
+ }
283
+ }
284
+ function getNestedValue(obj, path) {
285
+ if (!obj) return void 0;
286
+ const parts = path.split(".");
287
+ let current = obj;
288
+ for (const part of parts) {
289
+ if (current === null || current === void 0) return void 0;
290
+ const arrayMatch = part.match(/^(\w+)\[(\d+)\]$/);
291
+ if (arrayMatch) {
292
+ const key = arrayMatch[1];
293
+ const index = parseInt(arrayMatch[2], 10);
294
+ current = current[key];
295
+ if (Array.isArray(current)) {
296
+ current = current[index];
297
+ } else {
298
+ return void 0;
299
+ }
300
+ } else {
301
+ if (Array.isArray(current)) {
302
+ current = current.map((item) => item ? item[part] : void 0);
303
+ } else {
304
+ current = current[part];
305
+ }
306
+ }
307
+ }
308
+ return current;
309
+ }
310
+ function applyResponseMapping(payload, mapping) {
311
+ if (!payload) return payload;
312
+ if (Array.isArray(payload)) {
313
+ return payload.map((item) => applyResponseMapping(item, mapping));
314
+ }
315
+ const mappedResult = {};
316
+ let hasMappedAny = false;
317
+ for (const [targetKey, sourcePath] of Object.entries(mapping)) {
318
+ if (typeof sourcePath === "string") {
319
+ const val = getNestedValue(payload, sourcePath);
320
+ if (val !== void 0) {
321
+ mappedResult[targetKey] = val;
322
+ hasMappedAny = true;
323
+ }
324
+ }
325
+ }
326
+ return hasMappedAny ? mappedResult : payload;
327
+ }
328
+ function cleanParameterSchema(params) {
329
+ if (!params || typeof params !== "object") {
330
+ return { type: "object", properties: {} };
331
+ }
332
+ const cleaned = JSON.parse(JSON.stringify(params));
333
+ if (cleaned.properties && typeof cleaned.properties === "object") {
334
+ for (const key of Object.keys(cleaned.properties)) {
335
+ const prop = cleaned.properties[key];
336
+ if (prop && typeof prop === "object") {
337
+ delete prop.location;
338
+ }
339
+ }
340
+ }
341
+ return cleaned;
342
+ }
343
+ async function main() {
344
+ const propertyId = process.env.AKROPOLYS_PROPERTY_ID;
345
+ if (!propertyId) {
346
+ console.error("\u274C ERROR: AKROPOLYS_PROPERTY_ID environment variable is required");
347
+ process.exit(1);
348
+ }
349
+ const transport = new import_stdio.StdioServerTransport();
350
+ await server.connect(transport);
351
+ console.error(`\u2713 Akropolys MCP Proxy Server running for property: ${propertyId}`);
352
+ }
353
+ var import_dotenv, import_server, import_stdio, import_types, rateLimiter, vault, server;
354
+ var init_index = __esm({
355
+ "src/index.ts"() {
356
+ "use strict";
357
+ import_dotenv = __toESM(require("dotenv"));
358
+ import_server = require("@modelcontextprotocol/sdk/server/index.js");
359
+ import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
360
+ import_types = require("@modelcontextprotocol/sdk/types.js");
361
+ init_db();
362
+ init_cache();
363
+ init_vault();
364
+ init_ssrfValidator();
365
+ init_rateLimiter();
366
+ init_ssrfValidator();
367
+ rateLimiter = new McpRateLimiter(redisClient);
368
+ import_dotenv.default.config();
369
+ vault = null;
370
+ try {
371
+ vault = new LocalAESVault();
372
+ } catch (err) {
373
+ console.warn(`\u26A0\uFE0F WARNING: KMS Vault failed to initialize: ${err.message}. Raw tokens will be used.`);
374
+ }
375
+ server = new import_server.Server(
376
+ {
377
+ name: "akropolys-mcp",
378
+ version: "1.2.3"
379
+ },
380
+ {
381
+ capabilities: {
382
+ tools: {}
383
+ }
384
+ }
385
+ );
386
+ server.setRequestHandler(import_types.ListToolsRequestSchema, async () => {
387
+ try {
388
+ const { tools } = await getConfig();
389
+ return {
390
+ tools: tools.map((t) => ({
391
+ name: t.name,
392
+ description: t.description,
393
+ inputSchema: cleanParameterSchema(t.parameters)
394
+ }))
395
+ };
396
+ } catch (err) {
397
+ console.error("Error listing tools:", err);
398
+ return {
399
+ tools: []
400
+ };
401
+ }
402
+ });
403
+ server.setRequestHandler(import_types.CallToolRequestSchema, async (request) => {
404
+ const { name, arguments: args } = request.params;
405
+ const argumentsObj = args || {};
406
+ try {
407
+ const { property, tools } = await getConfig();
408
+ if (!property.allow_agent_access) {
409
+ throw new Error(`Agent access is not enabled for property "${property.id}"`);
410
+ }
411
+ const tool = tools.find((t) => t.name === name);
412
+ if (!tool) {
413
+ throw new Error(`Tool "${name}" not found`);
414
+ }
415
+ const decryptedAuthToken = await getDecryptedToken(property.auth_token);
416
+ let resolvedPath = tool.path;
417
+ const queryParams = {};
418
+ const headers = {};
419
+ const bodyParams = {};
420
+ if (property.auth_type === "bearer" && decryptedAuthToken) {
421
+ headers["Authorization"] = `Bearer ${decryptedAuthToken}`;
422
+ } else if (property.auth_type === "api_key" && decryptedAuthToken) {
423
+ headers["X-API-Key"] = decryptedAuthToken;
424
+ headers["X-Akropolys-Token"] = decryptedAuthToken;
425
+ headers["Authorization"] = decryptedAuthToken;
426
+ }
427
+ const paramDefs = tool.parameters?.properties || {};
428
+ const requiredParams = tool.parameters?.required || [];
429
+ for (const reqField of requiredParams) {
430
+ if (argumentsObj[reqField] === void 0) {
431
+ throw new Error(`Missing required parameter: "${reqField}"`);
432
+ }
433
+ }
434
+ for (const [key, val] of Object.entries(argumentsObj)) {
435
+ const def = paramDefs[key] || {};
436
+ const location = def.location || "body";
437
+ if (location === "path") {
438
+ resolvedPath = resolvedPath.replace(new RegExp(`:${key}`, "g"), String(val)).replace(new RegExp(`{${key}}`, "g"), String(val));
439
+ } else if (location === "query") {
440
+ queryParams[key] = String(val);
441
+ } else if (location === "header") {
442
+ headers[key] = String(val);
443
+ } else if (location === "body") {
444
+ bodyParams[key] = val;
445
+ }
446
+ }
447
+ const baseUrl = property.api_base.replace(/\/+$/, "");
448
+ const urlPath = resolvedPath.replace(/^\/+/, "");
449
+ let fullUrl = `${baseUrl}/${urlPath}`;
450
+ if (Object.keys(queryParams).length > 0) {
451
+ const qs = new URLSearchParams(queryParams).toString();
452
+ fullUrl += `?${qs}`;
453
+ }
454
+ const requestOptions = {
455
+ method: tool.method.toUpperCase(),
456
+ headers
457
+ };
458
+ if (["POST", "PUT", "PATCH"].includes(tool.method.toUpperCase())) {
459
+ if (!headers["Content-Type"]) {
460
+ headers["Content-Type"] = "application/json";
461
+ }
462
+ requestOptions.body = JSON.stringify(bodyParams);
463
+ }
464
+ await rateLimiter.assertAllowed(property.id);
465
+ const { safeUrl, hostHeader } = await assertSafeUrl(fullUrl);
466
+ headers["Host"] = hostHeader;
467
+ const response = await fetch(safeUrl, requestOptions);
468
+ const text = await response.text();
469
+ let jsonPayload;
470
+ try {
471
+ jsonPayload = JSON.parse(text);
472
+ } catch {
473
+ jsonPayload = { responseText: text };
474
+ }
475
+ if (!response.ok) {
476
+ return {
477
+ content: [
478
+ {
479
+ type: "text",
480
+ text: `API request failed with status ${response.status}: ${JSON.stringify(jsonPayload)}`
481
+ }
482
+ ],
483
+ isError: true
484
+ };
485
+ }
486
+ let normalized = jsonPayload;
487
+ if (tool.response_mapping && typeof tool.response_mapping === "object" && Object.keys(tool.response_mapping).length > 0) {
488
+ normalized = applyResponseMapping(jsonPayload, tool.response_mapping);
489
+ }
490
+ return {
491
+ content: [
492
+ {
493
+ type: "text",
494
+ text: JSON.stringify(normalized, null, 2)
495
+ }
496
+ ]
497
+ };
498
+ } catch (err) {
499
+ console.error(`Error executing tool "${name}":`, err);
500
+ return {
501
+ content: [
502
+ {
503
+ type: "text",
504
+ text: `Error: ${err.message}`
505
+ }
506
+ ],
507
+ isError: true
508
+ };
509
+ }
510
+ });
511
+ if (process.env.NODE_ENV !== "test") {
512
+ const shutdown = async (signal) => {
513
+ console.error(`[mcp] ${signal} received \u2014 shutting down`);
514
+ await closePool();
515
+ process.exit(0);
516
+ };
517
+ process.on("SIGINT", () => shutdown("SIGINT"));
518
+ process.on("SIGTERM", () => shutdown("SIGTERM"));
519
+ main().catch((err) => {
520
+ console.error("Fatal error in MCP server:", err);
521
+ process.exit(1);
522
+ });
523
+ }
524
+ }
525
+ });
526
+
527
+ // src/test.ts
528
+ var import_assert = __toESM(require("assert"));
529
+ init_vault();
530
+ process.env.NODE_ENV = "test";
531
+ async function testKMSVault() {
532
+ console.log("\u{1F9EA} Testing KMS Vault...");
533
+ const key = "test-kms-secret-master-key-longer-value";
534
+ process.env.AKROPOLYS_KMS_MASTER_KEY = key;
535
+ const vault2 = new LocalAESVault();
536
+ const plaintext = "super-secret-api-token-12345";
537
+ const ciphertext = await vault2.encrypt(plaintext);
538
+ import_assert.default.ok(ciphertext.includes(":"), "Ciphertext must contain colon delimiters");
539
+ const decrypted = await vault2.decrypt(ciphertext);
540
+ import_assert.default.strictEqual(decrypted, plaintext, "Decrypted text must match plaintext");
541
+ console.log("\u2705 KMS Vault passed.");
542
+ }
543
+ function testResponseMapping(getNestedValue2, applyResponseMapping2) {
544
+ console.log("\u{1F9EA} Testing Response Mapping...");
545
+ const payload = {
546
+ data: {
547
+ product: {
548
+ id: "123",
549
+ title: "Vivo X300",
550
+ pricing: { amount: 169999 },
551
+ tags: ["smartphone", "android"]
552
+ },
553
+ items: [
554
+ { name: "Item A", value: 10 },
555
+ { name: "Item B", value: 20 }
556
+ ]
557
+ }
558
+ };
559
+ import_assert.default.strictEqual(getNestedValue2(payload, "data.product.id"), "123");
560
+ import_assert.default.strictEqual(getNestedValue2(payload, "data.product.pricing.amount"), 169999);
561
+ import_assert.default.strictEqual(getNestedValue2(payload, "data.product.nonexistent"), void 0);
562
+ const itemNames = getNestedValue2(payload, "data.items.name");
563
+ import_assert.default.deepStrictEqual(itemNames, ["Item A", "Item B"]);
564
+ const mapping = {
565
+ productId: "data.product.id",
566
+ name: "data.product.title",
567
+ price: "data.product.pricing.amount",
568
+ tagsList: "data.product.tags",
569
+ itemNames: "data.items.name"
570
+ };
571
+ const normalized = applyResponseMapping2(payload, mapping);
572
+ import_assert.default.deepStrictEqual(normalized, {
573
+ productId: "123",
574
+ name: "Vivo X300",
575
+ price: 169999,
576
+ tagsList: ["smartphone", "android"],
577
+ itemNames: ["Item A", "Item B"]
578
+ });
579
+ console.log("\u2705 Response Mapping passed.");
580
+ }
581
+ function testParameterCleaning(cleanParameterSchema2) {
582
+ console.log("\u{1F9EA} Testing Parameter Cleaning...");
583
+ const devParams = {
584
+ type: "object",
585
+ properties: {
586
+ sku: {
587
+ type: "string",
588
+ location: "path",
589
+ description: "Stock keeping unit identifier"
590
+ },
591
+ quantity: {
592
+ type: "integer",
593
+ location: "query",
594
+ description: "Quantity to purchase"
595
+ }
596
+ },
597
+ required: ["sku"]
598
+ };
599
+ const cleaned = cleanParameterSchema2(devParams);
600
+ import_assert.default.deepStrictEqual(cleaned, {
601
+ type: "object",
602
+ properties: {
603
+ sku: {
604
+ type: "string",
605
+ description: "Stock keeping unit identifier"
606
+ },
607
+ quantity: {
608
+ type: "integer",
609
+ description: "Quantity to purchase"
610
+ }
611
+ },
612
+ required: ["sku"]
613
+ });
614
+ console.log("\u2705 Parameter Cleaning passed.");
615
+ }
616
+ async function testSSRFProtection(isPrivateIp2, validateUrlForSSRF) {
617
+ console.log("\u{1F9EA} Testing SSRF Protection...");
618
+ import_assert.default.ok(isPrivateIp2("127.0.0.1"), "Localhost should be classified as private");
619
+ import_assert.default.ok(isPrivateIp2("10.0.0.1"), "RFC 1918 10.x should be classified as private");
620
+ import_assert.default.ok(isPrivateIp2("172.16.0.1"), "RFC 1918 172.16.x should be classified as private");
621
+ import_assert.default.ok(isPrivateIp2("192.168.1.100"), "RFC 1918 192.168.x should be classified as private");
622
+ import_assert.default.ok(isPrivateIp2("169.254.169.254"), "AWS Metadata service IP should be classified as private");
623
+ import_assert.default.ok(isPrivateIp2("::1"), "IPv6 loopback should be classified as private");
624
+ import_assert.default.ok(isPrivateIp2("::"), "IPv6 unspecified should be classified as private");
625
+ import_assert.default.ok(!isPrivateIp2("8.8.8.8"), "Google DNS IP should be public");
626
+ import_assert.default.ok(!isPrivateIp2("1.1.1.1"), "Cloudflare DNS IP should be public");
627
+ try {
628
+ await validateUrlForSSRF("http://127.0.0.1:8080/admin");
629
+ import_assert.default.fail("Should block loopback URLs");
630
+ } catch (err) {
631
+ import_assert.default.ok(err.message.includes("SSRF Prevention"), "Error message must mention SSRF Prevention");
632
+ }
633
+ try {
634
+ await validateUrlForSSRF("http://169.254.169.254/latest/meta-data");
635
+ import_assert.default.fail("Should block AWS metadata service URLs");
636
+ } catch (err) {
637
+ import_assert.default.ok(err.message.includes("SSRF Prevention"), "Error message must mention SSRF Prevention");
638
+ }
639
+ await validateUrlForSSRF("https://httpbin.org/anything");
640
+ await validateUrlForSSRF("https://api.github.com");
641
+ console.log("\u2705 SSRF Protection passed.");
642
+ }
643
+ async function runAll() {
644
+ try {
645
+ await testKMSVault();
646
+ const { getNestedValue: getNestedValue2, applyResponseMapping: applyResponseMapping2, cleanParameterSchema: cleanParameterSchema2, isPrivateIp: isPrivateIp2, validateUrlForSSRF } = await Promise.resolve().then(() => (init_index(), index_exports));
647
+ testResponseMapping(getNestedValue2, applyResponseMapping2);
648
+ testParameterCleaning(cleanParameterSchema2);
649
+ await testSSRFProtection(isPrivateIp2, validateUrlForSSRF);
650
+ console.log("\u{1F389} ALL TESTS PASSED SUCCESSFULLY!");
651
+ } catch (err) {
652
+ console.error("\u274C TEST FAILURE:", err);
653
+ process.exit(1);
654
+ }
655
+ }
656
+ runAll();
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@akropolys/mcp",
3
+ "version": "1.5.3",
4
+ "description": "Akropolys MCP Server — Omni-vertical dynamic proxy server",
5
+ "license": "MIT",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "akropolys-mcp": "./dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsup",
12
+ "dev": "tsup --watch",
13
+ "start": "node dist/index.js"
14
+ },
15
+ "dependencies": {
16
+ "@modelcontextprotocol/sdk": "^0.6.0",
17
+ "pg": "^8.11.3",
18
+ "redis": "^4.6.13",
19
+ "dotenv": "^16.4.5"
20
+ },
21
+ "devDependencies": {
22
+ "@types/node": "^20.0.0",
23
+ "@types/pg": "^8.11.2",
24
+ "tsup": "^8.0.0",
25
+ "typescript": "^5.0.0"
26
+ }
27
+ }