Learn Claude Code
s14

MCP Tools

Nền tảng đa agent

External Tools, Standard Protocol

444 LOC5 công cụMCP tool bridge
External services can become agent tools through a standard discovery and call protocol.

s04 → s14 → s15 → s16 → s17

Tầng Harness: MCP Tools — kết nối tới dịch vụ, phát hiện tool, rồi thêm chúng vào vòng lặp Agent.


Vấn đề

Các tool cơ bản ở các chương trước được viết trực tiếp trong code.py. Ta có thể tích hợp hệ thống tài liệu và nền tảng triển khai bằng cách thêm search_docs, deploy_status, và trigger_deploy, nhưng mỗi dịch vụ lại đòi một bộ định nghĩa tool, schema tham số, và handler gọi riêng.

MCP tách các trách nhiệm đó. Server cung cấp danh sách tool và endpoint gọi. Harness kết nối tới nó, gán tên mà mô hình nhìn thấy, áp dụng kiểm tra quyền, rồi đưa các tool đã phát hiện cho mô hình.


Giải pháp

MCP Architecture

Chương này bắt đầu từ năm tool cơ bản và hooks của s04, rồi thêm ba phần:

  • MCPClient lưu định nghĩa tool và handler gọi mà server trả về.
  • connect_mcp kết nối tới một server và lấy danh sách tool của nó.
  • assemble_tool_pool ghép các tool cơ bản với tool từ mọi server đã kết nối.

Server docs và deploy là bản giả lập in-process cho tools/list, tools/call, và một tool pool động. Chương này không triển khai MCP transport thật.


Cách hoạt động

1. Vòng lặp Agent cơ bản giữ nguyên

Trước mỗi lần gọi mô hình, Harness lắp tool pool hiện tại:

def agent_loop(messages: list):
    while True:
        tools, handlers = assemble_tool_pool()
        response = client.messages.create(
            model=MODEL,
            system=assemble_system_prompt(),
            messages=messages,
            tools=tools,
            max_tokens=8000,
        )
        ...

Sau khi một server mới kết nối, lần gọi assemble_tool_pool() tiếp theo thêm tool của nó vào input mô hình. Kết quả tool vẫn được append vào messages dưới dạng block tool_result.

2. MCPClient lưu kết quả phát hiện và handler gọi

class MCPClient:
    def register(self, tool_defs, handlers):
        self.tools = list(tool_defs)
        self._handlers = dict(handlers)

    def call_tool(self, tool_name, args):
        handler = self._handlers.get(tool_name)
        if not handler:
            return f"MCP error: unknown tool '{tool_name}'"
        try:
            return str(handler(**args))
        except Exception as error:
            return f"MCP error: {type(error).__name__}: {error}"

register() đại diện cho danh sách tool đã phát hiện. call_tool() là ranh giới gọi. Lỗi được trả về mô hình thay vì kết thúc vòng lặp Agent.

3. connect_mcp chỉ kết nối và phát hiện

def connect_mcp(name: str) -> str:
    if name in mcp_clients:
        return f"MCP server '{name}' already connected"
    factory = MOCK_SERVERS.get(name)
    if not factory:
        return f"Unknown server '{name}'"
    server = factory()
    mcp_clients[name] = server
    ...

Ban đầu, mô hình thấy năm tool cơ bản và connect_mcp. Sau connect_mcp(name="docs"), Harness lưu client docs. Lần gọi mô hình tiếp theo cũng thấy:

mcp__docs__search
mcp__docs__get_version

4. Prefix tách tool từ các server khác nhau

Nhiều server có thể expose search hoặc status. Harness dùng:

mcp__{server}__{tool}

normalize_mcp_name() thay các ký tự ngoài bảng chữ cái tên tool của mô hình bằng dấu gạch dưới. Việc lắp tool pool cũng kiểm tra va chạm tên đã chuẩn hóa và giới hạn 64 ký tự:

prefixed = f"mcp__{safe_server}__{safe_tool}"
if prefixed in origins:
    raise ValueError("MCP tool name collision after normalization")

Do đó, docs.one/get.version và docs_one/get_version không thể silently map về cùng một tên.

5. Định nghĩa tool và handler vào pool cùng lúc

tools.append({
    "name": prefixed,
    "description": tool_def.get("description", ""),
    "input_schema": schema,
})
handlers[prefixed] = (
    lambda *, client=server, tool=raw_name, **kwargs:
    client.call_tool(tool, kwargs)
)

Mô hình thấy tên đã prefix. Handler gọi MCPClient với tên tool gốc của server. Default argument capture client và tool hiện tại để mỗi lambda không trỏ tới phần tử cuối trong vòng lặp.

6. Host quyết định quyền

MCP server có thể cung cấp readOnlyHint hoặc destructiveHint, nhưng các hint đó đến từ server và không phải ủy quyền. Chương này dùng policy phía host:

MCP_HOST_POLICY = {
    ("docs", "search"): "allow",
    ("docs", "get_version"): "allow",
    ("deploy", "status"): "allow",
    ("deploy", "trigger"): "confirm",
}

permission_hook() tra policy này bằng tên tool đã chuẩn hóa. Tool bên ngoài chưa cấu hình mặc định cần xác nhận. Description chứa readOnly không làm tool được tin tưởng.

7. Lỗi input dừng ở ranh giới tool

Mô hình có thể bỏ argument bắt buộc hoặc gửi field mà server không chấp nhận. Cả execute_tool() và MCPClient.call_tool() bắt các lỗi đó và trả về tool_result lỗi:

MCP error: TypeError: <lambda>() missing 1 required argument: 'query'

Mô hình có thể sửa argument ở lượt tiếp theo mà không kết thúc script bài học.


Thay đổi so với s04

Thành phầns04s14
Tool cơ bảnNăm tool cố địnhKhông đổi
Nguồn toolĐịnh nghĩa trong code.pyTool cơ bản cộng MCP tool đã phát hiện
Tool poolTOOLS cố địnhLắp mỗi lượt bởi assemble_tool_pool()
Tên tool bên ngoàiKhông cómcp__{server}__{tool}
QuyềnKiểm tra shell và pathThêm MCP policy phía host
MCP transportKhông cóBản giả lập server in-process minh họa ranh giới

Chương này không mang theo Task, Background, Cron, Team, hay worktree. Chúng gặp MCP trong s15 Integrated Harness.


Thử chạy

cd learn-claude-code
python s14_mcp_plugin/code.py

Nhập:

Connect to the docs server, search for agent hooks, and tell me the current documentation API version.

Một tool trace điển hình:

connect_mcp(name="docs")
mcp__docs__search(query="agent hooks")
mcp__docs__get_version()

Rồi nhập:

Connect to the deploy server and check the web service status. Do not trigger a deployment.

status chạy theo host policy. trigger cần xác nhận của người dùng.


Tiếp theo

MCP vẫn là một nhánh khóa học độc lập ở đây. s15 Integrated Harness kết hợp các tool cơ bản, hooks, skills, context, memory, tasks, background work, cron, teams, và MCP trong một runtime.