diff --git a/.claude/rules/context-requirements.md b/.claude/rules/context-requirements.md index c47e9263..8f4b3c27 100644 --- a/.claude/rules/context-requirements.md +++ b/.claude/rules/context-requirements.md @@ -43,4 +43,5 @@ Before implementation, always: - `exact`: Known exact pattern - `fuzzy`: Typo-tolerant search - `semantic`: Concept-based search -- `graph`: Dependency analysis \ No newline at end of file +- `graph`: Dependency analysis + diff --git a/.claude/rules/file-modification.md b/.claude/rules/file-modification.md index c58bdc06..d0eb7062 100644 --- a/.claude/rules/file-modification.md +++ b/.claude/rules/file-modification.md @@ -45,3 +45,49 @@ **Use semantic search** for exploratory tasks **Use indexed search** for large, stable codebases **Use Exa** for external/public knowledge + +## ⚡ Core Search Tools + +**rg (ripgrep)**: Fast content search with regex support +**find**: File/directory location by name patterns +**grep**: Built-in pattern matching (fallback when rg unavailable) +**get_modules_by_depth**: Program architecture analysis (MANDATORY before planning) + + +## 🔧 Quick Command Reference + +```bash +# Semantic File Discovery (codebase-retrieval via CCW) +ccw cli exec " +PURPOSE: Discover files relevant to task/feature +TASK: • List all files related to [task/feature description] +MODE: analysis +CONTEXT: @**/* +EXPECTED: Relevant file paths with relevance explanation +RULES: Focus on direct relevance to task requirements | analysis=READ-ONLY +" --tool gemini --cd [directory] + +# Program Architecture (MANDATORY before planning) +ccw tool exec get_modules_by_depth '{}' + +# Content Search (rg preferred) +rg "pattern" --type js -n # Search JS files with line numbers +rg -i "case-insensitive" # Ignore case +rg -C 3 "context" # Show 3 lines before/after + +# File Search +find . -name "*.ts" -type f # Find TypeScript files +find . -path "*/node_modules" -prune -o -name "*.js" -print + +# Workflow Examples +rg "IMPL-\d+" .workflow/ --type json # Find task IDs +find .workflow/ -name "*.json" -path "*/.task/*" # Locate task files +rg "status.*pending" .workflow/.task/ # Find pending tasks +``` + +## ⚡ Performance Tips + +- **rg > grep** for content search +- **Use --type filters** to limit file types +- **Exclude dirs**: `--glob '!node_modules'` +- **Use -F** for literal strings (no regex) diff --git a/.claude/workflows/context-search-strategy.md b/.claude/workflows/context-search-strategy.md index f659721e..dfc0fc96 100644 --- a/.claude/workflows/context-search-strategy.md +++ b/.claude/workflows/context-search-strategy.md @@ -13,7 +13,7 @@ **rg (ripgrep)**: Fast content search with regex support **find**: File/directory location by name patterns **grep**: Built-in pattern matching (fallback when rg unavailable) -**get_modules_by_depth.sh**: Program architecture analysis (MANDATORY before planning) +**get_modules_by_depth**: Program architecture analysis (MANDATORY before planning) diff --git a/.mcp.json b/.mcp.json index 54ab2051..4870b47d 100644 --- a/.mcp.json +++ b/.mcp.json @@ -1,22 +1,11 @@ { "mcpServers": { - "test-mcp-server": { - "command": "npx", - "args": [ - "-y", - "@modelcontextprotocol/server-filesystem", - "D:/Claude_dms3" - ] - }, "ccw-tools": { "command": "npx", "args": [ "-y", "ccw-mcp" - ], - "env": { - "CCW_ENABLED_TOOLS": "write_file,edit_file,codex_lens,smart_search" - } + ] } } -} +} \ No newline at end of file diff --git a/IMPLEMENTATION_SUMMARY.md b/IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index 93dc35af..00000000 --- a/IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,190 +0,0 @@ -# Implementation Summary: Rules CLI Generation Feature - -## Status: ✅ Complete - -## Files Modified - -### D:\Claude_dms3\ccw\src\core\routes\rules-routes.ts - -**Changes:** -1. Added import for `executeCliTool` from cli-executor -2. Implemented `generateRuleViaCLI()` function -3. Modified POST `/api/rules/create` endpoint to support `mode: 'cli-generate'` - -## Implementation Details - -### 1. New Function: `generateRuleViaCLI()` - -**Location:** lines 224-340 - -**Purpose:** Generate rule content using Gemini CLI based on different generation strategies - -**Parameters:** -- `generationType`: 'description' | 'template' | 'extract' -- `description`: Natural language description of the rule -- `templateType`: Template category for structured generation -- `extractScope`: File pattern for code analysis (e.g., 'src/**/*.ts') -- `extractFocus`: Focus areas for extraction (e.g., 'error handling, naming') -- `fileName`: Target filename (must end with .md) -- `location`: 'project' or 'user' -- `subdirectory`: Optional subdirectory path -- `projectPath`: Project root directory - -**Process Flow:** -1. Parse parameters and determine generation type -2. Build appropriate CLI prompt template based on type -3. Execute Gemini CLI with: - - Tool: 'gemini' - - Mode: 'write' for description/template, 'analysis' for extract - - Timeout: 10 minutes (600000ms) - - Working directory: projectPath -4. Validate CLI execution result -5. Extract generated content from stdout -6. Call `createRule()` to save the file -7. Return result with execution ID - -### 2. Prompt Templates - -#### Description Mode (write) -``` -PURPOSE: Generate Claude Code memory rule from description to guide Claude's behavior -TASK: • Analyze rule requirements • Generate markdown content with clear instructions -MODE: write -EXPECTED: Complete rule content in markdown format -RULES: $(cat ~/.claude/workflows/cli-templates/prompts/universal/00-universal-rigorous-style.txt) -``` - -#### Template Mode (write) -``` -PURPOSE: Generate Claude Code rule from template type -TASK: • Create rule based on {templateType} • Generate structured markdown content -MODE: write -EXPECTED: Complete rule content in markdown format following template structure -RULES: $(cat ~/.claude/workflows/cli-templates/prompts/universal/00-universal-rigorous-style.txt) -``` - -#### Extract Mode (analysis) -``` -PURPOSE: Extract coding rules from existing codebase to document patterns and conventions -TASK: • Analyze code patterns • Extract common conventions • Identify best practices -MODE: analysis -CONTEXT: @{extractScope || '**/*'} -EXPECTED: Rule content based on codebase analysis with examples -RULES: $(cat ~/.claude/workflows/cli-templates/prompts/analysis/02-analyze-code-patterns.txt) -``` - -### 3. API Endpoint Modification - -**Endpoint:** POST `/api/rules/create` - -**Enhanced Request Body:** -```json -{ - "mode": "cli-generate", // NEW: triggers CLI generation - "generationType": "description", // NEW: 'description' | 'template' | 'extract' - "description": "...", // NEW: for description mode - "templateType": "...", // NEW: for template mode - "extractScope": "src/**/*.ts", // NEW: for extract mode - "extractFocus": "...", // NEW: for extract mode - "fileName": "rule-name.md", // REQUIRED - "location": "project", // REQUIRED: 'project' | 'user' - "subdirectory": "", // OPTIONAL - "projectPath": "..." // OPTIONAL: defaults to initialPath -} -``` - -**Backward Compatibility:** Existing manual creation still works: -```json -{ - "fileName": "rule-name.md", - "content": "# Rule Content\n...", - "location": "project", - "paths": [], - "subdirectory": "" -} -``` - -**Response Format:** -```json -{ - "success": true, - "fileName": "rule-name.md", - "location": "project", - "path": "/absolute/path/to/rule-name.md", - "subdirectory": null, - "generatedContent": "# Generated Content\n...", - "executionId": "1734168000000-gemini" -} -``` - -## Error Handling - -### Validation Errors -- Missing `fileName`: "File name is required" -- Missing `location`: "Location is required (project or user)" -- Missing `generationType` in CLI mode: "generationType is required for CLI generation mode" -- Missing `description` for description mode: "description is required for description-based generation" -- Missing `templateType` for template mode: "templateType is required for template-based generation" -- Unknown `generationType`: "Unknown generation type: {type}" - -### CLI Execution Errors -- CLI tool failure: Returns `{ error: "CLI execution failed: ...", stderr: "..." }` -- Empty content: Returns `{ error: "CLI execution returned empty content", stdout: "...", stderr: "..." }` -- Timeout: CLI executor will timeout after 10 minutes -- File exists: "Rule '{fileName}' already exists in {location} location" - -## Testing - -### Test Document -Created: `D:\Claude_dms3\test-rules-cli-generation.md` - -Contains: -- API usage examples for all 3 generation types -- Request/response format examples -- Error handling scenarios -- Integration details - -### Compilation Test -✅ TypeScript compilation successful (`npm run build`) - -## Integration Points - -### Dependencies -- **cli-executor.ts**: Provides `executeCliTool()` for Gemini execution -- **createRule()**: Existing function for file creation -- **handlePostRequest()**: Existing request handler from RouteContext - -### CLI Tool -- **Tool**: Gemini (via `executeCliTool()`) -- **Timeout**: 10 minutes (600000ms) -- **Mode**: 'write' for generation, 'analysis' for extraction -- **Working Directory**: Project path for context access - -## Next Steps (Not Implemented) - -1. **UI Integration**: Add frontend interface in Rules Manager dashboard -2. **Streaming Output**: Display CLI execution progress in real-time -3. **Preview**: Show generated content before saving -4. **Refinement**: Allow iterative refinement of generated rules -5. **Templates Library**: Add predefined template types -6. **History**: Track generation history and allow regeneration - -## Verification Checklist - -- [x] Import cli-executor functions -- [x] Implement `generateRuleViaCLI()` with 3 generation types -- [x] Build appropriate prompts for each type -- [x] Use correct MODE (analysis vs write) -- [x] Set timeout to at least 10 minutes -- [x] Integrate with `createRule()` for file creation -- [x] Modify POST endpoint to support `mode: 'cli-generate'` -- [x] Validate required parameters -- [x] Return unified result format -- [x] Handle errors appropriately -- [x] Maintain backward compatibility -- [x] Verify TypeScript compilation -- [x] Create test documentation - -## Files Created -- `D:\Claude_dms3\test-rules-cli-generation.md`: Test documentation -- `D:\Claude_dms3\IMPLEMENTATION_SUMMARY.md`: This file diff --git a/ccw/src/core/routes/mcp-routes.ts b/ccw/src/core/routes/mcp-routes.ts index f2251f1d..f2a19578 100644 --- a/ccw/src/core/routes/mcp-routes.ts +++ b/ccw/src/core/routes/mcp-routes.ts @@ -77,7 +77,7 @@ function getMcpServersFromFile(filePath) { */ function addMcpServerToMcpJson(projectPath, serverName, serverConfig) { try { - const normalizedPath = normalizeProjectPathForConfig(projectPath); + const normalizedPath = normalizePathForFileSystem(projectPath); const mcpJsonPath = join(normalizedPath, '.mcp.json'); // Read existing .mcp.json or create new structure @@ -115,7 +115,7 @@ function addMcpServerToMcpJson(projectPath, serverName, serverConfig) { */ function removeMcpServerFromMcpJson(projectPath, serverName) { try { - const normalizedPath = normalizeProjectPathForConfig(projectPath); + const normalizedPath = normalizePathForFileSystem(projectPath); const mcpJsonPath = join(normalizedPath, '.mcp.json'); if (!existsSync(mcpJsonPath)) { @@ -238,22 +238,43 @@ function getMcpConfig() { } /** - * Normalize project path for .claude.json (Windows backslash format) + * Normalize path to filesystem format (for accessing .mcp.json files) + * Always uses forward slashes for cross-platform compatibility * @param {string} path * @returns {string} */ -function normalizeProjectPathForConfig(path) { - // Convert forward slashes to backslashes for Windows .claude.json format - let normalized = path.replace(/\//g, '\\'); - - // Handle /d/path format -> D:\path - if (normalized.match(/^\\[a-zA-Z]\\/)) { +function normalizePathForFileSystem(path) { + let normalized = path.replace(/\\/g, '/'); + + // Handle /d/path format -> D:/path + if (normalized.match(/^\/[a-zA-Z]\//)) { normalized = normalized.charAt(1).toUpperCase() + ':' + normalized.slice(2); } - + return normalized; } +/** + * Normalize project path to match existing format in .claude.json + * Checks both forward slash and backslash formats to find existing entry + * @param {string} path + * @param {Object} claudeConfig - Optional existing config to check format + * @returns {string} + */ +function normalizeProjectPathForConfig(path, claudeConfig = null) { + // IMPORTANT: Always normalize to forward slashes to prevent duplicate entries + // (e.g., prevents both "D:/Claude_dms3" and "D:\\Claude_dms3") + let normalizedForward = path.replace(/\\/g, '/'); + + // Handle /d/path format -> D:/path + if (normalizedForward.match(/^\/[a-zA-Z]\//)) { + normalizedForward = normalizedForward.charAt(1).toUpperCase() + ':' + normalizedForward.slice(2); + } + + // ALWAYS return forward slash format to prevent duplicates + return normalizedForward; +} + /** * Toggle MCP server enabled/disabled * @param {string} projectPath @@ -270,7 +291,7 @@ function toggleMcpServerEnabled(projectPath, serverName, enable) { const content = readFileSync(CLAUDE_CONFIG_PATH, 'utf8'); const config = JSON.parse(content); - const normalizedPath = normalizeProjectPathForConfig(projectPath); + const normalizedPath = normalizeProjectPathForConfig(projectPath, config); if (!config.projects || !config.projects[normalizedPath]) { return { error: `Project not found: ${normalizedPath}` }; @@ -332,7 +353,7 @@ function addMcpServerToProject(projectPath, serverName, serverConfig, useLegacyC const content = readFileSync(CLAUDE_CONFIG_PATH, 'utf8'); const config = JSON.parse(content); - const normalizedPath = normalizeProjectPathForConfig(projectPath); + const normalizedPath = normalizeProjectPathForConfig(projectPath, config); // Create project entry if it doesn't exist if (!config.projects) { @@ -387,8 +408,8 @@ function addMcpServerToProject(projectPath, serverName, serverConfig, useLegacyC */ function removeMcpServerFromProject(projectPath, serverName) { try { - const normalizedPath = normalizeProjectPathForConfig(projectPath); - const mcpJsonPath = join(normalizedPath, '.mcp.json'); + const normalizedPathForFile = normalizePathForFileSystem(projectPath); + const mcpJsonPath = join(normalizedPathForFile, '.mcp.json'); let removedFromMcpJson = false; let removedFromClaudeJson = false; @@ -409,6 +430,9 @@ function removeMcpServerFromProject(projectPath, serverName) { const content = readFileSync(CLAUDE_CONFIG_PATH, 'utf8'); const config = JSON.parse(content); + // Get normalized path that matches existing config format + const normalizedPath = normalizeProjectPathForConfig(projectPath, config); + if (config.projects && config.projects[normalizedPath]) { const projectConfig = config.projects[normalizedPath]; @@ -597,11 +621,13 @@ export async function handleMcpRoutes(ctx: RouteContext): Promise { // API: Copy MCP server to project if (pathname === '/api/mcp-copy-server' && req.method === 'POST') { handlePostRequest(req, res, async (body) => { - const { projectPath, serverName, serverConfig } = body; + const { projectPath, serverName, serverConfig, configType } = body; if (!projectPath || !serverName || !serverConfig) { return { error: 'projectPath, serverName, and serverConfig are required', status: 400 }; } - return addMcpServerToProject(projectPath, serverName, serverConfig); + // configType: 'mcp' = use .mcp.json (default), 'claude' = use .claude.json + const useLegacyConfig = configType === 'claude'; + return addMcpServerToProject(projectPath, serverName, serverConfig, useLegacyConfig); }); return true; } diff --git a/ccw/src/core/mcp-templates-db.ts b/ccw/src/core/routes/mcp-templates-db.ts similarity index 100% rename from ccw/src/core/mcp-templates-db.ts rename to ccw/src/core/routes/mcp-templates-db.ts diff --git a/ccw/src/core/routes/memory-routes.ts b/ccw/src/core/routes/memory-routes.ts index 443e375a..c646333c 100644 --- a/ccw/src/core/routes/memory-routes.ts +++ b/ccw/src/core/routes/memory-routes.ts @@ -733,7 +733,7 @@ Return ONLY valid JSON in this exact format (no markdown, no code blocks, just p } try { - const configPath = join(projectPath, '.claude', 'rules', 'active_memory.md'); + const configPath = join(projectPath, '.claude', 'CLAUDE.md'); const configJsonPath = join(projectPath, '.claude', 'active_memory_config.json'); const enabled = existsSync(configPath); let lastSync: string | null = null; @@ -784,16 +784,12 @@ Return ONLY valid JSON in this exact format (no markdown, no code blocks, just p return; } - const rulesDir = join(projectPath, '.claude', 'rules'); const claudeDir = join(projectPath, '.claude'); - const configPath = join(rulesDir, 'active_memory.md'); + const configPath = join(claudeDir, 'CLAUDE.md'); const configJsonPath = join(claudeDir, 'active_memory_config.json'); if (enabled) { // Enable: Create directories and initial file - if (!existsSync(rulesDir)) { - mkdirSync(rulesDir, { recursive: true }); - } if (!existsSync(claudeDir)) { mkdirSync(claudeDir, { recursive: true }); } @@ -803,8 +799,8 @@ Return ONLY valid JSON in this exact format (no markdown, no code blocks, just p writeFileSync(configJsonPath, JSON.stringify(config, null, 2), 'utf-8'); } - // Create initial active_memory.md with header - const initialContent = `# Active Memory + // Create initial CLAUDE.md with header + const initialContent = `# CLAUDE.md - Project Memory > Auto-generated understanding of frequently accessed files. > Last updated: ${new Date().toISOString()} @@ -867,7 +863,7 @@ Return ONLY valid JSON in this exact format (no markdown, no code blocks, just p return true; } - // API: Active Memory - Sync (analyze hot files using CLI and update active_memory.md) + // API: Active Memory - Sync (analyze hot files using CLI and update CLAUDE.md) if (pathname === '/api/memory/active/sync' && req.method === 'POST') { let body = ''; req.on('data', (chunk: Buffer) => { body += chunk.toString(); }); @@ -882,8 +878,8 @@ Return ONLY valid JSON in this exact format (no markdown, no code blocks, just p return; } - const rulesDir = join(projectPath, '.claude', 'rules'); - const configPath = join(rulesDir, 'active_memory.md'); + const claudeDir = join(projectPath, '.claude'); + const configPath = join(claudeDir, 'CLAUDE.md'); // Get hot files from memory store - with fallback let hotFiles: any[] = []; @@ -903,8 +899,8 @@ Return ONLY valid JSON in this exact format (no markdown, no code blocks, just p return isAbsolute(filePath) ? filePath : join(projectPath, filePath); }).filter((p: string) => existsSync(p)); - // Build the active memory content header - let content = `# Active Memory + // Build the CLAUDE.md content header + let content = `# CLAUDE.md - Project Memory > Auto-generated understanding of frequently accessed files using ${tool.toUpperCase()}. > Last updated: ${new Date().toISOString()} @@ -942,14 +938,29 @@ RULES: Be concise. Focus on practical understanding. Include function signatures }); if (result.success && result.execution?.output) { - // Extract stdout from output object - cliOutput = typeof result.execution.output === 'string' - ? result.execution.output - : result.execution.output.stdout || ''; + // Extract stdout from output object with proper serialization + const output = result.execution.output; + if (typeof output === 'string') { + cliOutput = output; + } else if (output && typeof output === 'object') { + // Handle object output - extract stdout or serialize the object + if (output.stdout && typeof output.stdout === 'string') { + cliOutput = output.stdout; + } else if (output.stderr && typeof output.stderr === 'string') { + cliOutput = output.stderr; + } else { + // Last resort: serialize the entire object as JSON + cliOutput = JSON.stringify(output, null, 2); + } + } else { + cliOutput = ''; + } } - // Add CLI output to content - content += cliOutput + '\n\n---\n\n'; + // Add CLI output to content (only if not empty) + if (cliOutput && cliOutput.trim()) { + content += cliOutput + '\n\n---\n\n'; + } } catch (cliErr) { // Fallback to basic analysis if CLI fails @@ -1007,8 +1018,8 @@ RULES: Be concise. Focus on practical understanding. Include function signatures } // Ensure directory exists - if (!existsSync(rulesDir)) { - mkdirSync(rulesDir, { recursive: true }); + if (!existsSync(claudeDir)) { + mkdirSync(claudeDir, { recursive: true }); } // Write the file diff --git a/ccw/src/templates/dashboard-js/components/mcp-manager.js b/ccw/src/templates/dashboard-js/components/mcp-manager.js index abf753cf..3e593718 100644 --- a/ccw/src/templates/dashboard-js/components/mcp-manager.js +++ b/ccw/src/templates/dashboard-js/components/mcp-manager.js @@ -87,15 +87,23 @@ async function toggleMcpServer(serverName, enable) { } } -async function copyMcpServerToProject(serverName, serverConfig) { +async function copyMcpServerToProject(serverName, serverConfig, configType = null) { try { + // If configType not specified, ask user to choose + if (!configType) { + const choice = await showConfigTypeDialog(); + if (!choice) return null; // User cancelled + configType = choice; + } + const response = await fetch('/api/mcp-copy-server', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ projectPath: projectPath, serverName: serverName, - serverConfig: serverConfig + serverConfig: serverConfig, + configType: configType // 'claude' for .claude.json, 'mcp' for .mcp.json }) }); @@ -105,7 +113,8 @@ async function copyMcpServerToProject(serverName, serverConfig) { if (result.success) { await loadMcpConfig(); renderMcpManager(); - showRefreshToast(`MCP server "${serverName}" added to project`, 'success'); + const location = configType === 'mcp' ? '.mcp.json' : '.claude.json'; + showRefreshToast(`MCP server "${serverName}" added to project (${location})`, 'success'); } return result; } catch (err) { @@ -115,6 +124,53 @@ async function copyMcpServerToProject(serverName, serverConfig) { } } +// Show dialog to let user choose config type +function showConfigTypeDialog() { + return new Promise((resolve) => { + const dialog = document.createElement('div'); + dialog.className = 'fixed inset-0 bg-black/50 flex items-center justify-center z-50'; + dialog.innerHTML = ` +
+

