feat: 实现 MCP 工具集中式路径验证,增强安全性和可配置性

- 新增 path-validator.ts:参考 MCP filesystem 服务器设计的集中式路径验证器
  - 支持 CCW_PROJECT_ROOT 和 CCW_ALLOWED_DIRS 环境变量配置
  - 多层路径验证:绝对路径解析 → 沙箱检查 → 符号链接验证
  - 向后兼容:未设置环境变量时回退到 process.cwd()

- 更新所有 MCP 工具使用集中式路径验证:
  - write-file.ts: 使用 validatePath()
  - edit-file.ts: 使用 validatePath({ mustExist: true })
  - read-file.ts: 使用 validatePath() + getProjectRoot()
  - smart-search.ts: 使用 getProjectRoot()
  - core-memory.ts: 使用 getProjectRoot()

- MCP 服务器启动时输出项目根目录和允许目录信息

- MCP 管理界面增强:
  - CCW Tools 安装卡片新增路径设置 UI
  - 支持 CCW_PROJECT_ROOT 和 CCW_ALLOWED_DIRS 配置
  - 添加"使用当前项目"快捷按钮
  - 支持 Claude 和 Codex 两种模式
  - 添加中英文国际化翻译

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
catlog22
2025-12-21 18:14:06 +08:00
parent f492f4839a
commit 45f92fe066
11 changed files with 330 additions and 16 deletions

View File

@@ -15,6 +15,7 @@ import { z } from 'zod';
import type { ToolSchema, ToolResult } from '../types/tool.js';
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
import { resolve, isAbsolute, dirname } from 'path';
import { validatePath } from '../utils/path-validator.js';
// Define Zod schemas for validation
const EditItemSchema = z.object({
@@ -71,8 +72,8 @@ interface LineModeResult {
* @param filePath - Path to file
* @returns Resolved path and content
*/
function readFile(filePath: string): { resolvedPath: string; content: string } {
const resolvedPath = isAbsolute(filePath) ? filePath : resolve(process.cwd(), filePath);
async function readFile(filePath: string): Promise<{ resolvedPath: string; content: string }> {
const resolvedPath = await validatePath(filePath, { mustExist: true });
if (!existsSync(resolvedPath)) {
throw new Error(`File not found: ${resolvedPath}`);
@@ -524,7 +525,7 @@ export async function handler(params: Record<string, unknown>): Promise<ToolResu
const { path: filePath, mode = 'update', dryRun = false } = parsed.data;
try {
const { resolvedPath, content } = readFile(filePath);
const { resolvedPath, content } = await readFile(filePath);
let result: UpdateModeResult | LineModeResult;
switch (mode) {