feat: Add CCW MCP server and tools integration

- Introduced `ccw-mcp` command for running CCW tools as an MCP server.
- Updated `package.json` to include new MCP dependencies and scripts.
- Enhanced CLI with new options for `codex_lens` tool.
- Implemented MCP server logic to expose CCW tools via Model Context Protocol.
- Added new tools and updated existing ones for better functionality and documentation.
- Created quick start and full documentation for MCP server usage.
- Added tests for MCP server functionality to ensure reliability.
This commit is contained in:
catlog22
2025-12-13 09:14:57 +08:00
parent 15122b9ebb
commit d4e59770d0
20 changed files with 1829 additions and 200 deletions

View File

@@ -579,20 +579,66 @@ async function createMcpServerWithConfig(name, serverConfig) {
showRefreshToast(`Failed to create MCP server: ${err.message}`, 'error');
}
}
// ========== CCW Tools MCP Installation ==========
async function installCcwToolsMcp() {
// Define CCW Tools MCP server configuration
// Use npx for better cross-platform compatibility (handles PATH issues)
const ccwToolsConfig = {
// Get selected tools from checkboxes
function getSelectedCcwTools() {
const checkboxes = document.querySelectorAll('.ccw-tool-checkbox:checked');
return Array.from(checkboxes).map(cb => cb.dataset.tool);
}
// Select tools by category
function selectCcwTools(type) {
const checkboxes = document.querySelectorAll('.ccw-tool-checkbox');
const coreTools = ['write_file', 'edit_file', 'codex_lens', 'smart_search'];
checkboxes.forEach(cb => {
if (type === 'all') {
cb.checked = true;
} else if (type === 'none') {
cb.checked = false;
} else if (type === 'core') {
cb.checked = coreTools.includes(cb.dataset.tool);
}
});
}
// Build CCW Tools config with selected tools
function buildCcwToolsConfig(selectedTools) {
const config = {
command: "npx",
args: ["-y", "ccw-mcp"]
};
// Add env if not all tools or not default 4 core tools
const coreTools = ['write_file', 'edit_file', 'codex_lens', 'smart_search'];
const isDefault = selectedTools.length === 4 &&
coreTools.every(t => selectedTools.includes(t)) &&
selectedTools.every(t => coreTools.includes(t));
if (selectedTools.length === 15) {
config.env = { CCW_ENABLED_TOOLS: 'all' };
} else if (!isDefault && selectedTools.length > 0) {
config.env = { CCW_ENABLED_TOOLS: selectedTools.join(',') };
}
return config;
}
async function installCcwToolsMcp() {
const selectedTools = getSelectedCcwTools();
if (selectedTools.length === 0) {
showRefreshToast('Please select at least one tool', 'warning');
return;
}
const ccwToolsConfig = buildCcwToolsConfig(selectedTools);
try {
// Show loading toast
showRefreshToast('Installing CCW Tools MCP...', 'info');
// Use the existing copyMcpServerToProject function
const response = await fetch('/api/mcp-copy-server', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -609,7 +655,7 @@ async function installCcwToolsMcp() {
if (result.success) {
await loadMcpConfig();
renderMcpManager();
showRefreshToast('CCW Tools MCP installed successfully', 'success');
showRefreshToast(`CCW Tools installed (${selectedTools.length} tools)`, 'success');
} else {
showRefreshToast(result.error || 'Failed to install CCW Tools MCP', 'error');
}
@@ -618,3 +664,42 @@ async function installCcwToolsMcp() {
showRefreshToast(`Failed to install CCW Tools MCP: ${err.message}`, 'error');
}
}
async function updateCcwToolsMcp() {
const selectedTools = getSelectedCcwTools();
if (selectedTools.length === 0) {
showRefreshToast('Please select at least one tool', 'warning');
return;
}
const ccwToolsConfig = buildCcwToolsConfig(selectedTools);
try {
showRefreshToast('Updating CCW Tools MCP...', 'info');
const response = await fetch('/api/mcp-copy-server', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
projectPath: projectPath,
serverName: 'ccw-tools',
serverConfig: ccwToolsConfig
})
});
if (!response.ok) throw new Error('Failed to update CCW Tools MCP');
const result = await response.json();
if (result.success) {
await loadMcpConfig();
renderMcpManager();
showRefreshToast(`CCW Tools updated (${selectedTools.length} tools)`, 'success');
} else {
showRefreshToast(result.error || 'Failed to update CCW Tools MCP', 'error');
}
} catch (err) {
console.error('Failed to update CCW Tools MCP:', err);
showRefreshToast(`Failed to update CCW Tools MCP: ${err.message}`, 'error');
}
}

View File

@@ -1,6 +1,33 @@
// MCP Manager View
// Renders the MCP server management interface
// CCW Tools available for MCP
const CCW_MCP_TOOLS = [
// Core tools (always recommended)
{ name: 'write_file', desc: 'Write/create files', core: true },
{ name: 'edit_file', desc: 'Edit/replace content', core: true },
{ name: 'codex_lens', desc: 'Code index & search', core: true },
{ name: 'smart_search', desc: 'Quick regex/NL search', core: true },
// Optional tools
{ name: 'session_manager', desc: 'Workflow sessions', core: false },
{ name: 'generate_module_docs', desc: 'Generate docs', core: false },
{ name: 'update_module_claude', desc: 'Update CLAUDE.md', core: false },
{ name: 'cli_executor', desc: 'Gemini/Qwen/Codex CLI', core: false },
];
// Get currently enabled tools from installed config
function getCcwEnabledTools() {
const currentPath = projectPath.replace(/\//g, '\\');
const projectData = mcpAllProjects[currentPath] || {};
const ccwConfig = projectData.mcpServers?.['ccw-tools'];
if (ccwConfig?.env?.CCW_ENABLED_TOOLS) {
const val = ccwConfig.env.CCW_ENABLED_TOOLS;
if (val.toLowerCase() === 'all') return CCW_MCP_TOOLS.map(t => t.name);
return val.split(',').map(t => t.trim());
}
return CCW_MCP_TOOLS.filter(t => t.core).map(t => t.name);
}
async function renderMcpManager() {
const container = document.getElementById('mainContent');
if (!container) return;
@@ -36,7 +63,7 @@ async function renderMcpManager() {
.filter(([name, info]) => !currentProjectServerNames.includes(name) && !info.isGlobal);
// Check if CCW Tools is already installed
const isCcwToolsInstalled = currentProjectServerNames.includes("ccw-tools");
const enabledTools = getCcwEnabledTools();
container.innerHTML = `
<div class="mcp-manager">
@@ -54,7 +81,7 @@ async function renderMcpManager() {
${isCcwToolsInstalled ? `
<span class="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-semibold rounded-full bg-success-light text-success">
<i data-lucide="check" class="w-3 h-3"></i>
Installed
${enabledTools.length} tools
</span>
` : `
<span class="inline-flex items-center gap-1 px-2 py-0.5 text-xs font-semibold rounded-full bg-primary/20 text-primary">
@@ -63,36 +90,35 @@ async function renderMcpManager() {
</span>
`}
</div>
<p class="text-sm text-muted-foreground mb-3">
CCW built-in tools for file editing, code search, session management, and more
</p>
<div class="flex items-center gap-4 text-xs text-muted-foreground">
<span class="flex items-center gap-1">
<i data-lucide="layers" class="w-3 h-3"></i>
15 tools available
</span>
<span class="flex items-center gap-1">
<i data-lucide="zap" class="w-3 h-3"></i>
Native integration
</span>
<span class="flex items-center gap-1">
<i data-lucide="shield-check" class="w-3 h-3"></i>
Built-in & tested
</span>
<!-- Tool Selection Grid -->
<div class="grid grid-cols-3 sm:grid-cols-5 gap-2 mb-3">
${CCW_MCP_TOOLS.map(tool => `
<label class="flex items-center gap-1.5 text-xs cursor-pointer hover:bg-muted/50 rounded px-1.5 py-1 transition-colors">
<input type="checkbox" class="ccw-tool-checkbox w-3 h-3"
data-tool="${tool.name}"
${enabledTools.includes(tool.name) ? 'checked' : ''}>
<span class="${tool.core ? 'font-medium' : 'text-muted-foreground'}">${tool.desc}</span>
</label>
`).join('')}
</div>
<div class="flex items-center gap-3 text-xs">
<button class="text-primary hover:underline" onclick="selectCcwTools('core')">Core only</button>
<button class="text-primary hover:underline" onclick="selectCcwTools('all')">All</button>
<button class="text-muted-foreground hover:underline" onclick="selectCcwTools('none')">None</button>
</div>
</div>
</div>
<div class="shrink-0">
${isCcwToolsInstalled ? `
<button class="px-4 py-2 text-sm bg-muted text-muted-foreground rounded-lg cursor-not-allowed" disabled>
<i data-lucide="check" class="w-4 h-4 inline mr-1"></i>
Installed
<button class="px-4 py-2 text-sm bg-primary text-primary-foreground rounded-lg hover:opacity-90 transition-opacity"
onclick="updateCcwToolsMcp()">
Update
</button>
` : `
<button class="px-4 py-2 text-sm bg-primary text-primary-foreground rounded-lg hover:opacity-90 transition-opacity flex items-center gap-2"
onclick="installCcwToolsMcp()">
<i data-lucide="download" class="w-4 h-4"></i>
Install CCW Tools
Install
</button>
`}
</div>