diff --git a/dap-gateway/src/main/resources/static/tester.html b/dap-gateway/src/main/resources/static/tester.html
index 064389c8..eec9092a 100644
--- a/dap-gateway/src/main/resources/static/tester.html
+++ b/dap-gateway/src/main/resources/static/tester.html
@@ -239,8 +239,10 @@
-
-
+
+
+
+
@@ -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);