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>
|
||||
</div>
|
||||
<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()">
|
||||
<button class="btn-sm whitespace-nowrap" onclick="downloadPostmanCollection()">Export Postman Collection</button>
|
||||
<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" 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 class="modal-body" style="padding: 0;">
|
||||
<textarea id="jsonExportViewer" class="code-textarea" style="height: 100%; border: none; border-radius: 0; padding: 20px;" readonly></textarea>
|
||||
@@ -922,11 +924,11 @@
|
||||
let currentExportJson = [];
|
||||
|
||||
function exportJson() {
|
||||
if (allTools.length === 0) {
|
||||
alert("No tools loaded.");
|
||||
if (filteredTools.length === 0) {
|
||||
alert("No tools match the current filter.");
|
||||
return;
|
||||
}
|
||||
const cleanTools = allTools.map(t => {
|
||||
const cleanTools = filteredTools.map(t => {
|
||||
const clean = { ...t };
|
||||
delete clean._customPayload;
|
||||
delete clean._lastStatus;
|
||||
@@ -979,7 +981,7 @@
|
||||
}
|
||||
|
||||
function downloadPostmanCollection() {
|
||||
if (allTools.length === 0) return;
|
||||
if (currentExportJson.length === 0) return;
|
||||
const pmItems = currentExportJson.map(tool => {
|
||||
const dummy = generateDummyPayload(tool);
|
||||
return {
|
||||
@@ -1004,7 +1006,7 @@
|
||||
host: ["{{baseUrl}}"],
|
||||
path: ["mcp", "api", "v1", "tools", "call"]
|
||||
},
|
||||
description: tool.description
|
||||
description: tool.description || ''
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -1030,6 +1032,131 @@
|
||||
link.click();
|
||||
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);
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user