@cocaxcode/api-testing-mcp 0.1.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/LICENSE +21 -0
- package/README.md +212 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +742 -0
- package/dist/index.js.map +1 -0
- package/package.json +61 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 cocaxcode
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# @cocaxcode/api-testing-mcp
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@cocaxcode/api-testing-mcp)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
MCP server for API testing. Lightweight, local, zero cloud dependencies.
|
|
7
|
+
|
|
8
|
+
Test your APIs directly from Claude Code, Claude Desktop, Cursor, or any MCP client — without leaving your workflow.
|
|
9
|
+
|
|
10
|
+
## Features
|
|
11
|
+
|
|
12
|
+
- **HTTP requests** — GET, POST, PUT, PATCH, DELETE with headers, body, query params
|
|
13
|
+
- **Authentication** — Bearer token, API Key, Basic Auth built-in
|
|
14
|
+
- **Collections** — Save, organize, and reuse requests locally
|
|
15
|
+
- **Environments** — Manage variables per environment (dev/staging/prod)
|
|
16
|
+
- **Variable interpolation** — Use `{{VARIABLE}}` in URLs, headers, and body
|
|
17
|
+
- **Response metrics** — Timing (ms) and response size for every request
|
|
18
|
+
- **Zero cloud dependencies** — Everything stored locally as JSON files
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
### Claude Desktop
|
|
23
|
+
|
|
24
|
+
Add to your `claude_desktop_config.json`:
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"api-testing": {
|
|
30
|
+
"command": "npx",
|
|
31
|
+
"args": ["-y", "@cocaxcode/api-testing-mcp"]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Claude Code
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
claude mcp add api-testing -- npx -y @cocaxcode/api-testing-mcp
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Custom storage directory
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"mcpServers": {
|
|
48
|
+
"api-testing": {
|
|
49
|
+
"command": "npx",
|
|
50
|
+
"args": ["-y", "@cocaxcode/api-testing-mcp"],
|
|
51
|
+
"env": {
|
|
52
|
+
"API_TESTING_DIR": "/path/to/your/.api-testing"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Tools
|
|
60
|
+
|
|
61
|
+
### `request`
|
|
62
|
+
|
|
63
|
+
Execute an HTTP request with optional authentication and variable interpolation.
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
request({
|
|
67
|
+
method: "GET",
|
|
68
|
+
url: "{{BASE_URL}}/api/users",
|
|
69
|
+
headers: { "Authorization": "Bearer {{TOKEN}}" },
|
|
70
|
+
query: { "page": "1" },
|
|
71
|
+
timeout: 5000
|
|
72
|
+
})
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Auth examples:**
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
// Bearer token
|
|
79
|
+
request({ method: "GET", url: "...", auth: { type: "bearer", token: "abc123" } })
|
|
80
|
+
|
|
81
|
+
// API Key
|
|
82
|
+
request({ method: "GET", url: "...", auth: { type: "api-key", key: "mykey", header: "X-API-Key" } })
|
|
83
|
+
|
|
84
|
+
// Basic Auth
|
|
85
|
+
request({ method: "GET", url: "...", auth: { type: "basic", username: "user", password: "pass" } })
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**Response format:**
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"status": 200,
|
|
93
|
+
"statusText": "OK",
|
|
94
|
+
"headers": { "content-type": "application/json" },
|
|
95
|
+
"body": { "users": [] },
|
|
96
|
+
"timing": { "total_ms": 142.35 },
|
|
97
|
+
"size_bytes": 1024
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### `collection_save`
|
|
102
|
+
|
|
103
|
+
Save a request to your local collection for reuse.
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
collection_save({
|
|
107
|
+
name: "get-users",
|
|
108
|
+
request: { method: "GET", url: "https://api.example.com/users" },
|
|
109
|
+
tags: ["users", "read"]
|
|
110
|
+
})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### `collection_list`
|
|
114
|
+
|
|
115
|
+
List all saved requests. Optionally filter by tag.
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
collection_list({ tag: "users" })
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### `collection_get`
|
|
122
|
+
|
|
123
|
+
Get the full details of a saved request.
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
collection_get({ name: "get-users" })
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### `collection_delete`
|
|
130
|
+
|
|
131
|
+
Delete a saved request from the collection.
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
collection_delete({ name: "get-users" })
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### `env_create`
|
|
138
|
+
|
|
139
|
+
Create a new environment with optional initial variables.
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
env_create({
|
|
143
|
+
name: "dev",
|
|
144
|
+
variables: { "BASE_URL": "http://localhost:3000", "TOKEN": "dev-token" }
|
|
145
|
+
})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### `env_list`
|
|
149
|
+
|
|
150
|
+
List all environments and which one is active.
|
|
151
|
+
|
|
152
|
+
### `env_set`
|
|
153
|
+
|
|
154
|
+
Set a variable in an environment (defaults to active environment).
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
env_set({ key: "TOKEN", value: "new-token-value" })
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### `env_get`
|
|
161
|
+
|
|
162
|
+
Get a specific variable or all variables from an environment.
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
env_get({ key: "BASE_URL" })
|
|
166
|
+
env_get({}) // returns all variables
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### `env_switch`
|
|
170
|
+
|
|
171
|
+
Switch the active environment. Active environment variables are used for `{{interpolation}}`.
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
env_switch({ name: "prod" })
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Storage
|
|
178
|
+
|
|
179
|
+
All data is stored locally as JSON files in `.api-testing/` (in your current working directory by default):
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
.api-testing/
|
|
183
|
+
├── active-env # Name of the active environment
|
|
184
|
+
├── collections/
|
|
185
|
+
│ ├── get-users.json
|
|
186
|
+
│ └── create-post.json
|
|
187
|
+
└── environments/
|
|
188
|
+
├── dev.json
|
|
189
|
+
└── prod.json
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
You can version these files in git if you want to share collections and environments with your team.
|
|
193
|
+
|
|
194
|
+
## Development
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
git clone https://github.com/cocaxcode/api-testing-mcp.git
|
|
198
|
+
cd api-testing-mcp
|
|
199
|
+
npm install
|
|
200
|
+
npm test
|
|
201
|
+
npm run build
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Test with MCP Inspector
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
npx @modelcontextprotocol/inspector node dist/index.js
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## License
|
|
211
|
+
|
|
212
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,742 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
#!/usr/bin/env node
|
|
3
|
+
|
|
4
|
+
// src/index.ts
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
|
|
7
|
+
// src/server.ts
|
|
8
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
|
+
|
|
10
|
+
// src/lib/storage.ts
|
|
11
|
+
import { mkdir, readFile, writeFile, readdir, unlink } from "fs/promises";
|
|
12
|
+
import { join } from "path";
|
|
13
|
+
var Storage = class {
|
|
14
|
+
baseDir;
|
|
15
|
+
collectionsDir;
|
|
16
|
+
environmentsDir;
|
|
17
|
+
activeEnvFile;
|
|
18
|
+
constructor(baseDir) {
|
|
19
|
+
this.baseDir = baseDir ?? process.env.API_TESTING_DIR ?? join(process.cwd(), ".api-testing");
|
|
20
|
+
this.collectionsDir = join(this.baseDir, "collections");
|
|
21
|
+
this.environmentsDir = join(this.baseDir, "environments");
|
|
22
|
+
this.activeEnvFile = join(this.baseDir, "active-env");
|
|
23
|
+
}
|
|
24
|
+
// ── Collections ──
|
|
25
|
+
async saveCollection(saved) {
|
|
26
|
+
await this.ensureDir("collections");
|
|
27
|
+
const filePath = join(this.collectionsDir, `${this.sanitizeName(saved.name)}.json`);
|
|
28
|
+
await this.writeJson(filePath, saved);
|
|
29
|
+
}
|
|
30
|
+
async getCollection(name) {
|
|
31
|
+
const filePath = join(this.collectionsDir, `${this.sanitizeName(name)}.json`);
|
|
32
|
+
return this.readJson(filePath);
|
|
33
|
+
}
|
|
34
|
+
async listCollections(tag) {
|
|
35
|
+
await this.ensureDir("collections");
|
|
36
|
+
const files = await this.listJsonFiles(this.collectionsDir);
|
|
37
|
+
const items = [];
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
const saved = await this.readJson(join(this.collectionsDir, file));
|
|
40
|
+
if (!saved) continue;
|
|
41
|
+
if (tag && !(saved.tags ?? []).includes(tag)) continue;
|
|
42
|
+
items.push({
|
|
43
|
+
name: saved.name,
|
|
44
|
+
method: saved.request.method,
|
|
45
|
+
url: saved.request.url,
|
|
46
|
+
tags: saved.tags ?? []
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return items;
|
|
50
|
+
}
|
|
51
|
+
async deleteCollection(name) {
|
|
52
|
+
const filePath = join(this.collectionsDir, `${this.sanitizeName(name)}.json`);
|
|
53
|
+
try {
|
|
54
|
+
await unlink(filePath);
|
|
55
|
+
return true;
|
|
56
|
+
} catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// ── Environments ──
|
|
61
|
+
async createEnvironment(env) {
|
|
62
|
+
await this.ensureDir("environments");
|
|
63
|
+
const filePath = join(this.environmentsDir, `${this.sanitizeName(env.name)}.json`);
|
|
64
|
+
await this.writeJson(filePath, env);
|
|
65
|
+
}
|
|
66
|
+
async getEnvironment(name) {
|
|
67
|
+
const filePath = join(this.environmentsDir, `${this.sanitizeName(name)}.json`);
|
|
68
|
+
return this.readJson(filePath);
|
|
69
|
+
}
|
|
70
|
+
async listEnvironments() {
|
|
71
|
+
await this.ensureDir("environments");
|
|
72
|
+
const files = await this.listJsonFiles(this.environmentsDir);
|
|
73
|
+
const activeEnv = await this.getActiveEnvironment();
|
|
74
|
+
const items = [];
|
|
75
|
+
for (const file of files) {
|
|
76
|
+
const env = await this.readJson(join(this.environmentsDir, file));
|
|
77
|
+
if (!env) continue;
|
|
78
|
+
items.push({
|
|
79
|
+
name: env.name,
|
|
80
|
+
active: env.name === activeEnv,
|
|
81
|
+
variableCount: Object.keys(env.variables).length
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return items;
|
|
85
|
+
}
|
|
86
|
+
async updateEnvironment(name, variables) {
|
|
87
|
+
const env = await this.getEnvironment(name);
|
|
88
|
+
if (!env) {
|
|
89
|
+
throw new Error(`Entorno '${name}' no encontrado`);
|
|
90
|
+
}
|
|
91
|
+
env.variables = { ...env.variables, ...variables };
|
|
92
|
+
env.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
93
|
+
const filePath = join(this.environmentsDir, `${this.sanitizeName(name)}.json`);
|
|
94
|
+
await this.writeJson(filePath, env);
|
|
95
|
+
}
|
|
96
|
+
async getActiveEnvironment() {
|
|
97
|
+
try {
|
|
98
|
+
const content = await readFile(this.activeEnvFile, "utf-8");
|
|
99
|
+
return content.trim() || null;
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async setActiveEnvironment(name) {
|
|
105
|
+
const env = await this.getEnvironment(name);
|
|
106
|
+
if (!env) {
|
|
107
|
+
throw new Error(`Entorno '${name}' no encontrado`);
|
|
108
|
+
}
|
|
109
|
+
await this.ensureDir("");
|
|
110
|
+
await writeFile(this.activeEnvFile, name, "utf-8");
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Carga las variables del entorno activo.
|
|
114
|
+
* Retorna objeto vacío si no hay entorno activo.
|
|
115
|
+
*/
|
|
116
|
+
async getActiveVariables() {
|
|
117
|
+
const activeName = await this.getActiveEnvironment();
|
|
118
|
+
if (!activeName) return {};
|
|
119
|
+
const env = await this.getEnvironment(activeName);
|
|
120
|
+
return env?.variables ?? {};
|
|
121
|
+
}
|
|
122
|
+
// ── Internal ──
|
|
123
|
+
async ensureDir(subdir) {
|
|
124
|
+
const dir = subdir ? join(this.baseDir, subdir) : this.baseDir;
|
|
125
|
+
await mkdir(dir, { recursive: true });
|
|
126
|
+
}
|
|
127
|
+
async readJson(filePath) {
|
|
128
|
+
try {
|
|
129
|
+
const content = await readFile(filePath, "utf-8");
|
|
130
|
+
return JSON.parse(content);
|
|
131
|
+
} catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
async writeJson(filePath, data) {
|
|
136
|
+
await writeFile(filePath, JSON.stringify(data, null, 2), "utf-8");
|
|
137
|
+
}
|
|
138
|
+
async listJsonFiles(dir) {
|
|
139
|
+
try {
|
|
140
|
+
const entries = await readdir(dir);
|
|
141
|
+
return entries.filter((f) => f.endsWith(".json")).sort();
|
|
142
|
+
} catch {
|
|
143
|
+
return [];
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Sanitiza un nombre para usarlo como nombre de archivo.
|
|
148
|
+
* Reemplaza caracteres no alfanuméricos por guiones.
|
|
149
|
+
*/
|
|
150
|
+
sanitizeName(name) {
|
|
151
|
+
return name.toLowerCase().replace(/[^a-z0-9_-]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// src/tools/request.ts
|
|
156
|
+
import { z } from "zod";
|
|
157
|
+
|
|
158
|
+
// src/lib/http-client.ts
|
|
159
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
160
|
+
function applyAuth(headers, auth) {
|
|
161
|
+
const result = { ...headers };
|
|
162
|
+
switch (auth.type) {
|
|
163
|
+
case "bearer":
|
|
164
|
+
if (auth.token) {
|
|
165
|
+
result["Authorization"] = `Bearer ${auth.token}`;
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
case "api-key":
|
|
169
|
+
if (auth.key) {
|
|
170
|
+
const headerName = auth.header ?? "X-API-Key";
|
|
171
|
+
result[headerName] = auth.key;
|
|
172
|
+
}
|
|
173
|
+
break;
|
|
174
|
+
case "basic":
|
|
175
|
+
if (auth.username && auth.password) {
|
|
176
|
+
const credentials = Buffer.from(`${auth.username}:${auth.password}`).toString("base64");
|
|
177
|
+
result["Authorization"] = `Basic ${credentials}`;
|
|
178
|
+
}
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
function buildUrl(baseUrl, query) {
|
|
184
|
+
const url = new URL(baseUrl);
|
|
185
|
+
if (query) {
|
|
186
|
+
for (const [key, value] of Object.entries(query)) {
|
|
187
|
+
url.searchParams.set(key, value);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return url.toString();
|
|
191
|
+
}
|
|
192
|
+
async function executeRequest(config) {
|
|
193
|
+
const timeout = config.timeout ?? DEFAULT_TIMEOUT;
|
|
194
|
+
const url = buildUrl(config.url, config.query);
|
|
195
|
+
let headers = { ...config.headers };
|
|
196
|
+
if (config.auth) {
|
|
197
|
+
headers = applyAuth(headers, config.auth);
|
|
198
|
+
}
|
|
199
|
+
let body;
|
|
200
|
+
if (config.body !== void 0 && config.body !== null) {
|
|
201
|
+
if (typeof config.body === "string") {
|
|
202
|
+
body = config.body;
|
|
203
|
+
} else {
|
|
204
|
+
body = JSON.stringify(config.body);
|
|
205
|
+
if (!headers["Content-Type"] && !headers["content-type"]) {
|
|
206
|
+
headers["Content-Type"] = "application/json";
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
const controller = new AbortController();
|
|
211
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
212
|
+
const startTime = performance.now();
|
|
213
|
+
try {
|
|
214
|
+
const response = await fetch(url, {
|
|
215
|
+
method: config.method,
|
|
216
|
+
headers,
|
|
217
|
+
body,
|
|
218
|
+
signal: controller.signal
|
|
219
|
+
});
|
|
220
|
+
const endTime = performance.now();
|
|
221
|
+
const totalMs = Math.round((endTime - startTime) * 100) / 100;
|
|
222
|
+
const responseText = await response.text();
|
|
223
|
+
let responseBody;
|
|
224
|
+
try {
|
|
225
|
+
responseBody = JSON.parse(responseText);
|
|
226
|
+
} catch {
|
|
227
|
+
responseBody = responseText;
|
|
228
|
+
}
|
|
229
|
+
const responseHeaders = {};
|
|
230
|
+
response.headers.forEach((value, key) => {
|
|
231
|
+
responseHeaders[key] = value;
|
|
232
|
+
});
|
|
233
|
+
const sizeBytes = Number(response.headers.get("content-length")) || Buffer.byteLength(responseText, "utf-8");
|
|
234
|
+
return {
|
|
235
|
+
status: response.status,
|
|
236
|
+
statusText: response.statusText,
|
|
237
|
+
headers: responseHeaders,
|
|
238
|
+
body: responseBody,
|
|
239
|
+
timing: { total_ms: totalMs },
|
|
240
|
+
size_bytes: sizeBytes
|
|
241
|
+
};
|
|
242
|
+
} catch (error) {
|
|
243
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
244
|
+
throw new Error(`Request timeout: superado el l\xEDmite de ${timeout}ms`);
|
|
245
|
+
}
|
|
246
|
+
throw error;
|
|
247
|
+
} finally {
|
|
248
|
+
clearTimeout(timeoutId);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// src/lib/interpolation.ts
|
|
253
|
+
var VARIABLE_PATTERN = /\{\{(\w+)\}\}/g;
|
|
254
|
+
function interpolateString(template, variables) {
|
|
255
|
+
return template.replace(VARIABLE_PATTERN, (match, varName) => {
|
|
256
|
+
return varName in variables ? variables[varName] : match;
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
function interpolateValue(value, variables) {
|
|
260
|
+
if (typeof value === "string") {
|
|
261
|
+
return interpolateString(value, variables);
|
|
262
|
+
}
|
|
263
|
+
if (Array.isArray(value)) {
|
|
264
|
+
return value.map((item) => interpolateValue(item, variables));
|
|
265
|
+
}
|
|
266
|
+
if (value !== null && typeof value === "object") {
|
|
267
|
+
const result = {};
|
|
268
|
+
for (const [key, val] of Object.entries(value)) {
|
|
269
|
+
result[key] = interpolateValue(val, variables);
|
|
270
|
+
}
|
|
271
|
+
return result;
|
|
272
|
+
}
|
|
273
|
+
return value;
|
|
274
|
+
}
|
|
275
|
+
function interpolateRecord(record, variables) {
|
|
276
|
+
if (!record) return void 0;
|
|
277
|
+
const result = {};
|
|
278
|
+
for (const [key, value] of Object.entries(record)) {
|
|
279
|
+
result[key] = interpolateString(value, variables);
|
|
280
|
+
}
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
function interpolateRequest(config, variables) {
|
|
284
|
+
return {
|
|
285
|
+
...config,
|
|
286
|
+
url: interpolateString(config.url, variables),
|
|
287
|
+
headers: interpolateRecord(config.headers, variables),
|
|
288
|
+
query: interpolateRecord(config.query, variables),
|
|
289
|
+
body: config.body !== void 0 ? interpolateValue(config.body, variables) : void 0
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/tools/request.ts
|
|
294
|
+
var AuthSchema = {
|
|
295
|
+
type: z.enum(["bearer", "api-key", "basic"]).describe("Tipo de autenticaci\xF3n"),
|
|
296
|
+
token: z.string().optional().describe("Token para Bearer auth"),
|
|
297
|
+
key: z.string().optional().describe("API key value"),
|
|
298
|
+
header: z.string().optional().describe("Header name para API key (default: X-API-Key)"),
|
|
299
|
+
username: z.string().optional().describe("Username para Basic auth"),
|
|
300
|
+
password: z.string().optional().describe("Password para Basic auth")
|
|
301
|
+
};
|
|
302
|
+
function registerRequestTool(server, storage) {
|
|
303
|
+
server.tool(
|
|
304
|
+
"request",
|
|
305
|
+
"Ejecuta un HTTP request. Soporta {{variables}} del entorno activo para URL, headers y body.",
|
|
306
|
+
{
|
|
307
|
+
method: z.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]).describe("HTTP method"),
|
|
308
|
+
url: z.string().describe("URL del endpoint. Soporta {{variables}} de entorno"),
|
|
309
|
+
headers: z.record(z.string()).optional().describe("Headers HTTP como key-value pairs"),
|
|
310
|
+
body: z.any().optional().describe("Body del request (JSON). Soporta {{variables}}"),
|
|
311
|
+
query: z.record(z.string()).optional().describe("Query parameters como key-value pairs"),
|
|
312
|
+
timeout: z.number().optional().describe("Timeout en milisegundos (default: 30000)"),
|
|
313
|
+
auth: z.object(AuthSchema).optional().describe("Configuraci\xF3n de autenticaci\xF3n")
|
|
314
|
+
},
|
|
315
|
+
async (params) => {
|
|
316
|
+
try {
|
|
317
|
+
const variables = await storage.getActiveVariables();
|
|
318
|
+
const config = {
|
|
319
|
+
method: params.method,
|
|
320
|
+
url: params.url,
|
|
321
|
+
headers: params.headers,
|
|
322
|
+
body: params.body,
|
|
323
|
+
query: params.query,
|
|
324
|
+
timeout: params.timeout,
|
|
325
|
+
auth: params.auth
|
|
326
|
+
};
|
|
327
|
+
const interpolated = interpolateRequest(config, variables);
|
|
328
|
+
const response = await executeRequest(interpolated);
|
|
329
|
+
return {
|
|
330
|
+
content: [
|
|
331
|
+
{
|
|
332
|
+
type: "text",
|
|
333
|
+
text: JSON.stringify(response, null, 2)
|
|
334
|
+
}
|
|
335
|
+
]
|
|
336
|
+
};
|
|
337
|
+
} catch (error) {
|
|
338
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
339
|
+
return {
|
|
340
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
341
|
+
isError: true
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// src/tools/collection.ts
|
|
349
|
+
import { z as z2 } from "zod";
|
|
350
|
+
var AuthSchema2 = {
|
|
351
|
+
type: z2.enum(["bearer", "api-key", "basic"]).describe("Tipo de autenticaci\xF3n"),
|
|
352
|
+
token: z2.string().optional().describe("Token para Bearer auth"),
|
|
353
|
+
key: z2.string().optional().describe("API key value"),
|
|
354
|
+
header: z2.string().optional().describe("Header name para API key (default: X-API-Key)"),
|
|
355
|
+
username: z2.string().optional().describe("Username para Basic auth"),
|
|
356
|
+
password: z2.string().optional().describe("Password para Basic auth")
|
|
357
|
+
};
|
|
358
|
+
function registerCollectionTools(server, storage) {
|
|
359
|
+
server.tool(
|
|
360
|
+
"collection_save",
|
|
361
|
+
"Guarda un request en la colecci\xF3n local. Si ya existe un request con el mismo nombre, lo sobreescribe.",
|
|
362
|
+
{
|
|
363
|
+
name: z2.string().describe("Nombre \xFAnico del request guardado"),
|
|
364
|
+
request: z2.object({
|
|
365
|
+
method: z2.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]),
|
|
366
|
+
url: z2.string(),
|
|
367
|
+
headers: z2.record(z2.string()).optional(),
|
|
368
|
+
body: z2.any().optional(),
|
|
369
|
+
query: z2.record(z2.string()).optional(),
|
|
370
|
+
auth: z2.object(AuthSchema2).optional()
|
|
371
|
+
}).describe("Configuraci\xF3n del request a guardar"),
|
|
372
|
+
tags: z2.array(z2.string()).optional().describe('Tags para organizar (ej: ["auth", "users"])')
|
|
373
|
+
},
|
|
374
|
+
async (params) => {
|
|
375
|
+
try {
|
|
376
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
377
|
+
const existing = await storage.getCollection(params.name);
|
|
378
|
+
const saved = {
|
|
379
|
+
name: params.name,
|
|
380
|
+
request: params.request,
|
|
381
|
+
tags: params.tags,
|
|
382
|
+
createdAt: existing?.createdAt ?? now,
|
|
383
|
+
updatedAt: now
|
|
384
|
+
};
|
|
385
|
+
await storage.saveCollection(saved);
|
|
386
|
+
return {
|
|
387
|
+
content: [
|
|
388
|
+
{
|
|
389
|
+
type: "text",
|
|
390
|
+
text: `Request '${params.name}' guardado (${params.request.method} ${params.request.url})`
|
|
391
|
+
}
|
|
392
|
+
]
|
|
393
|
+
};
|
|
394
|
+
} catch (error) {
|
|
395
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
396
|
+
return {
|
|
397
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
398
|
+
isError: true
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
);
|
|
403
|
+
server.tool(
|
|
404
|
+
"collection_list",
|
|
405
|
+
"Lista todos los requests guardados en la colecci\xF3n. Opcionalmente filtra por tag.",
|
|
406
|
+
{
|
|
407
|
+
tag: z2.string().optional().describe("Filtrar por tag")
|
|
408
|
+
},
|
|
409
|
+
async (params) => {
|
|
410
|
+
try {
|
|
411
|
+
const items = await storage.listCollections(params.tag);
|
|
412
|
+
if (items.length === 0) {
|
|
413
|
+
const msg = params.tag ? `No hay requests con tag '${params.tag}'` : "La colecci\xF3n est\xE1 vac\xEDa";
|
|
414
|
+
return { content: [{ type: "text", text: msg }] };
|
|
415
|
+
}
|
|
416
|
+
return {
|
|
417
|
+
content: [
|
|
418
|
+
{
|
|
419
|
+
type: "text",
|
|
420
|
+
text: JSON.stringify(items, null, 2)
|
|
421
|
+
}
|
|
422
|
+
]
|
|
423
|
+
};
|
|
424
|
+
} catch (error) {
|
|
425
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
426
|
+
return {
|
|
427
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
428
|
+
isError: true
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
);
|
|
433
|
+
server.tool(
|
|
434
|
+
"collection_get",
|
|
435
|
+
"Obtiene los detalles completos de un request guardado por su nombre.",
|
|
436
|
+
{
|
|
437
|
+
name: z2.string().describe("Nombre del request guardado")
|
|
438
|
+
},
|
|
439
|
+
async (params) => {
|
|
440
|
+
try {
|
|
441
|
+
const saved = await storage.getCollection(params.name);
|
|
442
|
+
if (!saved) {
|
|
443
|
+
return {
|
|
444
|
+
content: [
|
|
445
|
+
{
|
|
446
|
+
type: "text",
|
|
447
|
+
text: `Request '${params.name}' no encontrado`
|
|
448
|
+
}
|
|
449
|
+
],
|
|
450
|
+
isError: true
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
return {
|
|
454
|
+
content: [
|
|
455
|
+
{
|
|
456
|
+
type: "text",
|
|
457
|
+
text: JSON.stringify(saved, null, 2)
|
|
458
|
+
}
|
|
459
|
+
]
|
|
460
|
+
};
|
|
461
|
+
} catch (error) {
|
|
462
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
463
|
+
return {
|
|
464
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
465
|
+
isError: true
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
);
|
|
470
|
+
server.tool(
|
|
471
|
+
"collection_delete",
|
|
472
|
+
"Elimina un request guardado de la colecci\xF3n.",
|
|
473
|
+
{
|
|
474
|
+
name: z2.string().describe("Nombre del request a eliminar")
|
|
475
|
+
},
|
|
476
|
+
async (params) => {
|
|
477
|
+
try {
|
|
478
|
+
const deleted = await storage.deleteCollection(params.name);
|
|
479
|
+
if (!deleted) {
|
|
480
|
+
return {
|
|
481
|
+
content: [
|
|
482
|
+
{
|
|
483
|
+
type: "text",
|
|
484
|
+
text: `Request '${params.name}' no encontrado`
|
|
485
|
+
}
|
|
486
|
+
],
|
|
487
|
+
isError: true
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
return {
|
|
491
|
+
content: [
|
|
492
|
+
{
|
|
493
|
+
type: "text",
|
|
494
|
+
text: `Request '${params.name}' eliminado`
|
|
495
|
+
}
|
|
496
|
+
]
|
|
497
|
+
};
|
|
498
|
+
} catch (error) {
|
|
499
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
500
|
+
return {
|
|
501
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
502
|
+
isError: true
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// src/tools/environment.ts
|
|
510
|
+
import { z as z3 } from "zod";
|
|
511
|
+
function registerEnvironmentTools(server, storage) {
|
|
512
|
+
server.tool(
|
|
513
|
+
"env_create",
|
|
514
|
+
"Crea un nuevo entorno (ej: dev, staging, prod) con variables opcionales.",
|
|
515
|
+
{
|
|
516
|
+
name: z3.string().describe("Nombre del entorno (ej: dev, staging, prod)"),
|
|
517
|
+
variables: z3.record(z3.string()).optional().describe("Variables iniciales como key-value")
|
|
518
|
+
},
|
|
519
|
+
async (params) => {
|
|
520
|
+
try {
|
|
521
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
522
|
+
const env = {
|
|
523
|
+
name: params.name,
|
|
524
|
+
variables: params.variables ?? {},
|
|
525
|
+
createdAt: now,
|
|
526
|
+
updatedAt: now
|
|
527
|
+
};
|
|
528
|
+
await storage.createEnvironment(env);
|
|
529
|
+
const varCount = Object.keys(env.variables).length;
|
|
530
|
+
return {
|
|
531
|
+
content: [
|
|
532
|
+
{
|
|
533
|
+
type: "text",
|
|
534
|
+
text: `Entorno '${params.name}' creado con ${varCount} variable(s)`
|
|
535
|
+
}
|
|
536
|
+
]
|
|
537
|
+
};
|
|
538
|
+
} catch (error) {
|
|
539
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
540
|
+
return {
|
|
541
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
542
|
+
isError: true
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
);
|
|
547
|
+
server.tool(
|
|
548
|
+
"env_list",
|
|
549
|
+
"Lista todos los entornos disponibles e indica cu\xE1l est\xE1 activo.",
|
|
550
|
+
{},
|
|
551
|
+
async () => {
|
|
552
|
+
try {
|
|
553
|
+
const items = await storage.listEnvironments();
|
|
554
|
+
if (items.length === 0) {
|
|
555
|
+
return {
|
|
556
|
+
content: [{ type: "text", text: "No hay entornos configurados" }]
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
return {
|
|
560
|
+
content: [
|
|
561
|
+
{
|
|
562
|
+
type: "text",
|
|
563
|
+
text: JSON.stringify(items, null, 2)
|
|
564
|
+
}
|
|
565
|
+
]
|
|
566
|
+
};
|
|
567
|
+
} catch (error) {
|
|
568
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
569
|
+
return {
|
|
570
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
571
|
+
isError: true
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
);
|
|
576
|
+
server.tool(
|
|
577
|
+
"env_set",
|
|
578
|
+
"Establece una variable en un entorno. Si no se especifica entorno, usa el activo.",
|
|
579
|
+
{
|
|
580
|
+
key: z3.string().describe("Nombre de la variable"),
|
|
581
|
+
value: z3.string().describe("Valor de la variable"),
|
|
582
|
+
environment: z3.string().optional().describe("Entorno destino (default: entorno activo)")
|
|
583
|
+
},
|
|
584
|
+
async (params) => {
|
|
585
|
+
try {
|
|
586
|
+
const envName = params.environment ?? await storage.getActiveEnvironment();
|
|
587
|
+
if (!envName) {
|
|
588
|
+
return {
|
|
589
|
+
content: [
|
|
590
|
+
{
|
|
591
|
+
type: "text",
|
|
592
|
+
text: "No hay entorno activo. Usa env_create para crear uno y env_switch para activarlo."
|
|
593
|
+
}
|
|
594
|
+
],
|
|
595
|
+
isError: true
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
await storage.updateEnvironment(envName, { [params.key]: params.value });
|
|
599
|
+
return {
|
|
600
|
+
content: [
|
|
601
|
+
{
|
|
602
|
+
type: "text",
|
|
603
|
+
text: `Variable '${params.key}' establecida en entorno '${envName}'`
|
|
604
|
+
}
|
|
605
|
+
]
|
|
606
|
+
};
|
|
607
|
+
} catch (error) {
|
|
608
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
609
|
+
return {
|
|
610
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
611
|
+
isError: true
|
|
612
|
+
};
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
);
|
|
616
|
+
server.tool(
|
|
617
|
+
"env_get",
|
|
618
|
+
"Obtiene una variable espec\xEDfica o todas las variables de un entorno.",
|
|
619
|
+
{
|
|
620
|
+
key: z3.string().optional().describe("Variable espec\xEDfica. Si se omite, retorna todas"),
|
|
621
|
+
environment: z3.string().optional().describe("Entorno a consultar (default: entorno activo)")
|
|
622
|
+
},
|
|
623
|
+
async (params) => {
|
|
624
|
+
try {
|
|
625
|
+
const envName = params.environment ?? await storage.getActiveEnvironment();
|
|
626
|
+
if (!envName) {
|
|
627
|
+
return {
|
|
628
|
+
content: [
|
|
629
|
+
{
|
|
630
|
+
type: "text",
|
|
631
|
+
text: "No hay entorno activo. Usa env_switch para activar uno."
|
|
632
|
+
}
|
|
633
|
+
],
|
|
634
|
+
isError: true
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
const env = await storage.getEnvironment(envName);
|
|
638
|
+
if (!env) {
|
|
639
|
+
return {
|
|
640
|
+
content: [
|
|
641
|
+
{ type: "text", text: `Entorno '${envName}' no encontrado` }
|
|
642
|
+
],
|
|
643
|
+
isError: true
|
|
644
|
+
};
|
|
645
|
+
}
|
|
646
|
+
if (params.key) {
|
|
647
|
+
const value = env.variables[params.key];
|
|
648
|
+
if (value === void 0) {
|
|
649
|
+
return {
|
|
650
|
+
content: [
|
|
651
|
+
{
|
|
652
|
+
type: "text",
|
|
653
|
+
text: `Variable '${params.key}' no encontrada en entorno '${envName}'`
|
|
654
|
+
}
|
|
655
|
+
],
|
|
656
|
+
isError: true
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
return {
|
|
660
|
+
content: [
|
|
661
|
+
{
|
|
662
|
+
type: "text",
|
|
663
|
+
text: JSON.stringify({ key: params.key, value, environment: envName }, null, 2)
|
|
664
|
+
}
|
|
665
|
+
]
|
|
666
|
+
};
|
|
667
|
+
}
|
|
668
|
+
return {
|
|
669
|
+
content: [
|
|
670
|
+
{
|
|
671
|
+
type: "text",
|
|
672
|
+
text: JSON.stringify(
|
|
673
|
+
{ environment: envName, variables: env.variables },
|
|
674
|
+
null,
|
|
675
|
+
2
|
|
676
|
+
)
|
|
677
|
+
}
|
|
678
|
+
]
|
|
679
|
+
};
|
|
680
|
+
} catch (error) {
|
|
681
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
682
|
+
return {
|
|
683
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
684
|
+
isError: true
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
);
|
|
689
|
+
server.tool(
|
|
690
|
+
"env_switch",
|
|
691
|
+
"Cambia el entorno activo. Las variables del entorno activo se usan en {{interpolaci\xF3n}}.",
|
|
692
|
+
{
|
|
693
|
+
name: z3.string().describe("Nombre del entorno a activar")
|
|
694
|
+
},
|
|
695
|
+
async (params) => {
|
|
696
|
+
try {
|
|
697
|
+
await storage.setActiveEnvironment(params.name);
|
|
698
|
+
return {
|
|
699
|
+
content: [
|
|
700
|
+
{
|
|
701
|
+
type: "text",
|
|
702
|
+
text: `Entorno activo cambiado a '${params.name}'`
|
|
703
|
+
}
|
|
704
|
+
]
|
|
705
|
+
};
|
|
706
|
+
} catch (error) {
|
|
707
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
708
|
+
return {
|
|
709
|
+
content: [{ type: "text", text: `Error: ${message}` }],
|
|
710
|
+
isError: true
|
|
711
|
+
};
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// src/server.ts
|
|
718
|
+
var VERSION = "0.1.0";
|
|
719
|
+
function createServer(storageDir) {
|
|
720
|
+
const server = new McpServer({
|
|
721
|
+
name: "api-testing-mcp",
|
|
722
|
+
version: VERSION
|
|
723
|
+
});
|
|
724
|
+
const storage = new Storage(storageDir);
|
|
725
|
+
registerRequestTool(server, storage);
|
|
726
|
+
registerCollectionTools(server, storage);
|
|
727
|
+
registerEnvironmentTools(server, storage);
|
|
728
|
+
return server;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// src/index.ts
|
|
732
|
+
async function main() {
|
|
733
|
+
const server = createServer();
|
|
734
|
+
const transport = new StdioServerTransport();
|
|
735
|
+
await server.connect(transport);
|
|
736
|
+
console.error("api-testing-mcp server running on stdio");
|
|
737
|
+
}
|
|
738
|
+
main().catch((error) => {
|
|
739
|
+
console.error("Fatal:", error);
|
|
740
|
+
process.exit(1);
|
|
741
|
+
});
|
|
742
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/server.ts","../src/lib/storage.ts","../src/tools/request.ts","../src/lib/http-client.ts","../src/lib/interpolation.ts","../src/tools/collection.ts","../src/tools/environment.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { createServer } from './server.js'\n\nasync function main() {\n const server = createServer()\n const transport = new StdioServerTransport()\n await server.connect(transport)\n console.error('api-testing-mcp server running on stdio')\n}\n\nmain().catch((error) => {\n console.error('Fatal:', error)\n process.exit(1)\n})\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { Storage } from './lib/storage.js'\nimport { registerRequestTool } from './tools/request.js'\nimport { registerCollectionTools } from './tools/collection.js'\nimport { registerEnvironmentTools } from './tools/environment.js'\n\n// Leer version del package.json en build time no es posible con ESM fácilmente,\n// así que la definimos como constante sincronizada manualmente.\nconst VERSION = '0.1.0'\n\n/**\n * Crea y configura el MCP server con todos los tools registrados.\n * Exportada como factory para testabilidad con InMemoryTransport.\n */\nexport function createServer(storageDir?: string): McpServer {\n const server = new McpServer({\n name: 'api-testing-mcp',\n version: VERSION,\n })\n\n const storage = new Storage(storageDir)\n\n // Registrar tools\n registerRequestTool(server, storage)\n registerCollectionTools(server, storage)\n registerEnvironmentTools(server, storage)\n\n return server\n}\n","import { mkdir, readFile, writeFile, readdir, unlink } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport type {\n SavedRequest,\n CollectionListItem,\n Environment,\n EnvironmentListItem,\n} from './types.js'\n\nexport class Storage {\n private readonly baseDir: string\n private readonly collectionsDir: string\n private readonly environmentsDir: string\n private readonly activeEnvFile: string\n\n constructor(baseDir?: string) {\n this.baseDir = baseDir ?? process.env.API_TESTING_DIR ?? join(process.cwd(), '.api-testing')\n this.collectionsDir = join(this.baseDir, 'collections')\n this.environmentsDir = join(this.baseDir, 'environments')\n this.activeEnvFile = join(this.baseDir, 'active-env')\n }\n\n // ── Collections ──\n\n async saveCollection(saved: SavedRequest): Promise<void> {\n await this.ensureDir('collections')\n const filePath = join(this.collectionsDir, `${this.sanitizeName(saved.name)}.json`)\n await this.writeJson(filePath, saved)\n }\n\n async getCollection(name: string): Promise<SavedRequest | null> {\n const filePath = join(this.collectionsDir, `${this.sanitizeName(name)}.json`)\n return this.readJson<SavedRequest>(filePath)\n }\n\n async listCollections(tag?: string): Promise<CollectionListItem[]> {\n await this.ensureDir('collections')\n const files = await this.listJsonFiles(this.collectionsDir)\n const items: CollectionListItem[] = []\n\n for (const file of files) {\n const saved = await this.readJson<SavedRequest>(join(this.collectionsDir, file))\n if (!saved) continue\n\n if (tag && !(saved.tags ?? []).includes(tag)) continue\n\n items.push({\n name: saved.name,\n method: saved.request.method,\n url: saved.request.url,\n tags: saved.tags ?? [],\n })\n }\n\n return items\n }\n\n async deleteCollection(name: string): Promise<boolean> {\n const filePath = join(this.collectionsDir, `${this.sanitizeName(name)}.json`)\n try {\n await unlink(filePath)\n return true\n } catch {\n return false\n }\n }\n\n // ── Environments ──\n\n async createEnvironment(env: Environment): Promise<void> {\n await this.ensureDir('environments')\n const filePath = join(this.environmentsDir, `${this.sanitizeName(env.name)}.json`)\n await this.writeJson(filePath, env)\n }\n\n async getEnvironment(name: string): Promise<Environment | null> {\n const filePath = join(this.environmentsDir, `${this.sanitizeName(name)}.json`)\n return this.readJson<Environment>(filePath)\n }\n\n async listEnvironments(): Promise<EnvironmentListItem[]> {\n await this.ensureDir('environments')\n const files = await this.listJsonFiles(this.environmentsDir)\n const activeEnv = await this.getActiveEnvironment()\n const items: EnvironmentListItem[] = []\n\n for (const file of files) {\n const env = await this.readJson<Environment>(join(this.environmentsDir, file))\n if (!env) continue\n\n items.push({\n name: env.name,\n active: env.name === activeEnv,\n variableCount: Object.keys(env.variables).length,\n })\n }\n\n return items\n }\n\n async updateEnvironment(name: string, variables: Record<string, string>): Promise<void> {\n const env = await this.getEnvironment(name)\n if (!env) {\n throw new Error(`Entorno '${name}' no encontrado`)\n }\n\n env.variables = { ...env.variables, ...variables }\n env.updatedAt = new Date().toISOString()\n\n const filePath = join(this.environmentsDir, `${this.sanitizeName(name)}.json`)\n await this.writeJson(filePath, env)\n }\n\n async getActiveEnvironment(): Promise<string | null> {\n try {\n const content = await readFile(this.activeEnvFile, 'utf-8')\n return content.trim() || null\n } catch {\n return null\n }\n }\n\n async setActiveEnvironment(name: string): Promise<void> {\n // Verificar que el entorno existe\n const env = await this.getEnvironment(name)\n if (!env) {\n throw new Error(`Entorno '${name}' no encontrado`)\n }\n\n await this.ensureDir('')\n await writeFile(this.activeEnvFile, name, 'utf-8')\n }\n\n /**\n * Carga las variables del entorno activo.\n * Retorna objeto vacío si no hay entorno activo.\n */\n async getActiveVariables(): Promise<Record<string, string>> {\n const activeName = await this.getActiveEnvironment()\n if (!activeName) return {}\n\n const env = await this.getEnvironment(activeName)\n return env?.variables ?? {}\n }\n\n // ── Internal ──\n\n private async ensureDir(subdir: string): Promise<void> {\n const dir = subdir ? join(this.baseDir, subdir) : this.baseDir\n await mkdir(dir, { recursive: true })\n }\n\n private async readJson<T>(filePath: string): Promise<T | null> {\n try {\n const content = await readFile(filePath, 'utf-8')\n return JSON.parse(content) as T\n } catch {\n return null\n }\n }\n\n private async writeJson(filePath: string, data: unknown): Promise<void> {\n await writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8')\n }\n\n private async listJsonFiles(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir)\n return entries.filter((f) => f.endsWith('.json')).sort()\n } catch {\n return []\n }\n }\n\n /**\n * Sanitiza un nombre para usarlo como nombre de archivo.\n * Reemplaza caracteres no alfanuméricos por guiones.\n */\n private sanitizeName(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9_-]/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '')\n }\n}\n","import { z } from 'zod'\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { Storage } from '../lib/storage.js'\nimport { executeRequest } from '../lib/http-client.js'\nimport { interpolateRequest } from '../lib/interpolation.js'\nimport type { RequestConfig } from '../lib/types.js'\n\nconst AuthSchema = {\n type: z.enum(['bearer', 'api-key', 'basic']).describe('Tipo de autenticación'),\n token: z.string().optional().describe('Token para Bearer auth'),\n key: z.string().optional().describe('API key value'),\n header: z.string().optional().describe('Header name para API key (default: X-API-Key)'),\n username: z.string().optional().describe('Username para Basic auth'),\n password: z.string().optional().describe('Password para Basic auth'),\n}\n\nexport function registerRequestTool(server: McpServer, storage: Storage): void {\n server.tool(\n 'request',\n 'Ejecuta un HTTP request. Soporta {{variables}} del entorno activo para URL, headers y body.',\n {\n method: z\n .enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS'])\n .describe('HTTP method'),\n url: z.string().describe('URL del endpoint. Soporta {{variables}} de entorno'),\n headers: z\n .record(z.string())\n .optional()\n .describe('Headers HTTP como key-value pairs'),\n body: z.any().optional().describe('Body del request (JSON). Soporta {{variables}}'),\n query: z\n .record(z.string())\n .optional()\n .describe('Query parameters como key-value pairs'),\n timeout: z.number().optional().describe('Timeout en milisegundos (default: 30000)'),\n auth: z\n .object(AuthSchema)\n .optional()\n .describe('Configuración de autenticación'),\n },\n async (params) => {\n try {\n // Cargar variables del entorno activo\n const variables = await storage.getActiveVariables()\n\n // Construir RequestConfig\n const config: RequestConfig = {\n method: params.method,\n url: params.url,\n headers: params.headers,\n body: params.body,\n query: params.query,\n timeout: params.timeout,\n auth: params.auth,\n }\n\n // Interpolar variables\n const interpolated = interpolateRequest(config, variables)\n\n // Ejecutar request\n const response = await executeRequest(interpolated)\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(response, null, 2),\n },\n ],\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n }\n }\n },\n )\n}\n","import type { RequestConfig, RequestResponse, AuthConfig } from './types.js'\n\nconst DEFAULT_TIMEOUT = 30_000\n\n/**\n * Aplica la configuración de auth a los headers del request.\n */\nfunction applyAuth(\n headers: Record<string, string>,\n auth: AuthConfig,\n): Record<string, string> {\n const result = { ...headers }\n\n switch (auth.type) {\n case 'bearer':\n if (auth.token) {\n result['Authorization'] = `Bearer ${auth.token}`\n }\n break\n\n case 'api-key':\n if (auth.key) {\n const headerName = auth.header ?? 'X-API-Key'\n result[headerName] = auth.key\n }\n break\n\n case 'basic':\n if (auth.username && auth.password) {\n const credentials = Buffer.from(`${auth.username}:${auth.password}`).toString('base64')\n result['Authorization'] = `Basic ${credentials}`\n }\n break\n }\n\n return result\n}\n\n/**\n * Construye la URL final con query parameters.\n */\nfunction buildUrl(baseUrl: string, query?: Record<string, string>): string {\n const url = new URL(baseUrl)\n\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n url.searchParams.set(key, value)\n }\n }\n\n return url.toString()\n}\n\n/**\n * Ejecuta un HTTP request y retorna la respuesta con métricas de timing.\n */\nexport async function executeRequest(config: RequestConfig): Promise<RequestResponse> {\n const timeout = config.timeout ?? DEFAULT_TIMEOUT\n\n // Construir URL con query params\n const url = buildUrl(config.url, config.query)\n\n // Preparar headers\n let headers: Record<string, string> = { ...config.headers }\n\n // Aplicar auth\n if (config.auth) {\n headers = applyAuth(headers, config.auth)\n }\n\n // Preparar body\n let body: string | undefined\n if (config.body !== undefined && config.body !== null) {\n if (typeof config.body === 'string') {\n body = config.body\n } else {\n body = JSON.stringify(config.body)\n // Solo añadir Content-Type si no está definido\n if (!headers['Content-Type'] && !headers['content-type']) {\n headers['Content-Type'] = 'application/json'\n }\n }\n }\n\n // AbortController para timeout\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n // Medir timing\n const startTime = performance.now()\n\n try {\n const response = await fetch(url, {\n method: config.method,\n headers,\n body,\n signal: controller.signal,\n })\n\n const endTime = performance.now()\n const totalMs = Math.round((endTime - startTime) * 100) / 100\n\n // Parsear response body\n const responseText = await response.text()\n let responseBody: unknown\n try {\n responseBody = JSON.parse(responseText)\n } catch {\n responseBody = responseText\n }\n\n // Convertir headers a Record\n const responseHeaders: Record<string, string> = {}\n response.headers.forEach((value, key) => {\n responseHeaders[key] = value\n })\n\n // Calcular tamaño\n const sizeBytes =\n Number(response.headers.get('content-length')) ||\n Buffer.byteLength(responseText, 'utf-8')\n\n return {\n status: response.status,\n statusText: response.statusText,\n headers: responseHeaders,\n body: responseBody,\n timing: { total_ms: totalMs },\n size_bytes: sizeBytes,\n }\n } catch (error) {\n if (error instanceof Error && error.name === 'AbortError') {\n throw new Error(`Request timeout: superado el límite de ${timeout}ms`)\n }\n throw error\n } finally {\n clearTimeout(timeoutId)\n }\n}\n","import type { RequestConfig } from './types.js'\n\nconst VARIABLE_PATTERN = /\\{\\{(\\w+)\\}\\}/g\n\n/**\n * Resuelve todas las ocurrencias de {{variable}} en un string.\n * Las variables no encontradas se dejan intactas.\n */\nexport function interpolateString(\n template: string,\n variables: Record<string, string>,\n): string {\n return template.replace(VARIABLE_PATTERN, (match, varName: string) => {\n return varName in variables ? variables[varName] : match\n })\n}\n\n/**\n * Interpola {{variables}} recursivamente en un valor.\n * - string → interpolateString\n * - object → interpola cada valor (recursivo)\n * - array → interpola cada elemento\n * - otros tipos → retorna sin cambios\n */\nfunction interpolateValue(value: unknown, variables: Record<string, string>): unknown {\n if (typeof value === 'string') {\n return interpolateString(value, variables)\n }\n\n if (Array.isArray(value)) {\n return value.map((item) => interpolateValue(item, variables))\n }\n\n if (value !== null && typeof value === 'object') {\n const result: Record<string, unknown> = {}\n for (const [key, val] of Object.entries(value)) {\n result[key] = interpolateValue(val, variables)\n }\n return result\n }\n\n return value\n}\n\n/**\n * Interpola un Record<string, string> (headers, query params).\n * Solo interpola los valores, no las keys.\n */\nfunction interpolateRecord(\n record: Record<string, string> | undefined,\n variables: Record<string, string>,\n): Record<string, string> | undefined {\n if (!record) return undefined\n\n const result: Record<string, string> = {}\n for (const [key, value] of Object.entries(record)) {\n result[key] = interpolateString(value, variables)\n }\n return result\n}\n\n/**\n * Resuelve {{variables}} en todos los campos de un RequestConfig:\n * url, headers (valores), body (recursivo), query params (valores).\n */\nexport function interpolateRequest(\n config: RequestConfig,\n variables: Record<string, string>,\n): RequestConfig {\n return {\n ...config,\n url: interpolateString(config.url, variables),\n headers: interpolateRecord(config.headers, variables),\n query: interpolateRecord(config.query, variables),\n body: config.body !== undefined ? interpolateValue(config.body, variables) : undefined,\n }\n}\n","import { z } from 'zod'\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { Storage } from '../lib/storage.js'\nimport type { SavedRequest } from '../lib/types.js'\n\nconst AuthSchema = {\n type: z.enum(['bearer', 'api-key', 'basic']).describe('Tipo de autenticación'),\n token: z.string().optional().describe('Token para Bearer auth'),\n key: z.string().optional().describe('API key value'),\n header: z.string().optional().describe('Header name para API key (default: X-API-Key)'),\n username: z.string().optional().describe('Username para Basic auth'),\n password: z.string().optional().describe('Password para Basic auth'),\n}\n\nexport function registerCollectionTools(server: McpServer, storage: Storage): void {\n // ── collection_save ──\n server.tool(\n 'collection_save',\n 'Guarda un request en la colección local. Si ya existe un request con el mismo nombre, lo sobreescribe.',\n {\n name: z.string().describe('Nombre único del request guardado'),\n request: z\n .object({\n method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']),\n url: z.string(),\n headers: z.record(z.string()).optional(),\n body: z.any().optional(),\n query: z.record(z.string()).optional(),\n auth: z.object(AuthSchema).optional(),\n })\n .describe('Configuración del request a guardar'),\n tags: z\n .array(z.string())\n .optional()\n .describe('Tags para organizar (ej: [\"auth\", \"users\"])'),\n },\n async (params) => {\n try {\n const now = new Date().toISOString()\n const existing = await storage.getCollection(params.name)\n\n const saved: SavedRequest = {\n name: params.name,\n request: params.request,\n tags: params.tags,\n createdAt: existing?.createdAt ?? now,\n updatedAt: now,\n }\n\n await storage.saveCollection(saved)\n\n return {\n content: [\n {\n type: 'text' as const,\n text: `Request '${params.name}' guardado (${params.request.method} ${params.request.url})`,\n },\n ],\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n }\n }\n },\n )\n\n // ── collection_list ──\n server.tool(\n 'collection_list',\n 'Lista todos los requests guardados en la colección. Opcionalmente filtra por tag.',\n {\n tag: z.string().optional().describe('Filtrar por tag'),\n },\n async (params) => {\n try {\n const items = await storage.listCollections(params.tag)\n\n if (items.length === 0) {\n const msg = params.tag\n ? `No hay requests con tag '${params.tag}'`\n : 'La colección está vacía'\n return { content: [{ type: 'text' as const, text: msg }] }\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(items, null, 2),\n },\n ],\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n }\n }\n },\n )\n\n // ── collection_get ──\n server.tool(\n 'collection_get',\n 'Obtiene los detalles completos de un request guardado por su nombre.',\n {\n name: z.string().describe('Nombre del request guardado'),\n },\n async (params) => {\n try {\n const saved = await storage.getCollection(params.name)\n\n if (!saved) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `Request '${params.name}' no encontrado`,\n },\n ],\n isError: true,\n }\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(saved, null, 2),\n },\n ],\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n }\n }\n },\n )\n\n // ── collection_delete ──\n server.tool(\n 'collection_delete',\n 'Elimina un request guardado de la colección.',\n {\n name: z.string().describe('Nombre del request a eliminar'),\n },\n async (params) => {\n try {\n const deleted = await storage.deleteCollection(params.name)\n\n if (!deleted) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `Request '${params.name}' no encontrado`,\n },\n ],\n isError: true,\n }\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: `Request '${params.name}' eliminado`,\n },\n ],\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n }\n }\n },\n )\n}\n","import { z } from 'zod'\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { Storage } from '../lib/storage.js'\nimport type { Environment } from '../lib/types.js'\n\nexport function registerEnvironmentTools(server: McpServer, storage: Storage): void {\n // ── env_create ──\n server.tool(\n 'env_create',\n 'Crea un nuevo entorno (ej: dev, staging, prod) con variables opcionales.',\n {\n name: z.string().describe('Nombre del entorno (ej: dev, staging, prod)'),\n variables: z\n .record(z.string())\n .optional()\n .describe('Variables iniciales como key-value'),\n },\n async (params) => {\n try {\n const now = new Date().toISOString()\n const env: Environment = {\n name: params.name,\n variables: params.variables ?? {},\n createdAt: now,\n updatedAt: now,\n }\n\n await storage.createEnvironment(env)\n\n const varCount = Object.keys(env.variables).length\n return {\n content: [\n {\n type: 'text' as const,\n text: `Entorno '${params.name}' creado con ${varCount} variable(s)`,\n },\n ],\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n }\n }\n },\n )\n\n // ── env_list ──\n server.tool(\n 'env_list',\n 'Lista todos los entornos disponibles e indica cuál está activo.',\n {},\n async () => {\n try {\n const items = await storage.listEnvironments()\n\n if (items.length === 0) {\n return {\n content: [{ type: 'text' as const, text: 'No hay entornos configurados' }],\n }\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(items, null, 2),\n },\n ],\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n }\n }\n },\n )\n\n // ── env_set ──\n server.tool(\n 'env_set',\n 'Establece una variable en un entorno. Si no se especifica entorno, usa el activo.',\n {\n key: z.string().describe('Nombre de la variable'),\n value: z.string().describe('Valor de la variable'),\n environment: z\n .string()\n .optional()\n .describe('Entorno destino (default: entorno activo)'),\n },\n async (params) => {\n try {\n // Determinar entorno destino\n const envName = params.environment ?? (await storage.getActiveEnvironment())\n\n if (!envName) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No hay entorno activo. Usa env_create para crear uno y env_switch para activarlo.',\n },\n ],\n isError: true,\n }\n }\n\n await storage.updateEnvironment(envName, { [params.key]: params.value })\n\n return {\n content: [\n {\n type: 'text' as const,\n text: `Variable '${params.key}' establecida en entorno '${envName}'`,\n },\n ],\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n }\n }\n },\n )\n\n // ── env_get ──\n server.tool(\n 'env_get',\n 'Obtiene una variable específica o todas las variables de un entorno.',\n {\n key: z\n .string()\n .optional()\n .describe('Variable específica. Si se omite, retorna todas'),\n environment: z\n .string()\n .optional()\n .describe('Entorno a consultar (default: entorno activo)'),\n },\n async (params) => {\n try {\n const envName = params.environment ?? (await storage.getActiveEnvironment())\n\n if (!envName) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No hay entorno activo. Usa env_switch para activar uno.',\n },\n ],\n isError: true,\n }\n }\n\n const env = await storage.getEnvironment(envName)\n if (!env) {\n return {\n content: [\n { type: 'text' as const, text: `Entorno '${envName}' no encontrado` },\n ],\n isError: true,\n }\n }\n\n if (params.key) {\n const value = env.variables[params.key]\n if (value === undefined) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `Variable '${params.key}' no encontrada en entorno '${envName}'`,\n },\n ],\n isError: true,\n }\n }\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ key: params.key, value, environment: envName }, null, 2),\n },\n ],\n }\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify(\n { environment: envName, variables: env.variables },\n null,\n 2,\n ),\n },\n ],\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n }\n }\n },\n )\n\n // ── env_switch ──\n server.tool(\n 'env_switch',\n 'Cambia el entorno activo. Las variables del entorno activo se usan en {{interpolación}}.',\n {\n name: z.string().describe('Nombre del entorno a activar'),\n },\n async (params) => {\n try {\n await storage.setActiveEnvironment(params.name)\n\n return {\n content: [\n {\n type: 'text' as const,\n text: `Entorno activo cambiado a '${params.name}'`,\n },\n ],\n }\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error)\n return {\n content: [{ type: 'text' as const, text: `Error: ${message}` }],\n isError: true,\n }\n }\n },\n )\n}\n"],"mappings":"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","names":["z","AuthSchema","z"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cocaxcode/api-testing-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server para testing de APIs. Ligero, local, sin dependencias cloud.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"api-testing-mcp": "dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsup",
|
|
14
|
+
"dev": "tsup --watch",
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"test:watch": "vitest",
|
|
17
|
+
"test:coverage": "vitest run --coverage",
|
|
18
|
+
"lint": "eslint src/",
|
|
19
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
20
|
+
"format:check": "prettier --check \"src/**/*.ts\"",
|
|
21
|
+
"typecheck": "tsc --noEmit",
|
|
22
|
+
"prepare": "npm run build",
|
|
23
|
+
"inspector": "npx @modelcontextprotocol/inspector node dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20.0.0"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"mcp",
|
|
30
|
+
"api-testing",
|
|
31
|
+
"http",
|
|
32
|
+
"rest",
|
|
33
|
+
"model-context-protocol",
|
|
34
|
+
"claude",
|
|
35
|
+
"ai-tools"
|
|
36
|
+
],
|
|
37
|
+
"author": "cocaxcode",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/cocaxcode/api-testing-mcp.git"
|
|
42
|
+
},
|
|
43
|
+
"homepage": "https://github.com/cocaxcode/api-testing-mcp#readme",
|
|
44
|
+
"bugs": {
|
|
45
|
+
"url": "https://github.com/cocaxcode/api-testing-mcp/issues"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@modelcontextprotocol/sdk": "^1.27.0",
|
|
49
|
+
"zod": "^3.25.0"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@types/node": "^22.15.0",
|
|
53
|
+
"@vitest/coverage-v8": "^3.2.0",
|
|
54
|
+
"eslint": "^9.28.0",
|
|
55
|
+
"prettier": "^3.5.0",
|
|
56
|
+
"tsup": "^8.5.0",
|
|
57
|
+
"typescript": "^5.8.0",
|
|
58
|
+
"typescript-eslint": "^8.33.0",
|
|
59
|
+
"vitest": "^3.2.0"
|
|
60
|
+
}
|
|
61
|
+
}
|