mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-09-23 09:50:49 +02:00
Add files via upload
This commit is contained in:
@@ -3965,7 +3965,7 @@ header {
|
|||||||
|
|
||||||
.tool-item {
|
.tool-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
@@ -3980,8 +3980,10 @@ header {
|
|||||||
.tool-item input[type="checkbox"] {
|
.tool-item input[type="checkbox"] {
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
|
margin-top: 2px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
accent-color: var(--accent-color);
|
accent-color: var(--accent-color);
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tool-item-info {
|
.tool-item-info {
|
||||||
@@ -4021,6 +4023,93 @@ header {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 展开图标 */
|
||||||
|
.tool-expand-icon {
|
||||||
|
font-size: 0.625rem;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
transition: transform 0.2s;
|
||||||
|
user-select: none;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 展开后的详情面板 */
|
||||||
|
.tool-item-detail {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-detail-desc {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
line-height: 1.6;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-detail-section-title {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 参数表格 */
|
||||||
|
.tool-schema-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-schema-table th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 6px 10px;
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-schema-table td {
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
color: var(--text-primary);
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tool-schema-table code {
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||||
|
font-size: 0.8125rem;
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 可点击的外部工具徽章 */
|
||||||
|
.external-tool-badge.clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.2s, border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.external-tool-badge.clickable:hover {
|
||||||
|
background: rgba(255, 152, 0, 0.25);
|
||||||
|
border-color: rgba(255, 152, 0, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 外部 MCP 卡片高亮动画 */
|
||||||
|
.external-mcp-item.highlight {
|
||||||
|
animation: mcpHighlight 2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes mcpHighlight {
|
||||||
|
0% { box-shadow: 0 0 0 3px var(--accent-color); border-color: var(--accent-color); }
|
||||||
|
100% { box-shadow: none; border-color: var(--border-color); }
|
||||||
|
}
|
||||||
|
|
||||||
.tool-item.hidden {
|
.tool-item.hidden {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1564,15 +1564,15 @@
|
|||||||
"externalMcpModal": {
|
"externalMcpModal": {
|
||||||
"configJson": "Config JSON",
|
"configJson": "Config JSON",
|
||||||
"formatLabel": "Format:",
|
"formatLabel": "Format:",
|
||||||
"formatDesc": "JSON object; key = config name, value = config. Use Start/Stop buttons to control state.",
|
"formatDesc": "JSON object; key = config name, value = config. Use Start/Stop buttons to control state. Supports ${VAR} and ${VAR:-default} env variable syntax.",
|
||||||
"configExample": "Configuration example:",
|
"configExample": "Configuration example:",
|
||||||
"stdioMode": "stdio mode:",
|
"stdioMode": "stdio mode:",
|
||||||
"httpMode": "HTTP mode:",
|
"httpMode": "HTTP mode:",
|
||||||
"sseMode": "SSE mode:",
|
"sseMode": "SSE mode:",
|
||||||
"placeholder": "{\n \"hexstrike-ai\": {\n \"command\": \"python3\",\n \"args\": [\"/path/to/script.py\"],\n \"description\": \"Description\",\n \"timeout\": 300\n }\n}",
|
"placeholder": "{\n \"my-server\": {\n \"command\": \"python3\",\n \"args\": [\"${HOME}/mcp/server.py\"],\n \"env\": { \"API_KEY\": \"${API_KEY}\" },\n \"timeout\": 300\n }\n}",
|
||||||
"exampleStdio": "{\n \"hexstrike-ai\": {\n \"command\": \"python3\",\n \"args\": [\"/path/to/script.py\", \"--server\", \"http://example.com\"],\n \"description\": \"Description\",\n \"timeout\": 300\n }\n}",
|
"exampleStdio": "{\n \"my-server\": {\n \"command\": \"python3\",\n \"args\": [\"${HOME}/mcp/server.py\"],\n \"env\": { \"API_KEY\": \"${API_KEY}\", \"LOG_LEVEL\": \"${LOG_LEVEL:-INFO}\" },\n \"timeout\": 300\n }\n}",
|
||||||
"exampleHttp": "{\n \"cyberstrike-ai-http\": {\n \"transport\": \"http\",\n \"url\": \"http://127.0.0.1:8081/mcp\"\n }\n}",
|
"exampleHttp": "{\n \"remote-mcp\": {\n \"type\": \"http\",\n \"url\": \"https://mcp.example.com/mcp\",\n \"headers\": { \"Authorization\": \"Bearer ${MCP_TOKEN}\" }\n }\n}",
|
||||||
"exampleSse": "{\n \"cyberstrike-ai-sse\": {\n \"transport\": \"sse\",\n \"url\": \"http://127.0.0.1:8081/mcp/sse\"\n }\n}",
|
"exampleSse": "{\n \"sse-mcp\": {\n \"type\": \"sse\",\n \"url\": \"http://127.0.0.1:8081/mcp/sse\"\n }\n}",
|
||||||
"exampleDescription": "Example description",
|
"exampleDescription": "Example description",
|
||||||
"formatJson": "Format JSON",
|
"formatJson": "Format JSON",
|
||||||
"loadExample": "Load example"
|
"loadExample": "Load example"
|
||||||
|
|||||||
@@ -1564,15 +1564,15 @@
|
|||||||
"externalMcpModal": {
|
"externalMcpModal": {
|
||||||
"configJson": "配置JSON",
|
"configJson": "配置JSON",
|
||||||
"formatLabel": "配置格式:",
|
"formatLabel": "配置格式:",
|
||||||
"formatDesc": "JSON对象,key为配置名称,value为配置内容。状态通过\"启动/停止\"按钮控制,无需在JSON中配置。",
|
"formatDesc": "JSON对象,key为配置名称,value为配置内容。状态通过\"启动/停止\"按钮控制,无需在JSON中配置。支持 ${VAR} 和 ${VAR:-默认值} 环境变量语法。",
|
||||||
"configExample": "配置示例:",
|
"configExample": "配置示例:",
|
||||||
"stdioMode": "stdio模式:",
|
"stdioMode": "stdio模式:",
|
||||||
"httpMode": "HTTP模式:",
|
"httpMode": "HTTP模式:",
|
||||||
"sseMode": "SSE模式:",
|
"sseMode": "SSE模式:",
|
||||||
"placeholder": "{\n \"hexstrike-ai\": {\n \"command\": \"python3\",\n \"args\": [\"/path/to/script.py\"],\n \"description\": \"描述\",\n \"timeout\": 300\n }\n}",
|
"placeholder": "{\n \"my-server\": {\n \"command\": \"python3\",\n \"args\": [\"${HOME}/mcp/server.py\"],\n \"env\": { \"API_KEY\": \"${API_KEY}\" },\n \"timeout\": 300\n }\n}",
|
||||||
"exampleStdio": "{\n \"hexstrike-ai\": {\n \"command\": \"python3\",\n \"args\": [\"/path/to/script.py\", \"--server\", \"http://example.com\"],\n \"description\": \"描述\",\n \"timeout\": 300\n }\n}",
|
"exampleStdio": "{\n \"my-server\": {\n \"command\": \"python3\",\n \"args\": [\"${HOME}/mcp/server.py\"],\n \"env\": { \"API_KEY\": \"${API_KEY}\", \"LOG_LEVEL\": \"${LOG_LEVEL:-INFO}\" },\n \"timeout\": 300\n }\n}",
|
||||||
"exampleHttp": "{\n \"cyberstrike-ai-http\": {\n \"transport\": \"http\",\n \"url\": \"http://127.0.0.1:8081/mcp\"\n }\n}",
|
"exampleHttp": "{\n \"remote-mcp\": {\n \"type\": \"http\",\n \"url\": \"https://mcp.example.com/mcp\",\n \"headers\": { \"Authorization\": \"Bearer ${MCP_TOKEN}\" }\n }\n}",
|
||||||
"exampleSse": "{\n \"cyberstrike-ai-sse\": {\n \"transport\": \"sse\",\n \"url\": \"http://127.0.0.1:8081/mcp/sse\"\n }\n}",
|
"exampleSse": "{\n \"sse-mcp\": {\n \"type\": \"sse\",\n \"url\": \"http://127.0.0.1:8081/mcp/sse\"\n }\n}",
|
||||||
"exampleDescription": "示例描述",
|
"exampleDescription": "示例描述",
|
||||||
"formatJson": "格式化JSON",
|
"formatJson": "格式化JSON",
|
||||||
"loadExample": "加载示例"
|
"loadExample": "加载示例"
|
||||||
|
|||||||
+131
-23
@@ -501,26 +501,32 @@ function renderToolsList() {
|
|||||||
external_mcp: tool.external_mcp || ''
|
external_mcp: tool.external_mcp || ''
|
||||||
};
|
};
|
||||||
|
|
||||||
// 外部工具标签,显示来源信息
|
// 外部工具标签,显示来源信息(可点击跳转到对应 MCP 卡片)
|
||||||
let externalBadge = '';
|
let externalBadge = '';
|
||||||
if (toolState.is_external || tool.is_external) {
|
if (toolState.is_external || tool.is_external) {
|
||||||
const externalMcpName = toolState.external_mcp || tool.external_mcp || '';
|
const externalMcpName = toolState.external_mcp || tool.external_mcp || '';
|
||||||
const badgeText = externalMcpName ? (typeof window.t === 'function' ? window.t('mcp.externalFrom', { name: escapeHtml(externalMcpName) }) : `外部 (${escapeHtml(externalMcpName)})`) : (typeof window.t === 'function' ? window.t('mcp.externalBadge') : '外部');
|
const badgeText = externalMcpName ? (typeof window.t === 'function' ? window.t('mcp.externalFrom', { name: escapeHtml(externalMcpName) }) : `外部 (${escapeHtml(externalMcpName)})`) : (typeof window.t === 'function' ? window.t('mcp.externalBadge') : '外部');
|
||||||
const badgeTitle = externalMcpName ? (typeof window.t === 'function' ? window.t('mcp.externalToolFrom', { name: escapeHtml(externalMcpName) }) : `外部MCP工具 - 来源:${escapeHtml(externalMcpName)}`) : (typeof window.t === 'function' ? window.t('mcp.externalBadge') : '外部MCP工具');
|
const badgeTitle = externalMcpName ? (typeof window.t === 'function' ? window.t('mcp.externalToolFrom', { name: escapeHtml(externalMcpName) }) + ' — 点击跳转' : `外部MCP工具 - 来源:${escapeHtml(externalMcpName)} — 点击跳转`) : (typeof window.t === 'function' ? window.t('mcp.externalBadge') : '外部MCP工具');
|
||||||
externalBadge = `<span class="external-tool-badge" title="${badgeTitle}">${badgeText}</span>`;
|
if (externalMcpName) {
|
||||||
|
externalBadge = `<span class="external-tool-badge clickable" onclick="scrollToExternalMCP('${escapeHtml(externalMcpName)}', event)" title="${badgeTitle}">${badgeText}</span>`;
|
||||||
|
} else {
|
||||||
|
externalBadge = `<span class="external-tool-badge" title="${badgeTitle}">${badgeText}</span>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成唯一的checkbox id,使用工具唯一标识符
|
// 生成唯一的checkbox id,使用工具唯一标识符
|
||||||
const checkboxId = `tool-${escapeHtml(toolKey).replace(/::/g, '--')}`;
|
const checkboxId = `tool-${escapeHtml(toolKey).replace(/::/g, '--')}`;
|
||||||
|
|
||||||
toolItem.innerHTML = `
|
toolItem.innerHTML = `
|
||||||
<input type="checkbox" id="${checkboxId}" ${toolState.enabled ? 'checked' : ''} ${toolState.is_external || tool.is_external ? 'data-external="true"' : ''} onchange="handleToolCheckboxChange('${escapeHtml(toolKey)}', this.checked)" />
|
<input type="checkbox" id="${checkboxId}" ${toolState.enabled ? 'checked' : ''} ${toolState.is_external || tool.is_external ? 'data-external="true"' : ''} onchange="handleToolCheckboxChange('${escapeHtml(toolKey)}', this.checked)" />
|
||||||
<div class="tool-item-info">
|
<div class="tool-item-info" onclick="toggleToolDetail(this, '${escapeHtml(toolKey)}', ${tool.is_external ? 'true' : 'false'}, '${escapeHtml(tool.external_mcp || '')}', event)">
|
||||||
<div class="tool-item-name">
|
<div class="tool-item-name">
|
||||||
${escapeHtml(tool.name)}
|
${escapeHtml(tool.name)}
|
||||||
${externalBadge}
|
${externalBadge}
|
||||||
|
<span class="tool-expand-icon">▶</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="tool-item-desc">${escapeHtml(tool.description || (typeof window.t === 'function' ? window.t('mcp.noDescription') : '无描述'))}</div>
|
<div class="tool-item-desc">${escapeHtml(tool.description || (typeof window.t === 'function' ? window.t('mcp.noDescription') : '无描述'))}</div>
|
||||||
|
<div class="tool-item-detail" style="display:none"></div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
listContainer.appendChild(toolItem);
|
listContainer.appendChild(toolItem);
|
||||||
@@ -534,6 +540,103 @@ function renderToolsList() {
|
|||||||
updateToolsStats();
|
updateToolsStats();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 展开/折叠工具详情面板(按需从后端加载 schema)
|
||||||
|
function toggleToolDetail(infoEl, toolKey, isExternal, externalMcp, event) {
|
||||||
|
// 点击 checkbox 或外部工具徽章时不展开
|
||||||
|
if (event.target.tagName === 'INPUT' || event.target.closest('.external-tool-badge')) return;
|
||||||
|
|
||||||
|
const detail = infoEl.querySelector('.tool-item-detail');
|
||||||
|
const icon = infoEl.querySelector('.tool-expand-icon');
|
||||||
|
if (!detail) return;
|
||||||
|
|
||||||
|
const isOpen = detail.style.display !== 'none';
|
||||||
|
detail.style.display = isOpen ? 'none' : 'block';
|
||||||
|
if (icon) icon.textContent = isOpen ? '▶' : '▼';
|
||||||
|
|
||||||
|
// 首次展开时从后端按需加载
|
||||||
|
if (!isOpen && !detail.dataset.rendered) {
|
||||||
|
detail.dataset.rendered = '1';
|
||||||
|
const descEl = infoEl.querySelector('.tool-item-desc');
|
||||||
|
const fullDesc = descEl ? descEl.textContent : '';
|
||||||
|
|
||||||
|
// 先显示加载状态
|
||||||
|
detail.innerHTML = `
|
||||||
|
<div class="tool-detail-desc">${escapeHtml(fullDesc)}</div>
|
||||||
|
<div class="tool-detail-section-title">参数定义</div>
|
||||||
|
<div style="color:var(--text-tertiary);font-size:0.8125rem;padding:4px 0;">加载中...</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 解析工具名(外部工具 toolKey 格式为 mcpName::toolName)
|
||||||
|
let apiToolName = toolKey;
|
||||||
|
let query = '';
|
||||||
|
if (isExternal && externalMcp) {
|
||||||
|
const parts = toolKey.split('::');
|
||||||
|
apiToolName = parts.length > 1 ? parts[1] : toolKey;
|
||||||
|
query = '?external_mcp=' + encodeURIComponent(externalMcp);
|
||||||
|
}
|
||||||
|
|
||||||
|
apiFetch(`/api/config/tools/${encodeURIComponent(apiToolName)}/schema${query}`)
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
const schema = data.input_schema;
|
||||||
|
let schemaHTML = '';
|
||||||
|
if (schema) {
|
||||||
|
const props = schema.properties || {};
|
||||||
|
const required = schema.required || [];
|
||||||
|
const paramKeys = Object.keys(props);
|
||||||
|
if (paramKeys.length > 0) {
|
||||||
|
schemaHTML = `<table class="tool-schema-table">
|
||||||
|
<thead><tr><th>参数</th><th>类型</th><th>必填</th><th>说明</th></tr></thead>
|
||||||
|
<tbody>`;
|
||||||
|
paramKeys.forEach(key => {
|
||||||
|
const p = props[key] || {};
|
||||||
|
const type = p.type || (p.enum ? 'enum' : '—');
|
||||||
|
const isReq = required.includes(key);
|
||||||
|
const desc = p.description || '';
|
||||||
|
schemaHTML += `<tr>
|
||||||
|
<td><code>${escapeHtml(key)}</code></td>
|
||||||
|
<td>${escapeHtml(String(type))}</td>
|
||||||
|
<td>${isReq ? '<span style="color:#28a745">✔</span>' : ''}</td>
|
||||||
|
<td>${escapeHtml(desc)}</td>
|
||||||
|
</tr>`;
|
||||||
|
});
|
||||||
|
schemaHTML += '</tbody></table>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!schemaHTML) {
|
||||||
|
schemaHTML = '<div style="color:var(--text-tertiary);font-size:0.8125rem;padding:4px 0;">无参数定义</div>';
|
||||||
|
}
|
||||||
|
detail.innerHTML = `
|
||||||
|
<div class="tool-detail-desc">${escapeHtml(fullDesc)}</div>
|
||||||
|
<div class="tool-detail-section-title">参数定义</div>
|
||||||
|
${schemaHTML}
|
||||||
|
`;
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
detail.innerHTML = `
|
||||||
|
<div class="tool-detail-desc">${escapeHtml(fullDesc)}</div>
|
||||||
|
<div class="tool-detail-section-title">参数定义</div>
|
||||||
|
<div style="color:var(--text-tertiary);font-size:0.8125rem;padding:4px 0;">加载失败</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击外部工具徽章跳转到对应的外部 MCP 卡片
|
||||||
|
function scrollToExternalMCP(mcpName, event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
const items = document.querySelectorAll('.external-mcp-item');
|
||||||
|
for (const item of items) {
|
||||||
|
const h4 = item.querySelector('h4');
|
||||||
|
if (h4 && h4.textContent.includes(mcpName)) {
|
||||||
|
item.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||||
|
item.classList.add('highlight');
|
||||||
|
setTimeout(() => item.classList.remove('highlight'), 2000);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 渲染工具列表分页控件
|
// 渲染工具列表分页控件
|
||||||
function renderToolsPagination() {
|
function renderToolsPagination() {
|
||||||
const toolsList = document.getElementById('tools-list');
|
const toolsList = document.getElementById('tools-list');
|
||||||
@@ -1382,7 +1485,7 @@ function renderExternalMCPList(servers) {
|
|||||||
status === 'connecting' ? statusT('mcp.connecting') :
|
status === 'connecting' ? statusT('mcp.connecting') :
|
||||||
status === 'error' ? statusT('mcp.connectionFailed') :
|
status === 'error' ? statusT('mcp.connectionFailed') :
|
||||||
status === 'disabled' ? statusT('mcp.disabled') : statusT('mcp.disconnected');
|
status === 'disabled' ? statusT('mcp.disabled') : statusT('mcp.disconnected');
|
||||||
const transport = server.config.transport || (server.config.command ? 'stdio' : 'http');
|
const transport = server.config.type || server.config.transport || (server.config.command ? 'stdio' : 'http');
|
||||||
const transportIcon = transport === 'stdio' ? '⚙️' : '🌐';
|
const transportIcon = transport === 'stdio' ? '⚙️' : '🌐';
|
||||||
|
|
||||||
html += `
|
html += `
|
||||||
@@ -1393,11 +1496,11 @@ function renderExternalMCPList(servers) {
|
|||||||
<span class="external-mcp-status ${statusClass}">${statusText}</span>
|
<span class="external-mcp-status ${statusClass}">${statusText}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="external-mcp-item-actions">
|
<div class="external-mcp-item-actions">
|
||||||
${status === 'connected' || status === 'disconnected' || status === 'error' ?
|
${status === 'connected' || status === 'disconnected' || status === 'error' || status === 'disabled' ?
|
||||||
`<button class="btn-small" id="btn-toggle-${escapeHtml(name)}" onclick="toggleExternalMCP('${escapeHtml(name)}', '${status}')" title="${status === 'connected' ? statusT('mcp.stopConnection') : statusT('mcp.startConnection')}">
|
`<button class="btn-small" id="btn-toggle-${escapeHtml(name)}" onclick="toggleExternalMCP('${escapeHtml(name)}', '${status}')" title="${status === 'connected' ? statusT('mcp.stopConnection') : statusT('mcp.startConnection')}">
|
||||||
${status === 'connected' ? '⏸ ' + statusT('mcp.stop') : '▶ ' + statusT('mcp.start')}
|
${status === 'connected' ? '⏸ ' + statusT('mcp.stop') : '▶ ' + statusT('mcp.start')}
|
||||||
</button>` :
|
</button>` :
|
||||||
status === 'connecting' ?
|
status === 'connecting' ?
|
||||||
`<button class="btn-small" id="btn-toggle-${escapeHtml(name)}" disabled style="opacity: 0.6; cursor: not-allowed;">
|
`<button class="btn-small" id="btn-toggle-${escapeHtml(name)}" disabled style="opacity: 0.6; cursor: not-allowed;">
|
||||||
⏳ ${statusT('mcp.connecting')}
|
⏳ ${statusT('mcp.connecting')}
|
||||||
</button>` : ''}
|
</button>` : ''}
|
||||||
@@ -1552,24 +1655,29 @@ function formatExternalMCPJSON() {
|
|||||||
|
|
||||||
// 加载示例
|
// 加载示例
|
||||||
function loadExternalMCPExample() {
|
function loadExternalMCPExample() {
|
||||||
const desc = (typeof window.t === 'function' ? window.t('externalMcpModal.exampleDescription') : '示例描述');
|
|
||||||
const example = {
|
const example = {
|
||||||
"hexstrike-ai": {
|
"my-stdio-server": {
|
||||||
command: "python3",
|
command: "python3",
|
||||||
args: [
|
args: [
|
||||||
"/path/to/script.py",
|
"${HOME}/mcp-servers/main.py",
|
||||||
"--server",
|
"--port",
|
||||||
"http://example.com"
|
"${MCP_PORT:-3000}"
|
||||||
],
|
],
|
||||||
description: desc,
|
env: {
|
||||||
|
"API_KEY": "${API_KEY}",
|
||||||
|
"LOG_LEVEL": "${LOG_LEVEL:-INFO}"
|
||||||
|
},
|
||||||
timeout: 300
|
timeout: 300
|
||||||
},
|
},
|
||||||
"cyberstrike-ai-http": {
|
"my-http-server": {
|
||||||
transport: "http",
|
type: "http",
|
||||||
url: "http://127.0.0.1:8081/mcp"
|
url: "https://mcp.example.com/mcp",
|
||||||
|
headers: {
|
||||||
|
"Authorization": "Bearer ${MCP_TOKEN}"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"cyberstrike-ai-sse": {
|
"my-sse-server": {
|
||||||
transport: "sse",
|
type: "sse",
|
||||||
url: "http://127.0.0.1:8081/mcp/sse"
|
url: "http://127.0.0.1:8081/mcp/sse"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -1642,8 +1750,8 @@ async function saveExternalMCP() {
|
|||||||
// 移除 external_mcp_enable 字段(由按钮控制,但保留 enabled/disabled 用于向后兼容)
|
// 移除 external_mcp_enable 字段(由按钮控制,但保留 enabled/disabled 用于向后兼容)
|
||||||
delete config.external_mcp_enable;
|
delete config.external_mcp_enable;
|
||||||
|
|
||||||
// 验证配置内容
|
// 验证配置内容(同时支持官方 type 字段和旧版 transport 字段)
|
||||||
const transport = config.transport || (config.command ? 'stdio' : config.url ? 'http' : '');
|
const transport = config.type || config.transport || (config.command ? 'stdio' : config.url ? 'http' : '');
|
||||||
if (!transport) {
|
if (!transport) {
|
||||||
errorDiv.textContent = t('mcp.configNeedCommand', { name: name });
|
errorDiv.textContent = t('mcp.configNeedCommand', { name: name });
|
||||||
errorDiv.style.display = 'block';
|
errorDiv.style.display = 'block';
|
||||||
|
|||||||
Reference in New Issue
Block a user