@cocaxcode/api-testing-mcp 0.5.7 → 0.7.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 +19 -5
- package/dist/index.js +282 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -351,11 +351,24 @@ curl -X POST \
|
|
|
351
351
|
|
|
352
352
|
Save requests for reuse (with tags), manage variables across environments (dev/staging/prod), and switch contexts instantly.
|
|
353
353
|
|
|
354
|
+
### Project-Scoped Environments
|
|
355
|
+
|
|
356
|
+
Different projects can have different active environments. When you switch to an environment for a specific project, it only affects that project — other projects keep their own active environment.
|
|
357
|
+
|
|
358
|
+
```
|
|
359
|
+
"Switch to dev for this project" → dev is active only in the current project
|
|
360
|
+
"Switch to prod globally" → prod is the default for projects without a specific assignment
|
|
361
|
+
"Show me which projects have environments" → lists all project-environment assignments
|
|
362
|
+
"Clear the project environment for this project" → falls back to the global active environment
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
Resolution order: project-specific environment → global active environment.
|
|
366
|
+
|
|
354
367
|
---
|
|
355
368
|
|
|
356
369
|
## Tool Reference
|
|
357
370
|
|
|
358
|
-
|
|
371
|
+
27 tools organized in 8 categories:
|
|
359
372
|
|
|
360
373
|
| Category | Tools | Count |
|
|
361
374
|
|----------|-------|-------|
|
|
@@ -363,8 +376,8 @@ Save requests for reuse (with tags), manage variables across environments (dev/s
|
|
|
363
376
|
| **Testing** | `assert` | 1 |
|
|
364
377
|
| **Flows** | `flow_run` | 1 |
|
|
365
378
|
| **Collections** | `collection_save` `collection_list` `collection_get` `collection_delete` | 4 |
|
|
366
|
-
| **Environments** | `env_create` `env_list` `env_set` `env_get` `env_switch` `env_rename` `env_delete` |
|
|
367
|
-
| **API Specs** | `api_import` `api_endpoints` `api_endpoint_detail` |
|
|
379
|
+
| **Environments** | `env_create` `env_list` `env_set` `env_get` `env_switch` `env_rename` `env_delete` `env_spec` `env_project_clear` `env_project_list` | 10 |
|
|
380
|
+
| **API Specs** | `api_import` `api_spec_list` `api_endpoints` `api_endpoint_detail` | 4 |
|
|
368
381
|
| **Mock** | `mock` | 1 |
|
|
369
382
|
| **Utilities** | `load_test` `export_curl` `diff_responses` `bulk_test` | 4 |
|
|
370
383
|
|
|
@@ -378,7 +391,8 @@ All data lives in `~/.api-testing/` (user home directory) as plain JSON — no d
|
|
|
378
391
|
|
|
379
392
|
```
|
|
380
393
|
~/.api-testing/
|
|
381
|
-
├── active-env #
|
|
394
|
+
├── active-env # Global active environment name
|
|
395
|
+
├── project-envs.json # Per-project active environments
|
|
382
396
|
├── collections/ # Saved requests
|
|
383
397
|
├── environments/ # Environment variables (dev, prod, ...)
|
|
384
398
|
└── specs/ # Imported OpenAPI specs
|
|
@@ -412,7 +426,7 @@ Override the default directory in your MCP config:
|
|
|
412
426
|
git clone https://github.com/cocaxcode/api-testing-mcp.git
|
|
413
427
|
cd api-testing-mcp
|
|
414
428
|
npm install
|
|
415
|
-
npm test #
|
|
429
|
+
npm test # 83 tests across 10 suites
|
|
416
430
|
npm run build # ESM bundle via tsup
|
|
417
431
|
npm run typecheck # Strict TypeScript
|
|
418
432
|
```
|
package/dist/index.js
CHANGED
|
@@ -16,12 +16,14 @@ var Storage = class {
|
|
|
16
16
|
environmentsDir;
|
|
17
17
|
specsDir;
|
|
18
18
|
activeEnvFile;
|
|
19
|
+
projectEnvsFile;
|
|
19
20
|
constructor(baseDir) {
|
|
20
21
|
this.baseDir = baseDir ?? process.env.API_TESTING_DIR ?? join(homedir(), ".api-testing");
|
|
21
22
|
this.collectionsDir = join(this.baseDir, "collections");
|
|
22
23
|
this.environmentsDir = join(this.baseDir, "environments");
|
|
23
24
|
this.specsDir = join(this.baseDir, "specs");
|
|
24
25
|
this.activeEnvFile = join(this.baseDir, "active-env");
|
|
26
|
+
this.projectEnvsFile = join(this.baseDir, "project-envs.json");
|
|
25
27
|
}
|
|
26
28
|
// ── Collections ──
|
|
27
29
|
async saveCollection(saved) {
|
|
@@ -80,7 +82,8 @@ var Storage = class {
|
|
|
80
82
|
items.push({
|
|
81
83
|
name: env.name,
|
|
82
84
|
active: env.name === activeEnv,
|
|
83
|
-
variableCount: Object.keys(env.variables).length
|
|
85
|
+
variableCount: Object.keys(env.variables).length,
|
|
86
|
+
spec: env.spec
|
|
84
87
|
});
|
|
85
88
|
}
|
|
86
89
|
return items;
|
|
@@ -95,7 +98,14 @@ var Storage = class {
|
|
|
95
98
|
const filePath = join(this.environmentsDir, `${this.sanitizeName(name)}.json`);
|
|
96
99
|
await this.writeJson(filePath, env);
|
|
97
100
|
}
|
|
98
|
-
async getActiveEnvironment() {
|
|
101
|
+
async getActiveEnvironment(project) {
|
|
102
|
+
const projectPath = project ?? process.cwd();
|
|
103
|
+
const projectEnvs = await this.getProjectEnvs();
|
|
104
|
+
const projectEnv = projectEnvs[projectPath];
|
|
105
|
+
if (projectEnv) {
|
|
106
|
+
const env = await this.getEnvironment(projectEnv);
|
|
107
|
+
if (env) return projectEnv;
|
|
108
|
+
}
|
|
99
109
|
try {
|
|
100
110
|
const content = await readFile(this.activeEnvFile, "utf-8");
|
|
101
111
|
return content.trim() || null;
|
|
@@ -103,13 +113,49 @@ var Storage = class {
|
|
|
103
113
|
return null;
|
|
104
114
|
}
|
|
105
115
|
}
|
|
106
|
-
async setActiveEnvironment(name) {
|
|
116
|
+
async setActiveEnvironment(name, project) {
|
|
107
117
|
const env = await this.getEnvironment(name);
|
|
108
118
|
if (!env) {
|
|
109
119
|
throw new Error(`Entorno '${name}' no encontrado`);
|
|
110
120
|
}
|
|
111
|
-
|
|
112
|
-
|
|
121
|
+
if (project) {
|
|
122
|
+
const projectEnvs = await this.getProjectEnvs();
|
|
123
|
+
projectEnvs[project] = name;
|
|
124
|
+
await this.ensureDir("");
|
|
125
|
+
await this.writeJson(this.projectEnvsFile, projectEnvs);
|
|
126
|
+
} else {
|
|
127
|
+
await this.ensureDir("");
|
|
128
|
+
await writeFile(this.activeEnvFile, name, "utf-8");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
async clearProjectEnvironment(project) {
|
|
132
|
+
const projectEnvs = await this.getProjectEnvs();
|
|
133
|
+
if (!(project in projectEnvs)) return false;
|
|
134
|
+
delete projectEnvs[project];
|
|
135
|
+
await this.writeJson(this.projectEnvsFile, projectEnvs);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
async listProjectEnvironments() {
|
|
139
|
+
return this.getProjectEnvs();
|
|
140
|
+
}
|
|
141
|
+
async getProjectEnvs() {
|
|
142
|
+
return await this.readJson(this.projectEnvsFile) ?? {};
|
|
143
|
+
}
|
|
144
|
+
async setEnvironmentSpec(envName, specName) {
|
|
145
|
+
const env = await this.getEnvironment(envName);
|
|
146
|
+
if (!env) {
|
|
147
|
+
throw new Error(`Entorno '${envName}' no encontrado`);
|
|
148
|
+
}
|
|
149
|
+
env.spec = specName ?? void 0;
|
|
150
|
+
env.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
151
|
+
const filePath = join(this.environmentsDir, `${this.sanitizeName(envName)}.json`);
|
|
152
|
+
await this.writeJson(filePath, env);
|
|
153
|
+
}
|
|
154
|
+
async getActiveSpec() {
|
|
155
|
+
const activeName = await this.getActiveEnvironment();
|
|
156
|
+
if (!activeName) return null;
|
|
157
|
+
const env = await this.getEnvironment(activeName);
|
|
158
|
+
return env?.spec ?? null;
|
|
113
159
|
}
|
|
114
160
|
async renameEnvironment(oldName, newName) {
|
|
115
161
|
const env = await this.getEnvironment(oldName);
|
|
@@ -124,9 +170,23 @@ var Storage = class {
|
|
|
124
170
|
env.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
125
171
|
await this.createEnvironment(env);
|
|
126
172
|
await unlink(join(this.environmentsDir, `${this.sanitizeName(oldName)}.json`));
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
173
|
+
try {
|
|
174
|
+
const globalActive = await readFile(this.activeEnvFile, "utf-8");
|
|
175
|
+
if (globalActive.trim() === oldName) {
|
|
176
|
+
await writeFile(this.activeEnvFile, newName, "utf-8");
|
|
177
|
+
}
|
|
178
|
+
} catch {
|
|
179
|
+
}
|
|
180
|
+
const projectEnvs = await this.getProjectEnvs();
|
|
181
|
+
let changed = false;
|
|
182
|
+
for (const [project, envName] of Object.entries(projectEnvs)) {
|
|
183
|
+
if (envName === oldName) {
|
|
184
|
+
projectEnvs[project] = newName;
|
|
185
|
+
changed = true;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (changed) {
|
|
189
|
+
await this.writeJson(this.projectEnvsFile, projectEnvs);
|
|
130
190
|
}
|
|
131
191
|
}
|
|
132
192
|
async deleteEnvironment(name) {
|
|
@@ -135,13 +195,24 @@ var Storage = class {
|
|
|
135
195
|
throw new Error(`Entorno '${name}' no encontrado`);
|
|
136
196
|
}
|
|
137
197
|
await unlink(join(this.environmentsDir, `${this.sanitizeName(name)}.json`));
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
198
|
+
try {
|
|
199
|
+
const globalActive = await readFile(this.activeEnvFile, "utf-8");
|
|
200
|
+
if (globalActive.trim() === name) {
|
|
141
201
|
await unlink(this.activeEnvFile);
|
|
142
|
-
}
|
|
202
|
+
}
|
|
203
|
+
} catch {
|
|
204
|
+
}
|
|
205
|
+
const projectEnvs = await this.getProjectEnvs();
|
|
206
|
+
let changed = false;
|
|
207
|
+
for (const [project, envName] of Object.entries(projectEnvs)) {
|
|
208
|
+
if (envName === name) {
|
|
209
|
+
delete projectEnvs[project];
|
|
210
|
+
changed = true;
|
|
143
211
|
}
|
|
144
212
|
}
|
|
213
|
+
if (changed) {
|
|
214
|
+
await this.writeJson(this.projectEnvsFile, projectEnvs);
|
|
215
|
+
}
|
|
145
216
|
}
|
|
146
217
|
/**
|
|
147
218
|
* Carga las variables del entorno activo.
|
|
@@ -590,7 +661,8 @@ function registerEnvironmentTools(server, storage) {
|
|
|
590
661
|
"Crea un nuevo entorno (ej: dev, staging, prod) con variables opcionales.",
|
|
591
662
|
{
|
|
592
663
|
name: z3.string().describe("Nombre del entorno (ej: dev, staging, prod)"),
|
|
593
|
-
variables: z3.record(z3.string()).optional().describe("Variables iniciales como key-value")
|
|
664
|
+
variables: z3.record(z3.string()).optional().describe("Variables iniciales como key-value"),
|
|
665
|
+
spec: z3.string().optional().describe('Nombre del spec API asociado (ej: "cocaxcode-api")')
|
|
594
666
|
},
|
|
595
667
|
async (params) => {
|
|
596
668
|
try {
|
|
@@ -598,16 +670,18 @@ function registerEnvironmentTools(server, storage) {
|
|
|
598
670
|
const env = {
|
|
599
671
|
name: params.name,
|
|
600
672
|
variables: params.variables ?? {},
|
|
673
|
+
spec: params.spec,
|
|
601
674
|
createdAt: now,
|
|
602
675
|
updatedAt: now
|
|
603
676
|
};
|
|
604
677
|
await storage.createEnvironment(env);
|
|
605
678
|
const varCount = Object.keys(env.variables).length;
|
|
679
|
+
const specMsg = params.spec ? ` \u2014 spec: '${params.spec}'` : "";
|
|
606
680
|
return {
|
|
607
681
|
content: [
|
|
608
682
|
{
|
|
609
683
|
type: "text",
|
|
610
|
-
text: `Entorno '${params.name}' creado con ${varCount} variable(s)`
|
|
684
|
+
text: `Entorno '${params.name}' creado con ${varCount} variable(s)${specMsg}`
|
|
611
685
|
}
|
|
612
686
|
]
|
|
613
687
|
};
|
|
@@ -762,6 +836,41 @@ function registerEnvironmentTools(server, storage) {
|
|
|
762
836
|
}
|
|
763
837
|
}
|
|
764
838
|
);
|
|
839
|
+
server.tool(
|
|
840
|
+
"env_spec",
|
|
841
|
+
"Asocia o desasocia un spec API a un entorno. Si no se especifica entorno, usa el activo.",
|
|
842
|
+
{
|
|
843
|
+
spec: z3.string().optional().describe("Nombre del spec a asociar. Si se omite, desasocia el spec actual"),
|
|
844
|
+
environment: z3.string().optional().describe("Entorno destino (default: entorno activo)")
|
|
845
|
+
},
|
|
846
|
+
async (params) => {
|
|
847
|
+
try {
|
|
848
|
+
const envName = params.environment ?? await storage.getActiveEnvironment();
|
|
849
|
+
if (!envName) {
|
|
850
|
+
return {
|
|
851
|
+
content: [
|
|
852
|
+
{
|
|
853
|
+
type: "text",
|
|
854
|
+
text: "No hay entorno activo. Usa env_switch para activar uno."
|
|
855
|
+
}
|
|
856
|
+
],
|
|
857
|
+
isError: true
|
|
858
|
+
};
|
|
859
|
+
}
|
|
860
|
+
await storage.setEnvironmentSpec(envName, params.spec ?? null);
|
|
861
|
+
const message = params.spec ? `Spec '${params.spec}' asociado al entorno '${envName}'` : `Spec desasociado del entorno '${envName}'`;
|
|
862
|
+
return {
|
|
863
|
+
content: [{ type: "text", text: message }]
|
|
864
|
+
};
|
|
865
|
+
} catch (error) {
|
|
866
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
867
|
+
return {
|
|
868
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
869
|
+
isError: true
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
);
|
|
765
874
|
server.tool(
|
|
766
875
|
"env_rename",
|
|
767
876
|
"Renombra un entorno existente. Si es el entorno activo, actualiza la referencia.",
|
|
@@ -817,18 +926,95 @@ function registerEnvironmentTools(server, storage) {
|
|
|
817
926
|
);
|
|
818
927
|
server.tool(
|
|
819
928
|
"env_switch",
|
|
820
|
-
"Cambia el entorno activo.
|
|
929
|
+
"Cambia el entorno activo. Si se especifica project, solo aplica a ese directorio de proyecto.",
|
|
821
930
|
{
|
|
822
|
-
name: z3.string().describe("Nombre del entorno a activar")
|
|
931
|
+
name: z3.string().describe("Nombre del entorno a activar"),
|
|
932
|
+
project: z3.string().optional().describe("Ruta del proyecto (ej: C:/cocaxcode). Si se omite, cambia el entorno global")
|
|
823
933
|
},
|
|
824
934
|
async (params) => {
|
|
825
935
|
try {
|
|
826
|
-
await storage.setActiveEnvironment(params.name);
|
|
936
|
+
await storage.setActiveEnvironment(params.name, params.project);
|
|
937
|
+
const scope = params.project ? ` para proyecto '${params.project}'` : " (global)";
|
|
938
|
+
return {
|
|
939
|
+
content: [
|
|
940
|
+
{
|
|
941
|
+
type: "text",
|
|
942
|
+
text: `Entorno activo cambiado a '${params.name}'${scope}`
|
|
943
|
+
}
|
|
944
|
+
]
|
|
945
|
+
};
|
|
946
|
+
} catch (error) {
|
|
947
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
948
|
+
return {
|
|
949
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
950
|
+
isError: true
|
|
951
|
+
};
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
);
|
|
955
|
+
server.tool(
|
|
956
|
+
"env_project_clear",
|
|
957
|
+
"Elimina la asociaci\xF3n de entorno espec\xEDfico de un proyecto. El proyecto usar\xE1 el entorno global.",
|
|
958
|
+
{
|
|
959
|
+
project: z3.string().describe("Ruta del proyecto del que eliminar la asociaci\xF3n")
|
|
960
|
+
},
|
|
961
|
+
async (params) => {
|
|
962
|
+
try {
|
|
963
|
+
const removed = await storage.clearProjectEnvironment(params.project);
|
|
964
|
+
if (!removed) {
|
|
965
|
+
return {
|
|
966
|
+
content: [
|
|
967
|
+
{
|
|
968
|
+
type: "text",
|
|
969
|
+
text: `No hay entorno espec\xEDfico para el proyecto '${params.project}'`
|
|
970
|
+
}
|
|
971
|
+
]
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
return {
|
|
975
|
+
content: [
|
|
976
|
+
{
|
|
977
|
+
type: "text",
|
|
978
|
+
text: `Entorno espec\xEDfico eliminado para proyecto '${params.project}'. Usar\xE1 el entorno global.`
|
|
979
|
+
}
|
|
980
|
+
]
|
|
981
|
+
};
|
|
982
|
+
} catch (error) {
|
|
983
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
984
|
+
return {
|
|
985
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
986
|
+
isError: true
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
);
|
|
991
|
+
server.tool(
|
|
992
|
+
"env_project_list",
|
|
993
|
+
"Lista todos los proyectos con entornos espec\xEDficos asignados.",
|
|
994
|
+
{},
|
|
995
|
+
async () => {
|
|
996
|
+
try {
|
|
997
|
+
const projectEnvs = await storage.listProjectEnvironments();
|
|
998
|
+
const entries = Object.entries(projectEnvs);
|
|
999
|
+
if (entries.length === 0) {
|
|
1000
|
+
return {
|
|
1001
|
+
content: [
|
|
1002
|
+
{
|
|
1003
|
+
type: "text",
|
|
1004
|
+
text: "No hay entornos espec\xEDficos por proyecto. Todos usan el entorno global."
|
|
1005
|
+
}
|
|
1006
|
+
]
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
827
1009
|
return {
|
|
828
1010
|
content: [
|
|
829
1011
|
{
|
|
830
1012
|
type: "text",
|
|
831
|
-
text:
|
|
1013
|
+
text: JSON.stringify(
|
|
1014
|
+
entries.map(([project, env]) => ({ project, environment: env })),
|
|
1015
|
+
null,
|
|
1016
|
+
2
|
|
1017
|
+
)
|
|
832
1018
|
}
|
|
833
1019
|
]
|
|
834
1020
|
};
|
|
@@ -968,6 +1154,21 @@ function parseOpenApiSpec(doc, name, source) {
|
|
|
968
1154
|
|
|
969
1155
|
// src/tools/api-spec.ts
|
|
970
1156
|
import { readFile as readFile2 } from "fs/promises";
|
|
1157
|
+
async function resolveSpecName(name, storage) {
|
|
1158
|
+
if (name) return { name };
|
|
1159
|
+
const activeSpec = await storage.getActiveSpec();
|
|
1160
|
+
if (activeSpec) return { name: activeSpec };
|
|
1161
|
+
const specs = await storage.listSpecs();
|
|
1162
|
+
if (specs.length === 0) {
|
|
1163
|
+
return { error: "No hay specs importados. Usa api_import para importar uno." };
|
|
1164
|
+
}
|
|
1165
|
+
if (specs.length === 1) {
|
|
1166
|
+
return { name: specs[0].name };
|
|
1167
|
+
}
|
|
1168
|
+
return {
|
|
1169
|
+
error: `Hay ${specs.length} specs importados. Especifica cu\xE1l usar: ${specs.map((s) => s.name).join(", ")}`
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
971
1172
|
function registerApiSpecTools(server, storage) {
|
|
972
1173
|
server.tool(
|
|
973
1174
|
"api_import",
|
|
@@ -1030,6 +1231,10 @@ function registerApiSpecTools(server, storage) {
|
|
|
1030
1231
|
}
|
|
1031
1232
|
const spec = parseOpenApiSpec(rawDoc, params.name, params.source);
|
|
1032
1233
|
await storage.saveSpec(spec);
|
|
1234
|
+
const activeEnv = await storage.getActiveEnvironment();
|
|
1235
|
+
if (activeEnv) {
|
|
1236
|
+
await storage.setEnvironmentSpec(activeEnv, params.name);
|
|
1237
|
+
}
|
|
1033
1238
|
const tagCounts = {};
|
|
1034
1239
|
for (const ep of spec.endpoints) {
|
|
1035
1240
|
for (const tag of ep.tags ?? ["sin-tag"]) {
|
|
@@ -1052,6 +1257,7 @@ function registerApiSpecTools(server, storage) {
|
|
|
1052
1257
|
"Endpoints por tag:",
|
|
1053
1258
|
tagSummary,
|
|
1054
1259
|
"",
|
|
1260
|
+
activeEnv ? `Asociado al entorno '${activeEnv}'.` : "",
|
|
1055
1261
|
"Usa api_endpoints para ver los endpoints disponibles.",
|
|
1056
1262
|
"Usa api_endpoint_detail para ver el detalle de un endpoint espec\xEDfico."
|
|
1057
1263
|
].join("\n")
|
|
@@ -1067,24 +1273,66 @@ function registerApiSpecTools(server, storage) {
|
|
|
1067
1273
|
}
|
|
1068
1274
|
}
|
|
1069
1275
|
);
|
|
1276
|
+
server.tool(
|
|
1277
|
+
"api_spec_list",
|
|
1278
|
+
"Lista todos los specs de API importados. \xDAsalo para descubrir qu\xE9 APIs est\xE1n disponibles.",
|
|
1279
|
+
{},
|
|
1280
|
+
async () => {
|
|
1281
|
+
try {
|
|
1282
|
+
const items = await storage.listSpecs();
|
|
1283
|
+
if (items.length === 0) {
|
|
1284
|
+
return {
|
|
1285
|
+
content: [
|
|
1286
|
+
{
|
|
1287
|
+
type: "text",
|
|
1288
|
+
text: "No hay specs importados. Usa api_import para importar uno."
|
|
1289
|
+
}
|
|
1290
|
+
]
|
|
1291
|
+
};
|
|
1292
|
+
}
|
|
1293
|
+
return {
|
|
1294
|
+
content: [
|
|
1295
|
+
{
|
|
1296
|
+
type: "text",
|
|
1297
|
+
text: JSON.stringify(items, null, 2)
|
|
1298
|
+
}
|
|
1299
|
+
]
|
|
1300
|
+
};
|
|
1301
|
+
} catch (error) {
|
|
1302
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1303
|
+
return {
|
|
1304
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
1305
|
+
isError: true
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
);
|
|
1070
1310
|
server.tool(
|
|
1071
1311
|
"api_endpoints",
|
|
1072
|
-
"Lista los endpoints de un API importada. Filtra por tag, m\xE9todo o path.",
|
|
1312
|
+
"Lista los endpoints de un API importada. Filtra por tag, m\xE9todo o path. Si no se especifica nombre y solo hay un spec importado, lo usa autom\xE1ticamente.",
|
|
1073
1313
|
{
|
|
1074
|
-
name: z4.string().describe("Nombre del API importada"),
|
|
1314
|
+
name: z4.string().optional().describe("Nombre del API importada. Si se omite y solo hay un spec, lo usa autom\xE1ticamente"),
|
|
1075
1315
|
tag: z4.string().optional().describe('Filtrar por tag (ej: "blog", "auth", "users")'),
|
|
1076
1316
|
method: z4.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]).optional().describe("Filtrar por m\xE9todo HTTP"),
|
|
1077
1317
|
path: z4.string().optional().describe('Filtrar por path (b\xFAsqueda parcial, ej: "/blog" muestra todos los que contienen /blog)')
|
|
1078
1318
|
},
|
|
1079
1319
|
async (params) => {
|
|
1080
1320
|
try {
|
|
1081
|
-
const
|
|
1321
|
+
const resolved = await resolveSpecName(params.name, storage);
|
|
1322
|
+
if (resolved.error) {
|
|
1323
|
+
return {
|
|
1324
|
+
content: [{ type: "text", text: resolved.error }],
|
|
1325
|
+
isError: true
|
|
1326
|
+
};
|
|
1327
|
+
}
|
|
1328
|
+
const resolvedName = resolved.name;
|
|
1329
|
+
const spec = await storage.getSpec(resolvedName);
|
|
1082
1330
|
if (!spec) {
|
|
1083
1331
|
return {
|
|
1084
1332
|
content: [
|
|
1085
1333
|
{
|
|
1086
1334
|
type: "text",
|
|
1087
|
-
text: `Error: API '${
|
|
1335
|
+
text: `Error: API '${resolvedName}' no encontrada. Usa api_import para importarla primero.`
|
|
1088
1336
|
}
|
|
1089
1337
|
],
|
|
1090
1338
|
isError: true
|
|
@@ -1145,19 +1393,27 @@ function registerApiSpecTools(server, storage) {
|
|
|
1145
1393
|
"api_endpoint_detail",
|
|
1146
1394
|
"Muestra el detalle completo de un endpoint: par\xE1metros, body schema, y respuestas. \xDAtil para saber qu\xE9 datos enviar.",
|
|
1147
1395
|
{
|
|
1148
|
-
name: z4.string().describe("Nombre del API importada"),
|
|
1396
|
+
name: z4.string().optional().describe("Nombre del API importada. Si se omite y solo hay un spec, lo usa autom\xE1ticamente"),
|
|
1149
1397
|
method: z4.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]).describe("M\xE9todo HTTP del endpoint"),
|
|
1150
1398
|
path: z4.string().describe('Path exacto del endpoint (ej: "/blog", "/auth/login")')
|
|
1151
1399
|
},
|
|
1152
1400
|
async (params) => {
|
|
1153
1401
|
try {
|
|
1154
|
-
const
|
|
1402
|
+
const resolved = await resolveSpecName(params.name, storage);
|
|
1403
|
+
if (resolved.error) {
|
|
1404
|
+
return {
|
|
1405
|
+
content: [{ type: "text", text: resolved.error }],
|
|
1406
|
+
isError: true
|
|
1407
|
+
};
|
|
1408
|
+
}
|
|
1409
|
+
const resolvedName = resolved.name;
|
|
1410
|
+
const spec = await storage.getSpec(resolvedName);
|
|
1155
1411
|
if (!spec) {
|
|
1156
1412
|
return {
|
|
1157
1413
|
content: [
|
|
1158
1414
|
{
|
|
1159
1415
|
type: "text",
|
|
1160
|
-
text: `Error: API '${
|
|
1416
|
+
text: `Error: API '${resolvedName}' no encontrada. Usa api_import para importarla primero.`
|
|
1161
1417
|
}
|
|
1162
1418
|
],
|
|
1163
1419
|
isError: true
|
|
@@ -2232,7 +2488,7 @@ function registerLoadTestTool(server, storage) {
|
|
|
2232
2488
|
}
|
|
2233
2489
|
|
|
2234
2490
|
// src/server.ts
|
|
2235
|
-
var VERSION = "0.
|
|
2491
|
+
var VERSION = "0.7.0";
|
|
2236
2492
|
function createServer(storageDir) {
|
|
2237
2493
|
const server = new McpServer({
|
|
2238
2494
|
name: "api-testing-mcp",
|