MCP Terminal Controller
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@MCP Terminal Controller start the approved-job command and show me its output"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
MCP Terminal Controller
MCP Terminal Controller 是一个通过 MCP stdio 提供受限、异步 POSIX 进程会话的本地服务器。它使用官方 MCP SDK 2.3.0,要求 Python >= 3.11。客户端只能调用配置所有者(Owner)预先批准的 command_id;不能提交 argv、shell 命令、重定向或命令替换。
这是本地进程控制器,不是沙箱,不提供远程访问、Web/云端 UI,也不能在服务重启后恢复会话。
当前状态
项目版本:
0.1.0。默认 profile 和
demo_process.py启动的都是真实 OS 子进程,不是模拟器或内存假实现;它们仅用于可控的本地演示与测试。2026-10-22 实测:本机全量 83 passed,新目录/新虚拟环境复现 83 passed;标准 MCP 客户端独立完成 16 个场景,安装后的 console entry 也完成真实闭环。完整范围与证据见 ACCEPTANCE_REPORT.md。
Manus 初始连接已实测创建、交互输入、游标续读和终止;加载最终修复后的重连返回 transport closed,原因 UNKNOWN。最终版本以官方标准客户端验收,不声称 Manus 最终重载成功。
Related MCP server: ptywright
工作方式与边界
服务进程只使用标准输入/输出(
stdio)承载 MCP 协议。不得向服务的 stdout 写普通日志或业务文本;入口将日志写入 stderr。terminal_start接收固定 profile 的command_id,由 Owner 本地 JSON 配置映射为固定、真实的argv(其中可执行项argv[0]必须是绝对路径)。服务使用asyncio.create_subprocess_exec(),不启动 shell。每个会话分别连接子进程的
stdin、stdout、stderr。它们是 pipe,不是 PTY(伪终端):没有终端模拟、TTY 行编辑、窗口尺寸、控制序列解释或 job control 语义。交互程序可能因为isatty()为假而表现不同。输出以带
stream: "stdout" | "stderr"标签的记录返回。两个 pipe 的跨流到达顺序由调度决定;请勿假定 stdout 与 stderr 有全局行序。输出 buffer、会话记录和子进程状态都只在内存中保存。服务器重启、崩溃或被强制杀死后没有会话重新连接、历史回放或孤儿进程恢复机制。
安装
在项目根目录选择一种方式安装测试额外依赖:
uv sync --locked --extra test
source .venv/bin/activate或:
python -m pip install '.[test]'上述依赖声明包括 mcp==2.3.0;项目要求 Python >=3.11。仅支持具有 POSIX 进程组的系统;非 POSIX 平台会拒绝配置。
生成 Owner 本地配置
仓库提供 scripts/make_config.py,它调用 Config.defaults(),把 dataclass 字段导出为 JSON。生成内容中的工作目录、允许根目录、Python 可执行文件和演示脚本路径均为本机绝对路径,不含 ~ 或相对路径。
python scripts/make_config.py \
--root /绝对/允许/工作目录 \
--output /绝对/路径/mcp-terminal-controller.json选项:
选项 | 含义 |
| 必填;要写入的本地 Owner JSON 文件。父目录不存在时会创建。 |
| 可选;默认是运行脚本时的当前工作目录( |
该脚本只生成本机的 Owner 配置;它不能、也不会开放客户端任意命令执行。生成的默认 stream、interactive 等 profile 仍然是指向本地 demo_process.py 的固定真实子进程。生产 Owner 应审核并按需要替换为最小权限的固定 profile,而不是把用户输入拼进 argv。
配置格式与校验
启动时传入的 JSON 只能含 Config 的 dataclass 字段;未知字段会被拒绝。所有 allowed_roots 和 default_cwd 必须是存在的绝对目录,实际使用时会解析符号链接,并要求 cwd 位于至少一个允许根内。
{
"allowed_roots": ["/absolute/allowed/root"],
"default_cwd": "/absolute/allowed/root",
"commands": {
"approved-job": {
"argv": ["/absolute/path/to/executable", "--fixed-option"],
"allowed_env": ["SAFE_LABEL"]
}
},
"max_sessions": 8,
"max_records": 32,
"buffer_bytes": 1048576,
"buffer_records": 65536,
"default_timeout_ms": 30000,
"max_timeout_ms": 300000,
"retention_seconds": 300,
"max_input_bytes": 65536
}commands 中的每个 profile 必须有非空、固定的 argv,且 argv[0] 必须是绝对路径。command_id 最长 128 个字符;最多 128 个 profile。参数含义与合法范围如下。
字段 | 默认值 | 合法范围/规则 |
| 无 | 至少一个存在的目录;客户端 |
| 生成时的 | 存在且位于允许根内。 |
| 默认演示 profiles | 字典;每项为固定 |
| 8 | 1–64 个同时未结束会话。 |
| 32 | 1–1024 个保留会话记录;不得小于 |
| 1,048,576 | 4–16,777,216,单会话保留输出的 UTF-8 字节上限。 |
| 65,536 | 1–1,048,576,单会话保留输出记录上限。 |
| 30,000 | 100–3,600,000;不得大于 |
| 300,000 | 100–3,600,000。 |
| 300 | 1–86,400;最终状态会话记录的 TTL。 |
| 65,536 | 1–1,048,576;一次 |
子进程环境从固定的 PATH=/usr/bin:/bin、LANG=C.UTF-8、LC_ALL=C.UTF-8 开始,不会继承服务进程的 os.environ。客户端最多传 32 个环境变量,只有 profile allowed_env 中的名称才可覆写;名称还会拒绝 PYTHON*、LD_*、DYLD_*、PATH、HOME、SHELL、ENV、BASH_ENV、IFS、CDPATH 以及包含 TOKEN、SECRET、PASSWORD、CREDENTIAL、API_KEY、PRIVATE_KEY 的名称。另外拒绝 NODE_OPTIONS、PERL5OPT、RUBYOPT、GCONV_PATH、Java 启动选项等已知运行时注入变量。其他允许名称仍需 Owner 审核业务语义;黑名单不是通用安全保证。
启动与客户端配置
先激活已安装本项目的环境,生成或审核 Owner JSON,然后启动:
python -m mcp_terminal stdio --config /绝对/路径/mcp-terminal-controller.json省略 --config 会使用当前目录作为允许根的安全默认演示 profiles;生产使用建议显式提供 Owner 审核过的配置。stdio 是唯一 transport 参数。
以下是标准 mcpServers 配置形状示例。请用本机的绝对 Python 路径和配置路径替换占位符;示例不会、也不应修改用户现有客户端配置文件。
{
"mcpServers": {
"terminal-controller": {
"command": "/absolute/path/to/python",
"args": [
"-m",
"mcp_terminal",
"stdio",
"--config",
"/absolute/path/to/mcp-terminal-controller.json"
]
}
}
}客户端通过 MCP 的 tools/list 发现工具,并通过协议的 JSON 请求调用它们;不要把交互式 shell 文本写到服务 stdin。若客户端启动器需要环境变量,应由客户端自行管理,不要用它替代 Owner JSON 的命令批准策略。
五个 MCP 工具
所有应用错误均使用 {"ok": false, "error": {"code": "…", "message": "…"}} 的 MCP tool error 返回;原始 OS 异常文本不会返回给客户端。工具 调用入口拒绝未知参数,且整数为严格整数(布尔值和数字字符串不被当作整数接受)。
工具 | 参数(默认值) | 行为 |
|
| 启动 Owner 批准的固定 profile 并立即返回会话快照。没有 |
|
| 从绝对 cursor 非消费式读取输出。无新记录且会话仍运行时,可最多等待 |
|
| 将精确 UTF-8 文本写入运行中的子进程。需要换行时由客户端自行在 |
|
| 查询会话状态、真实退出码/信号、终止原因、输出可用性和 cursors。 |
|
| 幂等地终止该会话进程组:发送 |
启动、状态和终止快照包含 session_id、command_id、process_id、status、exit_code、exit_signal、termination_reason、stdin_closed、input_uncertain、base_cursor、next_cursor、created_at、last_activity_at、output_complete、output_incomplete。terminal_read 返回 records、base_cursor、next_cursor、truncated、has_more,以及当前 status 和输出完成标志。
推荐读取流程
调用
terminal_start,保存返回的session_id,初始读取 cursor 使用0,避免跳过启动期间已生成的输出。调用
terminal_read(session_id, cursor=上次 next_cursor);处理每一条{cursor, stream, text}记录。始终将下一次 cursor 更新为响应的
next_cursor。当has_more=true时立即继续读取;运行中无输出时可使用有限的wait_ms。用
terminal_status或 read 响应的output_complete判断尾部输出是否已完全排空。进程退出并不等于 pipe 尾部已经读取完毕。需要中止时调用
terminal_terminate;最终记录仍会在 TTL 内可读。
Cursor、Unicode 与截断恢复
Cursor 是绝对输出记录序号,不是字节偏移,也不是行号。buffer 将文本拆成 Python Unicode 码点,每个记录都保留 stdout 或 stderr 标签。max_bytes 约束所返回文本的 UTF-8 字节总量,不是 JSON 线长。每页还固定最多返回 1,024 条记录,达到上限时 has_more=true;它不改变 cursor 的计数单位。
buffer 达到 buffer_bytes 或 buffer_records 上限时,会淘汰最旧记录,并将 truncated 设为 true。如果客户端请求的 cursor 小于当前 base_cursor,服务返回 CURSOR_EXPIRED 和可恢复的 base_cursor,而不是静默丢失数据。此时表示已经出现不可见输出缺口;客户端应记录该缺口,将 cursor 移至错误中给出的 base_cursor,再继续读取。cursor 大于 next_cursor 则为 INVALID_CURSOR。
从子进程 pipe 读取时采用增量 UTF-8 解码;无效字节以替换字符表示。输出 reader 被取消、失败或在尾部排空期限内未完成时,output_incomplete=true,此时不要把已收到输出当作完整审计记录。
写入确认与背压
terminal_write 成功的 accepted_bytes 仅代表数据已被控制器接受并完成 pipe drain();它不是目标应用已经读取、解析或处理输入的确认。如需应用级确认,应由固定 profile 的协议通过 stdout/stderr 显式返回。
单次写入受 max_input_bytes 限制。若 drain 在 1 秒内无法完成,服务会关闭该会话 stdin 并返回 INPUT_BACKPRESSURE;此时字节可能已部分送达,不得盲目重试同一输入。进程关闭输入时会返回 STDIN_CLOSED;结束会话写入则为 SESSION_NOT_RUNNING。
写入排队后若请求被取消,已发送字节不能撤回;控制器关闭 stdin,并在状态中设置 input_uncertain=true。同一会话再次写入会被拒绝,客户端不得把取消视为“从未写入”,也不能跨新会话盲目重放。
资源配额、超时与生命周期
整个实例所有保留会话的 max_records × buffer_records 不得超过 2,097,152 条,max_records × buffer_bytes 不得超过 67,108,864 字节;超额配置被拒绝。这限制对象数与文本容量,不是操作系统 RSS/CPU 的硬隔离。默认 32 个保留会话。MCP 输入帧解析前限制为 1 MiB(含换行),实际 text/env 仍受更低的配置或字节限制。
同时运行会话达到
max_sessions时,新的启动返回SESSION_LIMIT。已保留会话(包括最终状态但尚未到 TTL 的会话)达到
max_records时,新的启动返回RECORD_LIMIT;服务不会为腾出空间而静默驱逐仍承诺保留的最终输出。会话到达
timeout_ms后,控制器按超时原因终止它;默认超时和最大可选超时由 Owner 配置决定。完成后,会话输出和状态保存
retention_seconds。TTL 到期后记录被移除,随后查询会得到SESSION_NOT_FOUND。TTL 是最终记录的保留时间,不是进程运行时限。最终状态为
EXITED、TERMINATED或FAILED;运行中可见RUNNING,终止过程中可见TERMINATING。自然退出的原因是natural;用户终止、超时、正常服务关闭分别会标为user、timeout、shutdown。
进程组清理与其边界
每个 profile 通过 start_new_session=True 启动,POSIX 会为它建立新 session/进程组(setsid 语义)。控制器以子进程 leader PID 为进程组 ID,使用 killpg() 向该组发信号:用户终止/超时/关闭时先 SIGTERM,等待所选宽限期后才 SIGKILL;若 leader 自然结束,仍会对其原组发送 SIGKILL,以避免已知同组后代继续持有 pipe 或存活。
这是有限的进程组清理,不是容器隔离:
只有仍在这个进程组内的后代会被处理。主动
setsid()、另建进程组、委托给守护进程/服务管理器或跨越其他权限边界的工作可能逃离,控制器不保证回收它们。SIGKILL不可捕获,目标应用没有机会执行优雅清理、刷新文件或发送自己的完成确认;因此可能产生不完整输出或外部副作用。无法向进程组发信号、退出无法在确认期限内完成或服务遭到非正常杀死时,不能承诺清理成功。服务正常关闭会尝试终止已知会话,但不存在重启后的恢复/补救。
如果业务需要强隔离、网络限制、文件系统隔离、资源控制或可恢复编排,请在本控制器之外使用操作系统账户、容器/VM、服务管理器和相应审计机制。
默认演示 profiles
默认配置包含以下固定 profile:stream、interactive、long、unicode、flood、exit7、empty、children、closed-stdin、split-utf8、invalid-utf8、eof、ignore-term。它们均由真实 Python 子进程运行 demo_process.py:例如 interactive 读取并回显输入,flood 产生足以触发 buffer 截断的输出,children 建立同进程组后代,ignore-term 用于验证 TERM→KILL 路径。它们不是生产命令批准清单。
测试与独立 MCP 验证
测试与验证脚本是两条独立命令,请分开运行。随包 evidence 保存已执行结果;这不替代你在目标环境的复现。
pytest
# 仅真实 wire-level 测试:pytest -m e2epytest 会发现 tests/。端到端测试会通过官方 MCP stdio client 启动新服务器子进程;其他测试还覆盖会话和输出 buffer 行为。
独立证据脚本不在进程内调用 SessionManager,而是使用官方 StdioServerParameters 和 ClientSession 启动实际 python -m mcp_terminal stdio,从 wire protocol 记录响应。示例:
python scripts/verify_mcp.py --output /tmp/mcp-evidence.json可用 --scenario 名称 重复选择单个或多个场景;省略时运行全部。--output 对验证脚本是必填项,并且无论成功或失败都会写出 JSON 证据;失败时脚本以非零状态退出。该脚本使用其自身路径定位项目,可从无关当前目录运行。运行前请确认依赖已安装,并自行检查 JSON 中的 success、failure、断言和调用记录。
安全说明
部署、配置所有权、威胁模型和漏洞报告方式见 SECURITY.md。最重要的原则是:仅让可信 Owner 写配置,仅批准确有必要的固定可执行文件与根目录,并将本服务视为在其运行用户权限下执行真实本地代码的接口。
SDK 兼容说明与来源
官方 SDK 2.3.0 的默认 stdio 文件读取在 stdin 保持打开时可能阻塞 SIGTERM 停机。本项目 transport.py 用 asyncio fd readiness 提供可取消的文件适配器,仍由官方 stdio_server 解析、序列化及处理协议。该模块集中使用 SDK _lowlevel_server 入口;另在不完整输出清理时使用 asyncio Process 的私有 transport 关闭句柄。它们是已记录的兼容风险,升级 SDK/Python 必须重跑真实协议与停机测试。支持 pipe,未实现 PTY、网络服务或重启恢复。
依赖包含 uv.lock 及带哈希的 requirements.lock / requirements-test.lock。需要 pip 哈希锁定时,在新虚拟环境中安装 python -m pip install --require-hashes -r requirements-test.lock,再执行 python -m pip install --no-deps .。不要把示例绝对路径直接复制到另一台机器;用 make_config.py 重新生成。
Available Tools
5 toolsterminal_readARead-onlyIdempotent
Read non-consuming output records starting at cursor, with a bounded optional wait.
Cursor is an absolute record sequence, not a byte index. Continue with next_cursor. Old cursors return CURSOR_EXPIRED with base_cursor, never silent loss. stdout/stderr retain stream labels. output_complete only becomes true after tail output is drained.
Inputs: session_id (required), cursor, wait_ms, max_bytes. Returns: object
terminal_startB
Start a real approved process and immediately return its persistent session ID.
Default profiles: stream, interactive, long, unicode, flood, exit7, empty, children, closed-stdin, split-utf8, invalid-utf8, eof, ignore-term. Owner config replaces profiles. No client argv or shell substitution is accepted. cwd must remain under approved roots.
Inputs: command_id (required), cwd, env, timeout_ms. Returns: object
terminal_statusBRead-onlyIdempotent
Query lifecycle, real exit code/signal, termination reason and output availability.
Inputs: session_id (required). Returns: object
terminal_terminateADestructiveIdempotent
Idempotently terminate the session process group: TERM, grace, KILL, confirm exit.
Final output and status remain readable for the configured retention interval.
Inputs: session_id (required), grace_ms. Returns: object
terminal_writeA
Write exact UTF-8 text to the running process; append newline yourself if needed.
Optionally close stdin after writing. accepted_bytes means transport acceptance, not application acknowledgement. Never retry blindly after INPUT_BACKPRESSURE.
Inputs: text (required), session_id (required), close_stdin. Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 5 tools
Each tool maps to a distinct lifecycle stage: start, write, read, status, terminate. The only near-overlap is status reporting 'output availability' versus read returning output, but the descriptions make clear status is metadata-only while read consumes records from a cursor.
All five tools share a uniform terminal_ prefix with a single-word verb suffix (start, write, read, status, terminate). Fully predictable and consistent.
Five tools is exactly the minimal complete set for process session control, with no redundant operations. Each tool earns its place in the lifecycle.
Start/write/read/status/terminate covers the full session lifecycle including graceful and forced termination. Minor gaps exist for interactive/pty use: no terminal_resize and no dedicated signal-send tool (Ctrl-C must be emulated via write), and no session listing, but agents can work around these.
Maintenance
Related MCP Connectors
Start and manage Reason cloud coding agents, projects and sessions from any MCP client.
Linux server administration over OAuth MCP: shell, Docker, systemd, files and monitoring.
Start, pause, resume, and run commands and file operations in Linux sandboxes.
Hosted MCP memory and agent control plane for durable conversations, jobs, and operations.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables MCP clients to spawn and control Codex CLI and Claude Code sessions on the host machine, with session management and filesystem access.4MIT
- AlicenseNot gradedqualityCmaintenanceEnables MCP clients to launch and interact with terminal/TUI programs, providing tools to inspect and control live CLI/TUI sessions.23 npm1Apache 2.0
- AlicenseNot gradedqualityCmaintenanceEnables management of long-running development processes (such as dev servers, compilers, and watchers) from MCP hosts. Provides tools to start, stop, restart, check status, view logs, and send input to managed processes.MIT
- AlicenseBqualityBmaintenanceEnables launching and supervising local Codex and Claude Code agent sessions and one-shot tasks, exposing run, list, stop, and output operations over MCP with session-scoped permissions and sandbox controls.2264 npm1MIT