${t('mcp.chooseInstallLocation')}

+
+ + +
+ +
+ `; + document.body.appendChild(dialog); + + const options = dialog.querySelectorAll('.config-type-option'); + options.forEach(btn => { + btn.addEventListener('click', () => { + resolve(btn.dataset.type); + document.body.removeChild(dialog); + }); + }); + + const cancelBtn = dialog.querySelector('.cancel-btn'); + cancelBtn.addEventListener('click', () => { + resolve(null); + document.body.removeChild(dialog); + }); + + // Close on backdrop click + dialog.addEventListener('click', (e) => { + if (e.target === dialog) { + resolve(null); + document.body.removeChild(dialog); + } + }); + }); +} + async function removeMcpServerFromProject(serverName) { try { const response = await fetch('/api/mcp-remove-server', { diff --git a/ccw/src/templates/dashboard-js/i18n.js b/ccw/src/templates/dashboard-js/i18n.js index a263252d..a25b9b25 100644 --- a/ccw/src/templates/dashboard-js/i18n.js +++ b/ccw/src/templates/dashboard-js/i18n.js @@ -431,7 +431,31 @@ const i18n = { 'mcp.jsonFormatsHint': 'Supports {"servers": {...}}, {"mcpServers": {...}}, and direct server config formats.', 'mcp.previewServers': 'Preview (servers to be added):', 'mcp.create': 'Create', - + 'mcp.chooseInstallLocation': 'Choose Installation Location', + 'mcp.installToClaudeJson': 'Install to .claude.json', + 'mcp.installToMcpJson': 'Install to .mcp.json (Recommended)', + 'mcp.claudeJsonDesc': 'Save in root .claude.json projects section (shared config)', + 'mcp.mcpJsonDesc': 'Save in project .mcp.json file (recommended for version control)', + + // MCP Templates + 'mcp.templates': 'MCP Templates', + 'mcp.savedTemplates': 'saved templates', + 'mcp.saveAsTemplate': 'Save as Template', + 'mcp.enterTemplateName': 'Enter template name', + 'mcp.enterTemplateDesc': 'Enter template description (optional)', + 'mcp.enterServerName': 'Enter server name', + 'mcp.templateSaved': 'Template "{name}" saved successfully', + 'mcp.templateSaveFailed': 'Failed to save template: {error}', + 'mcp.templateNotFound': 'Template "{name}" not found', + 'mcp.templateInstalled': 'Server "{name}" installed successfully', + 'mcp.templateInstallFailed': 'Failed to install template: {error}', + 'mcp.deleteTemplate': 'Delete Template', + 'mcp.deleteTemplateConfirm': 'Delete template "{name}"?', + 'mcp.templateDeleted': 'Template "{name}" deleted successfully', + 'mcp.templateDeleteFailed': 'Failed to delete template: {error}', + 'mcp.toProject': 'To Project', + 'mcp.toGlobal': 'To Global', + // Hook Manager 'hook.projectHooks': 'Project Hooks', 'hook.projectFile': '.claude/settings.json', @@ -1346,6 +1370,11 @@ const i18n = { 'mcp.jsonFormatsHint': '支持 {"servers": {...}}、{"mcpServers": {...}} 和直接服务器配置格式。', 'mcp.previewServers': '预览(将添加的服务器):', 'mcp.create': '创建', + 'mcp.chooseInstallLocation': '选择安装位置', + 'mcp.installToClaudeJson': '安装到 .claude.json', + 'mcp.installToMcpJson': '安装到 .mcp.json(推荐)', + 'mcp.claudeJsonDesc': '保存在根目录 .claude.json projects 字段下(共享配置)', + 'mcp.mcpJsonDesc': '保存在项目 .mcp.json 文件中(推荐用于版本控制)', // Hook Manager 'hook.projectHooks': '项目钩子', diff --git a/ccw/src/templates/dashboard-js/views/mcp-manager.js b/ccw/src/templates/dashboard-js/views/mcp-manager.js index afe6fc74..8c712ae8 100644 --- a/ccw/src/templates/dashboard-js/views/mcp-manager.js +++ b/ccw/src/templates/dashboard-js/views/mcp-manager.js @@ -43,6 +43,9 @@ async function renderMcpManager() { await loadMcpConfig(); } + // Load MCP templates + await loadMcpTemplates(); + const currentPath = projectPath.replace(/\//g, '\\'); const projectData = mcpAllProjects[currentPath] || {}; const projectServers = projectData.mcpServers || {}; @@ -269,6 +272,77 @@ async function renderMcpManager() { `} + + ${mcpTemplates.length > 0 ? ` +
+
+

+ + ${t('mcp.templates')} +

+ ${mcpTemplates.length} ${t('mcp.savedTemplates')} +
+ +
+ ${mcpTemplates.map(template => ` +
+
+
+

+ + ${escapeHtml(template.name)} +

+ ${template.description ? ` +

${escapeHtml(template.description)}

+ ` : ''} +
+
+ +
+
+ cmd + ${escapeHtml(template.serverConfig.command)} +
+ ${template.serverConfig.args && template.serverConfig.args.length > 0 ? ` +
+ args + ${escapeHtml(template.serverConfig.args.slice(0, 2).join(' '))}${template.serverConfig.args.length > 2 ? '...' : ''} +
+ ` : ''} +
+ +
+
+ + +
+ +
+
+ `).join('')} +
+
+ ` : ''} +
@@ -402,15 +476,25 @@ function renderProjectAvailableServerCard(entry) { ` : ''}
-
- +
+
+ + +
${canRemove ? `