Code Agent 解剖(07):外部工具怎么接进来?MCP 集成是怎么做的?
Code Agent è§£åï¼07ï¼ï¼å¤é¨å·¥å ·æä¹æ¥è¿æ¥ï¼MCP éææ¯æä¹åçï¼
å¬å¥Lab 2026-08-22 0 é 读9åéSkills è§£å³ä¸äºçé£ä¸ç±»è½å
ä¸ä¸ç¯ç Skills æ¯æ¬å° Markdown æä»¤ï¼ä¸å Pythonãä¸å¯æ°è¿ç¨ï¼æ¨¡åæéææ£æè¯»è¿æ¥ç §çåãéåãå¢é code review è§èãè¿ç§æµç¨ç¥è¯ã
å¤é¨è½å䏿¯è¿ä¸ªå½¢ç¶ãç½é¡µæç´¢ãææ¡£åºãæµè§å¨ãå
¬å¸å
é¨ APIï¼è·å¨å¦ä¸ä¸ªè¿ç¨éï¼æèªå·±çåæ°åçå½å¨æã为æ¯ä¸ªæå¡æåä¸ä¸ª Tool åç±»è½ç¨ï¼ä½æ¥ä¸å®¶å°±æ¹ä¸æ¬¡ä»£ç ã
MCPï¼Model Context Protocolï¼çå·¥ç¨å«ä¹å¾å ·ä½ï¼è®©å¤é¨è¿ç¨ææ ååè®®æ´é²å·¥å ·ï¼agent å¨å¯å¨æ¶åç°å®ä»¬ï¼ä¼ªè£ æå·²ç»æ³¨åè¿çæ¬å° Toolï¼ä¹å走第 05 ç¯å䏿¡ç®¡éââschemaãç¼æãæéãè§æµååã
ç»è®ºå æ¾è¿
MCP çå¯å¨å
¥å£ä¸å¨ extensions/mcp/ éï¼èå¨ agent ç»è£
ä¸ä¸æ æ¶ï¼
CodeAgent._initialize_runtime_components()
â
â¼
build_runtime_context() â runtime/factory.py
â
ââ _register_builtin_tools() å
置工å
·å
è¿ Registry
â
ââ if host.enable_mcp: â é»è®¤ Falseï¼æ²¡å¼åæ´æ®µè·³è¿
â host._register_mcp_tools()
â â
â â¼
â register_mcp_servers() â extensions/mcp/bootstrap.py
â â
â ââ load_mcp_servers() 读 mcp.json / MCP_SERVERS
â ââ MCPClientï¼stdio / httpï¼
â ââ list_tools() åç°è¿ç«¯å·¥å
·
â ââ MCPToolAdapter â ToolRegistry
â
ââ ContextBuilder(...) â æ _mcp_tools_prompt åè¿ Tool Contracts
â
â¼
ä¹åæ¯æ¥ ReAct å Read/Bash 䏿 ·ï¼
tools= schema / Orchestrator â Executor â adapter.run()
â
â¼
protocol.py æ MCP content æ¶æéç¨ä¿¡å°
为ä»ä¹ä» factory å¼å§çï¼ ç¬¬ 01 ç¯è®²è¿ build_runtime_context() æ¯å¯å¨æç»è£
ä¸ä¸æå¼æçå°æ¹ãSkills å MCP 齿å¨è¿éï¼ä½é¡ºåºåºå®ï¼å
å
置工å
·ï¼å MCPï¼æå ContextBuilderãMCP å·¥å
·å¿
é¡»å¨ ContextBuilder å建ä¹å注åå®ï¼è¿æ · tool_prompt_allowlist=frozenset(host.tool_registry.list_tools()) æè½å
å«å¤é¨å·¥å
·ï¼mcp_tools_prompt ä¹è½ä¸å¹¶ä¼ å
¥ã
å Skills çåçï¼
| Skills | MCP | |
|---|---|---|
| è½½ä½ | SKILL.md | å¤é¨è¿ç¨ / HTTP æå¡ |
| è¿ Registry çæ¯ä»ä¹ | ä¸ä¸ªæ¬å° Skill å·¥å
· | æ¯ä¸ªè¿ç«¯å·¥å ·åä¸ä¸ª Adapter |
| é»è®¤å¼ä¸å¼ | ææä»¶å°±å è½½ | å
³ï¼è¦ --enable-mcp |
| ä¾èµ | æ | å¯é extraï¼mcp SDK |
ç¬¬ä¸æ¥ï¼ä» factory ç if host.enable_mcp å¼å§
读 MCPï¼å»ºè®®å
æå¼ runtime/factory.py ç build_runtime_context()ãSkills åå
置工å
·å¤çå®ä¹åï¼ææ¯ MCP 忝ï¼
# runtime/factory.py â build_runtime_context()
host._register_builtin_tools() # â å
置工å
·å
è¿ Registry
host._mcp_clients = []
host._mcp_tools_prompt = ""
if host.enable_mcp: # â¡ é»è®¤ Falseï¼æ²¡å¼åæ´æ®µè·³è¿
host._register_mcp_tools()
host.context_builder = ContextBuilder(
tool_registry=host.tool_registry,
mcp_tools_prompt=host._mcp_tools_prompt, # ⢠MCP 说æä¹¦è¿ Tool Contracts
tool_prompt_allowlist=frozenset(host.tool_registry.list_tools()) | {"Task"},
...
)
è¿ä¸è¡å°±æ¯ MCP å¯å¨çæ»å¼å ³ï¼
enable_mcp为 False â ä¸é¢ä»ä¹é½ä¸åçï¼_mcp_clientsä¿æç©ºå表ï¼Registry éåªæå ç½®å·¥å ·- 为 True â è°
host._register_mcp_tools()ï¼è¿å¤é¨ serverãåç°å·¥å ·ã注å Adapter - æ 论å¼ä¸å¼ï¼æ¥çé½ä¼å»º
ContextBuilderï¼å¼äº MCP æ¶ï¼_mcp_tools_promptå·²ç»å¡«å¥½ï¼ä¼æ¼è¿ system ç Tool Contracts
enable_mcp ä»åªæ¥ï¼é»è®¤ Config.enable_mcp = Falseï¼CLI --enable-mcp æç¯å¢åé ENABLE_MCP=true æå¼ãè¿ä¸æ¯å«éº»ç¦ââstdio ä¼æèµ·åè¿ç¨ï¼SDKï¼mcpãanyioï¼ä¹ä¸æè¿æ ¸å¿å®è£
ã
ç¬¬äºæ¥ï¼_register_mcp_tools() å§æç» bootstrap
factory åªå夿åè°ç¨ï¼å
·ä½é»è¾å¨ host._register_mcp_tools()ï¼
# runtime/host.py â _register_mcp_tools()ï¼ç®åï¼
clients, tools_meta = register_mcp_servers(self.tool_registry, self.project_root)
self._mcp_clients = clients
self._mcp_tools_prompt = format_mcp_tools_prompt(tools_meta)
register_mcp_servers()è´è´£è¿ serverãåç°å·¥å ·ãå¾ å·²ætool_registryéå¡ Adapter- è¿åç
clientsåå°self._mcp_clientsï¼CodeAgent.close()æ¶é个close_sync()ï¼é¿å åè¿ç¨æ³æ¼ tools_metaæ ¼å¼åæèªç¶è¯è¨ï¼ä¾ ContextBuilder 追å## MCP Tools
没è£
MCP extra å´å¼äºå¼å
³ï¼è¿é伿 MCPExtraRequiredErrorï¼æç¤º pip install 'mycodeagent[mcp]'ãæå è½½å¨ bootstrap éââåªæèµ°è¿ _register_mcp_tools() æ import SDKï¼
# extensions/mcp/bootstrap.py â åªæçæ£æ³¨åæ¶æç¢° SDK
def _load_mcp_runtime():
try:
from extensions.mcp.adapter import register_mcp_tools
from extensions.mcp.client import MCPClient, MCPClientConfig
except ImportError as exc:
raise MCPExtraRequiredError(...) from exc
return MCPClient, MCPClientConfig, register_mcp_tools
æ ¸å¿å®è£
æµè¿ï¼æ²¡å¼ enable_mcpï¼factory ä¸ä¼èµ°å°è¿éï¼ä¸»å¾ªç¯ç
§æ ·è½è·ã
ç¬¬ä¸æ¥ï¼é ç½®ä»åªæ¥
load_mcp_servers() æä¼å
级读ï¼
- ç¯å¢åé
MCP_SERVERSï¼ä¸æ®µ JSONï¼ - é¡¹ç®æ ¹ä¸
mcp_servers.json/.mcp.json/mcp.json
å
¼å®¹ Claude 常è§çå
ä¸å± mcpServersï¼
{
"mcpServers": {
"docs": { "command": "uvx", "args": ["mcp-server-fetch"] },
"search": { "url": "https://example.com/mcp" }
}
}
没æ url å°±å½ stdioï¼ç¨ command + args æèµ·è¿ç¨ãæ urlï¼æ transport=httpï¼èµ° HTTPãuvx/uv ä¼é¢å¤æç¼åç®å½éå°é¡¹ç®ä¸ç .uv_cacheï¼é¿å
污æç¨æ·å
¨å±ç¯å¢ã
MCP_CONNECT_MODE é»è®¤ startupï¼å¯å¨æ¶è¿æ¥å¹¶ list_toolsã设æ disabled çäºé
ç½®å¨ãä½ä¸è¿ã
ç¬¬åæ¥ï¼åç°å·¥å ·ï¼å¡è¿ Registry
register_mcp_servers() 对æ¯ä¸ª server 建ä¸ä¸ª MCPClientï¼å list_tools_sync()ãæ¯ä¸ªè¿ç«¯å·¥å
·åæä¸ä¸ª MCPToolAdapterï¼
# extensions/mcp/adapter.py â åç° + å½åï¼ç®åï¼
raw_public_name = f"{namespace}:{remote_name}" # docs:search
public_name = sanitize_tool_name(raw_public_name) # docs_search
public_name = ensure_unique(public_name) # ä¸å
ç½®åå²çªå _2
adapter = MCPToolAdapter(client, public_name, remote_name, description, schema)
tool_registry.register_tool(adapter)
ä¸ä¸ªç»èï¼
- å½å空é´ï¼å
¬å¼å带 server åç¼ï¼é¿å
两家é½å«
search - æ¸
æ´ï¼Function Calling çåååªå
许
[a-zA-Z0-9_-]ï¼åå·ä¼åæä¸å线 - schema æå½±ï¼è¿ç«¯
inputSchemaè½¬ææ¬å°ToolParameterå表ï¼äºæ¯get_openai_tools()èªå¨å¸¦ä¸è¿äºå·¥å ·ââæ¨¡åçè§å®ä»¬ï¼åçè§Readçæ¹å¼ç¸å
Adapter ç run() ä¸å读æ¬å°æä»¶ï¼èæ¯ mcp_client.call_tool_sync(remote_name, parameters)ã对 Orchestrator / Executor æ¥è¯´ï¼è¿å°±æ¯åä¸ä¸ª Toolã
list_tools çåºå±ï¼session.list_tools() ååºä¸æ¡ JSON-RPC 请æ±ï¼
{"method": "tools/list", "params": {}}
server è¿å宿´é²çææå·¥å
·ï¼æ¯ä¸ªå¸¦ nameãdescriptionãinputSchemaãregister_mcp_tools æè¿ä¸ªå表éåä¸éï¼æ¯ä¸ªå·¥å
·åæä¸ä¸ª MCPToolAdapter è¿ Registryã
call_tool çåºå±ï¼æ¨¡å触åå·¥å
·è°ç¨åï¼Adapter ç run() ååºï¼
{"method": "tools/call", "params": {"name": "search", "arguments": {"query": "..."}}}
server æ§è¡åè¿å MCP content åï¼protocol.py æå®æå½±ææ¬é¡¹ç®çéç¨ä¿¡å°ï¼ç¬¬å
æ¥è¯¦è¿°ï¼ã
ç¬¬äºæ¥ï¼å¼æ¥ SDKï¼åæ¥å·¥å ·ç®¡é
MCP 宿¹ SDK å
¨æ¯ asyncï¼ä½ Tool.run() æ¯åæ¥çãè¿æ¯æ ¸å¿çç¾ï¼ä¸»å¾ªç¯æ¯åæ¥ä»£ç ï¼å·¥å
·æ§è¡ä¸è½çªç¶åæ awaitã
MCPClient èªå·±ææä¸ä¸ªç§æ event loopï¼ç¨ _run_sync æåç¨å µæåæ¥è°ç¨ï¼
# extensions/mcp/client.py
def _run_sync(self, coro):
try:
asyncio.get_running_loop()
# å·²å¨å«ç loop éï¼æ æ³å run_until_completeï¼ç´æ¥æ¥é
# é¿å
ãå¨ running loop é run_until_completeãè¿ç§æ»é
raise RuntimeError("cannot run inside an active event loop")
except RuntimeError:
pass # ä¸å¨ loop éï¼å®å
¨
if self._loop is None or self._loop.is_closed():
self._loop = asyncio.new_event_loop()
return self._loop.run_until_complete(coro)
# 坹夿´é²åæ¥çæ¬ï¼Tool.run() è°è¿äº
def list_tools_sync(self): return self._run_sync(self.list_tools())
def call_tool_sync(self, name, arguments): return self._run_sync(self.call_tool(name, arguments))
è¿æ¥å»ºç«ï¼ä¸¤ç§ transport çè¿æ¥æ¹å¼ä¸åï¼ä½å»ºå®å对ä¸å±å®å ¨ä¸æ ·ï¼
stdioï¼æ¬å°åè¿ç¨ï¼ï¼
StdioServerParameters(command="uvx", args=[...])
â æèµ·åè¿ç¨ï¼å»ºç« stdin/stdout 管é
â ClientSession(read, write)
â session.initialize() â MCP æ¡æï¼åååè®®çæ¬
httpï¼è¿ç¨æå¡ï¼ï¼
streamablehttp_client(url)
â å»ºç« HTTP è¿æ¥
â ClientSession(read, write)
â session.initialize()
è¿æ¥æ¯æçï¼ç¬¬ä¸æ¬¡ list_tools æ call_tool æçæ£ connect()ãä¼è¯è¢«å¯¹ç«¯å
³æï¼ClosedResourceErrorï¼æ¶ï¼å
close() åéè¿ä¸æ¬¡ãCodeAgent.close() ä¼æææ _mcp_clients é个 close_sync()ï¼é¿å
stdio åè¿ç¨æ³æ¼ã
ç¬¬å æ¥ï¼MCP ç»ææ¶æéç¨ä¿¡å°
è¿ç«¯è¿åçæ¯ MCP ç content åï¼text / resource / äºè¿å¶ï¼ï¼ä¸æ¯æ¬é¡¹ç®ç {status, data, text, ...}ãprotocol.py åæå½±ï¼
- æ½åº text åæ¼æ
text structuredContentæ¾è¿data.structured- éææ¬åæ
[binary content ...]/[resource uri]æè¦ isError=trueèµ° error ä¿¡å°
é误æåå åç±»ï¼æ¹ä¾¿æ¨¡åï¼åçæå¨ï¼åºåãåæ°åéäºãè¿æ¯ã坹颿äºãï¼
| æ åµ | error.code |
|---|---|
| schema æ ¡éªå¤±è´¥ | MCP_PARAM_ERROR |
| è§£æ/å è£ å¤±è´¥ | MCP_PARSE_ERROR |
| è¶ æ¶ / è¿æ¥å¤±è´¥ | MCP_TIMEOUT / MCP_NETWORK_ERROR |
| è¿ç«¯æ§è¡å¤±è´¥ | MCP_EXECUTION_ERROR |
è¿å第 05 ç¯çåè®®æ¯åä¸å¥é¡¶å±åæ®µãæ¨¡åä¸éè¦ç¥éå·¥å ·æ¯æ¬å° Python è¿æ¯ MCP è¿ç¨ã
ç¬¬ä¸æ¥ï¼æ¨¡åæä¹ãçè§ãå®ä»¬
两æ¡ééï¼å第 04 / 05 ç¯ä¸è´ï¼
tools=schemaï¼Adapter å·²å¨ Registry éï¼get_openai_tools()èªç¶å¸¦ä¸- Tool Contracts ææ¬ï¼
format_mcp_tools_prompt()çæ- name: desc+params: ...ï¼ContextBuilder追å## MCP Tools
å server 注åå¤±è´¥åªæ warningãè·³è¿ï¼ä¸ææ´ä¸ª agent å¯å¨ææãè¿æ¯å¤é¨è¿ç¨çæ£ç¡®å§¿æï¼å¯¹é¢éæ¶å¯è½æ²¡è£ 好ã
ä¸ä¸ªéè¦æ³¨æç MVP éå¶
æéåç±»å¨çç½ååæ¯ç¡¬ç¼ç çï¼
# tools/permissions.py â RiskClassifier.classify()
READ_ONLY_TOOLS = {"Read", "Grep", "Glob"} # â ALLOW
WRITE_TOOLS = {"Edit"} # â ALLOWï¼æè·¯å¾æè¡ï¼
"TodoWrite" # â ALLOW
"Bash" # â é»/ç°/ç½ååéæ¡å¹é
"Skill" # â ALLOW
# å
¶ä»ææåå â DENYï¼fail-closedï¼
MCP å·¥å
·åæ¯å¨æçï¼docs_searchãserver_name:tool_nameï¼ï¼ä¸å¨è¿ä¸ªç½ååéï¼èµ°å°æå䏿¡ï¼
return PermissionDecision(
action=PermissionAction.DENY,
risk=RiskLevel.UNKNOWN,
reason=f"unknown tool '{tool_name}' fails closed",
)
Executor æ¿å° DENY ç´æ¥çè·¯è¿å PERMISSION_DENIED é误ï¼å·¥å
·ä¸ä¼å®é
æ§è¡ã
è¿æ¯å½åå®ç°çå·²ç¥å±éï¼ä¸æ¯æç»è®¾è®¡ãæéç³»ç»è¿æ²¡æ©å±å°å¨æå·¥å ·åãè¦è®© MCP å·¥å ·çæ£å¯ç¨ï¼éè¦å¨åç±»å¨éå "已注åç MCP å·¥å · â ALLOW"åæ¯ï¼ææ¹æå¯é ç½®ç½ååã
è¿éæä¸ä¸ªéè¦ç设计ååå¼å¾ä¿çï¼è¿ Registry â å
许æ§è¡ãRegistry æ§å¶"模åè½å¨ schema éçå°ä»ä¹"ï¼æé鍿§å¶"ä»ä¹è½çæ£è½å°æ§è¡"ââ两è
å离ï¼å³ä½¿ Registry æ©å¤§ï¼æ§è¡æä»å¯ä»¥ç¬ç«æ¶ç´§ãMCP å·¥å
·ä¸æ¦å å
¥ç½ååï¼ç¼æä¾§åæ ·ä¿å®ï¼ToolOrchestrator åªæ Read/Grep/Glob å½å¹¶åå®å
¨ï¼MCP å·¥å
·èµ°ä¸²è¡æ¹æ¬¡ã
设计亮ç¹
- å¯éä¾èµï¼æ ¸å¿å®è£ é¶ MCPï¼å¼å ³ + extra åæ¶æ»¡è¶³æå è½½ SDK
- Adapter è䏿¯å¹³è¡ç®¡éï¼å¤é¨å·¥å
·å¤ç¨ Registry / schema / ç¼æï¼ä¸å¨ loop éå
if mcp - åç°å¤±è´¥å¯é级ï¼å个 server æäºï¼å ¶ä½å·¥å ·å主循ç¯ç»§ç»
- 注å䏿æå离ï¼ç®å½å¯ä»¥åå¤§ï¼æ§è¡æä» fail-closed
- ç»ææå½±ï¼MCP content å䏿³æ¼è¿æ¨¡åä¸ä¸æçãå¦ä¸ç§ JSONã
å°ç»
| æºå¶ | ä½ç¨ |
|---|---|
factory.py if host.enable_mcp | å¯å¨å ¥å£ï¼å ç½®å·¥å ·æ³¨ååæèµ° MCP |
_register_mcp_tools() | å§æ bootstrapï¼ä¿å clients / tools_prompt |
--enable-mcp / ENABLE_MCP | é»è®¤å ³éï¼é¿å æ è°æèµ·åè¿ç¨ |
mcp.json / MCP_SERVERS | 声æ stdio æ HTTP server |
MCPClient | æè¿æ¥ã忥å°è£ ãæçº¿éè¿ |
MCPToolAdapter | è¿ç«¯å·¥å
· â æ¬å° Tool |
protocol.py | MCP ç»æ â éç¨å·¥å ·ä¿¡å° |
| æé fail-closed | æªç¥ MCP å·¥å ·åé»è®¤æç»æ§è¡ |
ä¸ä¸ç¯è¿å
¥å agentï¼ä¸ä¸ªä»»å¡å¤ªå¤ææ¶ï¼Task å·¥å
·å¦ä½æå·¥ä½æç»å¦ä¸ä¸ªè½»é循ç¯ã
å ³äºæ¬ç³»åçæºç
æ¬ç³»åææåæååºäºå¼æºé¡¹ç® MyCodeAgentã
æºç éå·²ç»æç §æ¬ç³»åæç« ç讲解顺åºï¼å¨å ³é®ä½ç½®å å ¥äºé 奿³¨éââ读æç« æ¶å¯ä»¥å¯¹ç §ä»£ç ï¼ä¹å¯ä»¥ç´æ¥å é䏿¥èªå·±è·ãæ¹ãæ©å±ï¼åºäºå®å¼åä½ èªå·±ç agentã
git clone https://github.com/chendongqi/MyCodeAgent
cd MyCodeAgent
cp .env.example .env # å¡«å
¥ä½ ç LLM API key
uv sync
uv run python main.py
欢è¿è®¿é® PrimeSkills ââ ä¸ä¸ªç²¾å¿çåç AI Agent 䏿è½å¸åºï¼ææå 容åç»è¿çå®ä¼ä¸çº§å·¥ä½æµéªè¯ã没æå±å¤´ï¼åªæçæ£ææçä¸è¥¿ã
æ´å¤å®ç¨ç¥è¯åæè¶£äº§åï¼æ¬¢è¿è®¿é®æç个人主页
Aitishiku.com