anyapi-mcp-server 1.3.0 → 1.4.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 CHANGED
@@ -1,12 +1,19 @@
1
1
  # anyapi-mcp-server
2
2
 
3
- A universal [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that connects **any REST API** to AI assistants like Claude, Cursor, and other LLM-powered tools — just point it at an OpenAPI spec or Postman collection.
3
+ ### If it has an API, you can MCP it.
4
4
 
5
- Instead of building a custom MCP server for every API, `anyapi-mcp-server` reads your spec file and dynamically creates tools with **GraphQL-style field selection** and automatic schema inference.
5
+ <img src="public/datadog.gif" alt="anyapi-mcp-server demo Datadog API" width="1200" />
6
+
7
+ Traditional MCP servers hand-pick a handful of endpoints and call it a day — locking you into whatever subset someone decided was "enough." Why settle for a fraction of an API when you can have **all of it**?
8
+
9
+ `anyapi-mcp-server` is a universal [MCP](https://modelcontextprotocol.io) server that connects **any REST API** to AI assistants like Claude, Cursor, and other LLM-powered tools — just point it at an OpenAPI spec or Postman collection. Every endpoint the API provides becomes available instantly, with **GraphQL-style field selection** and automatic schema inference. No custom server code, no artificial limits.
10
+
11
+ Works with services like **Datadog**, **PostHog**, **Metabase**, **Cloudflare**, **Stripe**, **GitHub**, **Slack**, **Twilio**, **Shopify**, **HubSpot**, and anything else with a REST API — if it has an API, it just works.
6
12
 
7
13
  ## Features
8
14
 
9
- - **Works with any REST API** — provide an OpenAPI (JSON/YAML) or Postman Collection v2.x spec
15
+ - **Works with any REST API** — provide an OpenAPI (JSON/YAML) or Postman Collection v2.x spec as a local file or HTTPS URL
16
+ - **Remote spec caching** — HTTPS spec URLs are fetched once and cached locally in ``~/.cache/anyapi-mcp/` (Linux/macOS) or `%LOCALAPPDATA%\anyapi-mcp\` (Windows)`
10
17
  - **GraphQL-style queries** — select only the fields you need from API responses
11
18
  - **Automatic schema inference** — calls an endpoint once, infers the response schema, then lets you query specific fields
12
19
  - **Multi-sample merging** — samples up to 10 array elements to build richer schemas that capture fields missing from individual items
@@ -35,16 +42,9 @@ npm install -g anyapi-mcp-server
35
42
  | Flag | Description |
36
43
  |------|-------------|
37
44
  | `--name` | Server name (e.g. `petstore`) |
38
-
39
- ### Either one of
40
-
41
- | Flag | Description |
42
- |------|-------------|
43
- | `--spec` | Path to OpenAPI spec file (JSON or YAML) or Postman Collection |
45
+ | `--spec` | Path or HTTPS URL to OpenAPI spec (JSON or YAML) or Postman Collection. HTTPS URLs are cached locally in ``~/.cache/anyapi-mcp/` (Linux/macOS) or `%LOCALAPPDATA%\anyapi-mcp\` (Windows)`. Supports `${ENV_VAR}` interpolation. |
44
46
  | `--base-url` | API base URL (e.g. `https://api.example.com`). Supports `${ENV_VAR}` interpolation. |
45
47
 
46
- You can provide both `--spec` and `--base-url` together. If only `--spec` is given, the base URL is read from the spec. If only `--base-url` is given, endpoints are discovered dynamically.
47
-
48
48
  ### Optional arguments
49
49
 
50
50
  | Flag | Description |
@@ -67,7 +67,7 @@ Add to your MCP configuration (e.g. `~/.cursor/mcp.json` or Claude Desktop confi
67
67
  "-y",
68
68
  "anyapi-mcp-server",
69
69
  "--name", "cloudflare",
70
- "--spec", "/path/to/cloudflare-openapi.json",
70
+ "--spec", "https://raw.githubusercontent.com/cloudflare/api-schemas/main/openapi.json",
71
71
  "--base-url", "https://api.cloudflare.com/client/v4",
72
72
  "--header", "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}"
73
73
  ],
@@ -90,8 +90,8 @@ Add to your MCP configuration (e.g. `~/.cursor/mcp.json` or Claude Desktop confi
90
90
  "-y",
91
91
  "anyapi-mcp-server",
92
92
  "--name", "datadog",
93
- "--spec", "/path/to/datadog-openapi.json",
94
- "--base-url", "https://api.datadoghq.com/api/v1",
93
+ "--spec", "https://raw.githubusercontent.com/DataDog/datadog-api-client-typescript/master/.generator/schemas/v1/openapi.yaml",
94
+ "--base-url", "https://api.datadoghq.com",
95
95
  "--header", "DD-API-KEY: ${DD_API_KEY}",
96
96
  "--header", "DD-APPLICATION-KEY: ${DD_APP_KEY}"
97
97
  ],
@@ -259,7 +259,7 @@ OpenAPI/Postman spec
259
259
  response data
260
260
  ```
261
261
 
262
- 1. The spec file is parsed at startup into an endpoint index with tags, paths, parameters, and request body schemas
262
+ 1. The spec is loaded at startup (from a local file or fetched from an HTTPS URL with filesystem caching) and parsed into an endpoint index with tags, paths, parameters, and request body schemas
263
263
  2. `call_api` makes a real HTTP request, infers a GraphQL schema from the JSON response, and caches both the response (30s TTL) and the schema
264
264
  3. `query_api` re-uses the cached response if called within 30s, executes your GraphQL field selection against the data, and returns only the fields you asked for
265
265
  4. Write operations (POST/PUT/DELETE/PATCH) with OpenAPI request body schemas get a Mutation type with typed `GraphQLInputObjectType` inputs
@@ -1,4 +1,3 @@
1
- import { readFileSync } from "node:fs";
2
1
  import yaml from "js-yaml";
3
2
  const PAGE_SIZE = 20;
4
3
  const HTTP_METHODS = new Set(["get", "post", "put", "delete", "patch"]);
@@ -119,10 +118,14 @@ function postmanUrlToPath(url) {
119
118
  export class ApiIndex {
120
119
  byTag = new Map();
121
120
  allEndpoints = [];
122
- constructor(specPath) {
123
- const raw = readFileSync(specPath, "utf-8");
124
- const isYaml = /\.ya?ml$/i.test(specPath);
125
- const parsed = isYaml ? yaml.load(raw) : JSON.parse(raw);
121
+ constructor(specContent) {
122
+ let parsed;
123
+ try {
124
+ parsed = JSON.parse(specContent);
125
+ }
126
+ catch {
127
+ parsed = yaml.load(specContent);
128
+ }
126
129
  if (isPostmanCollection(parsed)) {
127
130
  this.parsePostman(parsed);
128
131
  }
package/build/config.js CHANGED
@@ -1,4 +1,8 @@
1
1
  import { resolve } from "node:path";
2
+ import { createHash } from "node:crypto";
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { homedir } from "node:os";
5
+ import { join } from "node:path";
2
6
  function getArg(flag) {
3
7
  const idx = process.argv.indexOf(flag);
4
8
  if (idx === -1 || !process.argv[idx + 1])
@@ -23,25 +27,51 @@ function interpolateEnv(value) {
23
27
  return envValue;
24
28
  });
25
29
  }
26
- const USAGE = `Usage: anyapi-mcp --name <name> --spec <path> --base-url <url> [--header "Key: Value"]...
30
+ const CACHE_DIR = process.platform === "win32"
31
+ ? join(process.env.LOCALAPPDATA ?? join(homedir(), "AppData", "Local"), "anyapi-mcp")
32
+ : join(homedir(), ".cache", "anyapi-mcp");
33
+ function isUrl(value) {
34
+ return /^https?:\/\//i.test(value);
35
+ }
36
+ async function loadSpec(specValue) {
37
+ if (!isUrl(specValue)) {
38
+ return readFileSync(resolve(specValue), "utf-8");
39
+ }
40
+ const hash = createHash("sha256").update(specValue).digest("hex");
41
+ const ext = /\.ya?ml$/i.test(specValue) ? ".yaml" : ".json";
42
+ const cachePath = join(CACHE_DIR, hash + ext);
43
+ if (existsSync(cachePath)) {
44
+ return readFileSync(cachePath, "utf-8");
45
+ }
46
+ const res = await fetch(specValue);
47
+ if (!res.ok) {
48
+ throw new Error(`Failed to fetch spec from ${specValue}: ${res.status} ${res.statusText}`);
49
+ }
50
+ const body = await res.text();
51
+ mkdirSync(CACHE_DIR, { recursive: true });
52
+ writeFileSync(cachePath, body, "utf-8");
53
+ return body;
54
+ }
55
+ const USAGE = `Usage: anyapi-mcp --name <name> --spec <path-or-url> --base-url <url> [--header "Key: Value"]...
27
56
 
28
57
  Required:
29
58
  --name Server name (e.g. "petstore")
30
- --spec Path to OpenAPI spec file (JSON or YAML)
59
+ --spec Path or URL to OpenAPI spec (JSON or YAML). HTTPS URLs are cached locally.
31
60
  --base-url API base URL (e.g. "https://api.example.com")
32
61
 
33
62
  Optional:
34
63
  --header HTTP header as "Key: Value" (repeatable)
35
64
  Supports \${ENV_VAR} interpolation in values
36
65
  --log Path to request/response log file (NDJSON format)`;
37
- export function loadConfig() {
66
+ export async function loadConfig() {
38
67
  const name = getArg("--name");
39
- const spec = getArg("--spec");
68
+ const specUrl = getArg("--spec");
40
69
  const baseUrl = getArg("--base-url");
41
- if (!name || !spec || !baseUrl) {
70
+ if (!name || !specUrl || !baseUrl) {
42
71
  console.error(USAGE);
43
72
  process.exit(1);
44
73
  }
74
+ const spec = await loadSpec(interpolateEnv(specUrl));
45
75
  const headers = {};
46
76
  for (const raw of getAllArgs("--header")) {
47
77
  const colonIdx = raw.indexOf(":");
@@ -56,7 +86,7 @@ export function loadConfig() {
56
86
  const logPath = getArg("--log");
57
87
  return {
58
88
  name,
59
- spec: resolve(spec),
89
+ spec,
60
90
  baseUrl: interpolateEnv(baseUrl).replace(/\/+$/, ""),
61
91
  headers: Object.keys(headers).length > 0 ? headers : undefined,
62
92
  logPath: logPath ? resolve(logPath) : undefined,
package/build/index.js CHANGED
@@ -8,7 +8,7 @@ import { callApi } from "./api-client.js";
8
8
  import { initLogger } from "./logger.js";
9
9
  import { generateSuggestions } from "./query-suggestions.js";
10
10
  import { getOrBuildSchema, executeQuery, schemaToSDL, truncateIfArray, } from "./graphql-schema.js";
11
- const config = loadConfig();
11
+ const config = await loadConfig();
12
12
  initLogger(config.logPath ?? null);
13
13
  const apiIndex = new ApiIndex(config.spec);
14
14
  const server = new McpServer({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anyapi-mcp-server",
3
- "version": "1.3.0",
3
+ "version": "1.4.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",