MCP Agent
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 Agent北京今天天气怎么样?"
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 Agent
从 JSON-RPC 协议层到 LLM 编排层全链路自建的 Agent 项目。
不是 LangChain/CrewAI 的封装层,而是基于 MCP SDK 构建的 Server-Client 双端系统,包含自实现的 ReAct 循环引擎、双 Provider 抽象、以及面向 Agent 场景的权限控制。
亮点
MCP 协议全链路掌控 — 基于官方 SDK 构建 Server/Client 双端,理解
tools/list/tools/call的序列化、路由、生命周期管理,而非在 LangChain 里加@Tool注解自建 ReAct 循环 — 不依赖 Agent 框架,手动实现 Think → Act → Observe 循环,LLM 返回的
tool_use与 MCP 的tools/call之间的映射关系完全透明双 Provider 架构 — 通过 Provider 模式抽象 Anthropic 和 OpenAI/DeepSeek 两种 Tool Call 消息格式的差异,ReAct 循环不感知 LLM 厂商
权限层融入协议 — 四道关卡(传输层鉴权 → 工具可见性 → 调用权限 → 参数级权限)在 Server 端实现,权限拒绝用自然语言让 Agent 自愈,而非抛 403
真实外部 API 集成 — OpenWeatherMap(含地理编码,支持中文城市名)+ GitHub REST API,真实 API 失败自动降级 Mock,不阻塞 Agent
双传输模式 — Stdio(子进程通信,本地开发)和 SSE(HTTP 服务,远程接入)均已实现
Related MCP server: MCP Server with External Tools
架构
┌─────────────────────────────────────────────────────────────────┐
│ 用户终端 │
│ "北京今天天气怎么样?需要带伞吗?" │
└────────────────────────┬────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ client/main.py — 交互式终端 │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ client/react_loop.py — ReAct 循环引擎 │ │
│ │ │ │
│ │ Think ──→ Act ──→ Observe ──→ Repeat │ │
│ │ │ │ │ │ │
│ │ ▼ ▼ ▼ │ │
│ │ LLMClient MCPClient 结果送回 LLM │ │
│ └──────┬─────────┬──────────────────────────────────────────┘ │
│ │ │ │
└─────────┼─────────┼───────────────────────────────────────────────┘
│ │
│ │ Stdio / SSE 传输
▼ ▼
┌─────────────────────────────────────────────────────────────────┐
│ server/main.py — MCP Server │
│ │
│ 收到的 JSON-RPC 请求:{"method": "tools/list" | "tools/call"} │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ 路由分发 │ │
│ │ │ │
│ │ @app.list_tools() → 读取注册中心 → 权限过滤 → 返回工具列表 │
│ │ @app.call_tool() → 权限检查 → 执行 handler → 返回结果 │
│ └──────────┬───────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────┐ │
│ │ server/registry.py — 工具注册中心 │ │
│ │ (name → {tool_def, handler} 查表路由) │ │
│ └──────────┬───────────────────────────────┘ │
│ │ │
│ ┌───────┼───────────────┐ │
│ ▼ ▼ ▼ │
│ get_weather get_github_info save_note │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ OpenWeather GitHub REST 本地文件系统 │
│ (Geo+Weather) API │
│ │
│ ┌──────────────────────────────────────────┐ │
│ │ server/auth/permission.py — 权限四关 │ │
│ │ ① 传输层鉴权 ② 工具可见性 ③ 调用权限 ④ 参数级权限 │ │
│ └──────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘目录
交互流程
一次完整的 ReAct 循环
你: 北京今天天气怎么样?需要带伞吗?
┌──────────────────────────────────────┐
Step 1 │ LLM (Claude/DeepSeek) │
│ 根据 tools/list 返回的 Tool 定义, │
│ 选择 get_weather(city="北京") 并返回 │
│ tool_use content block │
└──────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ MCP Client │
│ 提取 tool_use → 组装 JSON-RPC │
│ → tools/call → MCP Server │
│ ① Geo API: 北京 → (39.9, 116.4) │
│ ② Weather API: 26°C, 多云转晴 │
│ → 返回结果字符串 │
└──────────┬───────────────────────────┘
│
▼
┌──────────────────────────────────────┐
Step 2 │ LLM │
│ 拿到 tool_result → 继续推理 │
│ → 返回纯文本回复 │
└──────────┬───────────────────────────┘
│
▼
用户看到: ☀️ 北京市 当前天气 26°C多步工具链
你: 帮我查一下北京的天气,然后记一条笔记
Step 1: LLM 返回 tool_use → get_weather(city="北京")
Step 2: 结果送回 LLM → 继续推理 → 返回 tool_use → save_note(content="...", title="北京天气")
Step 3: 结果送回 LLM → 返回纯文本 → "已查好天气并保存为笔记"模块详解
Server 层
server/main.py — MCP Server 核心入口。注册两个端点路由:
app = Server("mcp-agent")
@app.list_tools()
async def list_tools():
"""Agent 通过 tools/list 发现工具——经过权限过滤。"""
all_tools = get_all_tools()
visible = perm.filter_tools_by_role(role, all_tools)
return [t["tool_def"] for t in visible]
@app.call_tool()
async def call_tool(name: str, arguments: dict):
"""执行工具调用——先过权限,再执行 handler。"""
if not perm.check_call_permission(user, name):
return permission_error("你无权调用这个工具")
tool = get_tool(name)
result = await tool["handler"](**arguments)
return result关键设计:
两个
@app装饰器对应 MCP 协议的两个端点,内部注册到self.request_handlers[RequestType]字典权限检查在 handler 执行之前,不侵入工具逻辑
tool["handler"](**arguments)用字典展开实现动态调用,Server 不需要 if-else
server/registry.py — 工具注册中心。独立为模块的原因见架构决策。
_tool_registry = {} # {name: {tool_def, handler}}
def register_tool(name, description, input_schema):
"""装饰器:把函数注册为 MCP Tool。"""
def decorator(func):
_tool_registry[name] = {
"tool_def": Tool(name=name, description=description, inputSchema=input_schema),
"handler": func,
}
return func
return decoratorTools 层
server/tools/weather.py — 天气查询。OpenWeatherMap 两步走策略:
get_weather(city="广州")
→ Geo API: /geo/1.0/direct?q=广州 → (23.13, 113.26)
→ Weather API: /data/2.5/weather?lat=23.13&lon=113.26
→ 格式化输出:🌤️ 广州市 当前天气 32°C两步策略(参考 embabel-weather):Geo 编码支持中文城市名,然后坐标查天气
自动降级:真实 API 失败回退 Mock 数据
生活建议:根据温湿度自动生成"不需要带伞""注意防晒"等提示
server/tools/github.py — GitHub 仓库/用户查询。调用 GitHub REST API,处理 404/403/超时。
server/tools/note.py — 本地笔记读写。演示 Tool 操作文件系统的场景:save_note(content, title, filename) + list_notes()。
Client 层
client/mcp_client.py — MCP 传输层封装。屏蔽 Stdio 和 SSE 的协议差异:
class MCPClient:
async def connect(self):
# Stdio: 作为子进程启动 python -m server.main
# → 获取 (read_stream, write_stream)
# → 创建 ClientSession → 完成 initialize 握手
# SSE: 连接 HTTP 服务端,同上
async def list_tools(self) -> list[dict]:
result = await self._inner.list_tools()
# _inner.list_tools() 内部:
# ① 创建 ListToolsRequest(method="tools/list" 硬编码在 Pydantic 模型中)
# ② 序列化为 JSON-RPC → 写入 stdin
# ③ 等待 stdout 响应 → 反序列化为 Python 对象
return [{"name": t.name, "description": t.description, "inputSchema": t.inputSchema}
for t in result.tools]
async def call_tool(self, name, arguments) -> str:
result = await self._inner.call_tool(name, arguments)
return "".join(c.text for c in result.content)client/llm_client.py — LLM Provider 系统。支持 Anthropic 和 OpenAI 兼容两种 API:
# 核心:BaseProvider → AnthropicProvider / OpenAIProvider
class AnthropicProvider(BaseProvider):
# 调用 anthropic.messages.create()
# 解析 response.content 中的 tool_use block
class OpenAIProvider(BaseProvider):
# 调用 openai.chat.completions.create()
# 解析 response.choices[0].message.tool_calls
# 自动选择:
# LLM_PROVIDER=openai → OpenAI
# ANTHROPIC_API_KEY=sk-ant-... → Anthropic
# ANTHROPIC_API_KEY=sk-...(非 sk-ant-) → OpenAI(兼容 Key 混放的情况)两种消息格式差异由 Provider 内部消化,ReAct 循环不感知:
# ReAct 循环中,通过 llm.format_tool_call() 和 format_tool_result() 抽象
messages.append(self.llm.format_tool_call(tool_call)) # Anthropic/OpenAI 各有一套
messages.append(self.llm.format_tool_result(tc_id, result))client/react_loop.py — ReAct 循环引擎:
class ReActLoop:
async def run(self, user_message: str) -> str:
messages = [{"role": "user", "content": user_message}]
tools = await self.mcp.list_tools()
for step in range(max_steps):
reply_text, tool_uses = await self.llm.chat(messages, tools)
if reply_text and not tool_uses: # 纯文本 → 终结
return reply_text
for tc in tool_uses:
messages.append(self.llm.format_tool_call(tc))
result = await self.mcp.call_tool(tc["name"], tc["input"])
messages.append(self.llm.format_tool_result(tc["id"], result))权限层
server/auth/permission.py — 面向 Agent 场景的四道关卡(设计思路见同名文章):
关卡 | 拦截点 | 实现 |
① 传输层鉴权 | 连接建立时 |
|
② 工具可见性 |
|
|
③ 调用权限 |
|
|
④ 参数级权限 |
|
|
关键设计:
权限拒绝不抛异常 — 返回自然语言提示让 Agent 重试(呼应"404 和 403 不适合 Agent 场景")
isError=False— 即使权限被拒也不设为 true,Agent 可以继续思考
通信协议详解
tools/list 的 JSON-RPC 链路
Client Server
│ │
│ self._inner.list_tools() │
│ → ListToolsRequest(method="tools/list") │
│ → 序列化: │
│ {"jsonrpc": "2.0", "id": 1, │
│ "method": "tools/list"} │
│ → 写入 stdin │
│ │
│ 收到 stdin │
│ 反序列化 │
│ 查找 handler: │
│ request_handlers[ │
│ ListToolsRequest ← key 是 Python 类型 │
│ ] = @app.list_tools() │
│ 执行 list_tools() │
│ │
│ ◄── 写入 stdout ── 返回工具列表 JSON-RPC │tools/call 的 JSON-RPC 链路
Client Server
│ │
│ self._inner.call_tool("get_weather", │
│ {"city": "北京"}) │
│ → CallToolRequest(method="tools/call") │
│ → {"method": "tools/call", │
│ "params": {"name": "get_weather", │
│ "arguments": {"city": "北京"}} │
│ → 写入 stdin │
│ │
│ → 权限关卡 ③④ │
│ → get_tool(name) │
│ → await handler( │
│ **arguments) │
│ → 调 OpenWeather │
│ → 返回结果 │函数名与 method 的映射关系
ClientSession.list_tools() 的 list_tools 这个函数名不是路由的 key。路由是由 Pydantic 模型的 method literal 决定的:
class ListToolsRequest(PaginatedRequest[Literal["tools/list"]]):
method: Literal["tools/list"] = "tools/list" # ← 硬编码
# Server 收到 JSON-RPC 后:
# 1. 根据 "tools/list" → 反序列化为 ListToolsRequest 实例
# 2. type(instance) == ListToolsRequest → 从 request_handlers 查
# 3. 找到 @app.list_tools() 注册的 handlerProvider 系统
两种格式差异由 Provider 封装,ReAct 循环不感知:
Anthropic (Claude) | OpenAI 兼容 (DeepSeek) | |
Assistant 消息 |
|
|
Tool 结果 |
|
|
SDK |
|
|
自动检测逻辑(按优先级):
1. LLM_PROVIDER=openai / =anthropic → 直接确定
2. ANTHROPIC_API_KEY=sk-ant-... → Anthropic
3. ANTHROPIC_API_KEY=sk-...(非ant) → OpenAI(兼容 Key 混放)
4. OPENAI_API_KEY=sk-... → OpenAI
5. 默认 → Anthropic架构决策
1. 为什么自建 ReAct 循环,而非 LangChain/CrewAI
如果要快速投产,LangGraph + langchain-mcp-adapters 是合理选择。但本项目的核心目的不是生产一个 Agent 框架——市面上已经有足够多了——而是展示从协议层到编排层的全链路掌控能力。
关键差异在于:LangChain 把 tools/list 和 tools/call 封装在 AgentExecutor 内部,开发者看不到 JSON-RPC 的流动。本项目让每一步都透明。
2. 为什么 registry.py 要独立
python -m server.main 会把 server/main.py 加载为 __main__。当 tools/weather.py 做 from server.main import register_tool 时,Python 又加载一次 server/main.py 作为 server.server。两次加载产生两个 _tool_registry 实例——装饰器写入一个,Server 启动读取另一个。
独立为模块后,不管 from 路径如何,始终是同一个模块实例。
3. 为什么权限在 Server 端而非 Client 端
权限是资源级的——同一个 query_inventory 工具,不同用户的 warehouse_id 范围不同。Client 端无法预知参数里的权限边界。Server 端在 tools/call 时拿到具体参数值后才能检查。
Agent 场景下 403 没有意义。用自然语言告诉 Agent "为什么不行",它才能调整参数重试。
4. 为什么工具注册用装饰器
装饰器紧邻函数定义,工具名、参数 Schema 和执行函数写在一起。不会出现"改了 inputSchema 忘了改函数签名"的问题。等价于在 dict 里注册 {name → {tool_def, handler}},只是语法更紧凑。
5. 为什么支持双 Provider
真实场景下可能需要混用不同 LLM(成本、性能、可用性)。ReAct 循环本身不应该绑定厂商消息格式。
快速开始
安装
pip install -e .
cp .env.example .env配置(二选一)
# 方案 A: Claude
ANTHROPIC_API_KEY=sk-ant-...
# 方案 B: DeepSeek
LLM_PROVIDER=openai
OPENAI_API_KEY=sk-...
OPENAI_BASE_URL=https://api.deepseek.com
OPENAI_MODEL=deepseek-chat运行
# 交互式 Agent(默认 Stdio 模式——Client 自动启动 Server 子进程)
python -m client.main
# Server SSE 模式(HTTP 服务,供远程连接——仅限本地开发)
# 生产环境使用 SSE 请加反向代理(Nginx/Caddy)提供 TLS 和访问控制
python -m server.main --transport sse示例
> 北京今天天气怎么样?需要带伞吗?
🔧 [1/10] get_weather(city='北京')
☀️ 北京市 当前天气
- 温度:26°C(体感 25°C)
- 天气:多云转晴
☀️ 不需要带伞。
> 查一下 quarktimes/tiny-claw-cli
🔧 [1/10] get_github_info(repo='quarktimes/tiny-claw-cli')
📦 quarktimes/tiny-claw-cli — Java Agent Harness CLI
- ⭐ Star: 1
> 帮我查北京天气,然后记一条笔记
🔧 [1/10] get_weather(city='北京')
🔧 [2/10] save_note(content='...', title='北京天气 2026-07-11')
✅ 已查好天气并保存为笔记文件结构
mcp-agent/
├── pyproject.toml # 项目元数据 + 依赖
├── .env.example # 环境变量模板
├── README.md
│
├── server/ # MCP Server 端
│ ├── main.py # Server 入口 + tools/list + tools/call 路由
│ ├── registry.py # 工具注册中心(规避 __main__ 双重导入)
│ ├── tools/
│ │ ├── weather.py # get_weather — OpenWeatherMap (Geo+Weather)
│ │ ├── github.py # get_github_info — GitHub REST API
│ │ └── note.py # save_note / list_notes — 本地文件系统
│ └── auth/
│ └── permission.py # 四道关卡权限服务
│
├── client/ # MCP Client + Agent 端
│ ├── main.py # 交互式终端入口
│ ├── mcp_client.py # MCP 传输层(Stdio / SSE)
│ ├── llm_client.py # LLM Provider 系统(Anthropic + OpenAI)
│ └── react_loop.py # ReAct 循环引擎
│
├── tests/
│ └── test_integration.py # 5 项集成测试
│
└── notes/ # save_note 输出目录技术要点
文件 | 涉及的技术点 |
| MCP Server 搭建、 |
|
|
| Tool 定义规范(name + description + inputSchema)、两步式 API 调用(Geo → Weather)、异常降级 |
| Tool 调用外部 REST API、404/403/超时处理 |
| Tool 操作本地文件系统、输入安全校验 |
| Agent 场景权限模型、频率限制(滑动窗口)、时段限制、资源级白名单 |
|
|
| Provider 模式、MCP Tool → LLM Tool 格式转换、Anthropic/OpenAI 消息格式差异 |
| ReAct 循环流程、Think→Act→Observe 三步、Provider 无关的消息格式化 |
This server cannot be deployed
Maintenance
Related MCP Connectors
The cloud for agents. Tools for AI agents to register, build, and deploy other agents. Zero human required.
Agent-first resource directory for AI agents: protocols, security, RAG, memory, evals, and more.
Sovereign Agent OS — Persistent Memory, Governance & Compliance for AI Agents.
The agent-native cloud: database, functions, AI, storage, computers. 50 tools, one API key.
Related MCP Servers
FlicenseCqualityDmaintenanceAn open-source agent toolkit that auto-syncs SDK versions, docs, and examples—built for seamless integration with LLMs, and AI agents ( MCP compatible).447-- AlicenseNot gradedqualityDmaintenanceEnables AI models to access external services including weather data, file system operations, and SQLite database interactions through a standardized JSON-RPC interface. Features production-ready architecture with security, rate limiting, and comprehensive error handling.205MIT
- FlicenseNot gradedqualityDmaintenanceEnables deployment of autonomous AI agents with memory and tool execution capabilities through a WebSocket-based MCP protocol. Provides production-ready infrastructure with REST API access, persistent state management, and extensible function registry for building self-hosted AI systems.-
- AlicenseNot gradedqualityCmaintenanceLocal-first AI agent for approval-gated automation and verifiable LLM workflows.1MIT