使用说明
壳牌MCP
A. 模型上下文协议(MCP) 允许AI代理直接访问主机操作系统外壳的服务器。它在MCP上暴露了四个工具 stdio 因此,任何兼容MCP的客户端(LangChain、Claude Desktop等)都可以通过标准化的接口执行命令、管理后台进程和检测主机操作系统。
为什么这个项目存在
这个项目源于构建编码代理的实践经验。在开发自己的编码代理时,我意识到shell访问是代理可以拥有的最关键的功能之一,而正确地完成它比看起来要困难。
我花了很多时间解决的关键挑战:
- 前台与后台执行:有些命令需要立即运行并返回,而其他命令(如开发服务器或长期构建)需要在代理继续工作时在后台继续运行。
- 超时和流程生命周期:知道何时等待,何时终止,以及如何优雅地处理早期挂起或崩溃的进程。
- 读取实时进程的输出:为代理提供一种无阻塞地检查后台工作的方法。
这些都是每个编码代理构建者都会面临的问题。此处的工具名称和功能(execut_command, read_output, kill_process, get_system)代表了大多数现实世界编码代理的需求。在您自己的项目中,您可能会拥有具有相同目的的工具,这些工具适用于您的堆栈和语言,但架构和模式保持不变。
将此项目视为参考实现。 一个工作示例,说明如何为编码代理构建shell工具,如何处理棘手的部分(后台进程、超时、优雅终止),以及如何通过MCP公开它们,以便任何客户端都可以使用它们。它不是一个框架;这是一个你可以学习和适应的蓝图。
它是如何建造的
堆栈
| 层 | 技术 | 角色 |
|---|---|---|
| MCP框架 | FastMCP (mcp[cli]) |
通过提供MCP服务器骨架、工具注册 @mcp.tool() 装饰师,以及 stdio 运输。这是使任何MCP客户端都能发现这些工具的核心。 |
| python | 3.13+ | 整个服务器是纯Python,除了标准库和FastMCP之外没有外部运行时依赖关系。 |
| 异步IO | stdlib | 用于执行非阻塞前台命令(asyncio.create_subprocess_shell)以及超时处理(asyncio.wait_for). |
| 子进程 | stdlib | 为后台进程生成提供动力(subprocess.Popen)和过程检查(lsof, tasklist). |
| 操作系统/信号 | stdlib | 处理跨平台进程生命周期:存在性检查(os.kill(pid, 0)),优雅的终止(SIGTERM),并强行杀死(SIGKILL). |
| 平台 | stdlib | OS检测,以便代理知道它是在与Linux、macOS还是Windows通信。 |
| 日志 | stdlib | 所有日志都转到 stderr (从来没有 stdout,因为MCP使用 stdout 用于JSON-RPC通信)。 |
| 紫外线 | 包管理器 | 管理依赖关系并运行服务器(uv run main.py). |
架构决策
-
每个文件一个工具 (
tools/目录):每个MCP工具都存在于自己的模块中,具有自己的docstring@mcp.tool()decorator在导入时自动注册它。这使东西保持干净,并使添加或删除工具变得容易。 -
ShellWrapper单例:所有shell逻辑都存在于一个类中(
ShellWrapper)实例化一次sw。工具文件是只调用sw.method()。这将MCP层与实际的shell逻辑分离,因此您可以测试或重用ShellWrapper独立地。 -
前台超时与后台PID:服务器显式地分离了这两种模式,而不是一个通用的“run”函数。前台命令阻止和返回输出。后台命令立即返回PID,代理可以稍后轮询或终止。这种区别对于需要启动服务器、运行构建或执行长任务的编码代理至关重要。
-
优雅的终止与回退:
kill_process第一次发送SIGTERM,最多等待1秒,然后升级到SIGKILL这可以防止僵尸进程,同时仍然给行为良好的进程一个清理的机会。 -
仅记录到stderr:MCP通过以下方式进行通信
stdout使用JSON-RPC。任何流浪print()会破坏协议。所有日志记录都路由到stderr通过Pythonlogging模块。
工具
| 工具 | 说明 |
|---|---|
get_system |
返回主机操作系统名称(Linux, Darwin, Windows)因此代理可以选择正确的shell语法。 |
execut_command |
在前台运行shell命令(超时) 或 在后台(返回PID)。 |
read_output |
检查正在运行的后台进程,并返回其当前状态和打开的资源。 |
kill_process |
优雅地终止后台进程(SIGTERM,然后SIGKILL回退)。 |
对于启动开发服务器的代理,这两种执行模式是如何发挥作用的, 检查并关闭它:
sequenceDiagram
actor Agent as AI Agent (MCP client)
participant MCP as Shell MCP Server
participant OS as Host Shell
Agent->>MCP: execut_command("npm test") [foreground]
MCP->>OS: asyncio.create_subprocess_shell + timeout
OS-->>MCP: exit code + output
MCP-->>Agent: full output (blocking call done)
Agent->>MCP: execut_command("npm run dev", background=true)
MCP->>OS: subprocess.Popen
MCP-->>Agent: PID 12345 (returns immediately)
Note over Agent: keeps working while the server runs
Agent->>MCP: read_output(12345)
MCP-->>Agent: process state + open resources
Agent->>MCP: kill_process(12345)
MCP->>OS: SIGTERM, wait 1s, SIGKILL if needed
MCP-->>Agent: terminated cleanly
项目结构
shell_mcp/
├── main.py # Entry point, starts the MCP server on stdio
├── mcp_engine.py # FastMCP server instance + instructions
├── shell_wrapper.py # Core shell logic (run, background, read, kill)
├── logger.py # Logging config (writes to stderr, not stdout)
├── tools/ # One file per MCP tool
│ ├── __init__.py
│ ├── execut_command.py
│ ├── get_system.py
│ ├── read_output.py
│ └── kill_process.py
└── pyproject.toml
完整的客户端示例(带和不带Postgres内存的LangChain, 克劳德桌面)在 集成指南 在......下面
先决条件
- Python 3.13+
- 紫外线,一个快速的Python包管理器
安装
# Clone the repository
git clone https://github.com/HabaAndrei/shell-mcp.git
cd shell-mcp
# Install dependencies with uv
uv sync
集成指南
1.LangChain+LangGraph代理(带PostgreSQL内存)
这是一个完整的、可复制粘贴的示例。它通过以下方式连接到Shell MCP服务器 stdio,创建一个LangChain代理,并运行一个会话循环。
步骤1:在客户端项目中安装依赖项
uv add langchain langchain-mcp-adapters langchain-openai langgraph-checkpoint-postgres python-dotenv
第二步:创建 .env 文件
OPENAI_API_KEY=sk-...
步骤3:创建 app.py
import os
import asyncio
from dotenv import load_dotenv
load_dotenv(override=True)
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent
from langchain_core.messages import HumanMessage
from langgraph.checkpoint.postgres.aio import AsyncPostgresSaver
# --- Config ---
DB_URI = "postgresql://postgres:postgres@localhost:5432/postgres"
THREAD_ID = "1"
SHELL_MCP_DIR = "/absolute/path/to/shell_mcp" # <-- change this
async def main():
# 1. Connect to Postgres for conversation memory
async with AsyncPostgresSaver.from_conn_string(DB_URI) as checkpointer:
await checkpointer.setup()
# 2. Start the MCP server as a subprocess
client = MultiServerMCPClient(
{
"shell_mcp": {
"transport": "stdio",
"command": "uv",
"args": ["run", "--directory", SHELL_MCP_DIR, "main.py"],
},
}
)
# 3. Fetch the tools the MCP server exposes
tools = await client.get_tools()
# 4. Create a LangChain agent with those tools
agent = create_agent(
"openai:gpt-4.1",
tools,
checkpointer=checkpointer,
)
config = {"configurable": {"thread_id": THREAD_ID}}
# 5. Chat loop
print("Chat with Shell MCP (type 'exit' to quit)\n")
while True:
user_input = input("You: ").strip()
if user_input.lower() == "exit":
break
response = await agent.ainvoke(
{"messages": [HumanMessage(content=user_input)]},
config=config,
)
print(f"AI: {response.get('messages')[-1].content}\n")
if __name__ == "__main__":
asyncio.run(main())
第四步:运行它
uv run app.py
2.无PostgreSQL的LangChain代理(内存中,设置更简单)
如果你不需要持久的对话记忆,你可以完全跳过Postgres:
import os
import asyncio
from dotenv import load_dotenv
load_dotenv(override=True)
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent
from langchain_core.messages import HumanMessage
SHELL_MCP_DIR = "/absolute/path/to/shell_mcp" # <-- change this
async def main():
client = MultiServerMCPClient(
{
"shell_mcp": {
"transport": "stdio",
"command": "uv",
"args": ["run", "--directory", SHELL_MCP_DIR, "main.py"],
},
}
)
tools = await client.get_tools()
agent = create_agent("openai:gpt-4.1", tools)
config = {"configurable": {"thread_id": "1"}}
response = await agent.ainvoke(
{"messages": [HumanMessage(content="What OS is this running on?")]},
config=config,
)
print(f"AI: {response.get('messages')[-1].content}")
if __name__ == "__main__":
asyncio.run(main())
3.克劳德桌面集成
将此添加到您的Claude Desktop MCP配置中(~/Library/Application Support/Claude/claude_desktop_config.json 在macOS上):
{
"mcpServers": {
"shell_mcp": {
"command": "uv",
"args": ["run", "--directory", "/absolute/path/to/shell_mcp", "main.py"]
}
}
}
重新启动Claude Desktop:四个shell工具将自动出现。
示例提示
集成后,您可以向代理询问以下问题:
- “这是什么操作系统?”
- “列出/Users/me/projects中的所有文件”
- “在后台/Users/me/app中运行npm Run-dev,等待一分钟,然后向我显示输出”
- “终止进程12345”
