@cocaxcode/api-testing-mcp 0.9.0 → 0.10.0
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/README.md +50 -25
- package/dist/index.js +262 -2
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<h1 align="center">@cocaxcode/api-testing-mcp</h1>
|
|
3
3
|
<p align="center">
|
|
4
4
|
<strong>The most complete API testing MCP server available.</strong><br/>
|
|
5
|
-
|
|
5
|
+
31 tools · Zero config · Zero dependencies · Everything runs inside your AI conversation.
|
|
6
6
|
</p>
|
|
7
7
|
</p>
|
|
8
8
|
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
<a href="https://www.npmjs.com/package/@cocaxcode/api-testing-mcp"><img src="https://img.shields.io/npm/dm/@cocaxcode/api-testing-mcp.svg?style=flat-square" alt="npm downloads" /></a>
|
|
12
12
|
<a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square" alt="License" /></a>
|
|
13
13
|
<img src="https://img.shields.io/badge/node-%3E%3D20-339933?style=flat-square&logo=node.js&logoColor=white" alt="Node" />
|
|
14
|
-
<img src="https://img.shields.io/badge/tools-
|
|
15
|
-
<img src="https://img.shields.io/badge/tests-
|
|
14
|
+
<img src="https://img.shields.io/badge/tools-31-blueviolet?style=flat-square" alt="29 tools" />
|
|
15
|
+
<img src="https://img.shields.io/badge/tests-110-brightgreen?style=flat-square" alt="96 tests" />
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
18
|
<p align="center">
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
|
|
30
30
|
## What is this?
|
|
31
31
|
|
|
32
|
-
An [MCP server](https://modelcontextprotocol.io) that gives your AI assistant the ability to **test, validate, mock, chain, diff, load-test, export
|
|
32
|
+
An [MCP server](https://modelcontextprotocol.io) that gives your AI assistant the ability to **test, validate, mock, chain, diff, load-test, import/export Postman collections, and manage** any API — all from natural language.
|
|
33
33
|
|
|
34
34
|
You describe what you need. The AI figures out the rest.
|
|
35
35
|
|
|
@@ -47,17 +47,17 @@ There are other API testing MCP servers out there. Here's why this one is differ
|
|
|
47
47
|
|
|
48
48
|
| Capability | @cocaxcode/api-testing-mcp | Others |
|
|
49
49
|
|---|:---:|:---:|
|
|
50
|
-
| HTTP requests with auth |
|
|
50
|
+
| HTTP requests with auth | 31 tools | 1-11 tools |
|
|
51
51
|
| Assertions (eq, neq, gt, lt, contains, exists, type...) | 10 operators | Status code only or none |
|
|
52
52
|
| Request flows with variable extraction | `flow_run` with `extract` | Not available |
|
|
53
|
-
| Collections with tags and CRUD | Full CRUD + tag filtering |
|
|
53
|
+
| Collections with tags and CRUD | Full CRUD + tag filtering | Basic or none |
|
|
54
54
|
| Environments with variable interpolation | CRUD + project-scoped | Manual `set_env_vars` or none |
|
|
55
55
|
| OpenAPI import with `$ref`, `allOf`, `oneOf`, `anyOf` | ~95% real-world coverage | Basic or none |
|
|
56
56
|
| Mock data generation from schemas | Types, formats, enums | Not available |
|
|
57
57
|
| Load testing with percentiles | p50/p95/p99 + req/s | Basic or none |
|
|
58
58
|
| Response diffing | Field-by-field comparison | Not available |
|
|
59
59
|
| Bulk testing by tag | Collection-wide pass/fail | Not available |
|
|
60
|
-
| **Postman
|
|
60
|
+
| **Postman import + export** | **Bidirectional: import from & export to Postman** | **Not available** |
|
|
61
61
|
| cURL export | With resolved variables | Not available |
|
|
62
62
|
| Project-scoped environments | Per-directory context | Not available |
|
|
63
63
|
| External dependencies | **Zero** — just Node.js | Playwright, Jest, pytest... |
|
|
@@ -183,6 +183,8 @@ You don't need to memorize tool names, parameters, or JSON structures — just t
|
|
|
183
183
|
| *"How fast is the health endpoint under load?"* | Fires 50 concurrent requests, reports p50/p95/p99 latencies |
|
|
184
184
|
| *"Run all my saved smoke tests"* | Executes every request tagged `smoke`, reports pass/fail |
|
|
185
185
|
| *"Export the create-user request as curl"* | Builds a ready-to-paste cURL command with resolved variables |
|
|
186
|
+
| *"Import my Postman collection from exported.json"* | Reads a Postman Collection v2.1, converts all requests into saved collection items |
|
|
187
|
+
| *"Import the Postman environment from prod.postman_environment.json"* | Imports variables from a Postman Environment file |
|
|
186
188
|
| *"Export my collection to Postman"* | Writes a `.postman_collection.json` file ready to import |
|
|
187
189
|
| *"Export the dev environment for Postman"* | Writes a `.postman_environment.json` file ready to import |
|
|
188
190
|
| *"Compare the users endpoint between dev and prod"* | Hits both URLs, diffs status codes, body, and timing |
|
|
@@ -399,9 +401,33 @@ BULK TEST — 8/8 passed | 1.2s total
|
|
|
399
401
|
login — POST /auth/login → 200 (156ms)
|
|
400
402
|
```
|
|
401
403
|
|
|
402
|
-
### Postman Export
|
|
404
|
+
### Postman Import & Export
|
|
403
405
|
|
|
404
|
-
|
|
406
|
+
**Bidirectional Postman support.** Import existing Postman collections and environments, or export yours for use in Postman. Migrate seamlessly between Postman and your AI workflow.
|
|
407
|
+
|
|
408
|
+
#### Import from Postman
|
|
409
|
+
|
|
410
|
+
```
|
|
411
|
+
"Import my Postman collection from ./exported.postman_collection.json"
|
|
412
|
+
"Import the collection and tag everything as legacy"
|
|
413
|
+
"Import the Postman environment from ./prod.postman_environment.json and activate it"
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
**Collection import features:**
|
|
417
|
+
- Postman Collection **v2.1** format (the default Postman export)
|
|
418
|
+
- **Folders become tags** — a request inside `Users > Admin` gets tags `["Users", "Admin"]`
|
|
419
|
+
- Auth inherited from folders/collection level (Bearer, API Key, Basic)
|
|
420
|
+
- Body parsing: raw JSON, x-www-form-urlencoded, form-data
|
|
421
|
+
- Query params, headers, and disabled items handled correctly
|
|
422
|
+
- `overwrite` option to update existing requests
|
|
423
|
+
|
|
424
|
+
**Environment import features:**
|
|
425
|
+
- Prefers `currentValue` over `value` (matches Postman runtime behavior)
|
|
426
|
+
- Skips disabled variables
|
|
427
|
+
- Optional `activate` flag to make it the active environment immediately
|
|
428
|
+
- Custom name override
|
|
429
|
+
|
|
430
|
+
#### Export to Postman
|
|
405
431
|
|
|
406
432
|
```
|
|
407
433
|
"Export my collection to Postman"
|
|
@@ -418,31 +444,30 @@ your-project/
|
|
|
418
444
|
└── dev.postman_environment.json ← Import in Postman: File → Import
|
|
419
445
|
```
|
|
420
446
|
|
|
421
|
-
**Collection features:**
|
|
447
|
+
**Collection export features:**
|
|
422
448
|
- Requests grouped in **folders by tag** (smoke, auth, users, etc.)
|
|
423
449
|
- Auth (Bearer, API Key, Basic) mapped to Postman's native auth format
|
|
424
450
|
- `{{variables}}` preserved as-is (Postman uses the same syntax)
|
|
425
451
|
- Headers, query params, and JSON body included
|
|
426
452
|
- Collection variables from your active environment
|
|
427
453
|
|
|
428
|
-
**Environment features:**
|
|
454
|
+
**Environment export features:**
|
|
429
455
|
- All variables exported with `enabled: true`
|
|
430
456
|
- Postman-compatible format (`_postman_variable_scope: "environment"`)
|
|
431
457
|
- Works with any environment (active or by name)
|
|
432
458
|
|
|
433
459
|
<details>
|
|
434
|
-
<summary>Example:
|
|
460
|
+
<summary>Example: round-trip workflow</summary>
|
|
435
461
|
|
|
436
462
|
```
|
|
437
|
-
You: "
|
|
438
|
-
|
|
463
|
+
You: "Import the Postman collection from ./legacy-api.postman_collection.json with tag migrated"
|
|
464
|
+
→ 47 requests imported with tag "migrated"
|
|
439
465
|
|
|
440
|
-
|
|
466
|
+
You: "Run all migrated requests"
|
|
467
|
+
→ Bulk test: 45/47 passed
|
|
441
468
|
|
|
442
|
-
You
|
|
443
|
-
|
|
444
|
-
```
|
|
445
|
-
You: "Export my collection to Postman in the exports folder"
|
|
469
|
+
You: "Fix the failing ones and export back to Postman"
|
|
470
|
+
→ Updated collection exported to postman/legacy-api.postman_collection.json
|
|
446
471
|
```
|
|
447
472
|
|
|
448
473
|
</details>
|
|
@@ -494,7 +519,7 @@ Resolution order: project-specific environment → global active environment.
|
|
|
494
519
|
|
|
495
520
|
## Tool Reference
|
|
496
521
|
|
|
497
|
-
|
|
522
|
+
31 tools organized in 8 categories:
|
|
498
523
|
|
|
499
524
|
| Category | Tools | Count |
|
|
500
525
|
|----------|-------|-------|
|
|
@@ -505,7 +530,7 @@ Resolution order: project-specific environment → global active environment.
|
|
|
505
530
|
| **Environments** | `env_create` `env_list` `env_set` `env_get` `env_switch` `env_rename` `env_delete` `env_spec` `env_project_clear` `env_project_list` | 10 |
|
|
506
531
|
| **API Specs** | `api_import` `api_spec_list` `api_endpoints` `api_endpoint_detail` | 4 |
|
|
507
532
|
| **Mock** | `mock` | 1 |
|
|
508
|
-
| **Utilities** | `load_test` `export_curl` `diff_responses` `bulk_test` `export_postman_collection` `export_postman_environment` |
|
|
533
|
+
| **Utilities** | `load_test` `export_curl` `diff_responses` `bulk_test` `import_postman_collection` `import_postman_environment` `export_postman_collection` `export_postman_environment` | 8 |
|
|
509
534
|
|
|
510
535
|
You don't need to call these tools directly. Just describe what you want and the AI picks the right one.
|
|
511
536
|
|
|
@@ -541,14 +566,14 @@ Override the default directory in your MCP config:
|
|
|
541
566
|
Built for reliability and testability:
|
|
542
567
|
|
|
543
568
|
- **Zero runtime dependencies** — only `@modelcontextprotocol/sdk` and `zod`
|
|
544
|
-
- **
|
|
569
|
+
- **110 integration tests** with mocked fetch (no network calls in CI)
|
|
545
570
|
- **Factory pattern** — `createServer(storageDir?)` for isolated test instances
|
|
546
571
|
- **Strict TypeScript** — zero `any`, full type coverage
|
|
547
572
|
- **< 95KB** bundled output via tsup
|
|
548
573
|
|
|
549
574
|
```
|
|
550
575
|
src/
|
|
551
|
-
├── tools/ #
|
|
576
|
+
├── tools/ # 31 MCP tool handlers (one file per category)
|
|
552
577
|
├── lib/ # Business logic (no MCP dependency)
|
|
553
578
|
│ ├── http-client # fetch wrapper with timing
|
|
554
579
|
│ ├── storage # JSON file storage engine
|
|
@@ -557,7 +582,7 @@ src/
|
|
|
557
582
|
│ ├── path # Dot-notation accessor (body.data.0.id)
|
|
558
583
|
│ ├── interpolation # {{variable}} resolver
|
|
559
584
|
│ └── openapi-parser # $ref + allOf/oneOf/anyOf resolution
|
|
560
|
-
└── __tests__/ # 10 test suites,
|
|
585
|
+
└── __tests__/ # 10 test suites, 110 tests
|
|
561
586
|
```
|
|
562
587
|
|
|
563
588
|
---
|
|
@@ -578,7 +603,7 @@ src/
|
|
|
578
603
|
git clone https://github.com/cocaxcode/api-testing-mcp.git
|
|
579
604
|
cd api-testing-mcp
|
|
580
605
|
npm install
|
|
581
|
-
npm test #
|
|
606
|
+
npm test # 110 tests across 10 suites
|
|
582
607
|
npm run build # ESM bundle via tsup
|
|
583
608
|
npm run typecheck # Strict TypeScript
|
|
584
609
|
```
|
package/dist/index.js
CHANGED
|
@@ -1860,7 +1860,7 @@ function registerFlowTool(server, storage) {
|
|
|
1860
1860
|
|
|
1861
1861
|
// src/tools/utilities.ts
|
|
1862
1862
|
import { z as z8 } from "zod";
|
|
1863
|
-
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
1863
|
+
import { mkdir as mkdir2, readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
|
|
1864
1864
|
import { join as join2 } from "path";
|
|
1865
1865
|
function registerUtilityTools(server, storage) {
|
|
1866
1866
|
server.tool(
|
|
@@ -2233,6 +2233,145 @@ Importa este archivo en Postman: File \u2192 Import \u2192 selecciona el archivo
|
|
|
2233
2233
|
}
|
|
2234
2234
|
}
|
|
2235
2235
|
);
|
|
2236
|
+
server.tool(
|
|
2237
|
+
"import_postman_collection",
|
|
2238
|
+
"Importa una Postman Collection v2.1 (JSON) como requests guardados en la colecci\xF3n. Soporta folders, auth, headers, body y query params.",
|
|
2239
|
+
{
|
|
2240
|
+
file: z8.string().describe("Ruta al archivo .postman_collection.json exportado de Postman"),
|
|
2241
|
+
tag: z8.string().optional().describe("Tag adicional para aplicar a todos los requests importados"),
|
|
2242
|
+
overwrite: z8.boolean().optional().describe("Sobreescribir requests existentes con el mismo nombre (default: false)")
|
|
2243
|
+
},
|
|
2244
|
+
async (params) => {
|
|
2245
|
+
try {
|
|
2246
|
+
const raw = await readFile3(params.file, "utf-8");
|
|
2247
|
+
const collection = JSON.parse(raw);
|
|
2248
|
+
if (!collection.item || !Array.isArray(collection.item)) {
|
|
2249
|
+
return {
|
|
2250
|
+
content: [
|
|
2251
|
+
{
|
|
2252
|
+
type: "text",
|
|
2253
|
+
text: 'Error: El archivo no parece ser una Postman Collection v\xE1lida. Falta la propiedad "item".'
|
|
2254
|
+
}
|
|
2255
|
+
],
|
|
2256
|
+
isError: true
|
|
2257
|
+
};
|
|
2258
|
+
}
|
|
2259
|
+
const overwrite = params.overwrite ?? false;
|
|
2260
|
+
const extraTag = params.tag;
|
|
2261
|
+
const flatItems = flattenPostmanItems(collection.item, collection.auth);
|
|
2262
|
+
let imported = 0;
|
|
2263
|
+
let skipped = 0;
|
|
2264
|
+
const errors = [];
|
|
2265
|
+
for (const item of flatItems) {
|
|
2266
|
+
try {
|
|
2267
|
+
const saved = parsePostmanItem(item, extraTag);
|
|
2268
|
+
if (!saved) continue;
|
|
2269
|
+
if (!overwrite) {
|
|
2270
|
+
const existing = await storage.getCollection(saved.name);
|
|
2271
|
+
if (existing) {
|
|
2272
|
+
skipped++;
|
|
2273
|
+
continue;
|
|
2274
|
+
}
|
|
2275
|
+
}
|
|
2276
|
+
await storage.saveCollection(saved);
|
|
2277
|
+
imported++;
|
|
2278
|
+
} catch (err) {
|
|
2279
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2280
|
+
errors.push(`${item.name ?? "unknown"}: ${msg}`);
|
|
2281
|
+
}
|
|
2282
|
+
}
|
|
2283
|
+
const lines = [
|
|
2284
|
+
`Postman Collection importada: ${imported} requests guardados.`
|
|
2285
|
+
];
|
|
2286
|
+
if (skipped > 0) lines.push(`${skipped} requests omitidos (ya exist\xEDan, usa overwrite: true para sobreescribir).`);
|
|
2287
|
+
if (errors.length > 0) {
|
|
2288
|
+
lines.push(`${errors.length} errores:`);
|
|
2289
|
+
for (const e of errors) lines.push(` - ${e}`);
|
|
2290
|
+
}
|
|
2291
|
+
if (collection.info?.name) lines.push(`
|
|
2292
|
+
Colecci\xF3n: ${collection.info.name}`);
|
|
2293
|
+
return {
|
|
2294
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
2295
|
+
};
|
|
2296
|
+
} catch (error) {
|
|
2297
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2298
|
+
return {
|
|
2299
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
2300
|
+
isError: true
|
|
2301
|
+
};
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2304
|
+
);
|
|
2305
|
+
server.tool(
|
|
2306
|
+
"import_postman_environment",
|
|
2307
|
+
"Importa un Postman Environment (JSON) como entorno local. Soporta variables con valores initial/current.",
|
|
2308
|
+
{
|
|
2309
|
+
file: z8.string().describe("Ruta al archivo .postman_environment.json exportado de Postman"),
|
|
2310
|
+
name: z8.string().optional().describe("Nombre para el entorno (default: usa el nombre del archivo Postman)"),
|
|
2311
|
+
overwrite: z8.boolean().optional().describe("Sobreescribir si ya existe un entorno con el mismo nombre (default: false)"),
|
|
2312
|
+
activate: z8.boolean().optional().describe("Activar el entorno importado como entorno activo (default: false)")
|
|
2313
|
+
},
|
|
2314
|
+
async (params) => {
|
|
2315
|
+
try {
|
|
2316
|
+
const raw = await readFile3(params.file, "utf-8");
|
|
2317
|
+
const postmanEnv = JSON.parse(raw);
|
|
2318
|
+
if (!postmanEnv.values || !Array.isArray(postmanEnv.values)) {
|
|
2319
|
+
return {
|
|
2320
|
+
content: [
|
|
2321
|
+
{
|
|
2322
|
+
type: "text",
|
|
2323
|
+
text: 'Error: El archivo no parece ser un Postman Environment v\xE1lido. Falta la propiedad "values".'
|
|
2324
|
+
}
|
|
2325
|
+
],
|
|
2326
|
+
isError: true
|
|
2327
|
+
};
|
|
2328
|
+
}
|
|
2329
|
+
const envName = params.name ?? postmanEnv.name ?? "postman-import";
|
|
2330
|
+
const overwrite = params.overwrite ?? false;
|
|
2331
|
+
const existing = await storage.getEnvironment(envName);
|
|
2332
|
+
if (existing && !overwrite) {
|
|
2333
|
+
return {
|
|
2334
|
+
content: [
|
|
2335
|
+
{
|
|
2336
|
+
type: "text",
|
|
2337
|
+
text: `Error: Ya existe un entorno '${envName}'. Usa overwrite: true para sobreescribir.`
|
|
2338
|
+
}
|
|
2339
|
+
],
|
|
2340
|
+
isError: true
|
|
2341
|
+
};
|
|
2342
|
+
}
|
|
2343
|
+
const variables = {};
|
|
2344
|
+
for (const v of postmanEnv.values) {
|
|
2345
|
+
if (!v.key) continue;
|
|
2346
|
+
if (v.enabled === false) continue;
|
|
2347
|
+
variables[v.key] = String(v.currentValue ?? v.value ?? "");
|
|
2348
|
+
}
|
|
2349
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2350
|
+
await storage.createEnvironment({
|
|
2351
|
+
name: envName,
|
|
2352
|
+
variables,
|
|
2353
|
+
createdAt: now,
|
|
2354
|
+
updatedAt: now
|
|
2355
|
+
});
|
|
2356
|
+
if (params.activate) {
|
|
2357
|
+
await storage.setActiveEnvironment(envName);
|
|
2358
|
+
}
|
|
2359
|
+
const lines = [
|
|
2360
|
+
`Postman Environment "${envName}" importado (${Object.keys(variables).length} variables).`
|
|
2361
|
+
];
|
|
2362
|
+
if (params.activate) lines.push("Entorno activado como activo.");
|
|
2363
|
+
return {
|
|
2364
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
2365
|
+
};
|
|
2366
|
+
} catch (error) {
|
|
2367
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2368
|
+
return {
|
|
2369
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
2370
|
+
isError: true
|
|
2371
|
+
};
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
);
|
|
2236
2375
|
server.tool(
|
|
2237
2376
|
"export_postman_environment",
|
|
2238
2377
|
"Exporta un entorno como Postman Environment (JSON). Escribe el archivo en disco, importable directamente en Postman.",
|
|
@@ -2387,6 +2526,127 @@ function buildPostmanAuth(auth) {
|
|
|
2387
2526
|
};
|
|
2388
2527
|
}
|
|
2389
2528
|
}
|
|
2529
|
+
function flattenPostmanItems(items, parentAuth, parentTags = []) {
|
|
2530
|
+
const result = [];
|
|
2531
|
+
for (const item of items) {
|
|
2532
|
+
if (item.item && Array.isArray(item.item)) {
|
|
2533
|
+
const folderTags = item.name ? [...parentTags, item.name] : parentTags;
|
|
2534
|
+
const folderAuth = item.auth ?? parentAuth;
|
|
2535
|
+
result.push(...flattenPostmanItems(item.item, folderAuth, folderTags));
|
|
2536
|
+
} else if (item.request) {
|
|
2537
|
+
result.push({
|
|
2538
|
+
...item,
|
|
2539
|
+
_folderTags: parentTags,
|
|
2540
|
+
_inheritedAuth: item.request?.auth ? void 0 : parentAuth
|
|
2541
|
+
});
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
return result;
|
|
2545
|
+
}
|
|
2546
|
+
var VALID_METHODS2 = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
2547
|
+
function parsePostmanItem(item, extraTag) {
|
|
2548
|
+
const req = item.request;
|
|
2549
|
+
if (!req) return null;
|
|
2550
|
+
const method = typeof req.method === "string" ? req.method.toUpperCase() : "GET";
|
|
2551
|
+
if (!VALID_METHODS2.has(method)) return null;
|
|
2552
|
+
const url = parsePostmanUrl(req.url);
|
|
2553
|
+
if (!url) return null;
|
|
2554
|
+
const headers = {};
|
|
2555
|
+
if (Array.isArray(req.header)) {
|
|
2556
|
+
for (const h of req.header) {
|
|
2557
|
+
if (h.disabled) continue;
|
|
2558
|
+
if (h.key && h.value !== void 0) {
|
|
2559
|
+
headers[h.key] = String(h.value);
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
const query = {};
|
|
2564
|
+
if (req.url && typeof req.url === "object" && Array.isArray(req.url.query)) {
|
|
2565
|
+
for (const q of req.url.query) {
|
|
2566
|
+
if (q.disabled) continue;
|
|
2567
|
+
if (q.key) {
|
|
2568
|
+
query[q.key] = String(q.value ?? "");
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
let body = void 0;
|
|
2573
|
+
if (req.body) {
|
|
2574
|
+
if (req.body.mode === "raw" && typeof req.body.raw === "string") {
|
|
2575
|
+
try {
|
|
2576
|
+
body = JSON.parse(req.body.raw);
|
|
2577
|
+
} catch {
|
|
2578
|
+
body = req.body.raw;
|
|
2579
|
+
}
|
|
2580
|
+
} else if (req.body.mode === "urlencoded" && Array.isArray(req.body.urlencoded)) {
|
|
2581
|
+
const formData = {};
|
|
2582
|
+
for (const p of req.body.urlencoded) {
|
|
2583
|
+
if (p.key) formData[p.key] = String(p.value ?? "");
|
|
2584
|
+
}
|
|
2585
|
+
body = formData;
|
|
2586
|
+
} else if (req.body.mode === "formdata" && Array.isArray(req.body.formdata)) {
|
|
2587
|
+
const formData = {};
|
|
2588
|
+
for (const p of req.body.formdata) {
|
|
2589
|
+
if (p.key && p.type !== "file") formData[p.key] = String(p.value ?? "");
|
|
2590
|
+
}
|
|
2591
|
+
body = formData;
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
const authSource = req.auth ?? item._inheritedAuth;
|
|
2595
|
+
const auth = parsePostmanAuth(authSource);
|
|
2596
|
+
const tags = [...item._folderTags ?? []];
|
|
2597
|
+
if (extraTag && !tags.includes(extraTag)) tags.push(extraTag);
|
|
2598
|
+
const name = item.name || `${method} ${url}`;
|
|
2599
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2600
|
+
const config = { method, url };
|
|
2601
|
+
if (Object.keys(headers).length > 0) config.headers = headers;
|
|
2602
|
+
if (Object.keys(query).length > 0) config.query = query;
|
|
2603
|
+
if (body !== void 0) config.body = body;
|
|
2604
|
+
if (auth) config.auth = auth;
|
|
2605
|
+
return {
|
|
2606
|
+
name,
|
|
2607
|
+
request: config,
|
|
2608
|
+
tags: tags.length > 0 ? tags : void 0,
|
|
2609
|
+
createdAt: now,
|
|
2610
|
+
updatedAt: now
|
|
2611
|
+
};
|
|
2612
|
+
}
|
|
2613
|
+
function parsePostmanUrl(url) {
|
|
2614
|
+
if (typeof url === "string") return url || null;
|
|
2615
|
+
if (url && typeof url === "object") {
|
|
2616
|
+
if (typeof url.raw === "string") return url.raw || null;
|
|
2617
|
+
const protocol = url.protocol ?? "https";
|
|
2618
|
+
const host = Array.isArray(url.host) ? url.host.join(".") : url.host;
|
|
2619
|
+
const path = Array.isArray(url.path) ? url.path.join("/") : url.path ?? "";
|
|
2620
|
+
if (host) return `${protocol}://${host}${path ? "/" + path : ""}`;
|
|
2621
|
+
}
|
|
2622
|
+
return null;
|
|
2623
|
+
}
|
|
2624
|
+
function parsePostmanAuth(auth) {
|
|
2625
|
+
if (!auth || !auth.type) return void 0;
|
|
2626
|
+
const getVal = (arr, key) => {
|
|
2627
|
+
if (!Array.isArray(arr)) return void 0;
|
|
2628
|
+
const item = arr.find((a) => a.key === key);
|
|
2629
|
+
return item?.value ? String(item.value) : void 0;
|
|
2630
|
+
};
|
|
2631
|
+
switch (auth.type) {
|
|
2632
|
+
case "bearer": {
|
|
2633
|
+
const token = getVal(auth.bearer, "token");
|
|
2634
|
+
return token ? { type: "bearer", token } : void 0;
|
|
2635
|
+
}
|
|
2636
|
+
case "apikey": {
|
|
2637
|
+
const key = getVal(auth.apikey, "key");
|
|
2638
|
+
const headerName = getVal(auth.apikey, "value");
|
|
2639
|
+
return key ? { type: "api-key", key, header: headerName } : void 0;
|
|
2640
|
+
}
|
|
2641
|
+
case "basic": {
|
|
2642
|
+
const username = getVal(auth.basic, "username");
|
|
2643
|
+
const password = getVal(auth.basic, "password");
|
|
2644
|
+
return username ? { type: "basic", username, password } : void 0;
|
|
2645
|
+
}
|
|
2646
|
+
default:
|
|
2647
|
+
return void 0;
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2390
2650
|
|
|
2391
2651
|
// src/tools/mock.ts
|
|
2392
2652
|
import { z as z9 } from "zod";
|
|
@@ -2697,7 +2957,7 @@ function registerLoadTestTool(server, storage) {
|
|
|
2697
2957
|
}
|
|
2698
2958
|
|
|
2699
2959
|
// src/server.ts
|
|
2700
|
-
var VERSION = "0.
|
|
2960
|
+
var VERSION = "0.10.0";
|
|
2701
2961
|
function createServer(storageDir) {
|
|
2702
2962
|
const server = new McpServer({
|
|
2703
2963
|
name: "api-testing-mcp",
|