@ivotoby/openapi-mcp-server 1.4.0 → 1.5.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.
Files changed (3) hide show
  1. package/README.md +102 -1
  2. package/dist/bundle.js +128 -19
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -118,6 +118,8 @@ The server can be configured through environment variables or command line argum
118
118
 
119
119
  - `API_BASE_URL` - Base URL for the API endpoints
120
120
  - `OPENAPI_SPEC_PATH` - Path or URL to OpenAPI specification
121
+ - `OPENAPI_SPEC_FROM_STDIN` - Set to "true" to read OpenAPI spec from standard input
122
+ - `OPENAPI_SPEC_INLINE` - Provide OpenAPI spec content directly as a string
121
123
  - `API_HEADERS` - Comma-separated key:value pairs for API headers
122
124
  - `SERVER_NAME` - Name for the MCP server (default: "mcp-openapi-server")
123
125
  - `SERVER_VERSION` - Version of the server (default: "1.0.0")
@@ -136,7 +138,7 @@ npx @ivotoby/openapi-mcp-server \
136
138
  --openapi-spec https://api.example.com/openapi.json \
137
139
  --headers "Authorization:Bearer token123,X-API-Key:your-api-key" \
138
140
  --name "my-mcp-server" \
139
- --version "1.0.0" \
141
+ --server-version "1.0.0" \
140
142
  --transport http \
141
143
  --port 3000 \
142
144
  --host 127.0.0.1 \
@@ -144,6 +146,105 @@ npx @ivotoby/openapi-mcp-server \
144
146
  --disable-abbreviation true
145
147
  ```
146
148
 
149
+ ## OpenAPI Specification Loading
150
+
151
+ The MCP server supports multiple methods for loading OpenAPI specifications, providing flexibility for different deployment scenarios:
152
+
153
+ ### 1. URL Loading (Default)
154
+
155
+ Load the OpenAPI spec from a remote URL:
156
+
157
+ ```bash
158
+ npx @ivotoby/openapi-mcp-server \
159
+ --api-base-url https://api.example.com \
160
+ --openapi-spec https://api.example.com/openapi.json
161
+ ```
162
+
163
+ ### 2. Local File Loading
164
+
165
+ Load the OpenAPI spec from a local file:
166
+
167
+ ```bash
168
+ npx @ivotoby/openapi-mcp-server \
169
+ --api-base-url https://api.example.com \
170
+ --openapi-spec ./path/to/openapi.yaml
171
+ ```
172
+
173
+ ### 3. Standard Input Loading
174
+
175
+ Read the OpenAPI spec from standard input (useful for piping or containerized environments):
176
+
177
+ ```bash
178
+ # Pipe from file
179
+ cat openapi.json | npx @ivotoby/openapi-mcp-server \
180
+ --api-base-url https://api.example.com \
181
+ --spec-from-stdin
182
+
183
+ # Pipe from curl
184
+ curl -s https://api.example.com/openapi.json | npx @ivotoby/openapi-mcp-server \
185
+ --api-base-url https://api.example.com \
186
+ --spec-from-stdin
187
+
188
+ # Using environment variable
189
+ export OPENAPI_SPEC_FROM_STDIN=true
190
+ echo '{"openapi": "3.0.0", ...}' | npx @ivotoby/openapi-mcp-server \
191
+ --api-base-url https://api.example.com
192
+ ```
193
+
194
+ ### 4. Inline Specification
195
+
196
+ Provide the OpenAPI spec content directly as a command line argument:
197
+
198
+ ```bash
199
+ npx @ivotoby/openapi-mcp-server \
200
+ --api-base-url https://api.example.com \
201
+ --spec-inline '{"openapi": "3.0.0", "info": {"title": "My API", "version": "1.0.0"}, "paths": {}}'
202
+
203
+ # Using environment variable
204
+ export OPENAPI_SPEC_INLINE='{"openapi": "3.0.0", ...}'
205
+ npx @ivotoby/openapi-mcp-server --api-base-url https://api.example.com
206
+ ```
207
+
208
+ ### Supported Formats
209
+
210
+ All loading methods support both JSON and YAML formats. The server automatically detects the format and parses accordingly.
211
+
212
+ ### Docker and Container Usage
213
+
214
+ For containerized deployments, you can mount OpenAPI specs or use stdin:
215
+
216
+ ```bash
217
+ # Mount local file
218
+ docker run -v /path/to/spec:/app/spec.json your-mcp-server \
219
+ --api-base-url https://api.example.com \
220
+ --openapi-spec /app/spec.json
221
+
222
+ # Use stdin with docker
223
+ cat openapi.json | docker run -i your-mcp-server \
224
+ --api-base-url https://api.example.com \
225
+ --spec-from-stdin
226
+ ```
227
+
228
+ ### Error Handling
229
+
230
+ The server provides detailed error messages for spec loading failures:
231
+
232
+ - **URL loading**: HTTP status codes and network errors
233
+ - **File loading**: File system errors (not found, permissions, etc.)
234
+ - **Stdin loading**: Empty input or read errors
235
+ - **Inline loading**: Missing content errors
236
+ - **Parsing errors**: Detailed JSON/YAML syntax error messages
237
+
238
+ ### Validation
239
+
240
+ Only one specification source can be used at a time. The server will validate that exactly one of the following is provided:
241
+
242
+ - `--openapi-spec` (URL or file path)
243
+ - `--spec-from-stdin`
244
+ - `--spec-inline`
245
+
246
+ If multiple sources are specified, the server will exit with an error message.
247
+
147
248
  ### OpenAPI Schema Processing
148
249
 
149
250
  #### 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.0",
3
+ "version": "1.5.0",
4
4
  "description": "An MCP server that exposes OpenAPI endpoints as resources",
5
5
  "license": "MIT",
6
6
  "type": "module",