mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-13 02:41:50 +08:00
feat: 添加工具调用支持,增强 CLI 工具和 MCP 管理功能
This commit is contained in:
@@ -186,37 +186,58 @@ function handleCliStreamStarted(payload) {
|
||||
}
|
||||
|
||||
function handleCliStreamOutput(payload) {
|
||||
const { executionId, chunkType, data } = payload;
|
||||
|
||||
const { executionId, chunkType, data, unit } = payload;
|
||||
|
||||
const exec = cliStreamExecutions[executionId];
|
||||
if (!exec) return;
|
||||
|
||||
// Parse and add output lines
|
||||
const content = typeof data === 'string' ? data : JSON.stringify(data);
|
||||
|
||||
// Use structured unit if available, otherwise fall back to data
|
||||
const unitContent = unit?.content;
|
||||
const unitType = unit?.type || chunkType;
|
||||
|
||||
// For tool_call type, format the content specially
|
||||
let content;
|
||||
if (unitType === 'tool_call' && typeof unitContent === 'object' && unitContent !== null) {
|
||||
// Format tool_call for display
|
||||
if (unitContent.action === 'invoke') {
|
||||
const params = unitContent.parameters ? JSON.stringify(unitContent.parameters) : '';
|
||||
content = `[Tool] ${unitContent.toolName}(${params})`;
|
||||
} else if (unitContent.action === 'result') {
|
||||
const status = unitContent.status || 'unknown';
|
||||
const output = unitContent.output ? `: ${unitContent.output.substring(0, 200)}${unitContent.output.length > 200 ? '...' : ''}` : '';
|
||||
content = `[Tool Result] ${status}${output}`;
|
||||
} else {
|
||||
content = JSON.stringify(unitContent);
|
||||
}
|
||||
} else {
|
||||
// Use data (already serialized) for backward compatibility
|
||||
content = typeof data === 'string' ? data : JSON.stringify(data);
|
||||
}
|
||||
|
||||
const lines = content.split('\n');
|
||||
|
||||
|
||||
lines.forEach(line => {
|
||||
if (line.trim() || lines.length === 1) { // Keep empty lines if it's the only content
|
||||
exec.output.push({
|
||||
type: chunkType || 'stdout',
|
||||
type: unitType || 'stdout',
|
||||
content: line,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// Trim if too long
|
||||
if (exec.output.length > MAX_OUTPUT_LINES) {
|
||||
exec.output = exec.output.slice(-MAX_OUTPUT_LINES);
|
||||
}
|
||||
|
||||
|
||||
// Update UI if this is the active tab
|
||||
if (activeStreamTab === executionId && isCliStreamViewerOpen) {
|
||||
requestAnimationFrame(() => {
|
||||
renderStreamContent(executionId);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Update badge to show activity
|
||||
updateStreamBadge();
|
||||
}
|
||||
@@ -348,7 +369,7 @@ function renderFormattedLine(line, searchFilter) {
|
||||
|
||||
// Type badge icons for backend chunkType (CliOutputUnit.type)
|
||||
// Maps to different CLI tools' output types:
|
||||
// - Gemini: init→metadata, message→stdout, result→metadata
|
||||
// - Gemini: init→metadata, message→stdout, result→metadata, tool_use/tool_result→tool_call
|
||||
// - Codex: reasoning→thought, agent_message→stdout, turn.completed→metadata
|
||||
// - Claude: system→metadata, assistant→stdout, result→metadata
|
||||
// - OpenCode: step_start→progress, text→stdout, step_finish→metadata
|
||||
@@ -360,7 +381,8 @@ function renderFormattedLine(line, searchFilter) {
|
||||
system: 'settings',
|
||||
stderr: 'alert-circle',
|
||||
metadata: 'info',
|
||||
stdout: 'message-circle'
|
||||
stdout: 'message-circle',
|
||||
tool_call: 'wrench'
|
||||
};
|
||||
|
||||
// Type badge labels for backend chunkType
|
||||
@@ -372,7 +394,8 @@ function renderFormattedLine(line, searchFilter) {
|
||||
system: 'System',
|
||||
stderr: 'Error',
|
||||
metadata: 'Info',
|
||||
stdout: 'Response'
|
||||
stdout: 'Response',
|
||||
tool_call: 'Tool'
|
||||
};
|
||||
|
||||
// Build type badge - prioritize content prefix, then fall back to chunkType
|
||||
|
||||
@@ -1207,28 +1207,28 @@ function setPreferredProjectConfigType(type) {
|
||||
const RECOMMENDED_MCP_SERVERS = [
|
||||
{
|
||||
id: 'ace-tool',
|
||||
name: 'ACE Tool',
|
||||
description: 'Augment Context Engine - Semantic code search with real-time codebase indexing',
|
||||
nameKey: 'mcp.ace-tool.name',
|
||||
descKey: 'mcp.ace-tool.desc',
|
||||
icon: 'search-code',
|
||||
category: 'search',
|
||||
fields: [
|
||||
{
|
||||
key: 'baseUrl',
|
||||
label: 'Base URL',
|
||||
labelKey: 'mcp.ace-tool.field.baseUrl',
|
||||
type: 'text',
|
||||
default: 'https://acemcp.heroman.wtf/relay/',
|
||||
placeholder: 'https://acemcp.heroman.wtf/relay/',
|
||||
required: true,
|
||||
description: 'ACE MCP relay server URL'
|
||||
descKey: 'mcp.ace-tool.field.baseUrl.desc'
|
||||
},
|
||||
{
|
||||
key: 'token',
|
||||
label: 'API Token',
|
||||
labelKey: 'mcp.ace-tool.field.token',
|
||||
type: 'password',
|
||||
default: '',
|
||||
placeholder: 'ace_xxxxxxxxxxxxxxxx',
|
||||
required: true,
|
||||
description: 'Your ACE API token (get from ACE dashboard)'
|
||||
descKey: 'mcp.ace-tool.field.token.desc'
|
||||
}
|
||||
],
|
||||
buildConfig: (values) => ({
|
||||
@@ -1244,8 +1244,8 @@ const RECOMMENDED_MCP_SERVERS = [
|
||||
},
|
||||
{
|
||||
id: 'chrome-devtools',
|
||||
name: 'Chrome DevTools',
|
||||
description: 'Browser automation and DevTools integration for web development',
|
||||
nameKey: 'mcp.chrome-devtools.name',
|
||||
descKey: 'mcp.chrome-devtools.desc',
|
||||
icon: 'chrome',
|
||||
category: 'browser',
|
||||
fields: [],
|
||||
@@ -1258,28 +1258,32 @@ const RECOMMENDED_MCP_SERVERS = [
|
||||
},
|
||||
{
|
||||
id: 'exa',
|
||||
name: 'Exa Search',
|
||||
description: 'AI-powered web search with real-time crawling and content extraction',
|
||||
nameKey: 'mcp.exa.name',
|
||||
descKey: 'mcp.exa.desc',
|
||||
icon: 'globe-2',
|
||||
category: 'search',
|
||||
fields: [
|
||||
{
|
||||
key: 'apiKey',
|
||||
label: 'EXA API Key',
|
||||
labelKey: 'mcp.exa.field.apiKey',
|
||||
type: 'password',
|
||||
default: '',
|
||||
placeholder: 'your-exa-api-key',
|
||||
required: true,
|
||||
description: 'Get your API key from exa.ai dashboard'
|
||||
required: false,
|
||||
descKey: 'mcp.exa.field.apiKey.desc'
|
||||
}
|
||||
],
|
||||
buildConfig: (values) => ({
|
||||
command: 'npx',
|
||||
args: ['-y', 'exa-mcp-server'],
|
||||
env: {
|
||||
EXA_API_KEY: values.apiKey
|
||||
buildConfig: (values) => {
|
||||
const config = {
|
||||
command: 'npx',
|
||||
args: ['-y', 'exa-mcp-server']
|
||||
};
|
||||
// Only add env if API key is provided
|
||||
if (values.apiKey) {
|
||||
config.env = { EXA_API_KEY: values.apiKey };
|
||||
}
|
||||
})
|
||||
return config;
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
@@ -1290,9 +1294,10 @@ function getRecommendedMcpServers() {
|
||||
|
||||
// Check if a recommended MCP is already installed
|
||||
function isRecommendedMcpInstalled(mcpId) {
|
||||
// Check in current project servers
|
||||
const currentPath = projectPath;
|
||||
const projectData = mcpAllProjects[currentPath] || {};
|
||||
// Check in current project servers (handle different path formats)
|
||||
const forwardSlashPath = projectPath.replace(/\\/g, '/');
|
||||
const backSlashPath = projectPath.replace(/\//g, '\\');
|
||||
const projectData = mcpAllProjects[forwardSlashPath] || mcpAllProjects[backSlashPath] || mcpAllProjects[projectPath] || {};
|
||||
const projectServers = projectData.mcpServers || {};
|
||||
|
||||
if (projectServers[mcpId]) return { installed: true, scope: 'project' };
|
||||
@@ -1321,6 +1326,8 @@ function openRecommendedMcpWizard(mcpId) {
|
||||
}
|
||||
|
||||
const hasFields = mcpDef.fields && mcpDef.fields.length > 0;
|
||||
const mcpName = t(mcpDef.nameKey);
|
||||
const mcpDesc = t(mcpDef.descKey);
|
||||
|
||||
const modal = document.createElement('div');
|
||||
modal.id = 'recommendedMcpWizardModal';
|
||||
@@ -1334,8 +1341,8 @@ function openRecommendedMcpWizard(mcpId) {
|
||||
<i data-lucide="${mcpDef.icon}" class="w-5 h-5 text-primary"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="text-lg font-semibold text-foreground">${t('mcp.wizard.install')} ${escapeHtml(mcpDef.name)}</h3>
|
||||
<p class="text-sm text-muted-foreground">${escapeHtml(mcpDef.description)}</p>
|
||||
<h3 class="text-lg font-semibold text-foreground">${t('mcp.wizard.install')} ${escapeHtml(mcpName)}</h3>
|
||||
<p class="text-sm text-muted-foreground">${escapeHtml(mcpDesc)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="closeRecommendedMcpWizard()" class="text-muted-foreground hover:text-foreground">
|
||||
@@ -1350,10 +1357,10 @@ function openRecommendedMcpWizard(mcpId) {
|
||||
${mcpDef.fields.map(field => `
|
||||
<div class="space-y-1.5">
|
||||
<label class="flex items-center gap-1.5 text-sm font-medium text-foreground">
|
||||
${escapeHtml(field.label)}
|
||||
${escapeHtml(t(field.labelKey))}
|
||||
${field.required ? '<span class="text-destructive">*</span>' : ''}
|
||||
</label>
|
||||
${field.description ? `<p class="text-xs text-muted-foreground">${escapeHtml(field.description)}</p>` : ''}
|
||||
${field.descKey ? `<p class="text-xs text-muted-foreground">${escapeHtml(t(field.descKey))}</p>` : ''}
|
||||
<input type="${field.type || 'text'}"
|
||||
id="wizard-field-${field.key}"
|
||||
class="w-full px-3 py-2 text-sm bg-background border border-border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary"
|
||||
@@ -1471,7 +1478,7 @@ async function submitRecommendedMcpWizard(mcpId) {
|
||||
const value = input ? input.value.trim() : '';
|
||||
|
||||
if (field.required && !value) {
|
||||
showRefreshToast(`${field.label} is required`, 'error');
|
||||
showRefreshToast(`${t(field.labelKey)} is required`, 'error');
|
||||
if (input) input.focus();
|
||||
hasError = true;
|
||||
break;
|
||||
@@ -1498,7 +1505,7 @@ async function submitRecommendedMcpWizard(mcpId) {
|
||||
}
|
||||
|
||||
closeRecommendedMcpWizard();
|
||||
showRefreshToast(`${mcpDef.name} installed successfully`, 'success');
|
||||
// Note: success toast is shown by the underlying API functions
|
||||
} catch (err) {
|
||||
console.error(`Failed to install ${mcpDef.name}:`, err);
|
||||
showRefreshToast(`Failed to install ${mcpDef.name}: ${err.message}`, 'error');
|
||||
|
||||
@@ -33,6 +33,8 @@ const i18n = {
|
||||
'common.minutes': 'minutes',
|
||||
'common.enabled': 'Enabled',
|
||||
'common.disabled': 'Disabled',
|
||||
'common.yes': 'Yes',
|
||||
'common.no': 'No',
|
||||
|
||||
// Header
|
||||
'header.project': 'Project:',
|
||||
@@ -908,6 +910,19 @@ const i18n = {
|
||||
'mcp.wizard.installTo': 'Install to',
|
||||
'mcp.wizard.project': 'Project',
|
||||
'mcp.wizard.global': 'Global',
|
||||
// Recommended MCP Server Definitions
|
||||
'mcp.ace-tool.name': 'ACE Tool',
|
||||
'mcp.ace-tool.desc': 'Augment Context Engine - Semantic code search with real-time codebase indexing',
|
||||
'mcp.ace-tool.field.baseUrl': 'Base URL',
|
||||
'mcp.ace-tool.field.baseUrl.desc': 'ACE MCP relay server URL',
|
||||
'mcp.ace-tool.field.token': 'API Token',
|
||||
'mcp.ace-tool.field.token.desc': 'Your ACE API token (get from ACE dashboard)',
|
||||
'mcp.chrome-devtools.name': 'Chrome DevTools',
|
||||
'mcp.chrome-devtools.desc': 'Browser automation and DevTools integration for web development',
|
||||
'mcp.exa.name': 'Exa Search',
|
||||
'mcp.exa.desc': 'AI-powered web search with real-time crawling and content extraction',
|
||||
'mcp.exa.field.apiKey': 'EXA API Key',
|
||||
'mcp.exa.field.apiKey.desc': 'Optional - Free tier has rate limits. Get key from exa.ai for higher limits',
|
||||
|
||||
// MCP CLI Mode
|
||||
'mcp.cliMode': 'CLI Mode',
|
||||
@@ -1757,6 +1772,33 @@ const i18n = {
|
||||
'apiSettings.nameRequired': 'Name is required',
|
||||
'apiSettings.status': 'Status',
|
||||
|
||||
// Model Pools (High Availability)
|
||||
'apiSettings.modelPools': 'Model Pools',
|
||||
'apiSettings.addModelPool': 'Add Model Pool',
|
||||
'apiSettings.editModelPool': 'Edit Model Pool',
|
||||
'apiSettings.poolName': 'Pool Name',
|
||||
'apiSettings.modelType': 'Model Type',
|
||||
'apiSettings.embedding': 'Embedding',
|
||||
'apiSettings.llm': 'LLM',
|
||||
'apiSettings.reranker': 'Reranker',
|
||||
'apiSettings.embeddingPools': 'Embedding Pools',
|
||||
'apiSettings.llmPools': 'LLM Pools',
|
||||
'apiSettings.rerankerPools': 'Reranker Pools',
|
||||
'apiSettings.cooldown': 'Cooldown',
|
||||
'apiSettings.maxConcurrent': 'Max Concurrent',
|
||||
'apiSettings.enablePool': 'Enable Pool',
|
||||
'apiSettings.autoDiscoverProviders': 'Auto-discover Providers',
|
||||
'apiSettings.excludedProviders': 'Excluded Providers',
|
||||
'apiSettings.noPoolSelected': 'No Pool Selected',
|
||||
'apiSettings.selectPoolFromList': 'Select a pool from the list to view details',
|
||||
'apiSettings.noPoolsConfigured': 'No model pools configured',
|
||||
'apiSettings.poolCreated': 'Pool created successfully',
|
||||
'apiSettings.poolDeleted': 'Pool deleted successfully',
|
||||
'apiSettings.poolUpdated': 'Pool updated successfully',
|
||||
'apiSettings.confirmDeletePool': 'Are you sure you want to delete this pool?',
|
||||
'apiSettings.legacyPool': 'Legacy',
|
||||
'apiSettings.pool': 'Pool',
|
||||
|
||||
// Common
|
||||
'common.cancel': 'Cancel',
|
||||
'common.optional': '(Optional)',
|
||||
@@ -2116,6 +2158,8 @@ const i18n = {
|
||||
'common.minutes': '分钟',
|
||||
'common.enabled': '已启用',
|
||||
'common.disabled': '已禁用',
|
||||
'common.yes': '是',
|
||||
'common.no': '否',
|
||||
|
||||
// Header
|
||||
'header.project': '项目:',
|
||||
@@ -2970,6 +3014,19 @@ const i18n = {
|
||||
'mcp.wizard.installTo': '安装到',
|
||||
'mcp.wizard.project': '项目',
|
||||
'mcp.wizard.global': '全局',
|
||||
// Recommended MCP Server Definitions
|
||||
'mcp.ace-tool.name': 'ACE 工具',
|
||||
'mcp.ace-tool.desc': 'Augment 上下文引擎 - 实时代码库索引的语义代码搜索',
|
||||
'mcp.ace-tool.field.baseUrl': '服务器地址',
|
||||
'mcp.ace-tool.field.baseUrl.desc': 'ACE MCP 中继服务器 URL',
|
||||
'mcp.ace-tool.field.token': 'API 令牌',
|
||||
'mcp.ace-tool.field.token.desc': '从 ACE 控制台获取您的 API 令牌',
|
||||
'mcp.chrome-devtools.name': 'Chrome 开发工具',
|
||||
'mcp.chrome-devtools.desc': '浏览器自动化和开发者工具集成,用于 Web 开发',
|
||||
'mcp.exa.name': 'Exa 搜索',
|
||||
'mcp.exa.desc': 'AI 驱动的网络搜索,支持实时爬取和内容提取',
|
||||
'mcp.exa.field.apiKey': 'EXA API 密钥',
|
||||
'mcp.exa.field.apiKey.desc': '可选 - 免费版有速率限制,从 exa.ai 获取密钥可提高配额',
|
||||
|
||||
// MCP CLI Mode
|
||||
'mcp.cliMode': 'CLI 模式',
|
||||
@@ -3850,6 +3907,33 @@ const i18n = {
|
||||
'apiSettings.tokenRequired': 'API 令牌为必填项',
|
||||
'apiSettings.status': '状态',
|
||||
|
||||
// Model Pools (High Availability)
|
||||
'apiSettings.modelPools': '高可用',
|
||||
'apiSettings.addModelPool': '添加模型池',
|
||||
'apiSettings.editModelPool': '编辑模型池',
|
||||
'apiSettings.poolName': '池名称',
|
||||
'apiSettings.modelType': '模型类型',
|
||||
'apiSettings.embedding': '嵌入',
|
||||
'apiSettings.llm': 'LLM',
|
||||
'apiSettings.reranker': '重排',
|
||||
'apiSettings.embeddingPools': '嵌入池',
|
||||
'apiSettings.llmPools': 'LLM 池',
|
||||
'apiSettings.rerankerPools': '重排池',
|
||||
'apiSettings.cooldown': '冷却时间',
|
||||
'apiSettings.maxConcurrent': '最大并发',
|
||||
'apiSettings.enablePool': '启用池',
|
||||
'apiSettings.autoDiscoverProviders': '自动发现供应商',
|
||||
'apiSettings.excludedProviders': '排除的供应商',
|
||||
'apiSettings.noPoolSelected': '未选择池',
|
||||
'apiSettings.selectPoolFromList': '从列表中选择一个池以查看详情',
|
||||
'apiSettings.noPoolsConfigured': '未配置模型池',
|
||||
'apiSettings.poolCreated': '池创建成功',
|
||||
'apiSettings.poolDeleted': '池删除成功',
|
||||
'apiSettings.poolUpdated': '池更新成功',
|
||||
'apiSettings.confirmDeletePool': '确定要删除此池吗?',
|
||||
'apiSettings.legacyPool': '旧版',
|
||||
'apiSettings.pool': '池',
|
||||
|
||||
// Common
|
||||
'common.cancel': '取消',
|
||||
'common.optional': '(可选)',
|
||||
|
||||
@@ -1157,22 +1157,19 @@ async function renderApiSettings() {
|
||||
// Build sidebar tabs HTML
|
||||
var sidebarTabsHtml = '<div class="sidebar-tabs">' +
|
||||
'<button class="sidebar-tab' + (activeSidebarTab === 'providers' ? ' active' : '') + '" onclick="switchSidebarTab(\'providers\')">' +
|
||||
'<i data-lucide="server"></i> ' + t('apiSettings.providers') +
|
||||
'<i data-lucide="server"></i><span>' + t('apiSettings.providers') + '</span>' +
|
||||
'</button>' +
|
||||
'<button class="sidebar-tab' + (activeSidebarTab === 'endpoints' ? ' active' : '') + '" onclick="switchSidebarTab(\'endpoints\')">' +
|
||||
'<i data-lucide="link"></i> ' + t('apiSettings.endpoints') +
|
||||
'<i data-lucide="link"></i><span>' + t('apiSettings.endpoints') + '</span>' +
|
||||
'</button>' +
|
||||
'<button class="sidebar-tab' + (activeSidebarTab === 'cli-settings' ? ' active' : '') + '" onclick="switchSidebarTab(\'cli-settings\')">' +
|
||||
'<i data-lucide="settings"></i> ' + t('apiSettings.cliSettings') +
|
||||
'<i data-lucide="settings"></i><span>' + t('apiSettings.cliSettings') + '</span>' +
|
||||
'</button>' +
|
||||
'<button class="sidebar-tab' + (activeSidebarTab === 'model-pools' ? ' active' : '') + '" onclick="switchSidebarTab(\'model-pools\')">' +
|
||||
'<i data-lucide="layers"></i> ' + t('apiSettings.modelPools') +
|
||||
'</button>' +
|
||||
'<button class="sidebar-tab' + (activeSidebarTab === 'embedding-pool' ? ' active' : '') + '" onclick="switchSidebarTab(\'embedding-pool\')">' +
|
||||
'<i data-lucide="repeat"></i> ' + t('apiSettings.embeddingPool') + ' (Legacy)' +
|
||||
'<i data-lucide="layers"></i><span>' + t('apiSettings.modelPools') + '</span>' +
|
||||
'</button>' +
|
||||
'<button class="sidebar-tab' + (activeSidebarTab === 'cache' ? ' active' : '') + '" onclick="switchSidebarTab(\'cache\')">' +
|
||||
'<i data-lucide="database"></i> ' + t('apiSettings.cache') +
|
||||
'<i data-lucide="database"></i><span>' + t('apiSettings.cache') + '</span>' +
|
||||
'</button>' +
|
||||
'</div>';
|
||||
|
||||
@@ -4152,7 +4149,7 @@ function renderModelPoolDetail(poolId) {
|
||||
'<div class="provider-detail-header">' +
|
||||
'<div>' +
|
||||
'<h2>' + escapeHtml(pool.name || pool.targetModel) + '</h2>' +
|
||||
'<p style="color: var(--text-secondary); margin-top: 0.5rem;">' + typeLabel + ' Pool</p>' +
|
||||
'<p style="color: var(--text-secondary); margin-top: 0.5rem;">' + typeLabel + t('apiSettings.pool') + '</p>' +
|
||||
'</div>' +
|
||||
'<div class="provider-actions">' +
|
||||
'<button class="btn btn-secondary" onclick="editModelPool(\'' + pool.id + '\')"><i data-lucide="edit-2"></i> ' + t('common.edit') + '</button>' +
|
||||
|
||||
@@ -566,6 +566,8 @@ async function renderMcpManager() {
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
${getRecommendedMcpServers().map(mcp => {
|
||||
const installStatus = isRecommendedMcpInstalled(mcp.id);
|
||||
const mcpName = t(mcp.nameKey);
|
||||
const mcpDesc = t(mcp.descKey);
|
||||
return `
|
||||
<div class="recommended-mcp-card bg-card border ${installStatus.installed ? 'border-success/50' : 'border-border'} rounded-lg p-4 hover:shadow-md transition-all">
|
||||
<div class="flex items-start justify-between mb-3">
|
||||
@@ -574,7 +576,7 @@ async function renderMcpManager() {
|
||||
<i data-lucide="${mcp.icon}" class="w-5 h-5 ${installStatus.installed ? 'text-success' : 'text-primary'}"></i>
|
||||
</div>
|
||||
<div>
|
||||
<h4 class="font-semibold text-foreground">${escapeHtml(mcp.name)}</h4>
|
||||
<h4 class="font-semibold text-foreground">${escapeHtml(mcpName)}</h4>
|
||||
<span class="text-xs px-1.5 py-0.5 bg-muted text-muted-foreground rounded">${mcp.category}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -585,7 +587,7 @@ async function renderMcpManager() {
|
||||
</span>
|
||||
` : ''}
|
||||
</div>
|
||||
<p class="text-sm text-muted-foreground mb-4 line-clamp-2">${escapeHtml(mcp.description)}</p>
|
||||
<p class="text-sm text-muted-foreground mb-4 line-clamp-2">${escapeHtml(mcpDesc)}</p>
|
||||
<div class="flex items-center justify-between">
|
||||
${mcp.fields.length > 0 ? `
|
||||
<span class="text-xs text-muted-foreground flex items-center gap-1">
|
||||
|
||||
Reference in New Issue
Block a user