feat(ui): Export tools respecting main filter and add OpenAPI/Markdown exports
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 17s
Some checks failed
Deploy to OCIWP / deploy (push) Failing after 17s
This commit is contained in:
@@ -239,8 +239,10 @@
|
|||||||
<button onclick="closeJsonExportModal()" class="text-zinc-400 hover:text-white">×</button>
|
<button onclick="closeJsonExportModal()" class="text-zinc-400 hover:text-white">×</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="border-b border-[#3f3f46] px-6 py-3 flex gap-3 items-center bg-[#09090b]">
|
<div class="border-b border-[#3f3f46] px-6 py-3 flex gap-3 items-center bg-[#09090b]">
|
||||||
<input type="text" id="jsonSearchInput" placeholder="Filter JSON by tool name or description..." class="bg-[#18181b] border border-[#3f3f46] text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2 outline-none" oninput="filterJsonExport()">
|
<input type="text" id="jsonSearchInput" placeholder="Search in filtered list..." class="bg-[#18181b] border border-[#3f3f46] text-white text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2 outline-none" oninput="filterJsonExport()">
|
||||||
<button class="btn-sm whitespace-nowrap" onclick="downloadPostmanCollection()">Export Postman Collection</button>
|
<button class="btn-sm whitespace-nowrap" style="color:#f97316; border-color:#ea580c;" onclick="downloadPostmanCollection()">Postman</button>
|
||||||
|
<button class="btn-sm whitespace-nowrap" style="color:#10b981; border-color:#059669;" onclick="downloadOpenApiSpec()">OpenAPI</button>
|
||||||
|
<button class="btn-sm whitespace-nowrap" style="color:#3b82f6; border-color:#2563eb;" onclick="downloadMarkdown()">Markdown</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body" style="padding: 0;">
|
<div class="modal-body" style="padding: 0;">
|
||||||
<textarea id="jsonExportViewer" class="code-textarea" style="height: 100%; border: none; border-radius: 0; padding: 20px;" readonly></textarea>
|
<textarea id="jsonExportViewer" class="code-textarea" style="height: 100%; border: none; border-radius: 0; padding: 20px;" readonly></textarea>
|
||||||
@@ -922,11 +924,11 @@
|
|||||||
let currentExportJson = [];
|
let currentExportJson = [];
|
||||||
|
|
||||||
function exportJson() {
|
function exportJson() {
|
||||||
if (allTools.length === 0) {
|
if (filteredTools.length === 0) {
|
||||||
alert("No tools loaded.");
|
alert("No tools match the current filter.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const cleanTools = allTools.map(t => {
|
const cleanTools = filteredTools.map(t => {
|
||||||
const clean = { ...t };
|
const clean = { ...t };
|
||||||
delete clean._customPayload;
|
delete clean._customPayload;
|
||||||
delete clean._lastStatus;
|
delete clean._lastStatus;
|
||||||
@@ -979,7 +981,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function downloadPostmanCollection() {
|
function downloadPostmanCollection() {
|
||||||
if (allTools.length === 0) return;
|
if (currentExportJson.length === 0) return;
|
||||||
const pmItems = currentExportJson.map(tool => {
|
const pmItems = currentExportJson.map(tool => {
|
||||||
const dummy = generateDummyPayload(tool);
|
const dummy = generateDummyPayload(tool);
|
||||||
return {
|
return {
|
||||||
@@ -1004,7 +1006,7 @@
|
|||||||
host: ["{{baseUrl}}"],
|
host: ["{{baseUrl}}"],
|
||||||
path: ["mcp", "api", "v1", "tools", "call"]
|
path: ["mcp", "api", "v1", "tools", "call"]
|
||||||
},
|
},
|
||||||
description: tool.description
|
description: tool.description || ''
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -1031,6 +1033,131 @@
|
|||||||
document.body.removeChild(link);
|
document.body.removeChild(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function downloadOpenApiSpec() {
|
||||||
|
if (currentExportJson.length === 0) return;
|
||||||
|
const paths = {};
|
||||||
|
|
||||||
|
// To make it standard OpenAPI, we'll map each tool as a distinct endpoint path (virtual)
|
||||||
|
// Or we just map /mcp/api/v1/tools/call and use tool names as different request bodies,
|
||||||
|
// but for documentation purposes, mapping them as separate virtual paths is usually better to view in Swagger UI.
|
||||||
|
currentExportJson.forEach(tool => {
|
||||||
|
const schemaProps = tool.parametersSchema?.properties || {};
|
||||||
|
const required = tool.parametersSchema?.required || [];
|
||||||
|
|
||||||
|
paths[`/mcp/api/v1/tools/call?name=${tool.name}`] = {
|
||||||
|
post: {
|
||||||
|
summary: tool.name,
|
||||||
|
description: tool.description || 'No description provided.',
|
||||||
|
tags: [tool.categoryKey || 'Others'],
|
||||||
|
requestBody: {
|
||||||
|
required: true,
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
jsonrpc: { type: "string", example: "2.0" },
|
||||||
|
method: { type: "string", example: "tools/call" },
|
||||||
|
id: { type: "integer", example: 1 },
|
||||||
|
params: {
|
||||||
|
type: "object",
|
||||||
|
properties: {
|
||||||
|
name: { type: "string", example: tool.name },
|
||||||
|
arguments: {
|
||||||
|
type: "object",
|
||||||
|
properties: schemaProps,
|
||||||
|
required: required.length > 0 ? required : undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
"200": {
|
||||||
|
description: "Successful response"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const openapi = {
|
||||||
|
openapi: "3.0.3",
|
||||||
|
info: {
|
||||||
|
title: "AXHUB MCP Tools",
|
||||||
|
version: "1.0.0",
|
||||||
|
description: "OpenAPI Specification for AXHUB MCP Tools"
|
||||||
|
},
|
||||||
|
servers: [
|
||||||
|
{ url: "http://localhost:8081", description: "Local Gateway" },
|
||||||
|
{ url: "https://axhubmcp.devjun.net", description: "Dev Gateway" }
|
||||||
|
],
|
||||||
|
paths: paths
|
||||||
|
};
|
||||||
|
|
||||||
|
const blob = new Blob([JSON.stringify(openapi, null, 2)], { type: 'application/json' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.setAttribute("href", url);
|
||||||
|
link.setAttribute("download", `AXHUB_MCP_Tools_OpenAPI.json`);
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadMarkdown() {
|
||||||
|
if (currentExportJson.length === 0) return;
|
||||||
|
let md = "# AXHUB MCP Tools Documentation\n\n";
|
||||||
|
|
||||||
|
// Group by category
|
||||||
|
const grouped = {};
|
||||||
|
currentExportJson.forEach(t => {
|
||||||
|
const cat = t.categoryKey || 'Others';
|
||||||
|
if(!grouped[cat]) grouped[cat] = [];
|
||||||
|
grouped[cat].push(t);
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(grouped).sort().forEach(cat => {
|
||||||
|
md += `## Category: ${cat.toUpperCase()}\n\n`;
|
||||||
|
grouped[cat].forEach(tool => {
|
||||||
|
md += `### \`${tool.name}\`\n\n`;
|
||||||
|
md += `**Description:** ${tool.description || 'N/A'}\n\n`;
|
||||||
|
|
||||||
|
const props = tool.parametersSchema?.properties || {};
|
||||||
|
const reqs = tool.parametersSchema?.required || [];
|
||||||
|
|
||||||
|
if (Object.keys(props).length > 0) {
|
||||||
|
md += `#### Parameters\n\n`;
|
||||||
|
md += `| Name | Type | Required | Description | Example |\n`;
|
||||||
|
md += `|------|------|----------|-------------|---------|\n`;
|
||||||
|
Object.keys(props).forEach(p => {
|
||||||
|
const pData = props[p];
|
||||||
|
const isReq = reqs.includes(p) ? '✅ Yes' : '❌ No';
|
||||||
|
let example = pData.example !== undefined ? pData.example : (pData.examples ? pData.examples[0] : '');
|
||||||
|
if (typeof example === 'object') example = JSON.stringify(example);
|
||||||
|
md += `| \`${p}\` | \`${pData.type || 'string'}\` | ${isReq} | ${pData.description || ''} | \`${example}\` |\n`;
|
||||||
|
});
|
||||||
|
md += `\n`;
|
||||||
|
} else {
|
||||||
|
md += `*No parameters required.*\n\n`;
|
||||||
|
}
|
||||||
|
md += `---\n\n`;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const blob = new Blob([md], { type: 'text/markdown' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement("a");
|
||||||
|
link.setAttribute("href", url);
|
||||||
|
link.setAttribute("download", `AXHUB_MCP_Tools_Docs.md`);
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', fetchTools);
|
document.addEventListener('DOMContentLoaded', fetchTools);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Reference in New Issue
Block a user