@ivotoby/openapi-mcp-server 1.6.1 → 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 +235 -65
  3. package/dist/cli.js +235 -65
  4. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -14374,14 +14374,17 @@ var WORD_ABBREVIATIONS = {
14374
14374
  // src/utils/tool-id.ts
14375
14375
  function parseToolId(toolId) {
14376
14376
  const [method, pathPart] = toolId.split("::", 2);
14377
- const path = pathPart ? "/" + pathPart.replace(/-/g, "/") : "";
14378
- return { method, path };
14377
+ if (!pathPart) {
14378
+ return { method, path: "" };
14379
+ }
14380
+ const path = pathPart.replace(/__/g, "/");
14381
+ return { method, path: "/" + path };
14379
14382
  }
14380
14383
  function sanitizeForToolId(input) {
14381
- return input.replace(/[^A-Za-z0-9_-]/g, "").replace(/^-+|-+$/g, "").replace(/-{2,}/g, "-");
14384
+ return input.replace(/[^A-Za-z0-9_-]/g, "").replace(/_{3,}/g, "__").replace(/^[_-]+|[_-]+$/g, "");
14382
14385
  }
14383
14386
  function generateToolId(method, path) {
14384
- const cleanPath = path.replace(/^\//, "").replace(/\{([^}]+)\}/g, "$1").replace(/\//g, "-");
14387
+ const cleanPath = path.replace(/^\//, "").replace(/\/+/g, "/").replace(/\{([^}]+)\}/g, "$1").replace(/\//g, "__");
14385
14388
  const sanitizedPath = sanitizeForToolId(cleanPath);
14386
14389
  return `${method.toUpperCase()}::${sanitizedPath}`;
14387
14390
  }
@@ -14509,8 +14512,79 @@ var OpenAPISpecLoader = class {
14509
14512
  visited.add(name);
14510
14513
  return this.inlineSchema(comp, components, visited);
14511
14514
  }
14515
+ return {};
14512
14516
  }
14513
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
+ }
14514
14588
  if (schemaObj.type === "object" && schemaObj.properties) {
14515
14589
  const newProps = {};
14516
14590
  for (const [propName, propSchema] of Object.entries(schemaObj.properties)) {
@@ -14523,14 +14597,12 @@ var OpenAPISpecLoader = class {
14523
14597
  return { ...schemaObj, properties: newProps };
14524
14598
  }
14525
14599
  if (schemaObj.type === "array" && schemaObj.items) {
14526
- return {
14527
- ...schemaObj,
14528
- items: this.inlineSchema(
14529
- schemaObj.items,
14530
- components,
14531
- new Set(visited)
14532
- )
14533
- };
14600
+ const inlinedItems = this.inlineSchema(
14601
+ schemaObj.items,
14602
+ components,
14603
+ new Set(visited)
14604
+ );
14605
+ return { ...schemaObj, items: inlinedItems };
14534
14606
  }
14535
14607
  return schemaObj;
14536
14608
  }
@@ -14561,6 +14633,24 @@ var OpenAPISpecLoader = class {
14561
14633
  }
14562
14634
  return void 0;
14563
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
+ }
14564
14654
  /**
14565
14655
  * Parse an OpenAPI specification into a map of tools
14566
14656
  */
@@ -14568,6 +14658,41 @@ var OpenAPISpecLoader = class {
14568
14658
  const tools = /* @__PURE__ */ new Map();
14569
14659
  for (const [path, pathItem] of Object.entries(spec.paths)) {
14570
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
+ }
14571
14696
  for (const [method, operation] of Object.entries(pathItem)) {
14572
14697
  if (method === "parameters" || !operation) continue;
14573
14698
  if (!["get", "post", "put", "patch", "delete", "options", "head"].includes(
@@ -14586,10 +14711,20 @@ var OpenAPISpecLoader = class {
14586
14711
  inputSchema: {
14587
14712
  type: "object",
14588
14713
  properties: {}
14589
- }
14714
+ },
14715
+ // Add metadata for filtering
14716
+ tags: op.tags || [],
14717
+ httpMethod: method.toUpperCase(),
14718
+ resourceName: this.extractResourceName(path),
14719
+ originalPath: path
14590
14720
  };
14591
14721
  tool["x-original-path"] = path;
14592
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
+ }
14593
14728
  if (op.parameters) {
14594
14729
  for (const param of op.parameters) {
14595
14730
  let paramObj;
@@ -14619,37 +14754,39 @@ var OpenAPISpecLoader = class {
14619
14754
  );
14620
14755
  continue;
14621
14756
  }
14622
- if (!paramObj) {
14623
- console.warn("Failed to process a parameter (paramObj is undefined):", param);
14624
- continue;
14757
+ if (paramObj) {
14758
+ const key = `${paramObj.name}::${paramObj.in}`;
14759
+ parameterMap.set(key, paramObj);
14625
14760
  }
14626
- if (paramObj.schema) {
14627
- const paramSchema = this.inlineSchema(
14628
- paramObj.schema,
14629
- spec.components?.schemas,
14630
- /* @__PURE__ */ new Set()
14631
- );
14632
- const paramDef = {
14633
- description: paramObj.description || `${paramObj.name} parameter`,
14634
- "x-parameter-location": paramObj.in
14635
- // Store parameter location (path, query, etc.)
14636
- };
14637
- const paramType = this.determineParameterType(paramSchema, paramObj.name);
14638
- if (paramType !== void 0) {
14639
- paramDef.type = paramType;
14640
- }
14641
- if (typeof paramSchema === "object" && paramSchema !== null) {
14642
- for (const [key, value] of Object.entries(paramSchema)) {
14643
- if (key === "description" && paramDef.description) continue;
14644
- if (key === "type" && paramDef.type) continue;
14645
- paramDef[key] = value;
14646
- }
14647
- }
14648
- tool.inputSchema.properties[paramObj.name] = paramDef;
14649
- if (paramObj.required === true) {
14650
- 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;
14651
14784
  }
14652
14785
  }
14786
+ tool.inputSchema.properties[paramObj.name] = paramDef;
14787
+ if (paramObj.required === true) {
14788
+ requiredParams.push(paramObj.name);
14789
+ }
14653
14790
  }
14654
14791
  }
14655
14792
  if (op.requestBody && "content" in op.requestBody) {
@@ -14667,7 +14804,24 @@ var OpenAPISpecLoader = class {
14667
14804
  spec.components?.schemas,
14668
14805
  /* @__PURE__ */ new Set()
14669
14806
  );
14670
- 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) {
14671
14825
  for (const [propName, propSchema] of Object.entries(inlinedSchema.properties)) {
14672
14826
  const paramName = tool.inputSchema.properties[propName] ? `body_${propName}` : propName;
14673
14827
  tool.inputSchema.properties[paramName] = propSchema;
@@ -14875,46 +15029,62 @@ var ToolsManager = class {
14875
15029
  this.tools = this.createDynamicTools();
14876
15030
  return;
14877
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
+ }
14878
15051
  const rawTools = this.specLoader.parseOpenAPISpec(spec);
14879
15052
  const filtered = /* @__PURE__ */ new Map();
14880
15053
  const includeToolsLower = this.config.includeTools?.map((t) => t.toLowerCase()) || [];
14881
15054
  const includeOperationsLower = this.config.includeOperations?.map((op) => op.toLowerCase()) || [];
14882
- const includeResourcesLower = this.config.includeResources || [];
15055
+ const includeResourcesLower = this.config.includeResources?.map((res) => res.toLowerCase()) || [];
14883
15056
  const includeTagsLower = this.config.includeTags?.map((tag) => tag.toLowerCase()) || [];
14884
- const resourcePathsLower = includeResourcesLower.map((res) => ({
14885
- exact: `/${res}`.toLowerCase(),
14886
- prefix: `/${res}/`.toLowerCase()
14887
- }));
14888
15057
  for (const [toolId, tool] of rawTools.entries()) {
15058
+ const extendedTool = tool;
14889
15059
  if (includeToolsLower.length > 0) {
14890
15060
  const toolIdLower = toolId.toLowerCase();
14891
15061
  const toolNameLower = tool.name.toLowerCase();
14892
15062
  if (!includeToolsLower.includes(toolIdLower) && !includeToolsLower.includes(toolNameLower)) {
14893
15063
  continue;
14894
15064
  }
15065
+ filtered.set(toolId, tool);
15066
+ continue;
14895
15067
  }
14896
15068
  if (includeOperationsLower.length > 0) {
14897
- const { method } = this.parseToolId(toolId);
14898
- if (!includeOperationsLower.includes(method.toLowerCase())) {
15069
+ const httpMethod = typeof extendedTool.httpMethod === "string" ? extendedTool.httpMethod.toLowerCase() : void 0;
15070
+ if (!httpMethod || !includeOperationsLower.includes(httpMethod)) {
14899
15071
  continue;
14900
15072
  }
14901
15073
  }
14902
- if (resourcePathsLower.length > 0) {
14903
- const { path } = this.parseToolId(toolId);
14904
- const pathLower = path.toLowerCase();
14905
- const match = resourcePathsLower.some(
14906
- (res) => pathLower === res.exact || pathLower.startsWith(res.prefix)
14907
- );
14908
- 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
+ }
14909
15079
  }
14910
15080
  if (includeTagsLower.length > 0) {
14911
- const { method, path } = this.parseToolId(toolId);
14912
- const methodLower = method.toLowerCase();
14913
- const pathItem = spec.paths[path];
14914
- if (!pathItem) continue;
14915
- const opObj = pathItem[methodLower];
14916
- const tags = Array.isArray(opObj?.tags) ? opObj.tags : [];
14917
- 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
+ }
14918
15088
  }
14919
15089
  filtered.set(toolId, tool);
14920
15090
  }
@@ -23443,8 +23613,8 @@ function loadConfig() {
23443
23613
  description: "Server version"
23444
23614
  }).option("tools", {
23445
23615
  type: "string",
23446
- choices: ["all", "dynamic"],
23447
- 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)"
23448
23618
  }).option("tool", {
23449
23619
  type: "array",
23450
23620
  string: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ivotoby/openapi-mcp-server",
3
- "version": "1.6.1",
3
+ "version": "1.7.0",
4
4
  "description": "An MCP server that exposes OpenAPI endpoints as resources",
5
5
  "license": "MIT",
6
6
  "type": "module",