@atlisp/mcp
Advanced tools
| <!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <title>@lisp MCP Server API Docs</title> | ||
| <style> | ||
| body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 900px; margin: 0 auto; padding: 20px; } | ||
| h1 { color: #333; } | ||
| h2 { color: #666; margin-top: 30px; } | ||
| .endpoint { background: #f5f5f5; padding: 15px; margin: 10px 0; border-radius: 5px; } | ||
| .method { display: inline-block; padding: 3px 8px; border-radius: 3px; font-weight: bold; font-size: 12px; } | ||
| .get { background: #61affe; color: white; } | ||
| .post { background: #49cc90; color: white; } | ||
| code { background: #eee; padding: 2px 5px; border-radius: 3px; } | ||
| .info { background: #f0f0f0; padding: 10px; margin: 10px 0; } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <h1>@lisp MCP Server API</h1> | ||
| <div class="info"> | ||
| <strong>Version:</strong> __VERSION__ | | ||
| <strong>Tools:</strong> __TOOLS_COUNT__ | | ||
| <strong>Resources:</strong> 11 | ||
| </div> | ||
| <h2>Endpoints</h2> | ||
| <div class="endpoint"> | ||
| <span class="method get">GET</span> <code>/health</code> - Health check | ||
| </div> | ||
| <div class="endpoint"> | ||
| <span class="method get">GET</span> <code>/metrics</code> - Prometheus metrics | ||
| </div> | ||
| <div class="endpoint"> | ||
| <span class="method post">POST</span> <code>/mcp</code> - MCP JSON-RPC endpoint | ||
| </div> | ||
| <div class="endpoint"> | ||
| <span class="method get">GET</span> <code>/sse</code> - Server-Sent Events | ||
| </div> | ||
| <div class="endpoint"> | ||
| <span class="method post">POST</span> <code>/message</code> - SSE message | ||
| </div> | ||
| <div class="endpoint"> | ||
| <span class="method get">GET</span> <code>/debug</code> - Debug panel | ||
| </div> | ||
| <div class="endpoint"> | ||
| <span class="method post">POST</span> <code>/config/reload</code> - Reload configuration | ||
| </div> | ||
| <div class="endpoint"> | ||
| <span class="method get">GET</span> <code>/rate-limit/status</code> - Rate limit status | ||
| </div> | ||
| <div class="endpoint"> | ||
| <span class="method post">POST</span> <code>/rate-limit/reset</code> - Reset rate limit | ||
| </div> | ||
| <h2>Quick Start</h2> | ||
| <pre> | ||
| # Health check | ||
| curl http://localhost:8110/health | ||
| # Call MCP tool | ||
| curl -X POST http://localhost:8110/mcp \ | ||
| -H "Content-Type: application/json" \ | ||
| -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_cad_info","arguments":{}}}' | ||
| </pre> | ||
| </body> | ||
| </html> |
| <!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <title>@lisp MCP Debug Panel</title> | ||
| <style> | ||
| body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; background: #1a1a2e; color: #eee; } | ||
| h1 { color: #00d4ff; } | ||
| h2 { color: #00d4ff; border-bottom: 1px solid #333; padding-bottom: 10px; } | ||
| .card { background: #16213e; padding: 20px; margin: 10px 0; border-radius: 8px; } | ||
| .stat { display: inline-block; margin: 10px 20px; } | ||
| .stat-label { color: #888; font-size: 12px; } | ||
| .stat-value { font-size: 24px; color: #00d4ff; } | ||
| .status-ok { color: #49cc90; } | ||
| .status-error { color: #f93e3e; } | ||
| table { width: 100%; border-collapse: collapse; } | ||
| th, td { padding: 10px; text-align: left; border-bottom: 1px solid #333; } | ||
| th { color: #888; } | ||
| code { background: #0f0f23; padding: 3px 8px; border-radius: 3px; color: #00d4ff; } | ||
| .refresh { background: #00d4ff; color: #1a1a2e; padding: 10px 20px; border: none; border-radius: 5px; cursor: pointer; margin: 10px 0; } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <h1>🛠️ @lisp MCP Debug Panel</h1> | ||
| <button class="refresh" onclick="location.reload()">🔄 Refresh</button> | ||
| <h2>System</h2> | ||
| <div class="card"> | ||
| <div class="stat"> | ||
| <div class="stat-label">Uptime</div> | ||
| <div class="stat-value">__UPTIME_MIN__m</div> | ||
| </div> | ||
| <div class="stat"> | ||
| <div class="stat-label">Memory RSS</div> | ||
| <div class="stat-value">__MEMORY_RSS_MB__ MB</div> | ||
| </div> | ||
| <div class="stat"> | ||
| <div class="stat-label">Heap Used</div> | ||
| <div class="stat-value">__HEAP_USED_MB__ MB</div> | ||
| </div> | ||
| <div class="stat"> | ||
| <div class="stat-label">MCP Sessions</div> | ||
| <div class="stat-value">__SESSIONS_COUNT__</div> | ||
| </div> | ||
| </div> | ||
| <h2>CAD Status</h2> | ||
| <div class="card"> | ||
| <span class="__CAD_STATUS_CLASS__"> | ||
| __CAD_STATUS_TEXT__ | ||
| </span> | ||
| </div> | ||
| <h2>Endpoints</h2> | ||
| <div class="card"> | ||
| <table> | ||
| <tr><th>Endpoint</th><th>Method</th><th>Description</th></tr> | ||
| <tr><td><code>/health</code></td><td>GET</td><td>Health check</td></tr> | ||
| <tr><td><code>/metrics</code></td><td>GET</td><td>Prometheus metrics</td></tr> | ||
| <tr><td><code>/mcp</code></td><td>POST</td><td>MCP JSON-RPC</td></tr> | ||
| <tr><td><code>/sse</code></td><td>GET</td><td>Server-Sent Events</td></tr> | ||
| <tr><td><code>/debug</code></td><td>GET</td><td>This panel</td></tr> | ||
| <tr><td><code>/api/docs</code></td><td>GET</td><td>API Documentation</td></tr> | ||
| </table> | ||
| </div> | ||
| <h2>Tools (__TOOLS_COUNT__)</h2> | ||
| <div class="card"> | ||
| <p>Use <code>tools/list</code> MCP method to get full tool list.</p> | ||
| </div> | ||
| <script> | ||
| setTimeout(() => location.reload(), 30000); | ||
| </script> | ||
| </body> | ||
| </html> |
| <!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <title>@lisp MCP Tool Playground</title> | ||
| <style> | ||
| * { box-sizing: border-box; margin: 0; padding: 0; } | ||
| body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #1a1a2e; color: #eee; padding: 20px; } | ||
| h1 { color: #00d4ff; margin-bottom: 20px; font-size: 24px; } | ||
| .header { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; margin-bottom: 20px; } | ||
| .search-box { flex: 1; min-width: 200px; background: #16213e; border: 1px solid #333; color: #eee; padding: 10px 14px; border-radius: 6px; font-size: 14px; outline: none; } | ||
| .search-box:focus { border-color: #00d4ff; } | ||
| .category-filter { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 20px; } | ||
| .cat-btn { background: #16213e; border: 1px solid #333; color: #888; padding: 6px 14px; border-radius: 16px; cursor: pointer; font-size: 13px; transition: all .15s; } | ||
| .cat-btn:hover { border-color: #00d4ff; color: #eee; } | ||
| .cat-btn.active { background: #00d4ff; color: #1a1a2e; border-color: #00d4ff; } | ||
| .tool-count { color: #888; font-size: 13px; margin-bottom: 12px; } | ||
| .category-group { margin-bottom: 24px; } | ||
| .category-title { color: #00d4ff; font-size: 16px; font-weight: 600; padding: 10px 0 8px; border-bottom: 1px solid #333; margin-bottom: 8px; cursor: pointer; display: flex; align-items: center; gap: 8px; } | ||
| .category-title .arrow { color: #555; transition: transform .15s; font-size: 12px; } | ||
| .category-title.collapsed .arrow { transform: rotate(-90deg); } | ||
| .category-title .count { color: #888; font-size: 12px; font-weight: 400; } | ||
| .tool-card { background: #16213e; border: 1px solid #333; border-radius: 6px; padding: 12px 16px; margin-bottom: 6px; cursor: pointer; transition: border-color .15s; } | ||
| .tool-card:hover { border-color: #00d4ff44; } | ||
| .tool-card.expanded { border-color: #00d4ff; } | ||
| .tool-name { color: #49cc90; font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; font-size: 14px; } | ||
| .tool-desc { color: #999; font-size: 13px; margin-top: 4px; line-height: 1.4; } | ||
| .tool-meta { display: flex; gap: 12px; margin-top: 6px; font-size: 12px; color: #666; } | ||
| .tool-meta span { background: #0f0f23; padding: 2px 8px; border-radius: 3px; } | ||
| .tool-details { display: none; margin-top: 12px; padding-top: 12px; border-top: 1px solid #333; } | ||
| .tool-card.expanded .tool-details { display: block; } | ||
| .params-table { width: 100%; border-collapse: collapse; font-size: 13px; margin-bottom: 12px; } | ||
| .params-table th { color: #888; text-align: left; padding: 6px 8px; border-bottom: 1px solid #333; font-weight: 500; } | ||
| .params-table td { padding: 6px 8px; border-bottom: 1px solid #222; color: #ccc; } | ||
| .params-table .required { color: #f93e3e; margin-left: 4px; } | ||
| .param-input { width: 100%; background: #0f0f23; border: 1px solid #333; color: #eee; padding: 6px 10px; border-radius: 4px; font-size: 13px; font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; } | ||
| .param-input:focus { border-color: #00d4ff; outline: none; } | ||
| .param-input.array-input { min-height: 60px; resize: vertical; } | ||
| .btn { background: #00d4ff; color: #1a1a2e; border: none; padding: 8px 18px; border-radius: 5px; cursor: pointer; font-size: 13px; font-weight: 600; transition: opacity .15s; } | ||
| .btn:hover { opacity: .85; } | ||
| .btn:disabled { opacity: .4; cursor: not-allowed; } | ||
| .btn.execute { background: #49cc90; } | ||
| .btn.execute.running { background: #888; } | ||
| .response-area { display: none; margin-top: 12px; } | ||
| .response-area.visible { display: block; } | ||
| .response-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px; } | ||
| .response-header label { color: #888; font-size: 12px; font-weight: 500; } | ||
| .response-status { font-size: 12px; padding: 2px 8px; border-radius: 3px; } | ||
| .response-status.ok { background: #49cc9033; color: #49cc90; } | ||
| .response-status.err { background: #f93e3e33; color: #f93e3e; } | ||
| .response-json { background: #0f0f23; border: 1px solid #333; border-radius: 4px; padding: 12px; font-family: 'SF Mono', Monaco, 'Cascadia Code', monospace; font-size: 12px; color: #00d4ff; white-space: pre-wrap; word-break: break-all; max-height: 400px; overflow: auto; } | ||
| .no-results { color: #666; text-align: center; padding: 40px; font-size: 14px; } | ||
| .toolbar { display: flex; gap: 8px; align-items: center; } | ||
| .expand-all-btn { background: none; border: 1px solid #333; color: #888; padding: 6px 14px; border-radius: 5px; cursor: pointer; font-size: 12px; } | ||
| .expand-all-btn:hover { border-color: #00d4ff44; color: #eee; } | ||
| .scroll-top { position: fixed; bottom: 20px; right: 20px; width: 40px; height: 40px; background: #16213e; border: 1px solid #333; border-radius: 50%; color: #00d4ff; font-size: 18px; cursor: pointer; display: none; align-items: center; justify-content: center; } | ||
| .scroll-top.visible { display: flex; } | ||
| .scroll-top:hover { border-color: #00d4ff; } | ||
| ::-webkit-scrollbar { width: 6px; } | ||
| ::-webkit-scrollbar-track { background: #0f0f23; } | ||
| ::-webkit-scrollbar-thumb { background: #333; border-radius: 3px; } | ||
| ::-webkit-scrollbar-thumb:hover { background: #555; } | ||
| @media (max-width: 600px) { | ||
| body { padding: 10px; } | ||
| .header { flex-direction: column; } | ||
| .search-box { width: 100%; } | ||
| } | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <h1>🧰 @lisp MCP Tool Playground</h1> | ||
| <div class="header"> | ||
| <input class="search-box" id="search" placeholder="Search tools by name or description..." oninput="filterTools()"> | ||
| <div class="toolbar"> | ||
| <button class="expand-all-btn" onclick="expandAll()">Expand All</button> | ||
| <button class="expand-all-btn" onclick="collapseAll()">Collapse All</button> | ||
| </div> | ||
| </div> | ||
| <div class="category-filter" id="categoryFilter"></div> | ||
| <div class="tool-count" id="toolCount"></div> | ||
| <div id="toolList"></div> | ||
| <button class="scroll-top" id="scrollTop" onclick="window.scrollTo({top:0,behavior:'smooth'})">↑</button> | ||
| <script> | ||
| var TOOLS = __TOOLS_JSON__; | ||
| var activeCategory = null; | ||
| var searchQuery = ''; | ||
| function categorize(name) { | ||
| if (/^batch_/.test(name)) return 'Batch'; | ||
| if (/^boolean_/.test(name)) return 'Boolean'; | ||
| if (/^(create_3d_|create_box|create_cone|create_cylinder|create_sphere|create_torus|create_wedge|extrude_|revolve_|slice_|thicken_|offset_surface|create_loft_|get_3d_)/.test(name)) return '3D'; | ||
| if (/xdata/i.test(name)) return 'Xdata'; | ||
| if (/sheet/i.test(name)) return 'Sheet Set'; | ||
| if (/pdf/i.test(name)) return 'PDF'; | ||
| if (/^(xref|dgn|dwf)/i.test(name)) return 'Xref'; | ||
| if (/constraint/i.test(name)) return 'Constraint'; | ||
| if (/parametric/i.test(name)) return 'Parametric'; | ||
| if (/pipeline/i.test(name)) return 'Pipeline'; | ||
| if (/trigger/i.test(name)) return 'Trigger'; | ||
| if (/repl/i.test(name)) return 'REPL'; | ||
| if (/^(plot_|publish_|save_pdf_layout)/.test(name)) return 'Plot'; | ||
| if (/^(connect_cad|eval_lisp|install_|init_|get_cad_info|get_platform_info|at_command|new_document|bring_to_front|get_system_status)/.test(name)) return 'System'; | ||
| if (/^(list_|search_|measure_|import_funlib)/.test(name)) return 'Query'; | ||
| if (/^(import_|export_)/.test(name)) return 'Import/Export'; | ||
| if (/^(create_layer|delete_layer|set_layer|set_current_layer)/.test(name)) return 'Layer'; | ||
| if (/^(create_block|insert_block|explode_block|get_block|set_block)/.test(name)) return 'Block'; | ||
| if (/^(create_dim|list_dim|set_dim|create_hatch|set_hatch)/.test(name)) return 'Dimension & Hatch'; | ||
| if (/^(create_ucs|set_ucs|delete_ucs|list_ucs|get_current_ucs|create_layout|delete_layout|rename_layout|set_layout|list_layouts|get_current_layout|create_named_view|list_named_views|restore_named_view)/.test(name)) return 'UCS & Layout'; | ||
| if (/^(open_dwg|save_dwg|close_dwg)/.test(name)) return 'Drawing'; | ||
| if (/^(create_text|list_text|create_mleader|list_mleader|set_mleader|create_linetype|list_linetypes|load_linetype)/.test(name)) return 'Style'; | ||
| if (/^(attach_|detach_)/.test(name)) return 'Underlay'; | ||
| if (/^(create_group|delete_group|list_groups|get_group)/.test(name)) return 'Group'; | ||
| if (/^(create_entity|delete_entity|get_entity|set_entity|select_entities)/.test(name)) return 'Entity'; | ||
| return 'Other'; | ||
| } | ||
| function getCategories() { | ||
| var cats = {}; | ||
| TOOLS.forEach(function(t) { | ||
| var c = categorize(t.name); | ||
| if (!cats[c]) cats[c] = []; | ||
| cats[c].push(t); | ||
| }); | ||
| var order = ['System','Query','Entity','Layer','Block','Drawing','Dimension & Hatch','UCS & Layout','3D','Batch','Boolean','Group','Style','Xdata','Sheet Set','PDF','Xref','Underlay','Import/Export','Constraint','Parametric','Plot','Pipeline','Trigger','REPL','Other']; | ||
| var sorted = {}; | ||
| order.forEach(function(c) { if (cats[c]) sorted[c] = cats[c]; }); | ||
| Object.keys(cats).filter(function(c) { return !order.includes(c); }).forEach(function(c) { sorted[c] = cats[c]; }); | ||
| return sorted; | ||
| } | ||
| function escapeHtml(s) { | ||
| var d = document.createElement('div'); | ||
| d.textContent = s; | ||
| return d.innerHTML; | ||
| } | ||
| function buildForm(inputSchema) { | ||
| var props = inputSchema.properties || {}; | ||
| var required = inputSchema.required || []; | ||
| var keys = Object.keys(props); | ||
| if (keys.length === 0) return '<p style="color:#666;font-size:13px;padding:6px 0">No parameters required.</p>'; | ||
| var html = '<table class="params-table"><tr><th>Parameter</th><th>Type</th><th>Description</th><th>Value</th></tr>'; | ||
| keys.forEach(function(k) { | ||
| var p = props[k]; | ||
| var isReq = required.includes(k); | ||
| var typeLabel = p.type || 'string'; | ||
| var inputHtml = ''; | ||
| if (p.enum) { | ||
| inputHtml = '<select class="param-input" data-param="' + k + '" data-type="string"><option value="">-- select --</option>'; | ||
| p.enum.forEach(function(v) { | ||
| inputHtml += '<option value="' + escapeHtml(v) + '">' + escapeHtml(v) + '</option>'; | ||
| }); | ||
| inputHtml += '</select>'; | ||
| } else if (p.type === 'boolean') { | ||
| inputHtml = '<select class="param-input" data-param="' + k + '" data-type="boolean"><option value="">-- select --</option><option value="true">true</option><option value="false">false</option></select>'; | ||
| } else if (p.type === 'integer' || p.type === 'number') { | ||
| inputHtml = '<input class="param-input" data-param="' + k + '" data-type="' + p.type + '" placeholder="' + typeLabel + '" step="any">'; | ||
| } else if (p.type === 'array' || (p.type === 'object' && !p.properties)) { | ||
| inputHtml = '<textarea class="param-input array-input" data-param="' + k + '" data-type="json" placeholder=\'JSON array/object, e.g. ["a","b"] or {"key":"val"}\' rows="2"></textarea>'; | ||
| } else if (p.type === 'object' && p.properties) { | ||
| inputHtml = '<textarea class="param-input array-input" data-param="' + k + '" data-type="json" placeholder=\'JSON object\' rows="2"></textarea>'; | ||
| } else { | ||
| inputHtml = '<input class="param-input" data-param="' + k + '" data-type="string" placeholder="' + escapeHtml(p.description || '') + '">'; | ||
| } | ||
| html += '<tr><td><code>' + escapeHtml(k) + '</code>' + (isReq ? '<span class="required">*</span>' : '') + '</td><td style="color:#888">' + typeLabel + '</td><td>' + escapeHtml(p.description || '-') + '</td><td>' + inputHtml + '</td></tr>'; | ||
| }); | ||
| html += '</table>'; | ||
| return html; | ||
| } | ||
| function collectParams(card) { | ||
| var params = {}; | ||
| card.querySelectorAll('.param-input').forEach(function(el) { | ||
| var key = el.dataset.param; | ||
| var val = el.value.trim(); | ||
| if (val === '') return; | ||
| var type = el.dataset.type; | ||
| if (type === 'number' || type === 'integer') val = Number(val); | ||
| else if (type === 'boolean') val = val === 'true'; | ||
| else if (type === 'json') { try { val = JSON.parse(val); } catch(e) { return; } } | ||
| params[key] = val; | ||
| }); | ||
| return params; | ||
| } | ||
| function renderTools() { | ||
| var categories = getCategories(); | ||
| var container = document.getElementById('toolList'); | ||
| var filterEl = document.getElementById('categoryFilter'); | ||
| container.innerHTML = ''; | ||
| filterEl.innerHTML = '<button class="cat-btn active" data-cat="" onclick="setCategory(this,\'\')">All</button>'; | ||
| var totalCount = 0; | ||
| var catKeys = Object.keys(categories); | ||
| catKeys.forEach(function(c) { | ||
| var count = categories[c].length; | ||
| if (activeCategory && activeCategory !== c) count = 0; | ||
| else if (searchQuery) { | ||
| var q = searchQuery.toLowerCase(); | ||
| count = categories[c].filter(function(t) { return t.name.toLowerCase().includes(q) || (t.description && t.description.toLowerCase().includes(q)); }).length; | ||
| } | ||
| totalCount += count; | ||
| filterEl.innerHTML += '<button class="cat-btn' + (activeCategory === c ? ' active' : '') + '" data-cat="' + c + '" onclick="setCategory(this,\'' + c + '\')">' + c + '</button>'; | ||
| }); | ||
| var countText = totalCount + ' tool' + (totalCount !== 1 ? 's' : ''); | ||
| if (searchQuery) countText += ' matching "' + searchQuery + '"'; | ||
| document.getElementById('toolCount').textContent = countText; | ||
| var hasResults = false; | ||
| catKeys.forEach(function(c) { | ||
| var tools = categories[c]; | ||
| if (searchQuery) { | ||
| var q = searchQuery.toLowerCase(); | ||
| tools = tools.filter(function(t) { return t.name.toLowerCase().includes(q) || (t.description && t.description.toLowerCase().includes(q)); }); | ||
| } | ||
| if (activeCategory && activeCategory !== c) tools = []; | ||
| if (tools.length === 0) return; | ||
| hasResults = true; | ||
| var groupHtml = '<div class="category-group"><div class="category-title collapsed" onclick="toggleCategory(this)"><span class="arrow">▼</span> ' + c + ' <span class="count">(' + tools.length + ')</span></div>'; | ||
| tools.forEach(function(t) { | ||
| var hasParams = t.inputSchema && t.inputSchema.properties && Object.keys(t.inputSchema.properties).length > 0; | ||
| var paramsCount = hasParams ? Object.keys(t.inputSchema.properties).length + ' params' : 'no params'; | ||
| var reqCount = (t.inputSchema.required && t.inputSchema.required.length) ? '<span>' + t.inputSchema.required.length + ' required</span>' : ''; | ||
| groupHtml += '<div class="tool-card" id="tool-' + t.name + '" onclick="toggleCard(this)">'; | ||
| groupHtml += '<div class="tool-name">' + escapeHtml(t.name) + '</div>'; | ||
| groupHtml += '<div class="tool-desc">' + escapeHtml(t.description || '') + '</div>'; | ||
| groupHtml += '<div class="tool-meta"><span>' + paramsCount + '</span>' + reqCount + '</div>'; | ||
| groupHtml += '<div class="tool-details">'; | ||
| if (hasParams) { | ||
| groupHtml += buildForm(t.inputSchema); | ||
| } else { | ||
| groupHtml += '<p style="color:#666;font-size:13px;padding:6px 0">No parameters required.</p>'; | ||
| } | ||
| groupHtml += '<button class="btn execute" onclick="event.stopPropagation();executeTool(this,\'' + t.name + '\')">▶ Execute</button>'; | ||
| groupHtml += '<div class="response-area" id="response-' + t.name + '">'; | ||
| groupHtml += '<div class="response-header"><label>Response</label><span class="response-status" id="status-' + t.name + '"></span></div>'; | ||
| groupHtml += '<div class="response-json" id="result-' + t.name + '"></div>'; | ||
| groupHtml += '</div></div></div>'; | ||
| }); | ||
| groupHtml += '</div>'; | ||
| container.innerHTML += groupHtml; | ||
| }); | ||
| if (!hasResults) { | ||
| container.innerHTML = '<div class="no-results">No tools found matching your search.</div>'; | ||
| } | ||
| if (searchQuery) { | ||
| container.querySelectorAll('.category-group').forEach(function(g) { | ||
| var visibleCards = g.querySelectorAll('.tool-card'); | ||
| if (visibleCards.length <= 3) { | ||
| var title = g.querySelector('.category-title'); | ||
| if (title) title.classList.remove('collapsed'); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| function toggleCategory(el) { | ||
| el.classList.toggle('collapsed'); | ||
| var group = el.parentElement; | ||
| var cards = group.querySelectorAll('.tool-card'); | ||
| cards.forEach(function(c) { c.style.display = el.classList.contains('collapsed') ? 'none' : ''; }); | ||
| } | ||
| function toggleCard(el) { | ||
| if (event && (event.target.closest('.param-input') || event.target.closest('.btn') || event.target.closest('.response-area'))) return; | ||
| el.classList.toggle('expanded'); | ||
| } | ||
| async function executeTool(btn, toolName) { | ||
| if (btn.disabled) return; | ||
| var card = btn.closest('.tool-card'); | ||
| var responseArea = document.getElementById('response-' + toolName); | ||
| var statusEl = document.getElementById('status-' + toolName); | ||
| var resultEl = document.getElementById('result-' + toolName); | ||
| btn.disabled = true; | ||
| btn.textContent = '⏳ Running...'; | ||
| btn.classList.add('running'); | ||
| responseArea.classList.remove('visible'); | ||
| statusEl.textContent = ''; | ||
| statusEl.className = 'response-status'; | ||
| resultEl.textContent = ''; | ||
| var arguments = collectParams(card); | ||
| var body = { jsonrpc: '2.0', id: 1, method: 'tools/call', params: { name: toolName, arguments: arguments } }; | ||
| try { | ||
| var res = await fetch('/mcp', { | ||
| method: 'POST', | ||
| headers: { 'Content-Type': 'application/json' }, | ||
| body: JSON.stringify(body) | ||
| }); | ||
| var data = await res.json(); | ||
| responseArea.classList.add('visible'); | ||
| if (res.ok) { | ||
| statusEl.textContent = res.status + ' OK'; | ||
| statusEl.className = 'response-status ok'; | ||
| } else { | ||
| statusEl.textContent = res.status + ' Error'; | ||
| statusEl.className = 'response-status err'; | ||
| } | ||
| resultEl.textContent = JSON.stringify(data, null, 2); | ||
| } catch (err) { | ||
| responseArea.classList.add('visible'); | ||
| statusEl.textContent = 'Network Error'; | ||
| statusEl.className = 'response-status err'; | ||
| resultEl.textContent = err.message; | ||
| } finally { | ||
| btn.disabled = false; | ||
| btn.textContent = '▶ Execute'; | ||
| btn.classList.remove('running'); | ||
| } | ||
| } | ||
| function setCategory(el, cat) { | ||
| document.querySelectorAll('.cat-btn').forEach(function(b) { b.classList.remove('active'); }); | ||
| el.classList.add('active'); | ||
| activeCategory = cat || null; | ||
| renderTools(); | ||
| } | ||
| function filterTools() { | ||
| searchQuery = document.getElementById('search').value; | ||
| renderTools(); | ||
| } | ||
| function expandAll() { | ||
| document.querySelectorAll('.category-title').forEach(function(t) { t.classList.remove('collapsed'); }); | ||
| document.querySelectorAll('.tool-card').forEach(function(c) { c.style.display = ''; }); | ||
| } | ||
| function collapseAll() { | ||
| document.querySelectorAll('.category-title').forEach(function(t) { t.classList.add('collapsed'); }); | ||
| } | ||
| window.addEventListener('scroll', function() { | ||
| document.getElementById('scrollTop').classList.toggle('visible', window.scrollY > 400); | ||
| }); | ||
| renderTools(); | ||
| </script> | ||
| </body> | ||
| </html> |
+2
-2
| { | ||
| "name": "@atlisp/mcp", | ||
| "version": "1.8.12", | ||
| "version": "1.8.13", | ||
| "description": "MCP Server for @lisp on CAD,support AutoCAD/GstarCAD/ZWCAD/BricsCAD or CAD platform compatible with AutoLISP", | ||
@@ -21,3 +21,3 @@ "type": "module", | ||
| "build:main": "esbuild src/atlisp-mcp.js --bundle --platform=node --target=node18 --format=esm --outdir=dist --external:edge-js --packages=external --banner:js=\"#!/usr/bin/env node\"", | ||
| "build:worker": "node -e \"const fs=require('fs');for(const f of ['cad-worker.js','lisp-security.js','logger.js','config.js'])fs.copyFileSync('src/'+f,'dist/'+f)\"", | ||
| "build:worker": "node -e \"const fs=require('fs');const path=require('path');for(const f of ['cad-worker.js','lisp-security.js','logger.js','config.js'])fs.copyFileSync('src/'+f,'dist/'+f);if(!fs.existsSync('dist/html'))fs.mkdirSync('dist/html');for(const f of fs.readdirSync('src/html'))fs.copyFileSync('src/html/'+f,'dist/html/'+f)\"", | ||
| "prepublishOnly": "npm run build", | ||
@@ -24,0 +24,0 @@ "test": "vitest run", |
772713
3.63%10
42.86%