From b9d068d6d4b3c23a29e2f1183ca3547396b10633 Mon Sep 17 00:00:00 2001 From: catlog22 Date: Thu, 18 Dec 2025 00:05:32 +0800 Subject: [PATCH] feat: Add real-time progress output for MCP init action MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - MCP server outputs progress to stderr during smart_search init - Progress format: [Progress] {percent}% - {message} - Does not interfere with JSON-RPC protocol (stdout) - Added executeInitWithProgress for external progress callback - Added executeToolWithProgress to tools/index.ts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- ccw/src/mcp-server/index.ts | 16 +++++++- ccw/src/tools/index.ts | 56 +++++++++++++++++++++++++++ ccw/src/tools/smart-search.ts | 73 +++++++++++++++++++++++++++++++++++ 3 files changed, 143 insertions(+), 2 deletions(-) diff --git a/ccw/src/mcp-server/index.ts b/ccw/src/mcp-server/index.ts index 32a8d855..8d9fa913 100644 --- a/ccw/src/mcp-server/index.ts +++ b/ccw/src/mcp-server/index.ts @@ -10,7 +10,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; -import { getAllToolSchemas, executeTool } from '../tools/index.js'; +import { getAllToolSchemas, executeTool, executeToolWithProgress } from '../tools/index.js'; import type { ToolSchema, ToolResult } from '../types/tool.js'; const SERVER_NAME = 'ccw-tools'; @@ -104,7 +104,19 @@ function createServer(): Server { } try { - const result: ToolResult = await executeTool(name, args || {}); + // For smart_search init action, use progress-aware execution + const isInitAction = name === 'smart_search' && args?.action === 'init'; + + let result: ToolResult; + if (isInitAction) { + // Execute with progress callback that writes to stderr + result = await executeToolWithProgress(name, args || {}, (progress) => { + // Output progress to stderr (visible in terminal, doesn't interfere with JSON-RPC) + console.error(`[Progress] ${progress.percent}% - ${progress.message}`); + }); + } else { + result = await executeTool(name, args || {}); + } if (!result.success) { return { diff --git a/ccw/src/tools/index.ts b/ccw/src/tools/index.ts index a1828ab5..0d86284a 100644 --- a/ccw/src/tools/index.ts +++ b/ccw/src/tools/index.ts @@ -18,8 +18,10 @@ import * as convertTokensToCssMod from './convert-tokens-to-css.js'; import * as sessionManagerMod from './session-manager.js'; import * as cliExecutorMod from './cli-executor.js'; import * as smartSearchMod from './smart-search.js'; +import { executeInitWithProgress } from './smart-search.js'; // codex_lens removed - functionality integrated into smart_search import * as readFileMod from './read-file.js'; +import type { ProgressInfo } from './codex-lens.js'; // Import legacy JS tools import { uiGeneratePreviewTool } from './ui-generate-preview.js'; @@ -260,6 +262,60 @@ function sanitizeResult(result: unknown): unknown { return result; } +/** + * Execute a tool with progress callback (for init actions) + */ +export async function executeToolWithProgress( + name: string, + params: Record = {}, + onProgress?: (progress: ProgressInfo) => void +): Promise<{ + success: boolean; + result?: unknown; + error?: string; +}> { + // For smart_search init, use special progress-aware execution + if (name === 'smart_search' && params.action === 'init') { + try { + // Notify dashboard - execution started + notifyDashboard({ + toolName: name, + status: 'started', + params: sanitizeParams(params) + }); + + const result = await executeInitWithProgress(params, onProgress); + + // Notify dashboard - execution completed + notifyDashboard({ + toolName: name, + status: 'completed', + result: sanitizeResult(result) + }); + + return { + success: result.success, + result, + error: result.error + }; + } catch (error) { + notifyDashboard({ + toolName: name, + status: 'failed', + error: (error as Error).message || 'Tool execution failed' + }); + + return { + success: false, + error: (error as Error).message || 'Tool execution failed' + }; + } + } + + // Fall back to regular execution for other tools + return executeTool(name, params); +} + /** * Get tool schema in MCP-compatible format */ diff --git a/ccw/src/tools/smart-search.ts b/ccw/src/tools/smart-search.ts index ba0ff335..b2301d0e 100644 --- a/ccw/src/tools/smart-search.ts +++ b/ccw/src/tools/smart-search.ts @@ -1138,3 +1138,76 @@ export async function handler(params: Record): Promise, + onProgress?: (progress: ProgressInfo) => void +): Promise { + const path = (params.path as string) || '.'; + const languages = params.languages as string[] | undefined; + + // Check CodexLens availability + const readyStatus = await ensureCodexLensReady(); + if (!readyStatus.ready) { + return { + success: false, + error: `CodexLens not available: ${readyStatus.error}. CodexLens will be auto-installed on first use.`, + }; + } + + const args = ['init', path]; + if (languages && languages.length > 0) { + args.push('--languages', languages.join(',')); + } + + // Track progress updates + const progressUpdates: ProgressInfo[] = []; + let lastProgress: ProgressInfo | null = null; + + const result = await executeCodexLens(args, { + cwd: path, + timeout: 300000, + onProgress: (progress: ProgressInfo) => { + progressUpdates.push(progress); + lastProgress = progress; + // Call external progress callback if provided + if (onProgress) { + onProgress(progress); + } + }, + }); + + // Build metadata with progress info + const metadata: SearchMetadata = { + action: 'init', + path, + }; + + if (lastProgress !== null) { + const p = lastProgress as ProgressInfo; + metadata.progress = { + stage: p.stage, + message: p.message, + percent: p.percent, + filesProcessed: p.filesProcessed, + totalFiles: p.totalFiles, + }; + } + + if (progressUpdates.length > 0) { + metadata.progressHistory = progressUpdates.slice(-5); + } + + return { + success: result.success, + error: result.error, + message: result.success + ? `CodexLens index created successfully for ${path}` + : undefined, + metadata, + }; +}