This commit is contained in:
bipproduction
2025-11-25 15:11:22 +08:00
parent e4189d40e9
commit e0236a907f
4 changed files with 1101 additions and 219 deletions

View File

@@ -16,8 +16,11 @@ interface McpTool {
/**
* Convert OpenAPI 3.x JSON spec into MCP-compatible tool definitions.
* * @param openApiJson OpenAPI JSON specification object.
* @param filterTag A string or array of strings. Operations must match at least one tag
* (case-insensitive partial match).
*/
export function convertOpenApiToMcpTools(openApiJson: any, filterTag: string): McpTool[] {
export function convertOpenApiToMcpTools(openApiJson: any, filterTag: string | string[]): McpTool[] {
const tools: McpTool[] = [];
if (!openApiJson || typeof openApiJson !== "object") {
@@ -25,6 +28,15 @@ export function convertOpenApiToMcpTools(openApiJson: any, filterTag: string): M
return tools;
}
// Cast filterTag to an array and normalize to lowercase for comparison
const filterTags = _.castArray(filterTag)
.filter(t => typeof t === "string" && t.trim() !== "")
.map(t => t.toLowerCase());
if (filterTags.length === 0) {
console.warn("Filter tag is empty or invalid. Returning all tools with tags.");
}
const paths = openApiJson.paths || {};
if (Object.keys(paths).length === 0) {
@@ -34,7 +46,6 @@ export function convertOpenApiToMcpTools(openApiJson: any, filterTag: string): M
for (const [path, methods] of Object.entries(paths)) {
if (!path || typeof path !== "string") continue;
if (path.startsWith("/mcp")) continue;
if (!methods || typeof methods !== "object") continue;
@@ -45,10 +56,19 @@ export function convertOpenApiToMcpTools(openApiJson: any, filterTag: string): M
if (!operation || typeof operation !== "object") continue;
const tags: string[] = Array.isArray(operation.tags) ? operation.tags : [];
const lowerCaseTags = tags.map(t => typeof t === "string" ? t.toLowerCase() : "");
if (!tags.length || !tags.some(t =>
typeof t === "string" && t.toLowerCase().includes(filterTag)
)) continue;
// ✅ MODIFIKASI: Pengecekan filterTags
if (filterTags.length > 0) {
const isTagMatch = lowerCaseTags.some(opTag =>
filterTags.some(fTag => opTag.includes(fTag))
);
if (!isTagMatch) continue;
} else if (tags.length === 0) {
// Jika tidak ada filter, hanya proses operation yang memiliki tags
continue;
}
try {
const tool = createToolFromOperation(path, method, operation, tags);
@@ -75,18 +95,20 @@ function createToolFromOperation(
tags: string[]
): McpTool | null {
try {
const rawName = _.snakeCase(operation.operationId || `${method}_${path}`) || "unnamed_tool";
const name = cleanToolName(rawName);
const rawName = _.snakeCase(`${operation.operationId}` || `${method}_${path}`) || "unnamed_tool";
const name = _.snakeCase(cleanToolName(operation.summary)) || cleanToolName(rawName);
if (!name || name === "unnamed_tool") {
console.warn(`Invalid tool name for ${method} ${path}`);
return null;
}
const description =
let description =
operation.description ||
operation.summary ||
`Execute ${method.toUpperCase()} ${path}`;
operation.summary;
description += `\n
Execute ${method.toUpperCase()} ${path}`;
// ✅ Extract schema berdasarkan method
let schema;
@@ -343,9 +365,8 @@ function cleanToolName(name: string): string {
.replace(/[^a-zA-Z0-9_]/g, "_")
.replace(/_+/g, "_")
.replace(/^_|_$/g, "")
.replace(/^(get|post|put|delete|patch|api)_/i, "")
.replace(/^(get_|post_|put_|delete_|patch_|api_)+/gi, "")
.replace(/(^_|_$)/g, "")
// ❗️ METHOD PREFIX TIDAK DIHAPUS LAGI (agar tidak duplicate)
.toLowerCase()
|| "unnamed_tool";
} catch (error) {
console.error("Error cleaning tool name:", error);
@@ -353,10 +374,14 @@ function cleanToolName(name: string): string {
}
}
/**
* Ambil OpenAPI JSON dari endpoint dan konversi ke tools MCP
* * @param url URL of the OpenAPI spec.
* @param filterTag A string or array of strings. Operations must match at least one tag
* (case-insensitive partial match).
*/
export async function getMcpTools(url: string, filterTag: string): Promise<McpTool[]> {
export async function getMcpTools(url: string, filterTag: string | string[]): Promise<McpTool[]> {
try {
console.log(`Fetching OpenAPI spec from: ${url}`);
@@ -370,12 +395,12 @@ export async function getMcpTools(url: string, filterTag: string): Promise<McpTo
const openApiJson = await response.json();
const tools = convertOpenApiToMcpTools(openApiJson, filterTag);
console.log(`✅ Successfully generated ${tools.length} MCP tools`);
const filterStr = _.castArray(filterTag).join(", ");
console.log(`✅ Successfully generated ${tools.length} MCP tools for tags: [${filterStr}]`);
return tools;
} catch (error) {
console.error("Error fetching MCP tools:", error);
throw error;
}
}
}