@ivotoby/openapi-mcp-server 1.13.0 → 1.14.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.
package/README.md CHANGED
@@ -132,6 +132,7 @@ The server can be configured through environment variables or command line argum
132
132
  - `ENDPOINT_PATH` - Endpoint path for HTTP transport (default: "/mcp")
133
133
  - `TOOLS_MODE` - Tools loading mode: "all" (load all endpoint-based tools), "dynamic" (load only meta-tools), or "explicit" (load only tools specified in includeTools) (default: "all")
134
134
  - `DISABLE_ABBREVIATION` - Disable name optimization (this could throw errors when name is > 64 chars)
135
+ - `VERBOSE` - Enable operational logging (`true` by default; set to `false` to suppress non-essential logs)
135
136
  - `PROMPTS_PATH` - Path or URL to prompts JSON/YAML file
136
137
  - `PROMPTS_INLINE` - Provide prompts directly as JSON string
137
138
  - `RESOURCES_PATH` - Path or URL to resources JSON/YAML file
@@ -152,7 +153,8 @@ npx @ivotoby/openapi-mcp-server \
152
153
  --port 3000 \
153
154
  --host 127.0.0.1 \
154
155
  --path /mcp \
155
- --disable-abbreviation true
156
+ --disable-abbreviation true \
157
+ --verbose false
156
158
  ```
157
159
 
158
160
  ## Mutual TLS (mTLS)
@@ -191,6 +193,8 @@ npx @ivotoby/openapi-mcp-server \
191
193
  - `--ca-cert` / `CA_CERT_PATH`: custom CA bundle for private/internal certificate authorities
192
194
  - `--reject-unauthorized` / `REJECT_UNAUTHORIZED`: set to `false` only when you intentionally want to allow self-signed or otherwise untrusted server certificates
193
195
 
196
+ Set `--verbose false` or `VERBOSE=false` if you want the server to stay quiet in scripts or embedded environments.
197
+
194
198
  ## OpenAPI Specification Loading
195
199
 
196
200
  The MCP server supports multiple methods for loading OpenAPI specifications, providing flexibility for different deployment scenarios:
package/dist/bundle.js CHANGED
@@ -14408,14 +14408,39 @@ function generateToolId(method, path2) {
14408
14408
  return `${method.toUpperCase()}::${sanitizedPath}`;
14409
14409
  }
14410
14410
 
14411
+ // src/utils/logger.ts
14412
+ var Logger = class {
14413
+ constructor(verbose = true) {
14414
+ this.verbose = verbose;
14415
+ }
14416
+ setVerbose(verbose) {
14417
+ this.verbose = verbose ?? true;
14418
+ }
14419
+ warn(message, ...optionalParams) {
14420
+ if (this.verbose) {
14421
+ console.warn(message, ...optionalParams);
14422
+ }
14423
+ }
14424
+ error(message, ...optionalParams) {
14425
+ if (this.verbose) {
14426
+ console.error(message, ...optionalParams);
14427
+ }
14428
+ }
14429
+ fatal(message, ...optionalParams) {
14430
+ console.error(message, ...optionalParams);
14431
+ }
14432
+ };
14433
+
14411
14434
  // src/openapi-loader.ts
14412
14435
  var OpenAPISpecLoader = class {
14413
14436
  /**
14414
14437
  * Disable name optimization
14415
14438
  */
14416
14439
  disableAbbreviation;
14440
+ logger;
14417
14441
  constructor(config) {
14418
14442
  this.disableAbbreviation = config?.disableAbbreviation ?? false;
14443
+ this.logger = new Logger(config?.verbose ?? true);
14419
14444
  }
14420
14445
  /**
14421
14446
  * Load an OpenAPI specification from various sources
@@ -14635,7 +14660,7 @@ var OpenAPISpecLoader = class {
14635
14660
  */
14636
14661
  determineParameterType(paramSchema, paramName) {
14637
14662
  if (Object.keys(paramSchema).length === 0 && typeof paramSchema !== "boolean") {
14638
- console.warn(
14663
+ this.logger.warn(
14639
14664
  `Parameter '${paramName}' schema was empty after inlining (potential cycle or unresolvable ref), defaulting to string.`
14640
14665
  );
14641
14666
  return "string";
@@ -14691,19 +14716,19 @@ var OpenAPISpecLoader = class {
14691
14716
  if (resolvedParam && "name" in resolvedParam && "in" in resolvedParam) {
14692
14717
  paramObj = resolvedParam;
14693
14718
  } else {
14694
- console.warn(
14719
+ this.logger.warn(
14695
14720
  `Could not resolve path-level parameter reference or invalid structure: ${param.$ref}`
14696
14721
  );
14697
14722
  continue;
14698
14723
  }
14699
14724
  } else {
14700
- console.warn(`Could not parse path-level parameter reference: ${param.$ref}`);
14725
+ this.logger.warn(`Could not parse path-level parameter reference: ${param.$ref}`);
14701
14726
  continue;
14702
14727
  }
14703
14728
  } else if ("name" in param && "in" in param) {
14704
14729
  paramObj = param;
14705
14730
  } else {
14706
- console.warn(
14731
+ this.logger.warn(
14707
14732
  "Skipping path-level parameter due to missing 'name' or 'in' properties and not being a valid $ref:",
14708
14733
  param
14709
14734
  );
@@ -14719,7 +14744,7 @@ var OpenAPISpecLoader = class {
14719
14744
  if (!["get", "post", "put", "patch", "delete", "options", "head"].includes(
14720
14745
  method.toLowerCase()
14721
14746
  )) {
14722
- console.warn(`Skipping non-HTTP method "${method}" for path ${path2}`);
14747
+ this.logger.warn(`Skipping non-HTTP method "${method}" for path ${path2}`);
14723
14748
  continue;
14724
14749
  }
14725
14750
  const op = operation;
@@ -14757,19 +14782,19 @@ var OpenAPISpecLoader = class {
14757
14782
  if (resolvedParam && "name" in resolvedParam && "in" in resolvedParam) {
14758
14783
  paramObj = resolvedParam;
14759
14784
  } else {
14760
- console.warn(
14785
+ this.logger.warn(
14761
14786
  `Could not resolve parameter reference or invalid structure: ${param.$ref}`
14762
14787
  );
14763
14788
  continue;
14764
14789
  }
14765
14790
  } else {
14766
- console.warn(`Could not parse parameter reference: ${param.$ref}`);
14791
+ this.logger.warn(`Could not parse parameter reference: ${param.$ref}`);
14767
14792
  continue;
14768
14793
  }
14769
14794
  } else if ("name" in param && "in" in param) {
14770
14795
  paramObj = param;
14771
14796
  } else {
14772
- console.warn(
14797
+ this.logger.warn(
14773
14798
  "Skipping parameter due to missing 'name' or 'in' properties and not being a valid $ref:",
14774
14799
  param
14775
14800
  );
@@ -14820,19 +14845,19 @@ var OpenAPISpecLoader = class {
14820
14845
  if (resolvedRequestBody && "content" in resolvedRequestBody) {
14821
14846
  requestBodyObj = resolvedRequestBody;
14822
14847
  } else {
14823
- console.warn(
14848
+ this.logger.warn(
14824
14849
  `Could not resolve requestBody reference or invalid structure: ${op.requestBody.$ref}`
14825
14850
  );
14826
14851
  continue;
14827
14852
  }
14828
14853
  } else {
14829
- console.warn(`Could not parse requestBody reference: ${op.requestBody.$ref}`);
14854
+ this.logger.warn(`Could not parse requestBody reference: ${op.requestBody.$ref}`);
14830
14855
  continue;
14831
14856
  }
14832
14857
  } else if ("content" in op.requestBody) {
14833
14858
  requestBodyObj = op.requestBody;
14834
14859
  } else {
14835
- console.warn("Skipping requestBody due to invalid structure:", op.requestBody);
14860
+ this.logger.warn("Skipping requestBody due to invalid structure:", op.requestBody);
14836
14861
  continue;
14837
14862
  }
14838
14863
  let mediaTypeObj;
@@ -15035,12 +15060,15 @@ var ToolsManager = class {
15035
15060
  this.config = config;
15036
15061
  this.config.toolsMode = this.config.toolsMode || "all";
15037
15062
  this.specLoader = new OpenAPISpecLoader({
15038
- disableAbbreviation: this.config.disableAbbreviation
15063
+ disableAbbreviation: this.config.disableAbbreviation,
15064
+ verbose: this.config.verbose
15039
15065
  });
15066
+ this.logger = new Logger(this.config.verbose);
15040
15067
  }
15041
15068
  tools = /* @__PURE__ */ new Map();
15042
15069
  specLoader;
15043
15070
  loadedSpec;
15071
+ logger;
15044
15072
  /**
15045
15073
  * Get the OpenAPI spec loader instance
15046
15074
  */
@@ -15126,7 +15154,7 @@ var ToolsManager = class {
15126
15154
  }
15127
15155
  this.tools = filtered2;
15128
15156
  for (const [toolId, tool] of this.tools.entries()) {
15129
- console.error(`Registered tool: ${toolId} (${tool.name})`);
15157
+ this.logger.error(`Registered tool: ${toolId} (${tool.name})`);
15130
15158
  }
15131
15159
  return;
15132
15160
  }
@@ -15172,7 +15200,7 @@ var ToolsManager = class {
15172
15200
  }
15173
15201
  this.tools = filtered;
15174
15202
  for (const [toolId, tool] of this.tools.entries()) {
15175
- console.error(`Registered tool: ${toolId} (${tool.name})`);
15203
+ this.logger.error(`Registered tool: ${toolId} (${tool.name})`);
15176
15204
  }
15177
15205
  }
15178
15206
  /**
@@ -19771,8 +19799,10 @@ var OpenAPIServer = class {
19771
19799
  promptsManager;
19772
19800
  resourcesManager;
19773
19801
  config;
19802
+ logger;
19774
19803
  constructor(config) {
19775
19804
  this.config = config;
19805
+ this.logger = new Logger(config.verbose);
19776
19806
  if (config.prompts?.length) {
19777
19807
  this.promptsManager = new PromptsManager({ prompts: config.prompts });
19778
19808
  }
@@ -19856,21 +19886,21 @@ var OpenAPIServer = class {
19856
19886
  });
19857
19887
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
19858
19888
  const { id, name, arguments: params } = request.params;
19859
- console.error("Received request:", request.params);
19860
- console.error("Using parameters from arguments:", params);
19889
+ this.logger.error("Received request:", request.params);
19890
+ this.logger.error("Using parameters from arguments:", params);
19861
19891
  const idOrName = typeof id === "string" ? id : typeof name === "string" ? name : "";
19862
19892
  if (!idOrName) {
19863
19893
  throw new Error("Tool ID or name is required");
19864
19894
  }
19865
19895
  const toolInfo = this.toolsManager.findTool(idOrName);
19866
19896
  if (!toolInfo) {
19867
- console.error(
19897
+ this.logger.error(
19868
19898
  `Available tools: ${Array.from(this.toolsManager.getAllTools()).map((t) => t.name).join(", ")}`
19869
19899
  );
19870
19900
  throw new Error(`Tool not found: ${idOrName}`);
19871
19901
  }
19872
19902
  const { toolId, tool } = toolInfo;
19873
- console.error(`Executing tool: ${toolId} (${tool.name})`);
19903
+ this.logger.error(`Executing tool: ${toolId} (${tool.name})`);
19874
19904
  try {
19875
19905
  const result = await this.apiClient.executeApiCall(toolId, params || {});
19876
19906
  return {
@@ -24918,6 +24948,9 @@ function loadConfig() {
24918
24948
  }).option("resources-inline", {
24919
24949
  type: "string",
24920
24950
  description: "Provide resources directly as JSON string"
24951
+ }).option("verbose", {
24952
+ type: "string",
24953
+ description: "Enable verbose logging"
24921
24954
  }).help().parseSync();
24922
24955
  let transportType;
24923
24956
  if (argv.transport === "http" || process.env.TRANSPORT_TYPE === "http") {
@@ -24980,6 +25013,7 @@ function loadConfig() {
24980
25013
  argv["reject-unauthorized"] ?? process.env.REJECT_UNAUTHORIZED,
24981
25014
  "--reject-unauthorized/REJECT_UNAUTHORIZED"
24982
25015
  );
25016
+ const verbose = parseOptionalBoolean(argv.verbose ?? process.env.VERBOSE, "--verbose/VERBOSE") ?? true;
24983
25017
  return {
24984
25018
  name: argv.name || process.env.SERVER_NAME || "mcp-openapi-server",
24985
25019
  version: argv["server-version"] || process.env.SERVER_VERSION || "1.0.0",
@@ -25006,7 +25040,8 @@ function loadConfig() {
25006
25040
  promptsPath: argv.prompts || process.env.PROMPTS_PATH,
25007
25041
  promptsInline: argv["prompts-inline"] || process.env.PROMPTS_INLINE,
25008
25042
  resourcesPath: argv.resources || process.env.RESOURCES_PATH,
25009
- resourcesInline: argv["resources-inline"] || process.env.RESOURCES_INLINE
25043
+ resourcesInline: argv["resources-inline"] || process.env.RESOURCES_INLINE,
25044
+ verbose
25010
25045
  };
25011
25046
  }
25012
25047
 
@@ -25019,7 +25054,6 @@ import {
25019
25054
  import * as http3 from "http";
25020
25055
  import { randomUUID } from "crypto";
25021
25056
  var StreamableHttpServerTransport = class {
25022
- // Track if using an external server
25023
25057
  /**
25024
25058
  * Initialize a new StreamableHttpServerTransport
25025
25059
  *
@@ -25034,11 +25068,12 @@ var StreamableHttpServerTransport = class {
25034
25068
  * further handlers from running for those requests. The MCP handler should be added after
25035
25069
  * custom handlers and will return early for non-MCP paths.
25036
25070
  */
25037
- constructor(port, host = "127.0.0.1", endpointPath = "/mcp", server) {
25071
+ constructor(port, host = "127.0.0.1", endpointPath = "/mcp", server, verbose = true) {
25038
25072
  this.port = port;
25039
25073
  this.host = host;
25040
25074
  this.endpointPath = endpointPath;
25041
25075
  this.isExternalServer = !!server;
25076
+ this.logger = new Logger(verbose);
25042
25077
  if (server) {
25043
25078
  this.server = server;
25044
25079
  this.server.on("request", this.handleRequest.bind(this));
@@ -25056,6 +25091,8 @@ var StreamableHttpServerTransport = class {
25056
25091
  // Maps request IDs to session IDs
25057
25092
  healthCheckPath = "/health";
25058
25093
  isExternalServer;
25094
+ // Track if using an external server
25095
+ logger;
25059
25096
  /**
25060
25097
  * Callback when message is received
25061
25098
  */
@@ -25078,7 +25115,7 @@ var StreamableHttpServerTransport = class {
25078
25115
  return new Promise((resolve6, reject) => {
25079
25116
  this.server.listen(this.port, this.host, () => {
25080
25117
  this.started = true;
25081
- console.error(
25118
+ this.logger.error(
25082
25119
  `Streamable HTTP transport listening on http://${this.host}:${this.port}${this.endpointPath}`
