@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.
@@ -0,0 +1,652 @@
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-e2e.ts
528
+ var import_dotenv2 = __toESM(require("dotenv"));
529
+ var import_assert = __toESM(require("assert"));
530
+ var import_pg2 = require("pg");
531
+ init_vault();
532
+ process.env.NODE_ENV = "test";
533
+ import_dotenv2.default.config();
534
+ async function runE2E() {
535
+ console.log("\u{1F9EA} Starting E2E Integration Test...");
536
+ const dbUrl = process.env.DATABASE_URL;
537
+ if (!dbUrl) {
538
+ console.error("\u26A0\uFE0F Skipping E2E test: DATABASE_URL not set");
539
+ return;
540
+ }
541
+ const kmsKey = "e2e-kms-secret-master-key";
542
+ process.env.AKROPOLYS_KMS_MASTER_KEY = kmsKey;
543
+ process.env.AKROPOLYS_PROPERTY_ID = "prop_e2e_test";
544
+ const client = new import_pg2.Client({
545
+ connectionString: dbUrl,
546
+ ssl: { rejectUnauthorized: false }
547
+ });
548
+ await client.connect();
549
+ try {
550
+ const vault2 = new LocalAESVault();
551
+ const mockPlaintextToken = "my-super-secret-token-value";
552
+ const encryptedToken = await vault2.encrypt(mockPlaintextToken);
553
+ await client.query("DELETE FROM developer_tools WHERE property_id = $1", ["prop_e2e_test"]);
554
+ await client.query("DELETE FROM developer_properties WHERE id = $1", ["prop_e2e_test"]);
555
+ const redisUrl2 = process.env.UPSTASH_REDIS_URL;
556
+ if (redisUrl2) {
557
+ const { createClient: createClient2 } = await import("redis");
558
+ const redisClient2 = createClient2({ url: redisUrl2 });
559
+ await redisClient2.connect();
560
+ await redisClient2.del("mcp:config:prop_e2e_test");
561
+ await redisClient2.disconnect();
562
+ console.log('Cleared Redis cache for "prop_e2e_test"');
563
+ }
564
+ const siteRes = await client.query("SELECT id FROM sites LIMIT 1");
565
+ if (siteRes.rows.length === 0) {
566
+ throw new Error("No sites found in the database. Cannot link property.");
567
+ }
568
+ const siteId = siteRes.rows[0].id;
569
+ console.log(`Inserting mock property "prop_e2e_test" linked to site: ${siteId}`);
570
+ await client.query(`
571
+ INSERT INTO developer_properties (id, site_id, name, api_base, auth_type, auth_token)
572
+ VALUES ($1, $2, $3, $4, $5, $6)
573
+ `, ["prop_e2e_test", siteId, "E2E Test Store", "https://httpbin.org", "bearer", encryptedToken]);
574
+ const parameters = {
575
+ type: "object",
576
+ properties: {
577
+ sku: { type: "string", location: "path", description: "Product SKU" },
578
+ qty: { type: "integer", location: "query", description: "Quantity" },
579
+ "x-test-header": { type: "string", location: "header", description: "Custom Header" },
580
+ note: { type: "string", location: "body", description: "Body note" }
581
+ },
582
+ required: ["sku"]
583
+ };
584
+ const responseMapping = {
585
+ url_echoed: "url",
586
+ header_echoed: "headers.X-Test-Header",
587
+ method_echoed: "method",
588
+ token_echoed: "headers.Authorization",
589
+ note_echoed: "json.note"
590
+ };
591
+ console.log('Inserting mock tool "get_product_e2e"');
592
+ await client.query(`
593
+ INSERT INTO developer_tools (property_id, name, description, method, path, parameters, response_mapping)
594
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
595
+ `, [
596
+ "prop_e2e_test",
597
+ "get_product_e2e",
598
+ "Mock tool for testing E2E proxy parameters mapping",
599
+ "POST",
600
+ "/anything/products/:sku",
601
+ JSON.stringify(parameters),
602
+ JSON.stringify(responseMapping)
603
+ ]);
604
+ const rawResp = await fetch("https://httpbin.org/anything/products/SKU-777-XYZ", {
605
+ method: "POST",
606
+ headers: { "Content-Type": "application/json" },
607
+ body: JSON.stringify({ note: "test" })
608
+ });
609
+ const rawJson = await rawResp.json();
610
+ console.log("Raw httpbin keys:", Object.keys(rawJson));
611
+ console.log("Raw httpbin url field is:", rawJson.url);
612
+ const { server: server2 } = await Promise.resolve().then(() => (init_index(), index_exports));
613
+ const handlersMap = server2._requestHandlers;
614
+ const callHandlerInfo = handlersMap.get("tools/call");
615
+ if (!callHandlerInfo) {
616
+ throw new Error("CallToolRequestSchema handler not registered on server");
617
+ }
618
+ const callHandler = callHandlerInfo;
619
+ console.log("Executing mock tool call with arguments...");
620
+ const result = await callHandler({
621
+ method: "tools/call",
622
+ params: {
623
+ name: "get_product_e2e",
624
+ arguments: {
625
+ sku: "SKU-777-XYZ",
626
+ qty: 5,
627
+ "x-test-header": "Akropolys-Integration-Test",
628
+ note: "E2E Integration logic runs successfully!"
629
+ }
630
+ }
631
+ });
632
+ console.log("Call result received:", JSON.stringify(result, null, 2));
633
+ import_assert.default.ok(!result.isError, "Call must succeed");
634
+ import_assert.default.ok(result.content && result.content[0] && result.content[0].type === "text", "Result content format incorrect");
635
+ const parsedPayload = JSON.parse(result.content[0].text);
636
+ import_assert.default.ok(parsedPayload.url_echoed.includes("/products/SKU-777-XYZ"), "Path parameters mapping failed: " + parsedPayload.url_echoed);
637
+ import_assert.default.strictEqual(parsedPayload.header_echoed, "Akropolys-Integration-Test", "Header mapping failed");
638
+ import_assert.default.strictEqual(parsedPayload.method_echoed, "POST", "HTTP method mapping failed");
639
+ import_assert.default.strictEqual(parsedPayload.token_echoed, `Bearer ${mockPlaintextToken}`, "Encrypted Auth Token decryption or insertion failed");
640
+ import_assert.default.strictEqual(parsedPayload.note_echoed, "E2E Integration logic runs successfully!", "Body mapping failed");
641
+ console.log("\u2705 E2E Integration Test passed perfectly!");
642
+ } finally {
643
+ console.log("Cleaning up mock database entities...");
644
+ await client.query("DELETE FROM developer_tools WHERE property_id = $1", ["prop_e2e_test"]);
645
+ await client.query("DELETE FROM developer_properties WHERE id = $1", ["prop_e2e_test"]);
646
+ await client.end();
647
+ }
648
+ }
649
+ runE2E().catch((err) => {
650
+ console.error("\u274C E2E Integration Test failed:", err);
651
+ process.exit(1);
652
+ });