anyapi-mcp-server 1.1.3 → 1.2.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 +42 -12
- package/build/api-index.js +23 -0
- package/build/index.js +154 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -54,40 +54,70 @@ You can provide both `--spec` and `--base-url` together. If only `--spec` is giv
|
|
|
54
54
|
|
|
55
55
|
Add to your MCP configuration (e.g. `~/.cursor/mcp.json` or Claude Desktop config):
|
|
56
56
|
|
|
57
|
+
#### Cloudflare API
|
|
58
|
+
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"mcpServers": {
|
|
62
|
+
"cloudflare": {
|
|
63
|
+
"command": "npx",
|
|
64
|
+
"args": [
|
|
65
|
+
"-y",
|
|
66
|
+
"anyapi-mcp-server",
|
|
67
|
+
"--name", "cloudflare",
|
|
68
|
+
"--spec", "/path/to/cloudflare-openapi.json",
|
|
69
|
+
"--base-url", "https://api.cloudflare.com/client/v4",
|
|
70
|
+
"--header", "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}"
|
|
71
|
+
],
|
|
72
|
+
"env": {
|
|
73
|
+
"CLOUDFLARE_API_TOKEN": "your-cloudflare-api-token"
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
#### Datadog API
|
|
81
|
+
|
|
57
82
|
```json
|
|
58
83
|
{
|
|
59
84
|
"mcpServers": {
|
|
60
|
-
"
|
|
85
|
+
"datadog": {
|
|
61
86
|
"command": "npx",
|
|
62
87
|
"args": [
|
|
63
88
|
"-y",
|
|
64
89
|
"anyapi-mcp-server",
|
|
65
|
-
"--name", "
|
|
66
|
-
"--spec", "/path/to/
|
|
67
|
-
"--base-url", "https://
|
|
68
|
-
|
|
90
|
+
"--name", "datadog",
|
|
91
|
+
"--spec", "/path/to/datadog-openapi.json",
|
|
92
|
+
"--base-url", "https://api.datadoghq.com/api/v1",
|
|
93
|
+
"--header", "DD-API-KEY: ${DD_API_KEY}",
|
|
94
|
+
"--header", "DD-APPLICATION-KEY: ${DD_APP_KEY}"
|
|
95
|
+
],
|
|
96
|
+
"env": {
|
|
97
|
+
"DD_API_KEY": "your-datadog-api-key",
|
|
98
|
+
"DD_APP_KEY": "your-datadog-app-key"
|
|
99
|
+
}
|
|
69
100
|
}
|
|
70
101
|
}
|
|
71
102
|
}
|
|
72
103
|
```
|
|
73
104
|
|
|
74
|
-
|
|
105
|
+
#### Metabase API
|
|
75
106
|
|
|
76
107
|
```json
|
|
77
108
|
{
|
|
78
109
|
"mcpServers": {
|
|
79
|
-
"
|
|
110
|
+
"metabase": {
|
|
80
111
|
"command": "npx",
|
|
81
112
|
"args": [
|
|
82
113
|
"-y",
|
|
83
114
|
"anyapi-mcp-server",
|
|
84
|
-
"--name", "
|
|
85
|
-
"--
|
|
86
|
-
"--
|
|
87
|
-
"--header", "Authorization: Bearer ${API_TOKEN}"
|
|
115
|
+
"--name", "metabase",
|
|
116
|
+
"--base-url", "https://your-metabase-instance.com/api",
|
|
117
|
+
"--header", "x-api-key: ${METABASE_API_KEY}"
|
|
88
118
|
],
|
|
89
119
|
"env": {
|
|
90
|
-
"
|
|
120
|
+
"METABASE_API_KEY": "your-metabase-api-key"
|
|
91
121
|
}
|
|
92
122
|
}
|
|
93
123
|
}
|
package/build/api-index.js
CHANGED
|
@@ -144,6 +144,22 @@ export class ApiIndex {
|
|
|
144
144
|
description: p.description,
|
|
145
145
|
}));
|
|
146
146
|
const requestBodySchema = extractRequestBodySchema(op.requestBody, rawSpec);
|
|
147
|
+
// Extract response descriptions
|
|
148
|
+
let responses;
|
|
149
|
+
if (op.responses) {
|
|
150
|
+
responses = Object.entries(op.responses).map(([code, resp]) => ({
|
|
151
|
+
statusCode: code,
|
|
152
|
+
description: resp.description ?? "",
|
|
153
|
+
}));
|
|
154
|
+
}
|
|
155
|
+
// Extract request body description
|
|
156
|
+
let requestBodyDescription;
|
|
157
|
+
if (op.requestBody && typeof op.requestBody === "object") {
|
|
158
|
+
const rb = op.requestBody;
|
|
159
|
+
if (typeof rb.description === "string") {
|
|
160
|
+
requestBodyDescription = rb.description;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
147
163
|
const endpoint = {
|
|
148
164
|
method: method.toUpperCase(),
|
|
149
165
|
path,
|
|
@@ -153,6 +169,13 @@ export class ApiIndex {
|
|
|
153
169
|
parameters,
|
|
154
170
|
hasRequestBody: !!op.requestBody,
|
|
155
171
|
requestBodySchema,
|
|
172
|
+
operationId: op.operationId,
|
|
173
|
+
deprecated: op.deprecated ?? undefined,
|
|
174
|
+
responses,
|
|
175
|
+
requestBodyDescription,
|
|
176
|
+
externalDocs: op.externalDocs?.url
|
|
177
|
+
? { url: op.externalDocs.url, description: op.externalDocs.description }
|
|
178
|
+
: undefined,
|
|
156
179
|
};
|
|
157
180
|
this.addEndpoint(endpoint);
|
|
158
181
|
}
|
package/build/index.js
CHANGED
|
@@ -13,7 +13,7 @@ initLogger(config.logPath ?? null);
|
|
|
13
13
|
const apiIndex = new ApiIndex(config.spec);
|
|
14
14
|
const server = new McpServer({
|
|
15
15
|
name: config.name,
|
|
16
|
-
version: "1.
|
|
16
|
+
version: "1.2.0",
|
|
17
17
|
});
|
|
18
18
|
// --- Tool 1: list_api ---
|
|
19
19
|
server.tool("list_api", `List available ${config.name} API endpoints. ` +
|
|
@@ -252,6 +252,159 @@ server.tool("query_api", `Fetch data from a ${config.name} API endpoint, returni
|
|
|
252
252
|
};
|
|
253
253
|
}
|
|
254
254
|
});
|
|
255
|
+
// --- Tool 4: explain_api ---
|
|
256
|
+
server.tool("explain_api", `Get detailed documentation for a ${config.name} API endpoint from the spec. ` +
|
|
257
|
+
"Returns all available spec information — summary, description, parameters, " +
|
|
258
|
+
"request body schema, response codes, deprecation status — without making any HTTP request. " +
|
|
259
|
+
"Use list_api first to discover endpoints, then explain_api to understand them before calling.", {
|
|
260
|
+
method: z
|
|
261
|
+
.enum(["GET", "POST", "PUT", "DELETE", "PATCH"])
|
|
262
|
+
.describe("HTTP method"),
|
|
263
|
+
path: z
|
|
264
|
+
.string()
|
|
265
|
+
.describe("API path template (e.g. '/api/card/{id}')"),
|
|
266
|
+
}, async ({ method, path }) => {
|
|
267
|
+
try {
|
|
268
|
+
const endpoint = apiIndex.getEndpoint(method, path);
|
|
269
|
+
if (!endpoint) {
|
|
270
|
+
return {
|
|
271
|
+
content: [
|
|
272
|
+
{
|
|
273
|
+
type: "text",
|
|
274
|
+
text: JSON.stringify({
|
|
275
|
+
error: `Endpoint not found: ${method} ${path}`,
|
|
276
|
+
hint: "Use list_api to discover available endpoints.",
|
|
277
|
+
}),
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
isError: true,
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
const result = {
|
|
284
|
+
method: endpoint.method,
|
|
285
|
+
path: endpoint.path,
|
|
286
|
+
summary: endpoint.summary,
|
|
287
|
+
};
|
|
288
|
+
if (endpoint.description) {
|
|
289
|
+
result.description = endpoint.description;
|
|
290
|
+
}
|
|
291
|
+
if (endpoint.operationId) {
|
|
292
|
+
result.operationId = endpoint.operationId;
|
|
293
|
+
}
|
|
294
|
+
if (endpoint.deprecated) {
|
|
295
|
+
result.deprecated = true;
|
|
296
|
+
}
|
|
297
|
+
result.tag = endpoint.tag;
|
|
298
|
+
if (endpoint.parameters.length > 0) {
|
|
299
|
+
result.parameters = endpoint.parameters.map((p) => ({
|
|
300
|
+
name: p.name,
|
|
301
|
+
in: p.in,
|
|
302
|
+
required: p.required,
|
|
303
|
+
...(p.description ? { description: p.description } : {}),
|
|
304
|
+
}));
|
|
305
|
+
}
|
|
306
|
+
if (endpoint.hasRequestBody) {
|
|
307
|
+
const bodyInfo = {};
|
|
308
|
+
if (endpoint.requestBodyDescription) {
|
|
309
|
+
bodyInfo.description = endpoint.requestBodyDescription;
|
|
310
|
+
}
|
|
311
|
+
if (endpoint.requestBodySchema) {
|
|
312
|
+
bodyInfo.contentType = endpoint.requestBodySchema.contentType;
|
|
313
|
+
bodyInfo.properties = endpoint.requestBodySchema.properties;
|
|
314
|
+
}
|
|
315
|
+
result.requestBody = bodyInfo;
|
|
316
|
+
}
|
|
317
|
+
if (endpoint.responses && endpoint.responses.length > 0) {
|
|
318
|
+
result.responses = endpoint.responses;
|
|
319
|
+
}
|
|
320
|
+
if (endpoint.externalDocs) {
|
|
321
|
+
result.externalDocs = endpoint.externalDocs;
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
content: [
|
|
325
|
+
{ type: "text", text: JSON.stringify(result, null, 2) },
|
|
326
|
+
],
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
catch (error) {
|
|
330
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
331
|
+
return {
|
|
332
|
+
content: [
|
|
333
|
+
{ type: "text", text: JSON.stringify({ error: message }) },
|
|
334
|
+
],
|
|
335
|
+
isError: true,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
// --- Tool 5: batch_query ---
|
|
340
|
+
server.tool("batch_query", `Fetch data from multiple ${config.name} API endpoints concurrently. ` +
|
|
341
|
+
"Each request in the batch follows the query_api flow — makes a real HTTP request " +
|
|
342
|
+
"and returns only the fields selected via GraphQL query. " +
|
|
343
|
+
"All requests execute in parallel; one failure does not affect the others. " +
|
|
344
|
+
"IMPORTANT: Run call_api first on each endpoint to discover schema field names.", {
|
|
345
|
+
requests: z
|
|
346
|
+
.array(z.object({
|
|
347
|
+
method: z
|
|
348
|
+
.enum(["GET", "POST", "PUT", "DELETE", "PATCH"])
|
|
349
|
+
.describe("HTTP method"),
|
|
350
|
+
path: z.string().describe("API path template"),
|
|
351
|
+
params: z
|
|
352
|
+
.record(z.unknown())
|
|
353
|
+
.optional()
|
|
354
|
+
.describe("Path and query parameters"),
|
|
355
|
+
body: z
|
|
356
|
+
.record(z.unknown())
|
|
357
|
+
.optional()
|
|
358
|
+
.describe("Request body for POST/PUT/PATCH"),
|
|
359
|
+
query: z
|
|
360
|
+
.string()
|
|
361
|
+
.describe("GraphQL selection query (use field names from call_api schema)"),
|
|
362
|
+
headers: z
|
|
363
|
+
.record(z.string())
|
|
364
|
+
.optional()
|
|
365
|
+
.describe("Additional HTTP headers for this request"),
|
|
366
|
+
}))
|
|
367
|
+
.min(1)
|
|
368
|
+
.max(10)
|
|
369
|
+
.describe("Array of requests to execute concurrently (1-10)"),
|
|
370
|
+
}, async ({ requests }) => {
|
|
371
|
+
try {
|
|
372
|
+
const settled = await Promise.allSettled(requests.map(async (req) => {
|
|
373
|
+
const rawData = await callApi(config, req.method, req.path, req.params, req.body, req.headers, "none");
|
|
374
|
+
const endpoint = apiIndex.getEndpoint(req.method, req.path);
|
|
375
|
+
const schema = getOrBuildSchema(rawData, req.method, req.path, endpoint?.requestBodySchema);
|
|
376
|
+
const queryResult = await executeQuery(schema, rawData, req.query);
|
|
377
|
+
return { method: req.method, path: req.path, data: queryResult };
|
|
378
|
+
}));
|
|
379
|
+
const results = settled.map((outcome, i) => {
|
|
380
|
+
if (outcome.status === "fulfilled") {
|
|
381
|
+
return outcome.value;
|
|
382
|
+
}
|
|
383
|
+
const message = outcome.reason instanceof Error
|
|
384
|
+
? outcome.reason.message
|
|
385
|
+
: String(outcome.reason);
|
|
386
|
+
return {
|
|
387
|
+
method: requests[i].method,
|
|
388
|
+
path: requests[i].path,
|
|
389
|
+
error: message,
|
|
390
|
+
};
|
|
391
|
+
});
|
|
392
|
+
return {
|
|
393
|
+
content: [
|
|
394
|
+
{ type: "text", text: JSON.stringify(results, null, 2) },
|
|
395
|
+
],
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
catch (error) {
|
|
399
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
400
|
+
return {
|
|
401
|
+
content: [
|
|
402
|
+
{ type: "text", text: JSON.stringify({ error: message }) },
|
|
403
|
+
],
|
|
404
|
+
isError: true,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
});
|
|
255
408
|
async function main() {
|
|
256
409
|
const transport = new StdioServerTransport();
|
|
257
410
|
await server.connect(transport);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anyapi-mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "A universal MCP server that connects any REST API (via OpenAPI spec) to AI assistants, with GraphQL-style field selection and automatic schema inference.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE",
|