@ivotoby/openapi-mcp-server 1.2.2 → 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.
Files changed (3) hide show
  1. package/README.md +4 -2
  2. package/dist/bundle.js +163 -24
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -126,6 +126,7 @@ The server can be configured through environment variables or command line argum
126
126
  - `HTTP_HOST` - Host for HTTP transport (default: "127.0.0.1")
127
127
  - `ENDPOINT_PATH` - Endpoint path for HTTP transport (default: "/mcp")
128
128
  - `TOOLS_MODE` - Tools loading mode: "all" (load all endpoint-based tools) or "dynamic" (load only meta-tools) (default: "all")
129
+ - `DISABLE_ABBREVIATION` - Disable name optimization (this could throw errors when name is > 64 chars)
129
130
 
130
131
  ### Command Line Arguments
131
132
 
@@ -139,7 +140,8 @@ npx @ivotoby/openapi-mcp-server \
139
140
  --transport http \
140
141
  --port 3000 \
141
142
  --host 127.0.0.1 \
142
- --path /mcp
143
+ --path /mcp \
144
+ --disable-abbreviation true
143
145
  ```
144
146
 
145
147
  ### OpenAPI Schema Processing
@@ -213,7 +215,7 @@ To see debug logs:
213
215
 
214
216
  2. When using HTTP transport:
215
217
  ```bash
