@ivotoby/openapi-mcp-server 1.6.0 → 1.7.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 (4) hide show
  1. package/README.md +291 -226
  2. package/dist/bundle.js +259 -79
  3. package/dist/cli.js +259 -79
  4. package/package.json +4 -2
package/dist/bundle.js CHANGED
@@ -14286,7 +14286,7 @@ var js_yaml_default = jsYaml;
14286
14286
  // src/openapi-loader.ts
14287
14287
  import crypto from "crypto";
14288
14288
 
14289
- // src/abbreviations.ts
14289
+ // src/utils/abbreviations.ts
14290
14290
  var REVISED_COMMON_WORDS_TO_REMOVE = [
14291
14291
  "controller",
14292
14292
  "api",
@@ -14371,6 +14371,24 @@ var WORD_ABBREVIATIONS = {
14371
14371
  query: "Qry"
14372
14372
  };
14373
14373
 
14374
+ // src/utils/tool-id.ts
14375
+ function parseToolId(toolId) {
14376
+ const [method, pathPart] = toolId.split("::", 2);
14377
+ if (!pathPart) {
14378
+ return { method, path: "" };
14379
+ }
14380
+ const path = pathPart.replace(/__/g, "/");
14381
+ return { method, path: "/" + path };
14382
+ }
14383
+ function sanitizeForToolId(input) {
14384
+ return input.replace(/[^A-Za-z0-9_-]/g, "").replace(/_{3,}/g, "__").replace(/^[_-]+|[_-]+$/g, "");
14385
+ }
14386
+ function generateToolId(method, path) {
14387
+ const cleanPath = path.replace(/^\//, "").replace(/\/+/g, "/").replace(/\{([^}]+)\}/g, "$1").replace(/\//g, "__");
14388
+ const sanitizedPath = sanitizeForToolId(cleanPath);
14389
+ return `${method.toUpperCase()}::${sanitizedPath}`;
14390
+ }
14391
+
14374
14392
  // src/openapi-loader.ts
14375
14393
  var OpenAPISpecLoader = class {
14376
14394
  /**
@@ -14494,8 +14512,79 @@ var OpenAPISpecLoader = class {
14494
14512
  visited.add(name);
14495
14513
  return this.inlineSchema(comp, components, visited);
14496
14514
  }
14515
+ return {};
14497
14516
  }
14498
14517
  const schemaObj = schema2;
14518
+ if (schemaObj.allOf) {
14519
+ const mergedSchema = {
14520
+ type: "object",
14521
+ properties: {},
14522
+ required: []
14523
+ };
14524
+ for (const subSchema of schemaObj.allOf) {
14525
+ const inlinedSubSchema = this.inlineSchema(
14526
+ subSchema,
14527
+ components,
14528
+ new Set(visited)
14529
+ );
14530
+ if (inlinedSubSchema.properties) {
14531
+ mergedSchema.properties = {
14532
+ ...mergedSchema.properties,
14533
+ ...inlinedSubSchema.properties
14534
+ };
14535
+ }
14536
+ if (inlinedSubSchema.required) {
14537
+ mergedSchema.required = [...mergedSchema.required || [], ...inlinedSubSchema.required];
14538
+ }
14539
+ for (const [key, value] of Object.entries(inlinedSubSchema)) {
14540
+ if (key !== "properties" && key !== "required" && !(key in mergedSchema)) {
14541
+ ;
14542
+ mergedSchema[key] = value;
14543
+ }
14544
+ }
14545
+ }
14546
+ if (mergedSchema.required && mergedSchema.required.length === 0) {
14547
+ delete mergedSchema.required;
14548
+ }
14549
+ return mergedSchema;
14550
+ }
14551
+ if (schemaObj.oneOf) {
14552
+ const inlinedOneOf = schemaObj.oneOf.map(
14553
+ (subSchema) => this.inlineSchema(
14554
+ subSchema,
14555
+ components,
14556
+ new Set(visited)
14557
+ )
14558
+ );
14559
+ return {
14560
+ ...schemaObj,
14561
+ oneOf: inlinedOneOf
14562
+ };
14563
+ }
14564
+ if (schemaObj.anyOf) {
14565
+ const inlinedAnyOf = schemaObj.anyOf.map(
14566
+ (subSchema) => this.inlineSchema(
14567
+ subSchema,
14568
+ components,
14569
+ new Set(visited)
14570
+ )
14571
+ );
14572
+ return {
14573
+ ...schemaObj,
14574
+ anyOf: inlinedAnyOf
14575
+ };
14576
+ }
14577
+ if (schemaObj.not) {
14578
+ const inlinedNot = this.inlineSchema(
14579
+ schemaObj.not,
14580
+ components,
14581
+ new Set(visited)
14582
+ );
14583
+ return {
14584
+ ...schemaObj,
14585
+ not: inlinedNot
14586
+ };
14587
+ }
14499
14588
  if (schemaObj.type === "object" && schemaObj.properties) {
14500
14589
  const newProps = {};
14501
14590
  for (const [propName, propSchema] of Object.entries(schemaObj.properties)) {
@@ -14508,14 +14597,12 @@ var OpenAPISpecLoader = class {
14508
14597
  return { ...schemaObj, properties: newProps };
14509
14598
  }
14510
14599
  if (schemaObj.type === "array" && schemaObj.items) {
14511
- return {
14512
- ...schemaObj,
14513
- items: this.inlineSchema(
14514
- schemaObj.items,
14515
- components,
14516
- new Set(visited)
14517
- )
14518
- };
14600
+ const inlinedItems = this.inlineSchema(
14601
+ schemaObj.items,
14602
+ components,
14603
+ new Set(visited)
14604
+ );
14605
+ return { ...schemaObj, items: inlinedItems };
14519
14606
  }
14520
14607
  return schemaObj;
14521
14608
  }
@@ -14546,6 +14633,24 @@ var OpenAPISpecLoader = class {
14546
14633
  }
14547
14634
  return void 0;
14548
14635
  }
14636
+ /**
14637
+ * Extract the primary resource name from an OpenAPI path
14638
+ * Examples:
14639
+ * - "/users" -> "users"
14640
+ * - "/users/{id}" -> "users"
14641
+ * - "/api/v1/users/{id}/posts" -> "posts"
14642
+ * - "/health" -> "health"
14643
+ */
14644
+ extractResourceName(path) {
14645
+ const segments = path.replace(/^\//, "").split("/");
14646
+ for (let i = segments.length - 1; i >= 0; i--) {
14647
+ const segment = segments[i];
14648
+ if (!segment.includes("{") && !segment.includes("}") && segment.length > 0) {
14649
+ return segment;
14650
+ }
14651
+ }
14652
+ return segments[0] || void 0;
14653
+ }
14549
14654
  /**
14550
14655
  * Parse an OpenAPI specification into a map of tools
14551
14656
  */
@@ -14553,6 +14658,41 @@ var OpenAPISpecLoader = class {
14553
14658
  const tools = /* @__PURE__ */ new Map();
14554
14659
  for (const [path, pathItem] of Object.entries(spec.paths)) {
14555
14660
  if (!pathItem) continue;
14661
+ const pathLevelParameters = [];
14662
+ if (pathItem.parameters) {
14663
+ for (const param of pathItem.parameters) {
14664
+ let paramObj;
14665
+ if ("$ref" in param && typeof param.$ref === "string") {
14666
+ const refMatch = param.$ref.match(/^#\/components\/parameters\/(.+)$/);
14667
+ if (refMatch && spec.components?.parameters) {
14668
+ const paramNameFromRef = refMatch[1];
14669
+ const resolvedParam = spec.components.parameters[paramNameFromRef];
14670
+ if (resolvedParam && "name" in resolvedParam && "in" in resolvedParam) {
14671
+ paramObj = resolvedParam;
14672
+ } else {
14673
+ console.warn(
14674
+ `Could not resolve path-level parameter reference or invalid structure: ${param.$ref}`
14675
+ );
14676
+ continue;
14677
+ }
14678
+ } else {
14679
+ console.warn(`Could not parse path-level parameter reference: ${param.$ref}`);
14680
+ continue;
14681
+ }
14682
+ } else if ("name" in param && "in" in param) {
14683
+ paramObj = param;
14684
+ } else {
14685
+ console.warn(
14686
+ "Skipping path-level parameter due to missing 'name' or 'in' properties and not being a valid $ref:",
14687
+ param
14688
+ );
14689
+ continue;
14690
+ }
14691
+ if (paramObj) {
14692
+ pathLevelParameters.push(paramObj);
14693
+ }
14694
+ }
14695
+ }
14556
14696
  for (const [method, operation] of Object.entries(pathItem)) {
14557
14697
  if (method === "parameters" || !operation) continue;
14558
14698
  if (!["get", "post", "put", "patch", "delete", "options", "head"].includes(
@@ -14562,9 +14702,8 @@ var OpenAPISpecLoader = class {
14562
14702
  continue;
14563
14703
  }
14564
14704
  const op = operation;
14565
- const cleanPath = path.replace(/^\//, "").replace(/\{([^}]+)\}/g, "$1");
14566
- const toolId = `${method.toUpperCase()}-${cleanPath}`.replace(/[^a-zA-Z0-9-]/g, "-");
14567
- let nameSource = op.operationId || op.summary || `${method.toUpperCase()} ${path}`;
14705
+ const toolId = generateToolId(method, path);
14706
+ const nameSource = op.operationId || op.summary || `${method.toUpperCase()} ${path}`;
14568
14707
  const name = this.abbreviateOperationId(nameSource);
14569
14708
  const tool = {
14570
14709
  name,
@@ -14572,9 +14711,20 @@ var OpenAPISpecLoader = class {
14572
14711
  inputSchema: {
14573
14712
  type: "object",
14574
14713
  properties: {}
14575
- }
14714
+ },
14715
+ // Add metadata for filtering
14716
+ tags: op.tags || [],
14717
+ httpMethod: method.toUpperCase(),
14718
+ resourceName: this.extractResourceName(path),
14719
+ originalPath: path
14576
14720
  };
14721
+ tool["x-original-path"] = path;
14577
14722
  const requiredParams = [];
14723
+ const parameterMap = /* @__PURE__ */ new Map();
14724
+ for (const pathParam of pathLevelParameters) {
14725
+ const key = `${pathParam.name}::${pathParam.in}`;
14726
+ parameterMap.set(key, pathParam);
14727
+ }
14578
14728
  if (op.parameters) {
14579
14729
  for (const param of op.parameters) {
14580
14730
  let paramObj;
@@ -14604,37 +14754,39 @@ var OpenAPISpecLoader = class {
14604
14754
  );
14605
14755
  continue;
14606
14756
  }
14607
- if (!paramObj) {
14608
- console.warn("Failed to process a parameter (paramObj is undefined):", param);
14609
- continue;
14757
+ if (paramObj) {
14758
+ const key = `${paramObj.name}::${paramObj.in}`;
14759
+ parameterMap.set(key, paramObj);
14610
14760
  }
14611
- if (paramObj.schema) {
14612
- const paramSchema = this.inlineSchema(
14613
- paramObj.schema,
14614
- spec.components?.schemas,
14615
- /* @__PURE__ */ new Set()
14616
- );
14617
- const paramDef = {
14618
- description: paramObj.description || `${paramObj.name} parameter`,
14619
- "x-parameter-location": paramObj.in
14620
- // Store parameter location (path, query, etc.)
14621
- };
14622
- const paramType = this.determineParameterType(paramSchema, paramObj.name);
14623
- if (paramType !== void 0) {
14624
- paramDef.type = paramType;
14625
- }
14626
- if (typeof paramSchema === "object" && paramSchema !== null) {
14627
- for (const [key, value] of Object.entries(paramSchema)) {
14628
- if (key === "description" && paramDef.description) continue;
14629
- if (key === "type" && paramDef.type) continue;
14630
- paramDef[key] = value;
14631
- }
14632
- }
14633
- tool.inputSchema.properties[paramObj.name] = paramDef;
14634
- if (paramObj.required === true) {
14635
- requiredParams.push(paramObj.name);
14761
+ }
14762
+ }
14763
+ for (const paramObj of parameterMap.values()) {
14764
+ if (paramObj.schema) {
14765
+ const paramSchema = this.inlineSchema(
14766
+ paramObj.schema,
14767
+ spec.components?.schemas,
14768
+ /* @__PURE__ */ new Set()
14769
+ );
14770
+ const paramDef = {
14771
+ description: paramObj.description || `${paramObj.name} parameter`,
14772
+ "x-parameter-location": paramObj.in
14773
+ // Store parameter location (path, query, etc.)
14774
+ };
14775
+ const paramType = this.determineParameterType(paramSchema, paramObj.name);
14776
+ if (paramType !== void 0) {
14777
+ paramDef.type = paramType;
14778
+ }
14779
+ if (typeof paramSchema === "object" && paramSchema !== null) {
14780
+ for (const [key, value] of Object.entries(paramSchema)) {
14781
+ if (key === "description" && paramDef.description) continue;
14782
+ if (key === "type" && paramDef.type) continue;
14783
+ paramDef[key] = value;
14636
14784
  }
14637
14785
  }
14786
+ tool.inputSchema.properties[paramObj.name] = paramDef;
14787
+ if (paramObj.required === true) {
14788
+ requiredParams.push(paramObj.name);
14789
+ }
14638
14790
  }
14639
14791
  }
14640
14792
  if (op.requestBody && "content" in op.requestBody) {
@@ -14652,7 +14804,24 @@ var OpenAPISpecLoader = class {
14652
14804
  spec.components?.schemas,
14653
14805
  /* @__PURE__ */ new Set()
14654
14806
  );
14655
- if (inlinedSchema.type === "object" && inlinedSchema.properties) {
14807
+ if (inlinedSchema.oneOf || inlinedSchema.anyOf) {
14808
+ const existingProperties = tool.inputSchema.properties || {};
14809
+ const hasExistingProperties = Object.keys(existingProperties).length > 0;
14810
+ if (hasExistingProperties) {
14811
+ const objectSchema = {
14812
+ type: "object",
14813
+ properties: existingProperties
14814
+ };
14815
+ if (tool.inputSchema.required) {
14816
+ objectSchema.required = tool.inputSchema.required;
14817
+ }
14818
+ tool.inputSchema = {
14819
+ allOf: [objectSchema, inlinedSchema]
14820
+ };
14821
+ } else {
14822
+ tool.inputSchema = inlinedSchema;
14823
+ }
14824
+ } else if (inlinedSchema.type === "object" && inlinedSchema.properties) {
14656
14825
  for (const [propName, propSchema] of Object.entries(inlinedSchema.properties)) {
14657
14826
  const paramName = tool.inputSchema.properties[propName] ? `body_${propName}` : propName;
14658
14827
  tool.inputSchema.properties[paramName] = propSchema;
@@ -14860,46 +15029,62 @@ var ToolsManager = class {
14860
15029
  this.tools = this.createDynamicTools();
14861
15030
  return;
14862
15031
  }
15032
+ if (this.config.toolsMode === "explicit") {
15033
+ const rawTools2 = this.specLoader.parseOpenAPISpec(spec);
15034
+ const filtered2 = /* @__PURE__ */ new Map();
15035
+ if (this.config.includeTools && this.config.includeTools.length > 0) {
15036
+ const includeToolsLower2 = this.config.includeTools.map((t) => t.toLowerCase());
15037
+ for (const [toolId, tool] of rawTools2.entries()) {
15038
+ const toolIdLower = toolId.toLowerCase();
15039
+ const toolNameLower = tool.name.toLowerCase();
15040
+ if (includeToolsLower2.includes(toolIdLower) || includeToolsLower2.includes(toolNameLower)) {
15041
+ filtered2.set(toolId, tool);
15042
+ }
15043
+ }
15044
+ }
15045
+ this.tools = filtered2;
15046
+ for (const [toolId, tool] of this.tools.entries()) {
15047
+ console.error(`Registered tool: ${toolId} (${tool.name})`);
15048
+ }
15049
+ return;
15050
+ }
14863
15051
  const rawTools = this.specLoader.parseOpenAPISpec(spec);
14864
15052
  const filtered = /* @__PURE__ */ new Map();
14865
15053
  const includeToolsLower = this.config.includeTools?.map((t) => t.toLowerCase()) || [];
14866
15054
  const includeOperationsLower = this.config.includeOperations?.map((op) => op.toLowerCase()) || [];
14867
- const includeResourcesLower = this.config.includeResources || [];
15055
+ const includeResourcesLower = this.config.includeResources?.map((res) => res.toLowerCase()) || [];
14868
15056
  const includeTagsLower = this.config.includeTags?.map((tag) => tag.toLowerCase()) || [];
14869
- const resourcePathsLower = includeResourcesLower.map((res) => ({
14870
- exact: `/${res}`.toLowerCase(),
14871
- prefix: `/${res}/`.toLowerCase()
14872
- }));
14873
15057
  for (const [toolId, tool] of rawTools.entries()) {
15058
+ const extendedTool = tool;
14874
15059
  if (includeToolsLower.length > 0) {
14875
15060
  const toolIdLower = toolId.toLowerCase();
14876
15061
  const toolNameLower = tool.name.toLowerCase();
14877
15062
  if (!includeToolsLower.includes(toolIdLower) && !includeToolsLower.includes(toolNameLower)) {
14878
15063
  continue;
14879
15064
  }
15065
+ filtered.set(toolId, tool);
15066
+ continue;
14880
15067
  }
14881
15068
  if (includeOperationsLower.length > 0) {
14882
- const { method } = this.parseToolId(toolId);
14883
- if (!includeOperationsLower.includes(method.toLowerCase())) {
15069
+ const httpMethod = typeof extendedTool.httpMethod === "string" ? extendedTool.httpMethod.toLowerCase() : void 0;
15070
+ if (!httpMethod || !includeOperationsLower.includes(httpMethod)) {
14884
15071
  continue;
14885
15072
  }
14886
15073
  }
14887
- if (resourcePathsLower.length > 0) {
14888
- const { path } = this.parseToolId(toolId);
14889
- const pathLower = path.toLowerCase();
14890
- const match = resourcePathsLower.some(
14891
- (res) => pathLower === res.exact || pathLower.startsWith(res.prefix)
14892
- );
14893
- if (!match) continue;
15074
+ if (includeResourcesLower.length > 0) {
15075
+ const resourceName = typeof extendedTool.resourceName === "string" ? extendedTool.resourceName.toLowerCase() : void 0;
15076
+ if (!resourceName || !includeResourcesLower.includes(resourceName)) {
15077
+ continue;
15078
+ }
14894
15079
  }
14895
15080
  if (includeTagsLower.length > 0) {
14896
- const { method, path } = this.parseToolId(toolId);
14897
- const methodLower = method.toLowerCase();
14898
- const pathItem = spec.paths[path];
14899
- if (!pathItem) continue;
14900
- const opObj = pathItem[methodLower];
14901
- const tags = Array.isArray(opObj?.tags) ? opObj.tags : [];
14902
- if (!tags.some((tag) => includeTagsLower.includes(tag.toLowerCase()))) continue;
15081
+ const toolTags = Array.isArray(extendedTool.tags) ? extendedTool.tags : [];
15082
+ const hasMatchingTag = toolTags.some(
15083
+ (tag) => typeof tag === "string" && includeTagsLower.includes(tag.toLowerCase())
15084
+ );
15085
+ if (!hasMatchingTag) {
15086
+ continue;
15087
+ }
14903
15088
  }
14904
15089
  filtered.set(toolId, tool);
14905
15090
  }
@@ -14940,11 +15125,12 @@ var ToolsManager = class {
14940
15125
  }
14941
15126
  /**
14942
15127
  * Get the path and method from a tool ID
15128
+ *
15129
+ * Note: This converts hyphens back to slashes to reconstruct the original API path.
15130
+ * This is consistent with ApiClient.parseToolId() which needs the actual path for HTTP requests.
14943
15131
  */
14944
15132
  parseToolId(toolId) {
14945
- const [method, ...pathParts] = toolId.split("-");
14946
- const path = "/" + pathParts.join("/").replace(/-/g, "/");
14947
- return { method, path };
15133
+ return parseToolId(toolId);
14948
15134
  }
14949
15135
  };
14950
15136
 
@@ -18394,13 +18580,9 @@ var ApiClient = class {
18394
18580
  if (axios_default.isAxiosError(error)) {
18395
18581
  const axiosError = error;
18396
18582
  if (!isRetry && isAuthError(axiosError)) {
18397
- try {
18398
- const shouldRetry = await this.authProvider.handleAuthError(axiosError);
18399
- if (shouldRetry) {
18400
- return this.executeApiCallWithRetry(toolId, params, true);
18401
- }
18402
- } catch (authHandlerError) {
18403
- throw authHandlerError;
18583
+ const shouldRetry = await this.authProvider.handleAuthError(axiosError);
18584
+ if (shouldRetry) {
18585
+ return this.executeApiCallWithRetry(toolId, params, true);
18404
18586
  }
18405
18587
  }
18406
18588
  throw new Error(
@@ -18413,13 +18595,11 @@ var ApiClient = class {
18413
18595
  /**
18414
18596
  * Parse a tool ID into HTTP method and path
18415
18597
  *
18416
- * @param toolId - Tool ID in format METHOD-path-parts
18598
+ * @param toolId - Tool ID in format METHOD::pathPart
18417
18599
  * @returns Object containing method and path
18418
18600
  */
18419
18601
  parseToolId(toolId) {
18420
- const [method, ...pathParts] = toolId.split("-");
18421
- const path = "/" + pathParts.join("/").replace(/-/g, "/");
18422
- return { method, path };
18602
+ return parseToolId(toolId);
18423
18603
  }
18424
18604
  /**
18425
18605
  * Process query parameters for GET requests
@@ -23433,8 +23613,8 @@ function loadConfig() {
23433
23613
  description: "Server version"
23434
23614
  }).option("tools", {
23435
23615
  type: "string",
23436
- choices: ["all", "dynamic"],
23437
- description: "Which tools to load: all or dynamic meta-tools"
23616
+ choices: ["all", "dynamic", "explicit"],
23617
+ description: "Which tools to load: all, dynamic meta-tools, or explicit (only includeTools)"
23438
23618
  }).option("tool", {
23439
23619
  type: "array",
23440
23620
  string: true,