Skip to content

Commit 1cd96b9

Browse files
author
catlog22
committed
chore: bump version to 7.2.7
- Enhance smart-search with advanced MCP integration - Add GEMINI_API_KEY configuration support in codexlens - Update MCP server with new tool handlers - Add tests for smart-search MCP usage - Update documentation
1 parent efbbaff commit 1cd96b9

16 files changed

Lines changed: 1215 additions & 111 deletions

File tree

ccw/frontend/src/components/codexlens/SettingsTab.test.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const mockConfig: CodexLensConfig = {
6161
const mockEnv: Record<string, string> = {
6262
CODEXLENS_EMBEDDING_BACKEND: 'local',
6363
CODEXLENS_EMBEDDING_MODEL: 'fast',
64+
CODEXLENS_AUTO_EMBED_MISSING: 'true',
6465
CODEXLENS_USE_GPU: 'true',
6566
CODEXLENS_RERANKER_ENABLED: 'true',
6667
CODEXLENS_RERANKER_BACKEND: 'onnx',
@@ -141,6 +142,7 @@ describe('SettingsTab', () => {
141142
expect(screen.getByText(/Concurrency/i)).toBeInTheDocument();
142143
expect(screen.getByText(/Cascade/i)).toBeInTheDocument();
143144
expect(screen.getByText(/Chunking/i)).toBeInTheDocument();
145+
expect(screen.getByText(/Auto Build Missing Vectors/i)).toBeInTheDocument();
144146
});
145147

146148
it('should initialize index dir from config', () => {

ccw/frontend/src/components/codexlens/envVarSchema.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ export const envVarGroupsSchema: EnvVarGroupsSchema = {
5656
},
5757
],
5858
},
59+
CODEXLENS_AUTO_EMBED_MISSING: {
60+
key: 'CODEXLENS_AUTO_EMBED_MISSING',
61+
labelKey: 'codexlens.envField.autoEmbedMissing',
62+
type: 'checkbox',
63+
default: 'true',
64+
settingsPath: 'embedding.auto_embed_missing',
65+
},
5966
CODEXLENS_USE_GPU: {
6067
key: 'CODEXLENS_USE_GPU',
6168
labelKey: 'codexlens.envField.useGpu',

ccw/frontend/src/components/mcp/McpServerDialog.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,8 +256,22 @@ export function McpServerDialog({
256256
// Parse JSON config and populate form
257257
const parseJsonConfig = useCallback(() => {
258258
try {
259-
const config = JSON.parse(jsonInput);
260-
259+
let config = JSON.parse(jsonInput);
260+
let extractedServerName = '';
261+
262+
// Auto-detect mcpServers wrapper format (Claude Code config format)
263+
// Supports both: { "mcpServers": { "name": {...} } } and direct { "command": ... }
264+
if (config.mcpServers && typeof config.mcpServers === 'object' && !Array.isArray(config.mcpServers)) {
265+
const serverNames = Object.keys(config.mcpServers);
266+
if (serverNames.length > 0) {
267+
extractedServerName = serverNames[0];
268+
const serverConfig = config.mcpServers[extractedServerName];
269+
if (serverConfig && typeof serverConfig === 'object') {
270+
config = serverConfig;
271+
}
272+
}
273+
}
274+
261275
// Detect transport type based on config structure
262276
if (config.url) {
263277
// HTTP transport
@@ -278,6 +292,7 @@ export function McpServerDialog({
278292

279293
setFormData(prev => ({
280294
...prev,
295+
name: extractedServerName || prev.name,
281296
url: config.url || '',
282297
headers,
283298
bearerTokenEnvVar: config.bearer_token_env_var || config.bearerTokenEnvVar || '',
@@ -291,6 +306,7 @@ export function McpServerDialog({
291306

292307
setFormData(prev => ({
293308
...prev,
309+
name: extractedServerName || prev.name,
294310
command: config.command || '',
295311
args,
296312
env,

ccw/frontend/src/locales/en/codexlens.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@
298298
"envField": {
299299
"backend": "Backend",
300300
"model": "Model",
301+
"autoEmbedMissing": "Auto Build Missing Vectors",
301302
"useGpu": "Use GPU",
302303
"highAvailability": "High Availability",
303304
"loadBalanceStrategy": "Load Balance Strategy",

ccw/frontend/src/locales/zh/codexlens.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,7 @@
298298
"envField": {
299299
"backend": "后端",
300300
"model": "模型",
301+
"autoEmbedMissing": "缺失向量时自动构建",
301302
"useGpu": "使用 GPU",
302303
"highAvailability": "高可用",
303304
"loadBalanceStrategy": "负载均衡策略",

ccw/src/core/routes/codexlens/config-handlers.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -955,6 +955,9 @@ export async function handleCodexLensConfigRoutes(ctx: RouteContext): Promise<bo
955955
if (settings.embedding?.use_gpu !== undefined) {
956956
settingsDefaults['CODEXLENS_USE_GPU'] = String(settings.embedding.use_gpu);
957957
}
958+
if (settings.embedding?.auto_embed_missing !== undefined) {
959+
settingsDefaults['CODEXLENS_AUTO_EMBED_MISSING'] = String(settings.embedding.auto_embed_missing);
960+
}
958961
if (settings.embedding?.strategy) {
959962
settingsDefaults['CODEXLENS_EMBEDDING_STRATEGY'] = settings.embedding.strategy;
960963
}
@@ -1219,6 +1222,7 @@ export async function handleCodexLensConfigRoutes(ctx: RouteContext): Promise<bo
12191222
'CODEXLENS_EMBEDDING_BACKEND': { path: ['embedding', 'backend'] },
12201223
'CODEXLENS_EMBEDDING_MODEL': { path: ['embedding', 'model'] },
12211224
'CODEXLENS_USE_GPU': { path: ['embedding', 'use_gpu'], transform: v => v === 'true' },
1225+
'CODEXLENS_AUTO_EMBED_MISSING': { path: ['embedding', 'auto_embed_missing'], transform: v => v === 'true' },
12221226
'CODEXLENS_EMBEDDING_STRATEGY': { path: ['embedding', 'strategy'] },
12231227
'CODEXLENS_EMBEDDING_COOLDOWN': { path: ['embedding', 'cooldown'], transform: v => parseFloat(v) },
12241228
'CODEXLENS_RERANKER_BACKEND': { path: ['reranker', 'backend'] },

ccw/src/mcp-server/index.ts

Lines changed: 99 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const SERVER_VERSION = '6.2.0';
2020
// Environment variable names for documentation
2121
const ENV_PROJECT_ROOT = 'CCW_PROJECT_ROOT';
2222
const ENV_ALLOWED_DIRS = 'CCW_ALLOWED_DIRS';
23+
const STDIO_DISCONNECT_ERROR_CODES = new Set(['EPIPE', 'ERR_STREAM_DESTROYED']);
2324

2425
// Default enabled tools (core set - file operations, core memory, and smart search)
2526
const DEFAULT_TOOLS: string[] = ['write_file', 'edit_file', 'read_file', 'read_many_files', 'read_outline', 'core_memory', 'smart_search'];
@@ -67,6 +68,47 @@ function formatToolResult(result: unknown): string {
6768
return String(result);
6869
}
6970

71+
/**
72+
* Detect broken stdio pipes so orphaned MCP processes can terminate cleanly.
73+
*/
74+
function isStdioDisconnectError(error: unknown): error is NodeJS.ErrnoException {
75+
if (error && typeof error === 'object') {
76+
const maybeErrnoError = error as NodeJS.ErrnoException;
77+
if (typeof maybeErrnoError.code === 'string' && STDIO_DISCONNECT_ERROR_CODES.has(maybeErrnoError.code)) {
78+
return true;
79+
}
80+
}
81+
82+
return error instanceof Error && /broken pipe/i.test(error.message);
83+
}
84+
85+
/**
86+
* Best-effort logging for teardown paths where stderr may already be gone.
87+
*/
88+
function safeStderrWrite(message: string): void {
89+
try {
90+
if (process.stderr.destroyed || !process.stderr.writable) {
91+
return;
92+
}
93+
94+
process.stderr.write(`${message}\n`);
95+
} catch {
96+
// Ignore logging failures while stdio is tearing down.
97+
}
98+
}
99+
100+
function safeLogError(prefix: string, error: unknown): void {
101+
if (error instanceof Error) {
102+
safeStderrWrite(`${prefix}: ${error.message}`);
103+
if (error.stack) {
104+
safeStderrWrite(error.stack);
105+
}
106+
return;
107+
}
108+
109+
safeStderrWrite(`${prefix}: ${String(error)}`);
110+
}
111+
70112
/**
71113
* Create and configure the MCP server
72114
*/
@@ -151,28 +193,77 @@ function createServer(): Server {
151193
async function main(): Promise<void> {
152194
const server = createServer();
153195
const transport = new StdioServerTransport();
196+
let shutdownPromise: Promise<void> | null = null;
197+
198+
const shutdown = (reason: string, exitCode = 0, error?: unknown): Promise<void> => {
199+
if (shutdownPromise) {
200+
return shutdownPromise;
201+
}
202+
203+
if (error && !isStdioDisconnectError(error)) {
204+
safeLogError(`[${SERVER_NAME}] ${reason}`, error);
205+
}
206+
207+
shutdownPromise = (async () => {
208+
try {
209+
await server.close();
210+
} catch (closeError) {
211+
if (!isStdioDisconnectError(closeError)) {
212+
safeLogError(`[${SERVER_NAME}] Failed to close server`, closeError);
213+
}
214+
}
215+
216+
process.exit(exitCode);
217+
})();
218+
219+
return shutdownPromise;
220+
};
221+
222+
const handleStreamClose = (streamName: string) => () => {
223+
void shutdown(`${streamName} disconnected`);
224+
};
225+
226+
const handleStreamError = (streamName: string) => (error: unknown) => {
227+
const exitCode = isStdioDisconnectError(error) ? 0 : 1;
228+
void shutdown(`${streamName} stream error`, exitCode, error);
229+
};
154230

155231
// Connect server to transport
156232
await server.connect(transport);
157233

158-
// Error handling - prevent process crashes from closing transport
234+
process.stdin.once('end', handleStreamClose('stdin'));
235+
process.stdin.once('close', handleStreamClose('stdin'));
236+
process.stdin.once('error', handleStreamError('stdin'));
237+
process.stdout.once('close', handleStreamClose('stdout'));
238+
process.stdout.once('error', handleStreamError('stdout'));
239+
process.stderr.once('close', handleStreamClose('stderr'));
240+
process.stderr.once('error', handleStreamError('stderr'));
241+
242+
// Error handling - stdio disconnects should terminate, other errors stay logged.
159243
process.on('uncaughtException', (error) => {
160-
console.error(`[${SERVER_NAME}] Uncaught exception:`, error.message);
161-
console.error(error.stack);
244+
if (isStdioDisconnectError(error)) {
245+
void shutdown('Uncaught stdio disconnect', 0, error);
246+
return;
247+
}
248+
249+
safeLogError(`[${SERVER_NAME}] Uncaught exception`, error);
162250
});
163251

164252
process.on('unhandledRejection', (reason) => {
165-
console.error(`[${SERVER_NAME}] Unhandled rejection:`, reason);
253+
if (isStdioDisconnectError(reason)) {
254+
void shutdown('Unhandled stdio disconnect', 0, reason);
255+
return;
256+
}
257+
258+
safeLogError(`[${SERVER_NAME}] Unhandled rejection`, reason);
166259
});
167260

168261
process.on('SIGINT', async () => {
169-
await server.close();
170-
process.exit(0);
262+
await shutdown('Received SIGINT');
171263
});
172264

173265
process.on('SIGTERM', async () => {
174-
await server.close();
175-
process.exit(0);
266+
await shutdown('Received SIGTERM');
176267
});
177268

178269
// Log server start (to stderr to not interfere with stdio protocol)

0 commit comments

Comments
 (0)