多工具MCP服务器

mcpp是一个轻量级的MCP服务器,用于从单个进程加载多个工具,减少协调开销。

  • by pacmac
  • Python
  • 本地部署
  • 更新于

使用说明

mcpp

一个轻量级的MCP服务器,从单个进程加载多个工具。mcpp不是为每个工具运行单独的MCP服务器,而是充当多路复用器:一个stdio服务器,许多工具模块,零协调开销。

为什么

这 模型上下文协议 为AI代理(Claude Code、Codex等)提供了一种调用外部工具的标准方法。但默认模式是 每个工具一台服务器 --每个都有自己的流程、启动成本和配置条目。这很快就会变得笨拙。

mcpp扭转了这一局面。您可以在单个注册表中注册工具模块 tools.yaml 文件。每个模块都是一个目录,其中包含 tool.yaml 舱单和a mcpptool.py 实施。服务器在启动时发现它们,通过一个 tools/list,以及路线 tools/call 请求到正确的模块。添加新工具意味着创建一个目录并重新启动服务器。服务器代码没有更改。没有新的MCP配置条目。

特性

  • 一台服务器,多种工具 --单进程、单配置条目、无限制的工具模块
  • 声明性合同 --每个工具在中定义其模式 tool.yaml (JSON模式),与实现完全分离
  • 延迟加载 --Python代码仅在第一次调用时导入,而不是在启动时导入。损坏的模块不会阻止服务器启动
  • 外部工具 --模块可以存在于磁盘上的任何地方;只要指向他们 tools.yaml
  • 双重受众反应 --工具可以使用MCP内容注释为代理返回人类可读的显示文本和结构化数据
  • 多用户、多项目 --the mcpp-plan 该工具从单个共享数据库中跟踪每个用户每个项目的任务
  • 超时执行 --每次呼叫超时通过 SIGALRM 防止失控的工具阻塞服务器
  • 内置帮助 --代理可以查询 help 在不导入任何工具代码的情况下发现可用工具及其运行时配置
  • 人工CLI -- cli.py 提供用于测试和直接使用的命令行界面

快速开始

1.安装

克隆仓库并确保Python 3.10+可用。唯一的外部依赖是 pyyaml.

git clone https://github.com/pacmac/mcpp.git
cd mcpp
pip install pyyaml

包括 计划 任务管理器(可选):

git clone https://github.com/pacmac/mcpp-plan.git ../mcpp-plan

然后将其添加到 tools.yaml:

modules:
  - path: ../mcpp-plan

2.配置您的代理

克劳德代码:

claude mcp add mcpp --scope user \
  -- python3 /path/to/mcpp/mcpp.py

Codex CLI (~/.codex/config.toml):

[mcp_servers.mcpp]
command = "python3"
args = ["/path/to/mcpp/mcpp.py"]
env = { MCPP_LOG_LEVEL = "error", MCPP_TIMEOUT_SECONDS = "30" }

任何兼容MCP的客户端 --mcpp通过stdio使用JSON-RPC 2.0:

python3 /path/to/mcpp/mcpp.py

3.使用它

配置后,您的代理将看到所有已注册的工具。在克劳德代码中,它们显示为 mcp__mcpp__<tool_name>:

> What tools do you have from mcpp?

I have these tools available:
- fetch_page -- fetch and cache web content
- what_add -- scaffold a new plan directory
- plan_task_new -- create a new task with steps
- plan_task_list -- list all tasks
  ... (19 plan tools total)

建筑

┌─────────────────────────────────────────────────────┐
│  AI Agent (Claude Code, Codex, etc.)                │
│                                                     │
│  tools/list  ──►  all tool schemas (from tool.yaml) │
│  tools/call  ──►  routed to correct module          │
└──────────────┬──────────────────────────────────────┘
               │ stdin/stdout (JSON-RPC 2.0)
┌──────────────▼──────────────────────────────────────┐
│  mcpp.py  (single process)                          │
│                                                     │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐          │
│  │tools.yaml│  │ tool     │  │ routing  │          │
│  │ registry │─►│ discovery│─►│ table    │          │
│  └──────────┘  └──────────┘  └────┬─────┘          │
│                                   │                 │
│            ┌──────────┬───────────┼──────────┐      │
│            ▼          ▼           ▼          ▼      │
│       fetch_page    what       plan      your_tool  │
│       tool.yaml    tool.yaml   tool.yaml  tool.yaml │
│       mcpptool.py  mcpptool.py mcpptool.py  ...    │
└─────────────────────────────────────────────────────┘

启动: 阅读 tools.yaml 注册表,解析每个模块的 tool.yaml 显示。尚未导入Python。

第一通电话: 懒散地导入模块 mcpptool.py,缓存它,路由呼叫。

后续通话: 直接命中缓存的模块。

工具如何工作

工具模块是一个包含两个文件的目录:

tools/my_tool/
  tool.yaml       # declares the contract (schema)
  mcpptool.py     # implements the logic

tool.yaml——合约

这是服务器在启动时读取的唯一文件。它定义了该工具在没有任何Python代码的情况下的功能。

