Code Editor MCP Server
## code-editor
面向多客户端并发、安全沙箱、编码感知的代码编辑 MCP 服务器。
> 路径访问更新:**所有参数必须是绝对路径**。路径白名单已移除,任意绝对路径均可访问;`CODE_EDIT_ROOT` 仅作为删除安全标记,不做访问边界或相对路径解析。每个工具可通过环境变量独立开关,关闭后该工具不会被注册,客户端与智能体完全不可见、不可调用。
### 安装与运行
```bash
# 安装(pip 或 uv 均可,包名 code-editor-mcp)
pip install code-editor-mcp # 或 uv pip install code-editor-mcp
# 更新最新版
pip install -U code-editor-mcp
# 直接启动 CLI 入口
code-editor
# 若从源码运行
uv sync
uv run python server.py
```
关键环境变量:
- `CODE_EDIT_ROOT`:安全标记(默认启动时的 CWD),仅用于删除安全校验,不做访问边界,也不参与相对路径解析。
- `CODE_EDIT_TOOL_ENABLED_<TOOL>`:工具开关(环境变量方式),`1/true/yes` 启用(默认),`0/false/no` 禁用。禁用后该工具不注册,客户端与智能体完全看不到。
- `CODE_EDIT_TOOL_SWITCHES_FILE`:工具开关配置文件路径(默认 `tools/tool_switches.json`)。
### 工具开关配置(推荐)
本地使用推荐直接编辑 `tools/tool_switches.json`,无需设置环境变量:
```json
{
"config_ops": false,
"read_files": false,
"dir_ops": false,
"file_ops": false,
"edit_blocks": true,
"convert_file_encoding": false
}
```
- 值为 `true` 启用、`false` 禁用;未列出的工具默认启用。
- 优先级:**配置文件 > 环境变量 > 默认启用**。
- 修改后重启 MCP 服务生效。
环境变量一览:
| 变量 | 作用 | 默认值 |
| --- | --- | --- |
| `CODE_EDIT_ROOT` | 安全标记(非访问边界,不做路径解析) | 当前工作目录 |
| `CODE_EDIT_TOOL_ENABLED_CONFIG_OPS` | 开关 `config_ops` 工具 | 启用 |
| `CODE_EDIT_TOOL_ENABLED_READ_FILES` | 开关 `read_files` 工具 | 启用 |
| `CODE_EDIT_TOOL_ENABLED_DIR_OPS` | 开关 `dir_ops` 工具 | 启用 |
| `CODE_EDIT_TOOL_ENABLED_FILE_OPS` | 开关 `file_ops` 工具 | 启用 |
| `CODE_EDIT_TOOL_ENABLED_EDIT_BLOCKS` | 开关 `edit_blocks` 工具 | 启用 |
| `CODE_EDIT_TOOL_ENABLED_CONVERT_FILE_ENCODING` | 开关 `convert_file_encoding` 工具 | 启用 |
| `CODE_EDIT_FILE_READ_LINE_LIMIT` | (legacy)内部 `read_file` 默认行数上限;`read_files` v2 使用 `page_line_count/max_lines_per_snippet` 控制输出 | 1000 |
| `CODE_EDIT_FILE_WRITE_LINE_LIMIT` | `file_ops` 写入行数警戒 | 50 |
### 设计要点
- 路径访问:白名单已移除,任意绝对路径均可访问;相对路径仍被拒绝。`CODE_EDIT_ROOT` 仅作删除安全标记,不做路径拼接。
- 工具开关:每个工具可通过 `CODE_EDIT_TOOL_ENABLED_<TOOL>` 独立禁用;禁用后不注册,客户端与智能体完全不可见、不可调用。
- 乐观锁:写/删类支持秒或纳秒级 `expected_mtime`,10ms 容忍;写入走原子写避免部分落盘。
- 默认忽略:`.git`、`__pycache__`、`node_modules`、`.DS_Store`、`.env*`、`.venv`、`*.log`、`*.pem`;`ignore_patterns` 传空字符串/空列表可关闭默认忽略。
- 编码感知:`read_files` v2 默认逐文件 `auto` 检测/复用编码(基于最新 mtime 刷新的元信息缓存);如自动结果乱码,可在每个 request 里显式传 `encoding` 覆盖。
- 安全删除:禁止删除当前根/其祖先/关键系统目录。
### MCP 工具(code-editor)
#### 文件系统工具
| 名称 | 工具 | 功能 | 主要参数/说明 | 常见误用 |
| --- | --- | --- | --- | --- |
| `config_ops` | `config_ops(action, path=None)` | 元信息查询 | `action`=`get_info`;`path` 为文件/目录绝对路径 | 拼错 action;误以为还有 set_root/list_roots |
| `read_files` | `read_files(requests, default_encoding="auto", page_line_count=400, max_snippets=64, max_total_chars=200000, max_chars_per_snippet=50000, max_lines_per_snippet=2000, parallelism=4)` | LLM 友好读取:多文件/同文件多片段 + 分页 | `requests` 为 `list[dict]`:支持 `mode`=`range`/`tail`/`all`;行号 **1-based**;可用 `cursor` 继续分页;返回结构化 `results[]`,每项含 `content`(纯内容)、`start_line/end_line`、`core_*`、`encoding`、`sha256_utf8`、可选 `next_cursor`;`encoding` 支持 `auto/utf-8/gbk/gb2312/gb18030`;任意绝对路径均可读取 | 继续传旧的 `file_paths`;把 `start_line` 当 0-based;一次请求太多片段触发 `max_total_chars`;对图片用 range/tail |
| `dir_ops` | `dir_ops(action, dir_path, depth=2, format="tree"|"flat", ignore_patterns=None, max_items=1000, expected_mtime=None, confirm_token=None, allow_nonempty=None, approval_token=None)` | 统一目录操作 | `action`=create/list/delete;绝对路径;list: tree 返回字符串列表、flat 返回字典列表;`ignore_patterns` 为 None 用默认忽略,空字符串/空列表关闭默认忽略;flat 下 `max_items` 限制返回条数;delete: 必须提供 `expected_mtime`、`confirm_token`、`allow_nonempty`,`confirm_token`=`delete:<normalized_abs_path>`(Path.resolve + os.path.normcase);高风险删除会返回结构化 `needs_approval` 并要求 `approval_token` 二次提交 | action 不支持/参数缺失;delete 未显式 allow_nonempty;confirm_token 不匹配 |
| `file_ops` | `file_ops(action, file_path=None, content=None, source_path=None, destination_path=None, expected_mtime=None, encoding="utf-8", approval_token=None)` | 综合文件操作:write/append/copy/move/delete | 所有路径必须绝对;write 覆盖、append 追加;write/append 需 file_path+content;copy/move 需 source_path+destination_path;delete 需 file_path;encoding 仅写入使用;expected_mtime:写/删校验目标文件,拷贝/移动校验源文件;删除返回结构化结果 | action 不支持或参数缺失;copy 目标已存在;delete 目标是目录 |
| `convert_file_encoding` | `convert_file_encoding(file_paths, source_encoding, target_encoding, error_handling="strict", mismatch_policy="warn-skip")` | 批量转码并覆盖写回 | 绝对路径列表;utf-8/gbk/gb2312/gb18030;错误处理 strict/replace/ignore;编码检测( charset-normalizer ),策略 fail-fast / warn-skip(默认) / force;结果返回 detectedEncoding/Confidence/mismatch;内置别名兼容 utf8/utf_8/utf-8-sig/ascii/cp936/gb-2312/gb-18030 | 相对路径;二进制文件 |
#### dir_ops 参数要点(避免误调用)
- `create`:仅 `dir_path` 必须;其余参数会被忽略。
- `list`:`format` 仅支持 `tree`/`flat`;`max_items` 必须为正整数或 None。
- `delete`:必须同时提供 `expected_mtime`、`confirm_token`、`allow_nonempty`(显式 True/False)。
- 删除行为统一移动到回收站,不支持永久删除。
- 命中高风险规则时会返回 `status=needs_approval`,响应内包含 `approval_token` 与 `llm_instruction`。
- 盘符根目录(如 `C:\` / `D:\` / `E:\`)会被永久阻断并返回 `status=blocked_critical`。
- `confirm_token` 生成规则(严格匹配):
```python
from pathlib import Path
import os
normalized = os.path.normcase(str(Path(dir_path).resolve()))
confirm_token = f"delete:{normalized}"
```
#### 代码精准编辑工具
| 名称 | 工具 | 功能 | 主要参数/说明 | 常见误用 |
| --- | --- | --- | --- | --- |
| `edit_blocks` | `edit_blocks(edits, error_policy="fail-fast", encoding="auto")` | 单编辑/批量编辑:搜索替换 | `edits` 支持 `dict`(单编辑,支持大文件)或 `list[dict]`(批量,支持同文件多处与多文件);每个 edit 必须有 `file_path`(注意不是 `path`);支持 `encoding="auto"` 自动检测;批量模式支持 `error_policy`: fail-fast/continue/rollback | 把 `read_files` 的 `path` 当成 `file_path`;传了不支持的编码;批量模式传空列表;大文件用批量模式(应改为单编辑 dict) |
#### edit_blocks 返回格式(v0.3.0+)
`edit_blocks` 返回结构化数据,包含修改位置信息便于验证:
```python
# edit_blocks(单编辑)返回示例
{
"status": "success",
"message": "Applied 2 edit(s) to /path/file.py (lines 10-12, 20-25)",
"file_path": "/path/file.py",
"replacements": 2,
"locations": [
{"start_line": 10, "end_line": 12, "start_col": 5, "end_col": 20},
{"start_line": 20, "end_line": 25, "start_col": 1, "end_col": 15}
]
}
# edit_blocks(批量)返回示例
{
"status": "success", # success/partial/error
"message": "Completed 3/3 edits",
"total_edits": 3,
"successful_edits": 3,
"failed_edits": 0,
"results": [
{"status": "success", "file_path": "...", "replacements": 1, "locations": [...], "message": "..."},
{"status": "success", "file_path": "...", "replacements": 1, "locations": [...], "message": "..."},
{"status": "success", "file_path": "...", "replacements": 1, "locations": [...], "message": "..."}
]
}
```
#### 使用示例
- 查询元信息:`config_ops(action="get_info", path="/abs/path/src/app.py")`。
- 带锁写入:`info = config_ops(action="get_info", path="/abs/path/src/app.py")` → `file_ops(action="write", file_path="/abs/path/src/app.py", content=content, expected_mtime=info["modified"])`。
- 精确替换(单编辑):`edit_blocks(edits={"file_path":"/abs/path/src/app.py","old_string":"old","new_string":"new","expected_replacements":1,"expected_mtime":info["modified"]})`。
- 单文件片段读取(range,行号 1-based):
```python
read_files([
{"path": "/abs/path/src/app.py", "mode": "range", "start_line": 10, "line_count": 60}
])
```
- 同文件多片段 + 跨文件(一次拿齐,减少多次调用):
```python
read_files([
{"path": "/abs/path/src/app.py", "mode": "range", "start_line": 1, "line_count": 80, "id": "app_head"},
{"path": "/abs/path/src/app.py", "mode": "range", "start_line": 200, "line_count": 80, "id": "app_mid"},
{"path": "/abs/path/README.md", "mode": "range", "start_line": 1, "line_count": 120, "id": "readme"},
], max_total_chars=200000, parallelism=4)
```
- 分页读取整文件(all + cursor):
```python
resp1 = read_files([{"path": "/abs/path/src/app.py", "mode": "all"}], page_line_count=400)
cursor = resp1["results"][0].get("next_cursor")
resp2 = read_files([{"cursor": cursor}], page_line_count=400) # 继续下一页
```
- 批量编辑(同文件多处修改):
```python
edit_blocks(edits=[
{"file_path": "/abs/app.py", "old_string": "foo", "new_string": "bar"},
{"file_path": "/abs/app.py", "old_string": "hello", "new_string": "world"},
], error_policy="rollback")
```
- 批量编辑(多文件):
```python
edit_blocks(edits=[
{"file_path": "/abs/a.py", "old_string": "v1", "new_string": "v2"},
{"file_path": "/abs/b.py", "old_string": "v1", "new_string": "v2"},
], error_policy="continue")
```
- 批量转码:`convert_file_encoding(["/abs/a.txt", "/abs/b.txt"], "gb2312", "utf-8", error_handling="replace", mismatch_policy="warn-skip")`。
- 列目录(扁平):`dir_ops(action="list", dir_path="/abs/path", format="flat", ignore_patterns=[".git", "node_modules"])`。
- 删除目录(显式确认,统一进回收站):`info = config_ops(action="get_info", path="/abs/path")` → `normalized = os.path.normcase(str(Path("/abs/path").resolve()))` → `token = f"delete:{normalized}"` → `dir_ops(action="delete", dir_path="/abs/path", expected_mtime=info["modified"], confirm_token=token, allow_nonempty=True)`。
- 高风险删除二次审批:首次调用 delete 可能返回 `status=needs_approval` + `approval.approval_token`,拿到用户明确同意后,带上 `approval_token` 再次调用同一 delete。
### MCP 客户端快速配置示例
```json
{
"mcpServers": {
"code-editor": {
"command": "code-editor",
"env": {
"CODE_EDIT_ROOT": "."
}
}
}
}
```
```toml
[mcp_servers.code-editor]
command = "code-editor"
# 将工作目录指向当前项目,使 CODE_EDIT_ROOT 默认跟随启动时的 CWD
cwd = "."
startup_timeout_sec = 120
```
### 安全/行为提示
- 路径验证:所有操作要求**绝对路径**;白名单已移除,任意绝对路径均可访问;`CODE_EDIT_ROOT` 仅为删除安全标记。
- 工具开关:通过 `CODE_EDIT_TOOL_ENABLED_<TOOL>` 可独立禁用任意工具;禁用后该工具不注册,客户端与智能体完全不可见、不可调用。
- 删除防护:删除只允许移动到回收站;任何失败都会直接拒绝(不会降级为永久删除)。
- 盘符根目录阻断:`C:\`、`D:\`、`E:\`(及其他文件系统根目录)会被永久阻断,并返回极高风险警告。
- 二次审批:仅“重要目录 + 大规模删除”触发;返回会强制提示模型先询问用户并等待批准。
TDQS
Scored across 10 tools
Most tools have distinct purposes, but edit_block and edit_blocks are very similar in function, differing only in whether they apply a single or multiple edits. This overlap could cause an agent to select the wrong one. Other tools like dir_ops and file_ops are clear.
Naming is a mix of verb_noun (convert_file_encoding, edit_block, read_file) and noun_verb (dir_ops, file_ops). The 'ops' suffix is not a verb, breaking the pattern. Also singular/plural vary (edit_block vs edit_blocks, read_file vs read_files) but are consistent in context.
10 tools is well-scoped for a code editor server, covering file reading/writing/editing, directory ops, encoding, and configuration. Each tool earns its place without bloat or deficiency.
The tool set covers most core code editing operations: read, write, edit, directory listing, encoding conversion, and root management. A notable gap is the lack of a content search across files, which would be useful for a code editor. Otherwise, the surface is solid.