216
- npx @ivotoby/openapi-mcp-server --transport http 2>debug.log
218
+ npx @ivotoby/openapi-mcp-server --transport http &2>debug.log
217
219
  ```
218
220
 
219
221
  ## For Developers
package/dist/bundle.js CHANGED
@@ -14373,6 +14373,13 @@ var WORD_ABBREVIATIONS = {
14373
14373
 
14374
14374
  // src/openapi-loader.ts
14375
14375
  var OpenAPISpecLoader = class {
14376
+ /**
14377
+ * Disable name optimization
14378
+ */
14379
+ disableAbbreviation;
14380
+ constructor(config) {
14381
+ this.disableAbbreviation = config?.disableAbbreviation ?? false;
14382
+ }
14376
14383
  /**
14377
14384
  * Load an OpenAPI specification from a file path or URL
14378
14385
  */
@@ -14443,6 +14450,33 @@ var OpenAPISpecLoader = class {
14443
14450
  }
14444
14451
  return schemaObj;
14445
14452
  }
14453
+ /**
14454
+ * Determine the appropriate JSON Schema type for a parameter
14455
+ * @param paramSchema The OpenAPI schema object after inlining
14456
+ * @param paramName The name of the parameter (for logging purposes)
14457
+ * @returns The determined type string, or undefined if the type should be omitted
14458
+ */
14459
+ determineParameterType(paramSchema, paramName) {
14460
+ if (Object.keys(paramSchema).length === 0 && typeof paramSchema !== "boolean") {
14461
+ console.warn(
14462
+ `Parameter '${paramName}' schema was empty after inlining (potential cycle or unresolvable ref), defaulting to string.`
14463
+ );
14464
+ return "string";
14465
+ }
14466
+ if (typeof paramSchema === "boolean") {
14467
+ return "boolean";
14468
+ }
14469
+ if (paramSchema.type) {
14470
+ return paramSchema.type;
14471
+ }
14472
+ const hasProperties = "properties" in paramSchema && paramSchema.properties && Object.keys(paramSchema.properties).length > 0;
14473
+ const hasItems = paramSchema.type === "array" && !!paramSchema.items;
14474
+ const hasComposition = "allOf" in paramSchema && paramSchema.allOf && paramSchema.allOf.length > 0 || "anyOf" in paramSchema && paramSchema.anyOf && paramSchema.anyOf.length > 0 || "oneOf" in paramSchema && paramSchema.oneOf && paramSchema.oneOf.length > 0;
14475
+ if (!hasProperties && !hasItems && !hasComposition) {
14476
+ return "string";
14477
+ }
14478
+ return void 0;
14479
+ }
14446
14480
  /**
14447
14481
  * Parse an OpenAPI specification into a map of tools
14448
14482
  */
@@ -14452,7 +14486,10 @@ var OpenAPISpecLoader = class {
14452
14486
  if (!pathItem) continue;
14453
14487
  for (const [method, operation] of Object.entries(pathItem)) {
14454
14488
  if (method === "parameters" || !operation) continue;
14455
- if (!["get", "put", "post", "delete", "options", "head", "patch", "trace"].includes(method)) {
14489
+ if (!["get", "post", "put", "patch", "delete", "options", "head"].includes(
14490
+ method.toLowerCase()
14491
+ )) {
14492
+ console.warn(`Skipping non-HTTP method "${method}" for path ${path}`);
14456
14493
  continue;
14457
14494
  }
14458
14495
  const op = operation;
@@ -14472,22 +14509,35 @@ var OpenAPISpecLoader = class {
14472
14509
  if (op.parameters) {
14473
14510
  for (const param of op.parameters) {
14474
14511
  let paramObj;
14475
- if (!("name" in param)) {
14476
- if ("$ref" in param && typeof param.$ref === "string") {
14477
- const refMatch = param.$ref.match(/^#\/components\/parameters\/(.+)$/);
14478
- if (refMatch && spec.components?.parameters) {
14479
- const paramName = refMatch[1];
14480
- const resolvedParam = spec.components.parameters[paramName];
14481
- if (!resolvedParam || !("name" in resolvedParam)) continue;
14512
+ if ("$ref" in param && typeof param.$ref === "string") {
14513
+ const refMatch = param.$ref.match(/^#\/components\/parameters\/(.+)$/);
14514
+ if (refMatch && spec.components?.parameters) {
14515
+ const paramNameFromRef = refMatch[1];
14516
+ const resolvedParam = spec.components.parameters[paramNameFromRef];
14517
+ if (resolvedParam && "name" in resolvedParam && "in" in resolvedParam) {
14482
14518
  paramObj = resolvedParam;
14483
14519
  } else {
14520
+ console.warn(
14521
+ `Could not resolve parameter reference or invalid structure: ${param.$ref}`
14522
+ );
14484
14523
  continue;
14485
14524
  }
14486
14525
  } else {
14526
+ console.warn(`Could not parse parameter reference: ${param.$ref}`);
14487
14527
  continue;
14488
14528
  }
14489
- } else {
14529
+ } else if ("name" in param && "in" in param) {
14490
14530
  paramObj = param;
14531
+ } else {
14532
+ console.warn(
14533
+ "Skipping parameter due to missing 'name' or 'in' properties and not being a valid $ref:",
14534
+ param
14535
+ );
14536
+ continue;
14537
+ }
14538
+ if (!paramObj) {
14539
+ console.warn("Failed to process a parameter (paramObj is undefined):", param);
14540
+ continue;
14491
14541
  }
14492
14542
  if (paramObj.schema) {
14493
14543
  const paramSchema = this.inlineSchema(
@@ -14496,16 +14546,20 @@ var OpenAPISpecLoader = class {
14496
14546
  /* @__PURE__ */ new Set()
14497
14547
  );
14498
14548
  const paramDef = {
14499
- description: paramObj.description || `${paramObj.name} parameter`
14549
+ description: paramObj.description || `${paramObj.name} parameter`,
14550
+ "x-parameter-location": paramObj.in
14551
+ // Store parameter location (path, query, etc.)
14500
14552
  };
14501
- if (paramSchema.type) {
14502
- paramDef.type = paramSchema.type;
14503
- } else {
14504
- paramDef.type = "string";
14553
+ const paramType = this.determineParameterType(paramSchema, paramObj.name);
14554
+ if (paramType !== void 0) {
14555
+ paramDef.type = paramType;
14505
14556
  }
14506
- for (const [key, value] of Object.entries(paramSchema)) {
14507
- if (key === "type" || key === "description") continue;
14508
- paramDef[key] = value;
14557
+ if (typeof paramSchema === "object" && paramSchema !== null) {
14558
+ for (const [key, value] of Object.entries(paramSchema)) {
14559
+ if (key === "description" && paramDef.description) continue;
14560
+ if (key === "type" && paramDef.type) continue;
14561
+ paramDef[key] = value;
14562
+ }
14509
14563
  }
14510
14564
  tool.inputSchema.properties[paramObj.name] = paramDef;
14511
14565
  if (paramObj.required === true) {
@@ -14649,14 +14703,20 @@ var OpenAPISpecLoader = class {
14649
14703
  return finalName;
14650
14704
  }
14651
14705
  abbreviateOperationId(originalId, maxLength = 64) {
14706
+ maxLength = this.disableAbbreviation ? Number.MAX_SAFE_INTEGER : maxLength;
14652
14707
  const {
14653
14708
  currentName: sanitizedName,
14654
14709
  originalWasLong,
14655
14710
  errorName
14656
14711
  } = this._initialSanitizeAndValidate(originalId, maxLength);
14657
14712
  if (errorName) return errorName;
14658
- let processedName = this._performSemanticAbbreviation(sanitizedName);
14659
- processedName = this._applyVowelRemovalIfOverLength(processedName, maxLength);
14713
+ let processedName;
14714
+ if (this.disableAbbreviation) {
14715
+ processedName = this.splitCombined(sanitizedName).join("-");
14716
+ } else {
14717
+ processedName = this._performSemanticAbbreviation(sanitizedName);
14718
+ processedName = this._applyVowelRemovalIfOverLength(processedName, maxLength);
14719
+ }
14660
14720
  processedName = this._truncateAndApplyHashIfNeeded(
14661
14721
  processedName,
14662
14722
  originalId,
@@ -14673,7 +14733,9 @@ var ToolsManager = class {
14673
14733
  constructor(config) {
14674
14734
  this.config = config;
14675
14735
  this.config.toolsMode = this.config.toolsMode || "all";
14676
- this.specLoader = new OpenAPISpecLoader();
14736
+ this.specLoader = new OpenAPISpecLoader({
14737
+ disableAbbreviation: this.config.disableAbbreviation
14738
+ });
14677
14739
  }
14678
14740
  tools = /* @__PURE__ */ new Map();
14679
14741
  specLoader;
@@ -14779,6 +14841,13 @@ var ToolsManager = class {
14779
14841
  getAllTools() {
14780
14842
  return Array.from(this.tools.values());
14781
14843
  }
14844
+ /**
14845
+ * Get all available tools with their IDs
14846
+ * Returns array of [toolId, tool] pairs
14847
+ */
14848
+ getToolsWithIds() {
14849
+ return Array.from(this.tools.entries());
14850
+ }
14782
14851
  /**
14783
14852
  * Find a tool by ID or name
14784
14853
  */
@@ -18130,6 +18199,24 @@ var ApiClient = class {
18130
18199
  });
18131
18200
  }
18132
18201
  axiosInstance;
18202
+ toolsMap = /* @__PURE__ */ new Map();
18203
+ /**
18204
+ * Set the available tools for the client
18205
+ *
18206
+ * @param tools - Map of tool ID to tool definition
18207
+ */
18208
+ setTools(tools) {
18209
+ this.toolsMap = tools;
18210
+ }
18211
+ /**
18212
+ * Get a tool definition by ID
18213
+ *
18214
+ * @param toolId - The tool ID
18215
+ * @returns The tool definition if found
18216
+ */
18217
+ getToolDefinition(toolId) {
18218
+ return this.toolsMap.get(toolId);
18219
+ }
18133
18220
  /**
18134
18221
  * Execute an API call based on the tool ID and parameters
18135
18222
  *
@@ -18140,15 +18227,57 @@ var ApiClient = class {
18140
18227
  async executeApiCall(toolId, params) {
18141
18228
  try {
18142
18229
  const { method, path } = this.parseToolId(toolId);
18230
+ const toolDef = this.getToolDefinition(toolId);
18231
+ const paramsCopy = { ...params };
18232
+ let resolvedPath = path;
18233
+ const escapeRegExp = (str2) => {
18234
+ return str2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
18235
+ };
18236
+ if (toolDef?.inputSchema?.properties) {
18237
+ for (const [key, value] of Object.entries(paramsCopy)) {
18238
+ const paramDef = toolDef.inputSchema.properties[key];
18239
+ const paramDef_any = paramDef;
18240
+ const paramLocation = paramDef_any?.["x-parameter-location"];
18241
+ if (paramLocation === "path") {
18242
+ const escapedKey = escapeRegExp(key);
18243
+ const paramRegex = new RegExp(`\\{${escapedKey}\\}|:${escapedKey}(?:\\/|$)`, "g");
18244
+ if (paramRegex.test(resolvedPath)) {
18245
+ resolvedPath = resolvedPath.replace(
18246
+ paramRegex,
18247
+ (match) => encodeURIComponent(value) + (match.endsWith("/") ? "/" : "")
18248
+ );
18249
+ } else {
18250
+ resolvedPath = resolvedPath.replace(`/${key}`, `/${encodeURIComponent(value)}`);
18251
+ }
18252
+ delete paramsCopy[key];
18253
+ }
18254
+ }
18255
+ } else {
18256
+ for (const key of Object.keys(paramsCopy)) {
18257
+ const value = paramsCopy[key];
18258
+ const escapedKey = escapeRegExp(key);
18259
+ const paramRegex = new RegExp(`\\{${escapedKey}\\}|:${escapedKey}(?:\\/|$)`, "g");
18260
+ if (paramRegex.test(resolvedPath)) {
18261
+ resolvedPath = resolvedPath.replace(
18262
+ paramRegex,
18263
+ (match) => encodeURIComponent(value) + (match.endsWith("/") ? "/" : "")
18264
+ );
18265
+ delete paramsCopy[key];
18266
+ } else if (resolvedPath.includes(`/${key}`)) {
18267
+ resolvedPath = resolvedPath.replace(`/${key}`, `/${encodeURIComponent(value)}`);
18268
+ delete paramsCopy[key];
18269
+ }
18270
+ }
18271
+ }
18143
18272
  const config = {
18144
18273
  method: method.toLowerCase(),
18145
- url: path,
18274
+ url: resolvedPath,
18146
18275
  headers: this.headers
18147
18276
  };
18148
18277
  if (["get", "delete", "head", "options"].includes(method.toLowerCase())) {
18149
- config.params = this.processQueryParams(params);
18278
+ config.params = this.processQueryParams(paramsCopy);
18150
18279
  } else {
18151
- config.data = params;
18280
+ config.data = paramsCopy;
18152
18281
  }
18153
18282
  const response = await this.axiosInstance(config);
18154
18283
  return response.data;
@@ -18271,6 +18400,11 @@ var OpenAPIServer = class {
18271
18400
  */
18272
18401
  async start(transport) {
18273
18402
  await this.toolsManager.initialize();
18403
+ const toolsMap = /* @__PURE__ */ new Map();
18404
+ for (const [toolId, tool] of this.toolsManager.getToolsWithIds()) {
18405
+ toolsMap.set(toolId, tool);
18406
+ }
18407
+ this.apiClient.setTools(toolsMap);
18274
18408
  await this.server.connect(transport);
18275
18409
  }
18276
18410
  };
@@ -23191,6 +23325,9 @@ function loadConfig() {
23191
23325
  type: "array",
23192
23326
  string: true,
23193
23327
  description: "Import only tools for specified HTTP methods (e.g., get, post)"
23328
+ }).option("disable-abbreviation", {
23329
+ type: "boolean",
23330
+ description: "Disable name optimization"
23194
23331
  }).help().parseSync();
23195
23332
  let transportType;
23196
23333
  if (argv.transport === "http" || process.env.TRANSPORT_TYPE === "http") {
@@ -23203,6 +23340,7 @@ function loadConfig() {
23203
23340
  const endpointPath = argv.path || process.env.ENDPOINT_PATH || "/mcp";
23204
23341
  const apiBaseUrl = argv["api-base-url"] || process.env.API_BASE_URL;
23205
23342
  const openApiSpec = argv["openapi-spec"] || process.env.OPENAPI_SPEC_PATH;
23343
+ const disableAbbreviation = argv["disable-abbreviation"] || (process.env.DISABLE_ABBREVIATION ? process.env.DISABLE_ABBREVIATION === "true" : false);
23206
23344
  if (!apiBaseUrl) {
23207
23345
  throw new Error("API base URL is required (--api-base-url or API_BASE_URL)");
23208
23346
  }
@@ -23224,7 +23362,8 @@ function loadConfig() {
23224
23362
  includeTags: argv.tag,
23225
23363
  includeResources: argv.resource,
23226
23364
  includeOperations: argv.operation,
23227
- toolsMode: argv.tools || process.env.TOOLS_MODE || "all"
23365
+ toolsMode: argv.tools || process.env.TOOLS_MODE || "all",
23366
+ disableAbbreviation: disableAbbreviation ? true : void 0
23228
23367
  };
23229
23368
  }
23230
23369
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ivotoby/openapi-mcp-server",
3
- "version": "1.2.2",
3
+ "version": "1.4.0",
4
4
  "description": "An MCP server that exposes OpenAPI endpoints as resources",
5
5
  "license": "MIT",
6
6
  "type": "module",