name: my_tool                    # unique module identifier
scope: local                     # "local" (workspace-relative) or "global"
about: "One-line description."   # shown in help listings

tools:
  - name: my_tool_search         # globally unique tool name
    description: "Search for items matching a query."
    inputSchema:
      type: object
      properties:
        query:
          type: string
          description: "Search query"
        limit:
          type: integer
          description: "Max results (default: 10)"
          minimum: 1
          maximum: 100
      required:
        - query

领域:

字段 必填 描述
name 是 模块标识符。在所有注册的模块中必须是唯一的
scope 没有 "local" (默认)--工具对工作区文件进行操作。 "global" --独立工作空间
about 否 仅显示一行描述 help 工具
tools 是 工具定义列表,每个都有 name, description,以及 inputSchema

一个模块可以定义多个工具。这 mcpp-plan 模块在一个模块名称下定义了19个工具。

工具命名约定: <module>_<entity>_<action>例如。 plan_task_new, plan_step_done.

mcpptool.py的实现

必须导出 execute() 功能:

def execute(tool_name: str, arguments: dict, context: dict | None = None) -> dict:
    """Handle a tool call.

    Args:
        tool_name:  Which tool was called (matches a name from tool.yaml)
        arguments:  Parameters from the agent, validated against inputSchema
        context:    Runtime info from the server:
                    {
                        "workspace_dir": "/abs/path/to/cwd",
                        "module_dir":    "/abs/path/to/this/module",
                        "module_scope":  "local"  # or "global"
                    }

    Returns:
        {"success": True, "result": <any>}                     # success
        {"success": True, "result": <data>, "display": "..."}  # success with display text
        {"success": False, "error": "description"}             # failure
    """

这就是整个合同。 一个函数,三个参数,一个字典返回。

可选出口

def get_info(context: dict | None = None) -> dict:
    """Return runtime configuration for agent discoverability.
    Called by the built-in help tool."""
    return {
        "params": {
            "query": {"values": None, "default": None},
            "limit": {"values": None, "default": 10},
        }
    }

def initialize() -> dict:
    """Called once at module import time. Return {"success": False, ...} to warn."""
    return {"success": True}

显示键

当工具同时返回时 result 和 display,服务器发出两个带有观众注释的MCP内容项:

return {
    "success": True,
    "result": {"tasks": [...]},             # structured data for the agent
    "display": "**Tasks**\n- alpha\n- beta"  # markdown for the human
}

代理接收结构化数据以进行推理。人类看到格式化的标记。这种分离使工具能够通过一次调用向两个受众提供丰富的输出。

注册表(tools.yaml)

顶级 tools.yaml 列出要加载的模块:

modules:
  - path: tools/fetch_page          # built-in, relative to tools.yaml
  - path: tools/what                # built-in
  - path: ../mcpp-plan              # external: github.com/pacmac/mcpp-plan
  - path: /opt/custom/my_tool       # absolute paths work too

路径是相对于包含以下内容的目录解析的 tools.yaml.以开头的目录 _ 被忽略(用于模板)。

要添加工具,请执行以下操作: 创建目录,向添加条目 tools.yaml,重新启动服务器。

要删除工具,请执行以下操作: 从中删除其条目 tools.yaml,重新启动。

创建新工具

来自模板

cp -r tools/_template_pkg tools/my_tool

编辑 tools/my_tool/tool.yaml 要定义架构,请编辑 tools/my_tool/mcpptool.py 实现你的逻辑。

从零开始

1.创建目录:

mkdir tools/my_tool

2.编写工具.yaml:

name: my_tool
scope: local
about: "Does something useful."

tools:
  - name: my_tool_run
    description: "Run the tool with given parameters."
    inputSchema:
      type: object
      properties:
        target:
          type: string
      required:
        - target

3.编写mcpptool.py:

from __future__ import annotations
from typing import Any

def execute(tool_name: str, arguments: dict[str, Any], context: dict[str, Any] | None = None) -> dict[str, Any]:
    if tool_name == "my_tool_run":
        target = arguments["target"]
        workspace = (context or {}).get("workspace_dir", ".")
        # ... do work ...
        return {"success": True, "result": {"target": target, "status": "done"}}

    return {"success": False, "error": f"Unknown tool: {tool_name}"}

4.注册:

增添 tools.yaml:

modules:
  - path: tools/my_tool
  # ... existing modules ...

5.重新启动服务器。 新工具出现在 tools/list 立即。

外部工具

工具模块不需要位于mcpp目录中。磁盘上的任何目录都可以是一个工具模块——只需指向它:

modules:
  - path: /home/user/projects/my-tool    # absolute path
  - path: ../../other-repo/tool-module   # relative to tools.yaml

这就是 mcpp-plan 该工具是集成的,它位于一个单独的存储库中,由注册表中的路径引用。该模块有自己的git历史、测试和发布周期。mcpp只是加载它。

外部工具的要求是相同的: 一 tool.yaml 舱单和a mcpptool.py 随着 execute().无需特殊包装,无需setup.py,无需pip安装。

多模块工具

