@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 +5 -1
- package/dist/bundle.js +79 -37
- package/dist/cli.js +78 -37
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19860
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
25737
|
+
logger.error("OpenAPI MCP Server running on stdio");
|
|
25697
25738
|
}
|
|
25698
25739
|
} catch (error) {
|
|
25699
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
19860
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
25737
|
+
logger.error("OpenAPI MCP Server running on stdio");
|
|
25697
25738
|
}
|
|
25698
25739
|
} catch (error) {
|
|
25699
|
-
|
|
25740
|
+
logger.fatal("Failed to start server:", error);
|
|
25700
25741
|
process.exit(1);
|
|
25701
25742
|
}
|
|
25702
25743
|
}
|