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

@@ -927,9 +927,28 @@ function selectCcwTools(type) {
});
}
// Get CCW path settings from input fields
function getCcwPathConfig() {
const projectRootInput = document.querySelector('.ccw-project-root-input');
const allowedDirsInput = document.querySelector('.ccw-allowed-dirs-input');
return {
projectRoot: projectRootInput?.value || '',
allowedDirs: allowedDirsInput?.value || ''
};
}
// Set CCW_PROJECT_ROOT to current project path
function setCcwProjectRootToCurrent() {
const input = document.querySelector('.ccw-project-root-input');
if (input && projectPath) {
input.value = projectPath;
}
}
// Build CCW Tools config with selected tools
// Uses isWindowsPlatform from state.js to generate platform-appropriate commands
function buildCcwToolsConfig(selectedTools) {
function buildCcwToolsConfig(selectedTools, pathConfig = {}) {
const { projectRoot, allowedDirs } = pathConfig;
// Windows requires 'cmd /c' wrapper to execute npx
// Other platforms (macOS, Linux) can run npx directly
const config = isWindowsPlatform
@@ -948,12 +967,30 @@ function buildCcwToolsConfig(selectedTools) {
coreTools.every(t => selectedTools.includes(t)) &&
selectedTools.every(t => coreTools.includes(t));
// Initialize env if needed
if (selectedTools.length === 15) {
config.env = { CCW_ENABLED_TOOLS: 'all' };
} else if (!isDefault && selectedTools.length > 0) {
config.env = { CCW_ENABLED_TOOLS: selectedTools.join(',') };
}
// Add path settings if provided
if (!config.env) {
config.env = {};
}
if (projectRoot && projectRoot.trim()) {
config.env.CCW_PROJECT_ROOT = projectRoot.trim();
}
if (allowedDirs && allowedDirs.trim()) {
config.env.CCW_ALLOWED_DIRS = allowedDirs.trim();
}
// Remove env object if empty
if (config.env && Object.keys(config.env).length === 0) {
delete config.env;
}
return config;
}
@@ -965,7 +1002,8 @@ async function installCcwToolsMcp(scope = 'workspace') {
return;
}
const ccwToolsConfig = buildCcwToolsConfig(selectedTools);
const pathConfig = getCcwPathConfig();
const ccwToolsConfig = buildCcwToolsConfig(selectedTools, pathConfig);
try {
const scopeLabel = scope === 'global' ? 'globally' : 'to workspace';
@@ -1032,7 +1070,8 @@ async function updateCcwToolsMcp(scope = 'workspace') {
return;
}
const ccwToolsConfig = buildCcwToolsConfig(selectedTools);
const pathConfig = getCcwPathConfig();
const ccwToolsConfig = buildCcwToolsConfig(selectedTools, pathConfig);
try {
const scopeLabel = scope === 'global' ? 'globally' : 'in workspace';
@@ -1126,7 +1165,8 @@ async function installCcwToolsMcpToCodex() {
return;
}
const ccwToolsConfig = buildCcwToolsConfig(selectedTools);
const pathConfig = getCcwPathConfig();
const ccwToolsConfig = buildCcwToolsConfig(selectedTools, pathConfig);
try {
const isUpdate = codexMcpServers && codexMcpServers['ccw-tools'];
@@ -1176,3 +1216,4 @@ window.openMcpCreateModal = openMcpCreateModal;
window.toggleProjectConfigType = toggleProjectConfigType;
window.getPreferredProjectConfigType = getPreferredProjectConfigType;
window.setPreferredProjectConfigType = setPreferredProjectConfigType;
window.setCcwProjectRootToCurrent = setCcwProjectRootToCurrent;