fmea-api-mcp-server 1.0.5 → 1.0.7
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/index.js +87 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -99,5 +99,9 @@ When the package is published to NPM:
|
|
|
99
99
|
## Features
|
|
100
100
|
- **Resources**: Can read JSON files in the `endpoints` folder.
|
|
101
101
|
- **Tools**:
|
|
102
|
-
|
|
102
|
+
- **Tools**:
|
|
103
|
+
- `search_apis`:
|
|
104
|
+
- Smart search with relevance scoring and pagination.
|
|
105
|
+
- Supports filters: `query` (use `*` for all), `method`, `version`, `page` (default 1).
|
|
106
|
+
- Results limited to 10 per page. Returns meta info (total, totalPages) and guidance.
|
|
103
107
|
- `get_api_details`: Get full details (schema, parameters) for a specific endpoint.
|
package/dist/index.js
CHANGED
|
@@ -104,7 +104,19 @@ class ApiDocsServer {
|
|
|
104
104
|
properties: {
|
|
105
105
|
query: {
|
|
106
106
|
type: "string",
|
|
107
|
-
description: "Search query (e.g. 'user login', '
|
|
107
|
+
description: "Search query (e.g. 'user login', 'create project').",
|
|
108
|
+
},
|
|
109
|
+
method: {
|
|
110
|
+
type: "string",
|
|
111
|
+
description: "Filter by HTTP method (e.g. 'GET', 'POST'). Optional.",
|
|
112
|
+
},
|
|
113
|
+
version: {
|
|
114
|
+
type: "string",
|
|
115
|
+
description: "Filter by API version (e.g. 'v1', 'v2'). Optional.",
|
|
116
|
+
},
|
|
117
|
+
page: {
|
|
118
|
+
type: "integer",
|
|
119
|
+
description: "Page number for pagination (default: 1).",
|
|
108
120
|
},
|
|
109
121
|
},
|
|
110
122
|
required: ["query"],
|
|
@@ -134,7 +146,10 @@ class ApiDocsServer {
|
|
|
134
146
|
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
135
147
|
if (request.params.name === "search_apis") {
|
|
136
148
|
const query = String(request.params.arguments?.query).toLowerCase();
|
|
137
|
-
const
|
|
149
|
+
const method = request.params.arguments?.method ? String(request.params.arguments?.method).toUpperCase() : undefined;
|
|
150
|
+
const version = request.params.arguments?.version ? String(request.params.arguments?.version).toLowerCase() : undefined;
|
|
151
|
+
const page = request.params.arguments?.page ? Number(request.params.arguments?.page) : 1;
|
|
152
|
+
const results = await this.searchInFiles(query, method, version, page);
|
|
138
153
|
return {
|
|
139
154
|
content: [
|
|
140
155
|
{
|
|
@@ -195,20 +210,50 @@ class ApiDocsServer {
|
|
|
195
210
|
}
|
|
196
211
|
return results;
|
|
197
212
|
}
|
|
198
|
-
//
|
|
199
|
-
async searchInFiles(query) {
|
|
213
|
+
// Smart search helper with scoring, filtering, limits, and pagination
|
|
214
|
+
async searchInFiles(query, filterMethod, filterVersion, page = 1) {
|
|
200
215
|
const files = await this.getAllFiles(ENDPOINTS_DIR);
|
|
201
|
-
|
|
216
|
+
let allMatches = [];
|
|
217
|
+
const isWildcard = query === "*" || query === "";
|
|
202
218
|
for (const filePath of files) {
|
|
203
219
|
try {
|
|
204
220
|
const content = await fs.readFile(filePath, "utf-8");
|
|
205
221
|
const json = JSON.parse(content);
|
|
206
222
|
const fileName = path.relative(ENDPOINTS_DIR, filePath);
|
|
223
|
+
// Version Filtering
|
|
224
|
+
if (filterVersion) {
|
|
225
|
+
const fileVersion = fileName.split(path.sep)[0];
|
|
226
|
+
if (fileVersion !== filterVersion && json.version !== filterVersion) {
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
207
230
|
if (json.endpoints && Array.isArray(json.endpoints)) {
|
|
208
231
|
for (const endpoint of json.endpoints) {
|
|
209
|
-
|
|
210
|
-
if (
|
|
211
|
-
|
|
232
|
+
// Method Filtering
|
|
233
|
+
if (filterMethod && endpoint.method.toUpperCase() !== filterMethod) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
// Scoring Logic
|
|
237
|
+
let score = 0;
|
|
238
|
+
if (isWildcard) {
|
|
239
|
+
score = 1; // All match in wildcard mode
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
const summary = (endpoint.summary || "").toLowerCase();
|
|
243
|
+
const description = (endpoint.description || "").toLowerCase();
|
|
244
|
+
const apiPath = (endpoint.path || "").toLowerCase();
|
|
245
|
+
const operationId = (endpoint.operationId || "").toLowerCase();
|
|
246
|
+
// Exact/High relevance matches
|
|
247
|
+
if (summary.includes(query) || operationId.includes(query))
|
|
248
|
+
score += 10;
|
|
249
|
+
if (description.includes(query))
|
|
250
|
+
score += 5;
|
|
251
|
+
if (apiPath.includes(query))
|
|
252
|
+
score += 3;
|
|
253
|
+
}
|
|
254
|
+
if (score > 0) {
|
|
255
|
+
allMatches.push({
|
|
256
|
+
score,
|
|
212
257
|
file: fileName,
|
|
213
258
|
method: endpoint.method,
|
|
214
259
|
path: endpoint.path,
|
|
@@ -223,7 +268,40 @@ class ApiDocsServer {
|
|
|
223
268
|
// Ignore parse errors
|
|
224
269
|
}
|
|
225
270
|
}
|
|
226
|
-
|
|
271
|
+
const totalFound = allMatches.length;
|
|
272
|
+
if (totalFound === 0) {
|
|
273
|
+
return {
|
|
274
|
+
results: [],
|
|
275
|
+
message: `No results found for '${query}'. Try using '*' to list all endpoints, or check your version/method filters.`
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
// Sort by score descending (only meaningful if not wildcard)
|
|
279
|
+
if (!isWildcard) {
|
|
280
|
+
allMatches.sort((a, b) => b.score - a.score);
|
|
281
|
+
}
|
|
282
|
+
// Pagination
|
|
283
|
+
const LIMIT = 10;
|
|
284
|
+
const totalPages = Math.ceil(totalFound / LIMIT);
|
|
285
|
+
const currentPage = Math.max(1, page); // Ensure page is at least 1
|
|
286
|
+
const start = (currentPage - 1) * LIMIT;
|
|
287
|
+
const end = start + LIMIT;
|
|
288
|
+
const slicedResults = allMatches.slice(start, end).map(({ score, ...rest }) => rest);
|
|
289
|
+
let warning = undefined;
|
|
290
|
+
if (totalPages > 1) {
|
|
291
|
+
warning = `Found ${totalFound} results. Showing page ${currentPage} of ${totalPages}.`;
|
|
292
|
+
if (currentPage < totalPages) {
|
|
293
|
+
warning += ` Use 'page: ${currentPage + 1}' to see next results.`;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return {
|
|
297
|
+
results: slicedResults,
|
|
298
|
+
meta: {
|
|
299
|
+
total: totalFound,
|
|
300
|
+
page: currentPage,
|
|
301
|
+
totalPages: totalPages
|
|
302
|
+
},
|
|
303
|
+
warning
|
|
304
|
+
};
|
|
227
305
|
}
|
|
228
306
|
// Helper to get full details of an API
|
|
229
307
|
async getApiDetails(apiPath, method) {
|