mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +08:00
feat: Add real-time progress output for MCP init action
- 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 <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,7 @@ import {
|
|||||||
CallToolRequestSchema,
|
CallToolRequestSchema,
|
||||||
ListToolsRequestSchema,
|
ListToolsRequestSchema,
|
||||||
} from '@modelcontextprotocol/sdk/types.js';
|
} 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';
|
import type { ToolSchema, ToolResult } from '../types/tool.js';
|
||||||
|
|
||||||
const SERVER_NAME = 'ccw-tools';
|
const SERVER_NAME = 'ccw-tools';
|
||||||
@@ -104,7 +104,19 @@ function createServer(): Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
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) {
|
if (!result.success) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -18,8 +18,10 @@ import * as convertTokensToCssMod from './convert-tokens-to-css.js';
|
|||||||
import * as sessionManagerMod from './session-manager.js';
|
import * as sessionManagerMod from './session-manager.js';
|
||||||
import * as cliExecutorMod from './cli-executor.js';
|
import * as cliExecutorMod from './cli-executor.js';
|
||||||
import * as smartSearchMod from './smart-search.js';
|
import * as smartSearchMod from './smart-search.js';
|
||||||
|
import { executeInitWithProgress } from './smart-search.js';
|
||||||
// codex_lens removed - functionality integrated into smart_search
|
// codex_lens removed - functionality integrated into smart_search
|
||||||
import * as readFileMod from './read-file.js';
|
import * as readFileMod from './read-file.js';
|
||||||
|
import type { ProgressInfo } from './codex-lens.js';
|
||||||
|
|
||||||
// Import legacy JS tools
|
// Import legacy JS tools
|
||||||
import { uiGeneratePreviewTool } from './ui-generate-preview.js';
|
import { uiGeneratePreviewTool } from './ui-generate-preview.js';
|
||||||
@@ -260,6 +262,60 @@ function sanitizeResult(result: unknown): unknown {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a tool with progress callback (for init actions)
|
||||||
|
*/
|
||||||
|
export async function executeToolWithProgress(
|
||||||
|
name: string,
|
||||||
|
params: Record<string, unknown> = {},
|
||||||
|
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
|
* Get tool schema in MCP-compatible format
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1138,3 +1138,76 @@ export async function handler(params: Record<string, unknown>): Promise<ToolResu
|
|||||||
return { success: false, error: (error as Error).message };
|
return { success: false, error: (error as Error).message };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute init action with external progress callback
|
||||||
|
* Used by MCP server for streaming progress
|
||||||
|
*/
|
||||||
|
export async function executeInitWithProgress(
|
||||||
|
params: Record<string, unknown>,
|
||||||
|
onProgress?: (progress: ProgressInfo) => void
|
||||||
|
): Promise<SearchResult> {
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user