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