@ivotoby/openapi-mcp-server 1.4.1 → 1.5.1

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.
Files changed (3) hide show
  1. package/README.md +105 -2
  2. package/dist/bundle.js +128 -19
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # OpenAPI MCP Server
2
2
 
3
+ [![smithery badge](https://smithery.ai/badge/@ivo-toby/mcp-openapi-server)](https://smithery.ai/server/@ivo-toby/mcp-openapi-server)
4
+
3
5
  A Model Context Protocol (MCP) server that exposes OpenAPI endpoints as MCP resources. This server allows Large Language Models to discover and interact with REST APIs defined by OpenAPI specifications through the MCP protocol.
4
6
 
5
7
  ## Overview
@@ -63,7 +65,7 @@ npx @ivotoby/openapi-mcp-server \
63
65
  # Initialize a session (first request)
64
66
  curl -X POST http://localhost:3000/mcp \
65
67
  -H "Content-Type: application/json" \
66
- -d '{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"client":{"name":"curl-client","version":"1.0.0"},"protocol":{"name":"mcp","version":"2025-03-26"}}}'
68
+ -d '{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl-client","version":"1.0.0"}}}'
67
69
 
68
70
  # The response includes a Mcp-Session-Id header that you must use for subsequent requests
69
71
  # and the InitializeResult directly in the POST response body.
@@ -118,6 +120,8 @@ The server can be configured through environment variables or command line argum
118
120
 
119
121
  - `API_BASE_URL` - Base URL for the API endpoints
120
122
  - `OPENAPI_SPEC_PATH` - Path or URL to OpenAPI specification
123
+ - `OPENAPI_SPEC_FROM_STDIN` - Set to "true" to read OpenAPI spec from standard input
124
+ - `OPENAPI_SPEC_INLINE` - Provide OpenAPI spec content directly as a string
121
125
  - `API_HEADERS` - Comma-separated key:value pairs for API headers
122
126
  - `SERVER_NAME` - Name for the MCP server (default: "mcp-openapi-server")
123
127
  - `SERVER_VERSION` - Version of the server (default: "1.0.0")
@@ -136,7 +140,7 @@ npx @ivotoby/openapi-mcp-server \
136
140
  --openapi-spec https://api.example.com/openapi.json \
137
141
  --headers "Authorization:Bearer token123,X-API-Key:your-api-key" \
138
142
  --name "my-mcp-server" \
139
- --version "1.0.0" \
143
+ --server-version "1.0.0" \
140
144
  --transport http \
141
145
  --port 3000 \
142
146
  --host 127.0.0.1 \
@@ -144,6 +148,105 @@ npx @ivotoby/openapi-mcp-server \
144
148
  --disable-abbreviation true
145
149
  ```
146
150
 
151
+ ## OpenAPI Specification Loading
152
+
153
+ The MCP server supports multiple methods for loading OpenAPI specifications, providing flexibility for different deployment scenarios:
154
+
155
+ ### 1. URL Loading (Default)
156
+
157
+ Load the OpenAPI spec from a remote URL:
158
+
159
+ ```bash
160
+ npx @ivotoby/openapi-mcp-server \
161
+ --api-base-url https://api.example.com \
162
+ --openapi-spec https://api.example.com/openapi.json
163
+ ```
164
+
165
+ ### 2. Local File Loading
166
+
167
+ Load the OpenAPI spec from a local file:
168
+
169
+ ```bash
170
+ npx @ivotoby/openapi-mcp-server \
171
+ --api-base-url https://api.example.com \
172
+ --openapi-spec ./path/to/openapi.yaml
173
+ ```
174
+
175
+ ### 3. Standard Input Loading
176
+
177
+ Read the OpenAPI spec from standard input (useful for piping or containerized environments):
178
+
179
+ ```bash
180
+ # Pipe from file
181
+ cat openapi.json | npx @ivotoby/openapi-mcp-server \
182
+ --api-base-url https://api.example.com \
183
+ --spec-from-stdin
184
+
185
+ # Pipe from curl
186
+ curl -s https://api.example.com/openapi.json | npx @ivotoby/openapi-mcp-server \
187
+ --api-base-url https://api.example.com \
188
+ --spec-from-stdin
189
+
190
+ # Using environment variable
191
+ export OPENAPI_SPEC_FROM_STDIN=true
192
+ echo '{"openapi": "3.0.0", ...}' | npx @ivotoby/openapi-mcp-server \
193
+ --api-base-url https://api.example.com
194
+ ```
195
+
196
+ ### 4. Inline Specification
197
+
198
+ Provide the OpenAPI spec content directly as a command line argument:
199
+
200
+ ```bash
201
+ npx @ivotoby/openapi-mcp-server \
202
+ --api-base-url https://api.example.com \
203
+ --spec-inline '{"openapi": "3.0.0", "info": {"title": "My API", "version": "1.0.0"}, "paths": {}}'
204
+
205
+ # Using environment variable
206
+ export OPENAPI_SPEC_INLINE='{"openapi": "3.0.0", ...}'
207
+ npx @ivotoby/openapi-mcp-server --api-base-url https://api.example.com
208
+ ```
209
+
210
+ ### Supported Formats
211
+
212
+ All loading methods support both JSON and YAML formats. The server automatically detects the format and parses accordingly.
213
+
214
+ ### Docker and Container Usage
215
+
216
+ For containerized deployments, you can mount OpenAPI specs or use stdin:
217
+
218
+ ```bash
219
+ # Mount local file
220
+ docker run -v /path/to/spec:/app/spec.json your-mcp-server \
221
+ --api-base-url https://api.example.com \
222
+ --openapi-spec /app/spec.json
223
+
224
+ # Use stdin with docker
225
+ cat openapi.json | docker run -i your-mcp-server \
226
+ --api-base-url https://api.example.com \
227
+ --spec-from-stdin
228
+ ```
229
+
230
+ ### Error Handling
231
+
232
+ The server provides detailed error messages for spec loading failures:
233
+
234
+ - **URL loading**: HTTP status codes and network errors
235
+ - **File loading**: File system errors (not found, permissions, etc.)
236
+ - **Stdin loading**: Empty input or read errors
237
+ - **Inline loading**: Missing content errors
238
+ - **Parsing errors**: Detailed JSON/YAML syntax error messages
239
+
240
+ ### Validation
241
+
242
+ Only one specification source can be used at a time. The server will validate that exactly one of the following is provided:
243
+
244
+ - `--openapi-spec` (URL or file path)
245
+ - `--spec-from-stdin`
246
+ - `--spec-inline`
247
+
248
+ If multiple sources are specified, the server will exit with an error message.
249
+
147
250
  ### OpenAPI Schema Processing
148
251
 
149
252
  #### Reference Resolution
package/dist/bundle.js CHANGED
@@ -14381,27 +14381,96 @@ var OpenAPISpecLoader = class {
14381
14381
  this.disableAbbreviation = config?.disableAbbreviation ?? false;
14382
14382
  }
14383
14383
  /**
14384
- * Load an OpenAPI specification from a file path or URL
14384
+ * Load an OpenAPI specification from various sources
14385
14385
  */
14386
- async loadOpenAPISpec(specPathOrUrl) {
14386
+ async loadOpenAPISpec(specPathOrUrl, inputMethod = "url", inlineContent) {
14387
14387
  let specContent;
14388
- if (specPathOrUrl.startsWith("http://") || specPathOrUrl.startsWith("https://")) {
14389
- const response = await fetch(specPathOrUrl);
14390
- if (!response.ok) {
14391
- throw new Error(`Failed to fetch OpenAPI spec from URL: ${specPathOrUrl}`);
14388
+ try {
14389
+ switch (inputMethod) {
14390
+ case "url":
14391
+ specContent = await this.loadFromUrl(specPathOrUrl);
14392
+ break;
14393
+ case "file":
14394
+ specContent = await this.loadFromFile(specPathOrUrl);
14395
+ break;
14396
+ case "stdin":
14397
+ specContent = await this.loadFromStdin();
14398
+ break;
14399
+ case "inline":
14400
+ if (!inlineContent) {
14401
+ throw new Error("Inline content is required when using 'inline' input method");
14402
+ }
14403
+ specContent = inlineContent;
14404
+ break;
14405
+ default:
14406
+ throw new Error(`Unsupported input method: ${inputMethod}`);
14392
14407
  }
14393
- specContent = await response.text();
14394
- } else {
14395
- specContent = await readFile(specPathOrUrl, "utf-8");
14408
+ } catch (error) {
14409
+ if (error instanceof Error) {
14410
+ throw new Error(`Failed to load OpenAPI spec from ${inputMethod}: ${error.message}`);
14411
+ }
14412
+ throw error;
14413
+ }
14414
+ return this.parseSpecContent(specContent, inputMethod);
14415
+ }
14416
+ /**
14417
+ * Load spec content from URL
14418
+ */
14419
+ async loadFromUrl(url2) {
14420
+ const response = await fetch(url2);
14421
+ if (!response.ok) {
14422
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
14423
+ }
14424
+ return await response.text();
14425
+ }
14426
+ /**
14427
+ * Load spec content from local file
14428
+ */
14429
+ async loadFromFile(filePath) {
14430
+ return await readFile(filePath, "utf-8");
14431
+ }
14432
+ /**
14433
+ * Load spec content from standard input
14434
+ */
14435
+ async loadFromStdin() {
14436
+ return new Promise((resolve5, reject) => {
14437
+ let data = "";
14438
+ process.stdin.setEncoding("utf8");
14439
+ process.stdin.on("data", (chunk) => {
14440
+ data += chunk;
14441
+ });
14442
+ process.stdin.on("end", () => {
14443
+ if (data.trim().length === 0) {
14444
+ reject(new Error("No data received from stdin"));
14445
+ } else {
14446
+ resolve5(data);
14447
+ }
14448
+ });
14449
+ process.stdin.on("error", (error) => {
14450
+ reject(new Error(`Error reading from stdin: ${error.message}`));
14451
+ });
14452
+ process.stdin.resume();
14453
+ });
14454
+ }
14455
+ /**
14456
+ * Parse spec content as JSON or YAML
14457
+ */
14458
+ parseSpecContent(specContent, source) {
14459
+ if (!specContent || specContent.trim().length === 0) {
14460
+ throw new Error(`Empty or invalid spec content from ${source}`);
14396
14461
  }
14397
14462
  try {
14398
14463
  return JSON.parse(specContent);
14399
14464
  } catch (jsonError) {
14400
14465
  try {
14401
- return js_yaml_default.load(specContent);
14466
+ const yamlResult = js_yaml_default.load(specContent);
14467
+ if (!yamlResult || typeof yamlResult !== "object") {
14468
+ throw new Error("YAML parsing resulted in invalid object");
14469
+ }
14470
+ return yamlResult;
14402
14471
  } catch (yamlError) {
14403
14472
  throw new Error(
14404
- `Failed to parse OpenAPI spec as JSON or YAML: ${jsonError.message} | ${yamlError.message}`
14473
+ `Failed to parse as JSON or YAML. JSON error: ${jsonError.message}. YAML error: ${yamlError.message}`
14405
14474
  );
14406
14475
  }
14407
14476
  }
@@ -14782,7 +14851,11 @@ var ToolsManager = class {
14782
14851
  * Initialize tools from the OpenAPI specification
14783
14852
  */
14784
14853
  async initialize() {
14785
- const spec = await this.specLoader.loadOpenAPISpec(this.config.openApiSpec);
14854
+ const spec = await this.specLoader.loadOpenAPISpec(
14855
+ this.config.openApiSpec,
14856
+ this.config.specInputMethod,
14857
+ this.config.inlineSpecContent
14858
+ );
14786
14859
  if (this.config.toolsMode === "dynamic") {
14787
14860
  this.tools = this.createDynamicTools();
14788
14861
  return;
@@ -23293,6 +23366,12 @@ function loadConfig() {
23293
23366
  alias: "s",
23294
23367
  type: "string",
23295
23368
  description: "Path or URL to OpenAPI specification"
23369
+ }).option("spec-from-stdin", {
23370
+ type: "boolean",
23371
+ description: "Read OpenAPI spec from standard input"
23372
+ }).option("spec-inline", {
23373
+ type: "string",
23374
+ description: "Provide OpenAPI spec content directly as a string"
23296
23375
  }).option("headers", {
23297
23376
  alias: "H",
23298
23377
  type: "string",
@@ -23301,7 +23380,7 @@ function loadConfig() {
23301
23380
  alias: "n",
23302
23381
  type: "string",
23303
23382
  description: "Server name"
23304
- }).option("version", {
23383
+ }).option("server-version", {
23305
23384
  alias: "v",
23306
23385
  type: "string",
23307
23386
  description: "Server version"
@@ -23338,21 +23417,51 @@ function loadConfig() {
23338
23417
  const httpPort = argv.port ?? (process.env.HTTP_PORT ? parseInt(process.env.HTTP_PORT, 10) : 3e3);
23339
23418
  const httpHost = argv.host || process.env.HTTP_HOST || "127.0.0.1";
23340
23419
  const endpointPath = argv.path || process.env.ENDPOINT_PATH || "/mcp";
23341
- const apiBaseUrl = argv["api-base-url"] || process.env.API_BASE_URL;
23420
+ const specFromStdin = argv["spec-from-stdin"] || process.env.OPENAPI_SPEC_FROM_STDIN === "true";
23421
+ const specInline = argv["spec-inline"] || process.env.OPENAPI_SPEC_INLINE;
23342
23422
  const openApiSpec = argv["openapi-spec"] || process.env.OPENAPI_SPEC_PATH;
23423
+ const specInputCount = [specFromStdin, !!specInline, !!openApiSpec].filter(Boolean).length;
23424
+ if (specInputCount === 0) {
23425
+ throw new Error(
23426
+ "OpenAPI spec is required. Use one of: --openapi-spec, --spec-from-stdin, or --spec-inline"
23427
+ );
23428
+ }
23429
+ if (specInputCount > 1) {
23430
+ throw new Error("Only one OpenAPI spec input method can be specified at a time");
23431
+ }
23432
+ let specInputMethod;
23433
+ let specPath;
23434
+ let inlineSpecContent;
23435
+ if (specFromStdin) {
23436
+ specInputMethod = "stdin";
23437
+ specPath = "stdin";
23438
+ } else if (specInline) {
23439
+ specInputMethod = "inline";
23440
+ specPath = "inline";
23441
+ inlineSpecContent = specInline;
23442
+ } else if (openApiSpec) {
23443
+ if (openApiSpec.startsWith("http://") || openApiSpec.startsWith("https://")) {
23444
+ specInputMethod = "url";
23445
+ } else {
23446
+ specInputMethod = "file";
23447
+ }
23448
+ specPath = openApiSpec;
23449
+ } else {
23450
+ throw new Error("OpenAPI spec is required");
23451
+ }
23452
+ const apiBaseUrl = argv["api-base-url"] || process.env.API_BASE_URL;
23343
23453
  const disableAbbreviation = argv["disable-abbreviation"] || (process.env.DISABLE_ABBREVIATION ? process.env.DISABLE_ABBREVIATION === "true" : false);
23344
23454
  if (!apiBaseUrl) {
23345
23455
  throw new Error("API base URL is required (--api-base-url or API_BASE_URL)");
23346
23456
  }
23347
- if (!openApiSpec) {
23348
- throw new Error("OpenAPI spec is required (--openapi-spec or OPENAPI_SPEC_PATH)");
23349
- }
23350
23457
  const headers = parseHeaders(argv.headers || process.env.API_HEADERS);
23351
23458
  return {
23352
23459
  name: argv.name || process.env.SERVER_NAME || "mcp-openapi-server",
23353
- version: argv.version || process.env.SERVER_VERSION || "1.0.0",
23460
+ version: argv["server-version"] || process.env.SERVER_VERSION || "1.0.0",
23354
23461
  apiBaseUrl,
23355
- openApiSpec,
23462
+ openApiSpec: specPath,
23463
+ specInputMethod,
23464
+ inlineSpecContent,
23356
23465
  headers,
23357
23466
  transportType,
23358
23467
  httpPort,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ivotoby/openapi-mcp-server",
3
- "version": "1.4.1",
3
+ "version": "1.5.1",
4
4
  "description": "An MCP server that exposes OpenAPI endpoints as resources",
5
5
  "license": "MIT",
6
6
  "type": "module",