一个模块可以暴露许多工具。这 mcpp-plan 该模块通过四组中的19个工具演示了这一点:

name: plan
scope: local
about: "Manage project tasks and steps."

tools:
  # Task management (7 tools)
  - name: plan_task_new
  - name: plan_task_list
  - name: plan_task_show
  - name: plan_task_status
  - name: plan_task_switch
  - name: plan_task_archive
  - name: plan_task_notes

  # Step tracking (7 tools)
  - name: plan_step_list
  - name: plan_step_show
  - name: plan_step_switch
  - name: plan_step_done
  - name: plan_step_new
  - name: plan_step_delete
  - name: plan_step_notes

  # User identity (2 tools)
  - name: plan_user_show
  - name: plan_user_set

  # Project metadata (3 tools)
  - name: plan_project_show
  - name: plan_project_set
  - name: plan_readme

这 execute() 内部功能路线:

tool_map = {
    "plan_task_new": _cmd_task_new,
    "plan_task_list": _cmd_task_list,
    # ...
}

def execute(tool_name, arguments, context=None):
    handler = tool_map.get(tool_name)
    if not handler:
        return {"success": False, "error": f"Unknown tool: {tool_name}"}
    return handler(workspace_dir, arguments)

内置帮助

每个mcpp实例都包含一个 help 工具自动。代理商可以调用它来发现可用的内容:

# No arguments: list all modules
help()
# Returns: module names and their 'about' descriptions (no Python imports needed)

# With tool name: get runtime details
help(tool="fetch_page")
# Returns: output of that module's get_info(), including param defaults and valid values

多用户、多项目支持

这 mcpp-plan 该工具演示了mcpp对复杂、有状态工具的支持:

  • 单一共享数据库 (~/.config/plan/plan.db)--所有项目、所有用户、一个SQLite文件
  • 自动项目检测 --工作空间目录映射到项目;每个项目都有独立的任务列表
  • 每个用户状态 --每个操作系统用户在每个项目中都有自己的活动任务; su 让其他用户看到他们的任务
  • 跨项目隔离 --奔跑从 /project-a 显示项目a的任务;逃避 /project-b 显示了项目b。相同的服务器、相同的数据库、不同的作用域
~/.config/plan/plan.db
├── project: webapp     (CWD: /home/user/webapp)
│   ├── user: alice     → active task: auth-refactor
│   └── user: bob       → active task: api-migration
├── project: cli-tool   (CWD: /home/user/cli-tool)
│   └── user: alice     → active task: add-flags

这 scope: local 融入 tool.yaml 告诉服务器通过 workspace_dir 在上下文中,该工具用于解析正确的项目。

配置

环境变量

变量 默认值 描述
MCPP_BASE_DIR 目录包含 mcpp.py 在哪里可以找到 tools.yaml
MCPP_LOG_LEVEL info debug, info, warning, error
MCPP_TIMEOUT_SECONDS 30 每次工具调用超时(秒)

克劳德代码

# Add with environment overrides
claude mcp add mcpp --scope user \
  --env MCPP_LOG_LEVEL=error \
  --env MCPP_TIMEOUT_SECONDS=60 \
  -- python3 /path/to/mcpp/mcpp.py

直接JSON配置(任何MCP客户端)

{
  "mcpServers": {
    "mcpp": {
      "command": "python3",
      "args": ["/path/to/mcpp/mcpp.py"],
      "env": {
        "MCPP_LOG_LEVEL": "error"
      }
    }
  }
}

人工CLI

对于没有AI代理的测试和直接使用:

# List all registered tools
python3 cli.py list

# Call a tool
python3 cli.py call fetch_page --url https://example.com --max_chars 5000

# Get help on a specific tool
python3 cli.py call help --tool plan_task_new

项目布局

mcpp/
├── mcpp.py              # MCP server (reads stdin, writes stdout)
├── cli.py               # Human CLI wrapper
├── tools.yaml           # Module registry
├── tools/
│   ├── _template_pkg/   # Copy-paste starter for new tools
│   │   ├── tool.yaml
│   │   ├── mcpptool.py
│   │   └── helper.py
│   ├── fetch_page/      # URL fetcher with disk cache
│   │   ├── tool.yaml
│   │   └── mcpptool.py
│   └── what/            # Project scaffolding
│       ├── tool.yaml
│       └── mcpptool.py
└── README.md

协议遵从

mcpp实现了 模型上下文协议 使用JSON-RPC 2.0通过stdio:

MCP方法 支持 注意事项
initialize 是 返回协议版本 2025-06-18
tools/list 是 返回所有模块的所有工具模式
tools/call 是 路由到正确的模块,强制超时
shutdown 是 设置关机标志
notifications/* 是 默默承认
内容注释 是 audience: ["user"] / ["assistant"] 通过显示键

受启发于

这个项目的灵感来自 安德烈亚斯·斯皮斯 (GitHub)和他的 人工智能辅助编码视频.

许可证

PolyForm非商业版1.0.0

免费供个人使用、研究、教育、非营利组织和政府使用。不允许用于商业用途。看 许可证 全文。