25083
25120
  );
25084
25121
  resolve6();
@@ -25131,34 +25168,34 @@ var StreamableHttpServerTransport = class {
25131
25168
  * @param message JSON-RPC message
25132
25169
  */
25133
25170
  async send(message) {
25134
- console.error(`StreamableHttpServerTransport: Sending message: ${JSON.stringify(message)}`);
25171
+ this.logger.error(`StreamableHttpServerTransport: Sending message: ${JSON.stringify(message)}`);
25135
25172
  let targetSessionId;
25136
25173
  let messageIdForThisResponse = null;
25137
25174
  if (isJSONRPCResponse(message) && message.id !== null) {
25138
25175
  messageIdForThisResponse = message.id;
25139
25176
  targetSessionId = this.requestSessionMap.get(messageIdForThisResponse);
25140
- console.error(
25177
+ this.logger.error(
25141
25178
  `StreamableHttpServerTransport: Potential target session for response ID ${messageIdForThisResponse}: ${targetSessionId}`
25142
25179
  );
25143
25180
  if (targetSessionId && this.initResponseHandlers.has(targetSessionId)) {
25144
- console.error(
25181
+ this.logger.error(
25145
25182
  `StreamableHttpServerTransport: Session ${targetSessionId} has initResponseHandlers. Invoking them for message ID ${messageIdForThisResponse}.`
25146
25183
  );
25147
25184
  const handlers = this.initResponseHandlers.get(targetSessionId);
25148
25185
  [...handlers].forEach((handler) => handler(message));
25149
25186
  if (!this.requestSessionMap.has(messageIdForThisResponse)) {
25150
- console.error(
25187
+ this.logger.error(
25151
25188
  `StreamableHttpServerTransport: Response for ID ${messageIdForThisResponse} was handled by an initResponseHandler (e.g., synchronous POST response for initialize or tools/list).`
25152
25189
  );
25153
25190
  return;
25154
25191
  } else {
25155
- console.error(
25192
+ this.logger.error(
25156
25193
  `StreamableHttpServerTransport: Response for ID ${messageIdForThisResponse} was NOT exclusively handled by an initResponseHandler or handler did not remove from requestSessionMap. Proceeding to GET stream / broadcast if applicable.`
25157
25194
  );
25158
25195
  }
25159
25196
  }
25160
25197
  if (this.requestSessionMap.has(messageIdForThisResponse)) {
25161
- console.error(
25198
+ this.logger.error(
25162
25199
  `StreamableHttpServerTransport: Deleting request ID ${messageIdForThisResponse} from requestSessionMap as it's being processed for GET stream or broadcast.`
25163
25200
  );
25164
25201
  this.requestSessionMap.delete(messageIdForThisResponse);
@@ -25166,7 +25203,7 @@ var StreamableHttpServerTransport = class {
25166
25203
  }
25167
25204
  if (!targetSessionId) {
25168
25205
  const idForLog = messageIdForThisResponse !== null ? messageIdForThisResponse : isJSONRPCRequest(message) ? message.id : "N/A";
25169
- console.warn(
25206
+ this.logger.warn(
25170
25207
  `StreamableHttpServerTransport: No specific target session for message (ID: ${idForLog}). Broadcasting to all applicable sessions.`
25171
25208
  );
25172
25209
  for (const [sid, session2] of this.sessions.entries()) {
@@ -25178,12 +25215,12 @@ var StreamableHttpServerTransport = class {
25178
25215
  }
25179
25216
  const session = this.sessions.get(targetSessionId);
25180
25217
  if (session && session.activeResponses.size > 0) {
25181
- console.error(
25218
+ this.logger.error(
25182
25219
  `StreamableHttpServerTransport: Sending message (ID: ${messageIdForThisResponse}) to GET stream for session ${targetSessionId} (${session.activeResponses.size} active connections).`
25183
25220
  );
25184
25221
  this.sendMessageToSession(targetSessionId, session, message);
25185
25222
  } else if (targetSessionId) {
25186
- console.error(
25223
+ this.logger.error(
25187
25224
  `StreamableHttpServerTransport: No active GET connections for session ${targetSessionId} to send message (ID: ${messageIdForThisResponse}). Message might not be delivered if not handled by POST.`
25188
25225
  );
25189
25226
  }
@@ -25662,20 +25699,22 @@ async function loadResources(pathOrUrl, inline) {
25662
25699
 
25663
25700
  // src/index.ts
25664
25701
  async function main() {
25702
+ const logger = new Logger(true);
25665
25703
  try {
25666
25704
  const config = loadConfig();
25705
+ logger.setVerbose(config.verbose);
25667
25706
  if (config.promptsPath || config.promptsInline) {
25668
25707
  const prompts = await loadPrompts(config.promptsPath, config.promptsInline);
25669
25708
  if (prompts) {
25670
25709
  config.prompts = prompts;
25671
- console.error(`Loaded ${prompts.length} prompt(s)`);
25710
+ logger.error(`Loaded ${prompts.length} prompt(s)`);
25672
25711
  }
25673
25712
  }
25674
25713
  if (config.resourcesPath || config.resourcesInline) {
25675
25714
  const resources = await loadResources(config.resourcesPath, config.resourcesInline);
25676
25715
  if (resources) {
25677
25716
  config.resources = resources;
25678
- console.error(`Loaded ${resources.length} resource(s)`);
25717
+ logger.error(`Loaded ${resources.length} resource(s)`);
25679
25718
  }
25680
25719
  }
25681
25720
  const server = new OpenAPIServer(config);
@@ -25684,24 +25723,27 @@ async function main() {
25684
25723
  transport = new StreamableHttpServerTransport(
25685
25724
  config.httpPort,
25686
25725
  config.httpHost,
25687
- config.endpointPath
25726
+ config.endpointPath,
25727
+ void 0,
25728
+ config.verbose
25688
25729
  );
25689
25730
  await server.start(transport);
25690
- console.error(
25731
+ logger.error(
25691
25732
  `OpenAPI MCP Server running on http://${config.httpHost}:${config.httpPort}${config.endpointPath}`
25692
25733
  );
25693
25734
  } else {
25694
25735
  transport = new StdioServerTransport();
25695
25736
  await server.start(transport);
25696
- console.error("OpenAPI MCP Server running on stdio");
25737
+ logger.error("OpenAPI MCP Server running on stdio");
25697
25738
  }
25698
25739
  } catch (error) {
25699
- console.error("Failed to start server:", error);
25740
+ logger.fatal("Failed to start server:", error);
25700
25741
  process.exit(1);
25701
25742
  }
25702
25743
  }
25703
25744
  export {
25704
25745
  ApiClient,
25746
+ Logger,
25705
25747
  OpenAPIServer,
25706
25748
  OpenAPISpecLoader,
25707
25749
  PromptsManager,
package/dist/cli.js CHANGED
@@ -14408,14 +14408,39 @@ function generateToolId(method, path2) {
14408
14408
  return `${method.toUpperCase()}::${sanitizedPath}`;
14409
14409
  }
14410
14410
 
14411
+ // src/utils/logger.ts
14412
+ var Logger = class {
14413
+ constructor(verbose = true) {
14414
+ this.verbose = verbose;
14415
+ }
14416
+ setVerbose(verbose) {
14417
+ this.verbose = verbose ?? true;
14418
+ }
14419
+ warn(message, ...optionalParams) {
14420
+ if (this.verbose) {
14421
+ console.warn(message, ...optionalParams);
14422
+ }
14423
+ }
14424
+ error(message, ...optionalParams) {
14425
+ if (this.verbose) {
14426
+ console.error(message, ...optionalParams);
14427
+ }
14428
+ }
14429
+ fatal(message, ...optionalParams) {
14430
+ console.error(message, ...optionalParams);
14431
+ }
14432
+ };
14433
+
14411
14434
  // src/openapi-loader.ts
14412
14435
  var OpenAPISpecLoader = class {
14413
14436
  /**
14414
14437
  * Disable name optimization
14415
14438
  */
14416
14439
  disableAbbreviation;
14440
+ logger;
14417
14441
  constructor(config) {
14418
14442
  this.disableAbbreviation = config?.disableAbbreviation ?? false;
14443
+ this.logger = new Logger(config?.verbose ?? true);
14419
14444
  }
14420
14445
  /**
14421
14446
  * Load an OpenAPI specification from various sources
@@ -14635,7 +14660,7 @@ var OpenAPISpecLoader = class {
14635
14660
  */
14636
14661
  determineParameterType(paramSchema, paramName) {
14637
14662
  if (Object.keys(paramSchema).length === 0 && typeof paramSchema !== "boolean") {
14638
- console.warn(
14663
+ this.logger.warn(
14639
14664
  `Parameter '${paramName}' schema was empty after inlining (potential cycle or unresolvable ref), defaulting to string.`
14640
14665
  );
14641
14666
  return "string";
@@ -14691,19 +14716,19 @@ var OpenAPISpecLoader = class {
14691
14716
  if (resolvedParam && "name" in resolvedParam && "in" in resolvedParam) {
14692
14717
  paramObj = resolvedParam;
14693
14718
  } else {
14694
- console.warn(
14719
+ this.logger.warn(
14695
14720
  `Could not resolve path-level parameter reference or invalid structure: ${param.$ref}`
14696
14721
  );
14697
14722
  continue;
14698
14723
  }
14699
14724
  } else {
14700
- console.warn(`Could not parse path-level parameter reference: ${param.$ref}`);
14725
+ this.logger.warn(`Could not parse path-level parameter reference: ${param.$ref}`);
14701
14726
  continue;
14702
14727
  }
14703
14728
  } else if ("name" in param && "in" in param) {
14704
14729
  paramObj = param;
14705
14730
  } else {
14706
- console.warn(
14731
+ this.logger.warn(
14707
14732
  "Skipping path-level parameter due to missing 'name' or 'in' properties and not being a valid $ref:",
14708
14733
  param
14709
14734
  );
@@ -14719,7 +14744,7 @@ var OpenAPISpecLoader = class {
14719
14744
  if (!["get", "post", "put", "patch", "delete", "options", "head"].includes(
14720
14745
  method.toLowerCase()
14721
14746
  )) {
14722
- console.warn(`Skipping non-HTTP method "${method}" for path ${path2}`);
14747
+ this.logger.warn(`Skipping non-HTTP method "${method}" for path ${path2}`);
14723
14748
  continue;
14724
14749
  }
14725
14750
  const op = operation;
@@ -14757,19 +14782,19 @@ var OpenAPISpecLoader = class {
14757
14782
  if (resolvedParam && "name" in resolvedParam && "in" in resolvedParam) {
14758
14783
  paramObj = resolvedParam;
14759
14784
  } else {
14760
- console.warn(
14785
+ this.logger.warn(
14761
14786
  `Could not resolve parameter reference or invalid structure: ${param.$ref}`
14762
14787
  );
14763
14788
  continue;
14764
14789
  }
14765
14790
  } else {
14766
- console.warn(`Could not parse parameter reference: ${param.$ref}`);
14791
+ this.logger.warn(`Could not parse parameter reference: ${param.$ref}`);
14767
14792
  continue;
14768
14793
  }
14769
14794
  } else if ("name" in param && "in" in param) {
14770
14795
  paramObj = param;
14771
14796
  } else {
14772
- console.warn(
14797
+ this.logger.warn(
14773
14798
  "Skipping parameter due to missing 'name' or 'in' properties and not being a valid $ref:",
14774
14799
  param
14775
14800
  );
@@ -14820,19 +14845,19 @@ var OpenAPISpecLoader = class {
14820
14845
  if (resolvedRequestBody && "content" in resolvedRequestBody) {
14821
14846
  requestBodyObj = resolvedRequestBody;
14822
14847
  } else {
14823
- console.warn(
14848
+ this.logger.warn(
14824
14849
  `Could not resolve requestBody reference or invalid structure: ${op.requestBody.$ref}`
14825
14850
  );
14826
14851
  continue;
14827
14852
  }
14828
14853
  } else {
14829
- console.warn(`Could not parse requestBody reference: ${op.requestBody.$ref}`);
14854
+ this.logger.warn(`Could not parse requestBody reference: ${op.requestBody.$ref}`);
14830
14855
  continue;
14831
14856
  }
14832
14857
  } else if ("content" in op.requestBody) {
14833
14858
  requestBodyObj = op.requestBody;
14834
14859
  } else {
14835
- console.warn("Skipping requestBody due to invalid structure:", op.requestBody);
14860
+ this.logger.warn("Skipping requestBody due to invalid structure:", op.requestBody);
14836
14861
  continue;
14837
14862
  }
14838
14863
  let mediaTypeObj;
@@ -15035,12 +15060,15 @@ var ToolsManager = class {
15035
15060
  this.config = config;
15036
15061
  this.config.toolsMode = this.config.toolsMode || "all";
15037
15062
  this.specLoader = new OpenAPISpecLoader({
15038
- disableAbbreviation: this.config.disableAbbreviation
15063
+ disableAbbreviation: this.config.disableAbbreviation,
15064
+ verbose: this.config.verbose
15039
15065
  });
15066
+ this.logger = new Logger(this.config.verbose);
15040
15067
  }
15041
15068
  tools = /* @__PURE__ */ new Map();
15042
15069
  specLoader;
15043
15070
  loadedSpec;
15071
+ logger;
15044
15072
  /**
15045
15073
  * Get the OpenAPI spec loader instance
15046
15074
  */
@@ -15126,7 +15154,7 @@ var ToolsManager = class {
15126
15154
  }
15127
15155
  this.tools = filtered2;
15128
15156
  for (const [toolId, tool] of this.tools.entries()) {
15129
- console.error(`Registered tool: ${toolId} (${tool.name})`);
15157
+ this.logger.error(`Registered tool: ${toolId} (${tool.name})`);
15130
15158
  }
15131
15159
  return;
15132
15160
  }
@@ -15172,7 +15200,7 @@ var ToolsManager = class {
15172
15200
  }
15173
15201
  this.tools = filtered;
15174
15202
  for (const [toolId, tool] of this.tools.entries()) {
15175
- console.error(`Registered tool: ${toolId} (${tool.name})`);
15203
+ this.logger.error(`Registered tool: ${toolId} (${tool.name})`);
15176
15204
  }
15177
15205
  }
15178
15206
  /**
@@ -19771,8 +19799,10 @@ var OpenAPIServer = class {
19771
19799
  promptsManager;
19772
19800
  resourcesManager;
19773
19801
  config;
19802
+ logger;
19774
19803
  constructor(config) {
19775
19804
  this.config = config;
19805
+ this.logger = new Logger(config.verbose);
19776
19806
  if (config.prompts?.length) {
19777
19807
  this.promptsManager = new PromptsManager({ prompts: config.prompts });
19778
19808
  }
@@ -19856,21 +19886,21 @@ var OpenAPIServer = class {
19856
19886
  });
19857
19887
  this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
19858
19888
  const { id, name, arguments: params } = request.params;
19859
- console.error("Received request:", request.params);
19860
- console.error("Using parameters from arguments:", params);
19889
+ this.logger.error("Received request:", request.params);
19890
+ this.logger.error("Using parameters from arguments:", params);
19861
19891
  const idOrName = typeof id === "string" ? id : typeof name === "string" ? name : "";
19862
19892
  if (!idOrName) {
19863
19893
  throw new Error("Tool ID or name is required");
19864
19894
  }
19865
19895
  const toolInfo = this.toolsManager.findTool(idOrName);
19866
19896
  if (!toolInfo) {
19867
- console.error(
19897
+ this.logger.error(
19868
19898
  `Available tools: ${Array.from(this.toolsManager.getAllTools()).map((t) => t.name).join(", ")}`
19869
19899
  );
19870
19900
  throw new Error(`Tool not found: ${idOrName}`);
19871
19901
  }
19872
19902
  const { toolId, tool } = toolInfo;
19873
- console.error(`Executing tool: ${toolId} (${tool.name})`);
19903
+ this.logger.error(`Executing tool: ${toolId} (${tool.name})`);
19874
19904
  try {
19875
19905
  const result = await this.apiClient.executeApiCall(toolId, params || {});
19876
19906
  return {
@@ -24918,6 +24948,9 @@ function loadConfig() {
24918
24948
  }).option("resources-inline", {
24919
24949
  type: "string",
24920
24950
  description: "Provide resources directly as JSON string"
24951
+ }).option("verbose", {
24952
+ type: "string",
24953
+ description: "Enable verbose logging"
24921
24954
  }).help().parseSync();
24922
24955
  let transportType;
24923
24956
  if (argv.transport === "http" || process.env.TRANSPORT_TYPE === "http") {
@@ -24980,6 +25013,7 @@ function loadConfig() {
24980
25013
  argv["reject-unauthorized"] ?? process.env.REJECT_UNAUTHORIZED,
24981
25014
  "--reject-unauthorized/REJECT_UNAUTHORIZED"
24982
25015
  );
25016
+ const verbose = parseOptionalBoolean(argv.verbose ?? process.env.VERBOSE, "--verbose/VERBOSE") ?? true;
24983
25017
  return {
24984
25018
  name: argv.name || process.env.SERVER_NAME || "mcp-openapi-server",
24985
25019
  version: argv["server-version"] || process.env.SERVER_VERSION || "1.0.0",
@@ -25006,7 +25040,8 @@ function loadConfig() {
25006
25040
  promptsPath: argv.prompts || process.env.PROMPTS_PATH,
25007
25041
  promptsInline: argv["prompts-inline"] || process.env.PROMPTS_INLINE,
25008
25042
  resourcesPath: argv.resources || process.env.RESOURCES_PATH,
25009
- resourcesInline: argv["resources-inline"] || process.env.RESOURCES_INLINE
25043
+ resourcesInline: argv["resources-inline"] || process.env.RESOURCES_INLINE,
25044
+ verbose
25010
25045
  };
25011
25046
  }
25012
25047
 
@@ -25019,7 +25054,6 @@ import {
25019
25054
  import * as http3 from "http";
25020
25055
  import { randomUUID } from "crypto";
25021
25056
  var StreamableHttpServerTransport = class {
25022
- // Track if using an external server
25023
25057
  /**
25024
25058
  * Initialize a new StreamableHttpServerTransport
25025
25059
  *
@@ -25034,11 +25068,12 @@ var StreamableHttpServerTransport = class {
25034
25068
  * further handlers from running for those requests. The MCP handler should be added after
25035
25069
  * custom handlers and will return early for non-MCP paths.
25036
25070
  */
25037
- constructor(port, host = "127.0.0.1", endpointPath = "/mcp", server) {
25071
+ constructor(port, host = "127.0.0.1", endpointPath = "/mcp", server, verbose = true) {
25038
25072
  this.port = port;
25039
25073
  this.host = host;
25040
25074
  this.endpointPath = endpointPath;
25041
25075
  this.isExternalServer = !!server;
25076
+ this.logger = new Logger(verbose);
25042
25077
  if (server) {
25043
25078
  this.server = server;
25044
25079
  this.server.on("request", this.handleRequest.bind(this));
@@ -25056,6 +25091,8 @@ var StreamableHttpServerTransport = class {
25056
25091
  // Maps request IDs to session IDs
25057
25092
  healthCheckPath = "/health";
25058
25093
  isExternalServer;
25094
+ // Track if using an external server
25095
+ logger;
25059
25096
  /**
25060
25097
  * Callback when message is received
25061
25098
  */
@@ -25078,7 +25115,7 @@ var StreamableHttpServerTransport = class {
25078
25115
  return new Promise((resolve6, reject) => {
25079
25116
  this.server.listen(this.port, this.host, () => {
25080
25117
  this.started = true;
25081
- console.error(
25118
+ this.logger.error(
25082
25119
  `Streamable HTTP transport listening on http://${this.host}:${this.port}${this.endpointPath}`
25083
25120
  );
25084
25121
  resolve6();
@@ -25131,34 +25168,34 @@ var StreamableHttpServerTransport = class {
25131
25168
  * @param message JSON-RPC message
25132
25169
  */
25133
25170
  async send(message) {
25134
- console.error(`StreamableHttpServerTransport: Sending message: ${JSON.stringify(message)}`);
25171
+ this.logger.error(`StreamableHttpServerTransport: Sending message: ${JSON.stringify(message)}`);
25135
25172
  let targetSessionId;
25136
25173
  let messageIdForThisResponse = null;
25137
25174
  if (isJSONRPCResponse(message) && message.id !== null) {
25138
25175
  messageIdForThisResponse = message.id;
25139
25176
  targetSessionId = this.requestSessionMap.get(messageIdForThisResponse);
25140
- console.error(
25177
+ this.logger.error(
25141
25178
  `StreamableHttpServerTransport: Potential target session for response ID ${messageIdForThisResponse}: ${targetSessionId}`
25142
25179
  );
25143
25180
  if (targetSessionId && this.initResponseHandlers.has(targetSessionId)) {
25144
- console.error(
25181
+ this.logger.error(
25145
25182
  `StreamableHttpServerTransport: Session ${targetSessionId} has initResponseHandlers. Invoking them for message ID ${messageIdForThisResponse}.`
25146
25183
  );
25147
25184
  const handlers = this.initResponseHandlers.get(targetSessionId);
25148
25185
  [...handlers].forEach((handler) => handler(message));
25149
25186
  if (!this.requestSessionMap.has(messageIdForThisResponse)) {
25150
- console.error(
25187
+ this.logger.error(
25151
25188
  `StreamableHttpServerTransport: Response for ID ${messageIdForThisResponse} was handled by an initResponseHandler (e.g., synchronous POST response for initialize or tools/list).`
25152
25189
  );
25153
25190
  return;
25154
25191
  } else {
25155
- console.error(
25192
+ this.logger.error(
25156
25193
  `StreamableHttpServerTransport: Response for ID ${messageIdForThisResponse} was NOT exclusively handled by an initResponseHandler or handler did not remove from requestSessionMap. Proceeding to GET stream / broadcast if applicable.`
25157
25194
  );
25158
25195
  }
25159
25196
  }
25160
25197
  if (this.requestSessionMap.has(messageIdForThisResponse)) {
25161
- console.error(
25198
+ this.logger.error(
25162
25199
  `StreamableHttpServerTransport: Deleting request ID ${messageIdForThisResponse} from requestSessionMap as it's being processed for GET stream or broadcast.`
25163
25200
  );
25164
25201
  this.requestSessionMap.delete(messageIdForThisResponse);
@@ -25166,7 +25203,7 @@ var StreamableHttpServerTransport = class {
25166
25203
  }
25167
25204
  if (!targetSessionId) {
25168
25205
  const idForLog = messageIdForThisResponse !== null ? messageIdForThisResponse : isJSONRPCRequest(message) ? message.id : "N/A";
25169
- console.warn(
25206
+ this.logger.warn(
25170
25207
  `StreamableHttpServerTransport: No specific target session for message (ID: ${idForLog}). Broadcasting to all applicable sessions.`
25171
25208
  );
25172
25209
  for (const [sid, session2] of this.sessions.entries()) {
@@ -25178,12 +25215,12 @@ var StreamableHttpServerTransport = class {
25178
25215
  }
25179
25216
  const session = this.sessions.get(targetSessionId);
25180
25217
  if (session && session.activeResponses.size > 0) {
25181
- console.error(
25218
+ this.logger.error(
25182
25219
  `StreamableHttpServerTransport: Sending message (ID: ${messageIdForThisResponse}) to GET stream for session ${targetSessionId} (${session.activeResponses.size} active connections).`
25183
25220
  );
25184
25221
  this.sendMessageToSession(targetSessionId, session, message);
25185
25222
  } else if (targetSessionId) {
25186
- console.error(
25223
+ this.logger.error(
25187
25224
  `StreamableHttpServerTransport: No active GET connections for session ${targetSessionId} to send message (ID: ${messageIdForThisResponse}). Message might not be delivered if not handled by POST.`
25188
25225
  );
25189
25226
  }
@@ -25662,20 +25699,22 @@ async function loadResources(pathOrUrl, inline) {
25662
25699
 
25663
25700
  // src/index.ts
25664
25701
  async function main() {
25702
+ const logger = new Logger(true);
25665
25703
  try {
25666
25704
  const config = loadConfig();
25705
+ logger.setVerbose(config.verbose);
25667
25706
  if (config.promptsPath || config.promptsInline) {
25668
25707
  const prompts = await loadPrompts(config.promptsPath, config.promptsInline);
25669
25708
  if (prompts) {
25670
25709
  config.prompts = prompts;
25671
- console.error(`Loaded ${prompts.length} prompt(s)`);
25710
+ logger.error(`Loaded ${prompts.length} prompt(s)`);
25672
25711
  }
25673
25712
  }
25674
25713
  if (config.resourcesPath || config.resourcesInline) {
25675
25714
  const resources = await loadResources(config.resourcesPath, config.resourcesInline);
25676
25715
  if (resources) {
25677
25716
  config.resources = resources;
25678
- console.error(`Loaded ${resources.length} resource(s)`);
25717
+ logger.error(`Loaded ${resources.length} resource(s)`);
25679
25718
  }
25680
25719
  }
25681
25720
  const server = new OpenAPIServer(config);
@@ -25684,19 +25723,21 @@ async function main() {
25684
25723
  transport = new StreamableHttpServerTransport(
25685
25724
  config.httpPort,
25686
25725
  config.httpHost,
25687
- config.endpointPath
25726
+ config.endpointPath,
25727
+ void 0,
25728
+ config.verbose
25688
25729
  );
25689
25730
  await server.start(transport);
25690
- console.error(
25731
+ logger.error(
25691
25732
  `OpenAPI MCP Server running on http://${config.httpHost}:${config.httpPort}${config.endpointPath}`
25692
25733
  );
25693
25734
  } else {
25694
25735
  transport = new StdioServerTransport();
25695
25736
  await server.start(transport);
25696
- console.error("OpenAPI MCP Server running on stdio");
25737
+ logger.error("OpenAPI MCP Server running on stdio");
25697
25738
  }
25698
25739
  } catch (error) {
25699
- console.error("Failed to start server:", error);
25740
+ logger.fatal("Failed to start server:", error);
25700
25741
  process.exit(1);
25701
25742
  }
25702
25743
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ivotoby/openapi-mcp-server",
3
- "version": "1.13.0",
3
+ "version": "1.14.0",
4
4
  "description": "An MCP server that exposes OpenAPI endpoints as resources",
5
5
  "license": "MIT",
6
6
  "type": "module",