Shell MCP服务器

一个为AI代理提供直接访问主机操作系统shell的Model Context Protocol (MCP)服务器,通过标准化接口执行命令、管理后台进程和检测主机操作系统。

  • by HabaAndrei
  • Python
  • 本地部署
  • 1 星标
  • 更新于
在 GitHub 查看 HabaAndrei/shell-mcp

使用说明

壳牌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).

架构决策

  1. 每个文件一个工具 (tools/ 目录):每个MCP工具都存在于自己的模块中,具有自己的docstring @mcp.tool() decorator在导入时自动注册它。这使东西保持干净,并使添加或删除工具变得容易。

  2. ShellWrapper单例:所有shell逻辑都存在于一个类中(ShellWrapper)实例化一次 sw。工具文件是只调用 sw.method()。这将MCP层与实际的shell逻辑分离,因此您可以测试或重用 ShellWrapper 独立地。

  3. 前台超时与后台PID:服务器显式地分离了这两种模式,而不是一个通用的“run”函数。前台命令阻止和返回输出。后台命令立即返回PID,代理可以稍后轮询或终止。这种区别对于需要启动服务器、运行构建或执行长任务的编码代理至关重要。

  4. 优雅的终止与回退: kill_process 第一次发送 SIGTERM,最多等待1秒,然后升级到 SIGKILL这可以防止僵尸进程,同时仍然给行为良好的进程一个清理的机会。

  5. 仅记录到stderr:MCP通过以下方式进行通信 stdout 使用JSON-RPC。任何流浪 print() 会破坏协议。所有日志记录都路由到 stderr 通过Python logging 模块。

工具

工具 说明
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”

许可证

麻省理工学院