Tool tùy chỉnh mở rộng Agent SDK bằng cách cho phép bạn định nghĩa các hàm riêng mà Claude có thể gọi trong quá trình hội thoại. Sử dụng MCP server chạy trong cùng process (in-process) của SDK, bạn có thể cho Claude quyền truy cập vào cơ sở dữ liệu, API bên ngoài, logic nghiệp vụ đặc thù, hoặc bất kỳ khả năng nào khác mà ứng dụng của bạn cần.
Trang này hướng dẫn cách định nghĩa tool với input schema và handler, đóng gói chúng vào một MCP server, truyền vào query, và kiểm soát tool nào Claude được phép truy cập. Trang cũng đề cập đến xử lý lỗi, tool annotation, và cách trả về nội dung không phải văn bản như hình ảnh.
Tra cứu nhanh
Phần tiêu đề “Tra cứu nhanh”| Nếu bạn muốn… | Hãy làm thế này |
|---|---|
| Định nghĩa một tool | Dùng @tool (Python) hoặc tool() (TypeScript) với tên, mô tả, schema, và handler. Xem Tạo tool tùy chỉnh. |
| Đăng ký tool với Claude | Bọc trong create_sdk_mcp_server / createSdkMcpServer và truyền vào mcpServers trong query(). Xem Gọi tool tùy chỉnh. |
| Duyệt trước (pre-approve) một tool | Thêm vào danh sách tool được phép. Xem Cấu hình tool được phép. |
| Loại bỏ một built-in tool khỏi context của Claude | Truyền một mảng tools chỉ liệt kê những built-in tool bạn muốn giữ lại. Xem Cấu hình tool được phép. |
| Cho Claude gọi tool song song | Đặt readOnlyHint: true cho các tool không có side effect. Xem Thêm tool annotation. |
| Kiểm soát thông báo lỗi Claude đọc được | Trả về isError: true để tự soạn thông báo thay vì để lộ exception gốc. Xem Xử lý lỗi. |
| Trả về hình ảnh hoặc file | Dùng block image hoặc resource trong mảng content. Xem Trả về hình ảnh và resource. |
| Trả về kết quả dạng JSON máy đọc được | Đặt structuredContent trong kết quả. Xem Trả về dữ liệu có cấu trúc. |
| Mở rộng lên nhiều tool | Dùng tool search để nạp tool theo yêu cầu. |
Tạo tool tùy chỉnh
Phần tiêu đề “Tạo tool tùy chỉnh”Một tool được định nghĩa bởi bốn thành phần, truyền vào helper tool() trong TypeScript hoặc decorator @tool trong Python:
- Tên (Name): định danh duy nhất mà Claude dùng để gọi tool.
- Mô tả (Description): tool làm gì. Claude đọc phần này để quyết định khi nào nên gọi tool.
- Input schema: các tham số Claude phải cung cấp. Trong TypeScript, đây luôn là một Zod schema, và
argscủa handler được suy ra kiểu tự động từ schema đó. Trong Python, đây là một dict ánh xạ tên tham số sang kiểu dữ liệu, ví dụ{"latitude": float}, mà SDK sẽ chuyển thành JSON Schema giúp bạn. Decorator Python cũng chấp nhận trực tiếp một dict JSON Schema đầy đủ khi bạn cần enum, khoảng giá trị, trường tùy chọn, hoặc object lồng nhau. - Handler: hàm bất đồng bộ chạy khi Claude gọi tool. Handler nhận các tham số đã được xác thực và phải trả về một object gồm:
content(bắt buộc): mảng các block kết quả, mỗi block cótypelà"text","image","audio","resource", hoặc"resource_link". Xem Trả về hình ảnh và resource cho các block không phải văn bản.structuredContent(tùy chọn): một object JSON chứa kết quả dưới dạng dữ liệu máy đọc được, trả về kèm theocontent. Xem Trả về dữ liệu có cấu trúc.isError(tùy chọn): đặttrueđể báo hiệu tool thất bại, giúp Claude phản ứng phù hợp. Xem Xử lý lỗi.
Sau khi định nghĩa tool, bọc nó trong một server bằng createSdkMcpServer (TypeScript) hoặc create_sdk_mcp_server (Python). Server này chạy trong cùng process với ứng dụng của bạn, không phải là một process riêng biệt.
Ví dụ: tool thời tiết
Phần tiêu đề “Ví dụ: tool thời tiết”Ví dụ này định nghĩa tool get_temperature và bọc nó vào một MCP server. Nó chỉ thiết lập tool; để truyền vào query và chạy, xem Gọi tool tùy chỉnh bên dưới.
from typing import Anyimport httpxfrom claude_agent_sdk import tool, create_sdk_mcp_server
# Định nghĩa một tool: tên, mô tả, input schema, handler@tool( "get_temperature", "Get the current temperature at a location", {"latitude": float, "longitude": float},)async def get_temperature(args: dict[str, Any]) -> dict[str, Any]: async with httpx.AsyncClient() as client: response = await client.get( "https://api.open-meteo.com/v1/forecast", params={ "latitude": args["latitude"], "longitude": args["longitude"], "current": "temperature_2m", "temperature_unit": "fahrenheit", }, ) data = response.json()
# Trả về một content array - Claude sẽ thấy đây là kết quả của tool return { "content": [ { "type": "text", "text": f"Temperature: {data['current']['temperature_2m']}°F", } ] }
# Bọc tool trong một MCP server chạy cùng processweather_server = create_sdk_mcp_server( name="weather", version="1.0.0", tools=[get_temperature],)import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";import { z } from "zod";
// Định nghĩa một tool: tên, mô tả, input schema, handlerconst getTemperature = tool( "get_temperature", "Get the current temperature at a location", { latitude: z.number().describe("Latitude coordinate"), // .describe() thêm mô tả trường mà Claude sẽ thấy longitude: z.number().describe("Longitude coordinate") }, async (args) => { // args được suy ra kiểu từ schema: { latitude: number; longitude: number } const response = await fetch( `https://api.open-meteo.com/v1/forecast?latitude=${args.latitude}&longitude=${args.longitude}¤t=temperature_2m&temperature_unit=fahrenheit` ); const data: any = await response.json();
// Trả về một content array - Claude sẽ thấy đây là kết quả của tool return { content: [{ type: "text", text: `Temperature: ${data.current.temperature_2m}°F` }] }; });
// Bọc tool trong một MCP server chạy cùng processconst weatherServer = createSdkMcpServer({ name: "weather", version: "1.0.0", tools: [getTemperature]});Xem tham chiếu TypeScript tool() hoặc tham chiếu Python @tool để biết chi tiết đầy đủ về tham số, bao gồm các định dạng input JSON Schema và cấu trúc giá trị trả về.
Gọi tool tùy chỉnh
Phần tiêu đề “Gọi tool tùy chỉnh”Truyền MCP server bạn đã tạo vào query qua tùy chọn mcpServers. Key trong mcpServers sẽ trở thành đoạn {server_name} trong tên đầy đủ của mỗi tool: mcp__{server_name}__{tool_name}. Liệt kê tên này trong allowedTools để tool chạy mà không cần prompt xin quyền.
Các đoạn code dưới đây tái sử dụng weatherServer từ ví dụ trên để hỏi Claude thời tiết ở một địa điểm cụ thể.
import asynciofrom claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
async def main(): options = ClaudeAgentOptions( mcp_servers={"weather": weather_server}, allowed_tools=["mcp__weather__get_temperature"], )
async for message in query( prompt="What's the temperature in San Francisco?", options=options, ): # ResultMessage là message cuối cùng sau khi mọi tool call hoàn tất if isinstance(message, ResultMessage) and message.subtype == "success": print(message.result)
asyncio.run(main())import { query } from "@anthropic-ai/claude-agent-sdk";
for await (const message of query({ prompt: "What's the temperature in San Francisco?", options: { mcpServers: { weather: weatherServer }, allowedTools: ["mcp__weather__get_temperature"] }})) { // "result" là message cuối cùng sau khi mọi tool call hoàn tất if (message.type === "result" && message.subtype === "success") { console.log(message.result); }}Kết hợp đoạn code này với phần định nghĩa tool và server từ ví dụ tool thời tiết vào một file, rồi chạy bằng python weather.py (Python) hoặc npx tsx weather.ts (TypeScript). Claude sẽ gọi get_temperature và script in ra một dòng trả lời với nhiệt độ hiện tại tại San Francisco.
Thêm nhiều tool hơn
Phần tiêu đề “Thêm nhiều tool hơn”Một server chứa bao nhiêu tool tùy bạn liệt kê trong mảng tools. Với nhiều hơn một tool trên cùng server, bạn có thể liệt kê từng tool riêng trong allowedTools hoặc dùng wildcard mcp__weather__* để bao phủ mọi tool mà server đó cung cấp.
Ví dụ dưới đây định nghĩa thêm tool thứ hai, get_precipitation_chance, và thay thế định nghĩa weatherServer từ ví dụ tool thời tiết bằng một phiên bản liệt kê cả hai tool trong mảng.
# Định nghĩa tool thứ hai cho cùng server@tool( "get_precipitation_chance", "Get the hourly precipitation probability for a location. " "Optionally pass 'hours' (1-24) to control how many hours to return.", {"latitude": float, "longitude": float},)async def get_precipitation_chance(args: dict[str, Any]) -> dict[str, Any]: # 'hours' không có trong schema - đọc bằng .get() để nó trở thành tùy chọn hours = args.get("hours", 12) async with httpx.AsyncClient() as client: response = await client.get( "https://api.open-meteo.com/v1/forecast", params={ "latitude": args["latitude"], "longitude": args["longitude"], "hourly": "precipitation_probability", "forecast_days": 1, }, ) data = response.json() chances = data["hourly"]["precipitation_probability"][:hours]
return { "content": [ { "type": "text", "text": f"Next {hours} hours: {'%, '.join(map(str, chances))}%", } ] }
# Xây lại server với cả hai tool trong mảngweather_server = create_sdk_mcp_server( name="weather", version="1.0.0", tools=[get_temperature, get_precipitation_chance],)// Định nghĩa tool thứ hai cho cùng serverconst getPrecipitationChance = tool( "get_precipitation_chance", "Get the hourly precipitation probability for a location", { latitude: z.number(), longitude: z.number(), hours: z .number() .int() .min(1) .max(24) .default(12) // .default() làm cho tham số trở thành tùy chọn .describe("How many hours of forecast to return") }, async (args) => { const response = await fetch( `https://api.open-meteo.com/v1/forecast?latitude=${args.latitude}&longitude=${args.longitude}&hourly=precipitation_probability&forecast_days=1` ); const data: any = await response.json(); const chances = data.hourly.precipitation_probability.slice(0, args.hours);
return { content: [{ type: "text", text: `Next ${args.hours} hours: ${chances.join("%, ")}%` }] }; });
// Xây lại server với cả hai tool trong mảngconst weatherServer = createSdkMcpServer({ name: "weather", version: "1.0.0", tools: [getTemperature, getPrecipitationChance]});Tool search được bật mặc định và trì hoãn nạp SDK MCP tool: Claude thấy tên của mỗi tool trong một danh sách gọn nhẹ và nạp đầy đủ schema khi cần. Khi tắt tool search, mỗi tool trong mảng này sẽ chiếm không gian context window ở mọi lượt hội thoại. Trong TypeScript, truyền alwaysLoad: true trong tham số extras của tool() hoặc trong options của createSdkMcpServer() để giữ schema đầy đủ của một tool ngay trong prompt ban đầu.
Thêm tool annotation
Phần tiêu đề “Thêm tool annotation”Tool annotation là metadata tùy chọn mô tả cách tool hoạt động. Truyền chúng dưới dạng tham số thứ năm cho helper tool() trong TypeScript, hoặc qua từ khóa annotations cho decorator @tool trong Python. Tất cả các trường hint đều là kiểu Boolean.
| Trường | Mặc định | Ý nghĩa |
|---|---|---|
readOnlyHint | false | Tool không thay đổi môi trường. Kiểm soát xem tool có được phép gọi song song với các tool chỉ đọc khác không. |
destructiveHint | true | Tool có thể thực hiện các cập nhật mang tính phá hủy. Chỉ mang tính thông tin. |
idempotentHint | false | Gọi lặp lại cùng tham số không gây thêm hiệu ứng. Chỉ mang tính thông tin. |
openWorldHint | true | Tool tương tác với hệ thống bên ngoài process của bạn. Chỉ mang tính thông tin. |
Annotation chỉ là metadata, không phải cơ chế thực thi. Một tool đánh dấu readOnlyHint: true vẫn có thể ghi vào đĩa nếu đó là điều handler thực sự làm. Hãy giữ annotation phản ánh đúng hành vi thực tế của handler.
Ví dụ dưới đây thêm readOnlyHint cho tool get_temperature từ ví dụ tool thời tiết.
from claude_agent_sdk import tool, ToolAnnotations
@tool( "get_temperature", "Get the current temperature at a location", {"latitude": float, "longitude": float}, annotations=ToolAnnotations( readOnlyHint=True ), # Cho phép Claude gộp lệnh này với các lệnh chỉ đọc khác)async def get_temperature(args): return {"content": [{"type": "text", "text": "..."}]}import { tool } from "@anthropic-ai/claude-agent-sdk";import { z } from "zod";
tool( "get_temperature", "Get the current temperature at a location", { latitude: z.number(), longitude: z.number() }, async (args) => ({ content: [{ type: "text", text: `...` }] }), { annotations: { readOnlyHint: true } } // Cho phép Claude gộp lệnh này với các lệnh chỉ đọc khác);Xem ToolAnnotations trong tham chiếu TypeScript hoặc Python.
Kiểm soát quyền truy cập tool
Phần tiêu đề “Kiểm soát quyền truy cập tool”Ví dụ tool thời tiết ở trên đã đăng ký một server và liệt kê tool trong allowedTools. Phần này giải thích cách tên tool được xây dựng và cách giới hạn quyền truy cập khi bạn có nhiều tool hoặc muốn hạn chế built-in tool.
Định dạng tên tool
Phần tiêu đề “Định dạng tên tool”Khi các MCP tool được đưa vào context của Claude, tên của chúng theo một định dạng cụ thể:
- Mẫu:
mcp__{server_name}__{tool_name} - Ví dụ: tool tên
get_temperaturetrong serverweathersẽ trở thànhmcp__weather__get_temperature
Cấu hình tool được phép
Phần tiêu đề “Cấu hình tool được phép”Tùy chọn tools cùng với danh sách allowed/disallowed ảnh hưởng đến hai lớp: khả năng hiển thị (availability), quyết định tool có xuất hiện trong context của Claude hay không, và quyền hạn (permission), quyết định một lệnh gọi có được duyệt khi Claude thực hiện hay không. tools và các mục disallowedTools không kèm tham số phạm vi thay đổi khả năng hiển thị. allowedTools và các quy tắc disallowedTools có phạm vi chỉ thay đổi quyền hạn.
| Tùy chọn | Lớp | Hiệu ứng |
|---|---|---|
tools: ["Read", "Grep"] | Khả năng hiển thị | Chỉ những built-in tool được liệt kê mới có trong context của Claude. Các built-in tool khác bị loại bỏ. MCP tool không bị ảnh hưởng. |
tools: [] | Khả năng hiển thị | Mọi built-in tool bị loại bỏ. Claude chỉ có thể dùng MCP tool của bạn. |
| allowed tools | Quyền hạn | Tool được liệt kê chạy mà không cần prompt xin quyền. Tool chưa liệt kê vẫn hiển thị; lệnh gọi sẽ đi qua luồng cấp quyền. |
| disallowed tools | Cả hai | Tên tool trần như "Bash" loại tool đó khỏi context của Claude, giống như khi bỏ nó khỏi tools. Quy tắc có phạm vi như "Bash(rm *)" giữ tool trong context nhưng chỉ từ chối những lệnh gọi khớp mẫu. |
Để loại bỏ hoàn toàn một built-in tool, bỏ nó khỏi tools hoặc liệt kê tên trần trong disallowedTools (Python: disallowed_tools); cả hai cách đều giữ tool ngoài context nên Claude không bao giờ thử gọi nó. Một quy tắc disallowedTools có phạm vi sẽ chặn các lệnh gọi khớp mẫu nhưng vẫn để tool hiển thị, nên Claude có thể tốn một lượt thử gọi nó. Xem Cấu hình quyền hạn để biết thứ tự đánh giá đầy đủ.
Xử lý lỗi
Phần tiêu đề “Xử lý lỗi”Lỗi trong handler không làm dừng agent loop. MCP server chạy cùng process của SDK bắt các exception chưa được xử lý và trả về dưới dạng kết quả lỗi, nên cách bạn báo lỗi quyết định Claude đọc được gì, chứ không quyết định query có thất bại hay không:
| Điều gì xảy ra | Kết quả |
|---|---|
| Handler ném ra một exception chưa được xử lý | MCP server chuyển nó thành kết quả lỗi mang theo thông báo exception gốc. Claude thấy thông báo đó, và agent loop tiếp tục. |
Handler tự bắt lỗi và trả về isError: true (TS) / "is_error": True (Python) | Claude thấy thông báo do bạn soạn. Bạn có thể thêm ngữ cảnh mà exception gốc thiếu, như request nào thất bại hoặc nên thử gì tiếp theo. |
Trong cả hai trường hợp, Claude có thể thử lại, thử một tool khác, hoặc giải thích lỗi cho người dùng. Hãy tự bắt lỗi khi thông báo exception gốc không đủ để Claude hành động dựa trên đó.
Ví dụ dưới đây bắt hai loại lỗi trong handler và tự soạn thông báo lỗi mà Claude đọc được. Mã trạng thái HTTP khác 200 được bắt từ response và trả về dưới dạng kết quả lỗi. Lỗi mạng hoặc JSON không hợp lệ được bắt bởi try/except (Python) hoặc try/catch (TypeScript) bao quanh và cũng trả về kết quả lỗi. Trong cả hai trường hợp, Claude nhận được thông báo mô tả sự cố thay vì một chuỗi exception trần trụi.
import jsonimport httpxfrom typing import Anyfrom claude_agent_sdk import tool
@tool( "fetch_data", "Fetch data from an API", {"endpoint": str}, # Schema đơn giản)async def fetch_data(args: dict[str, Any]) -> dict[str, Any]: try: async with httpx.AsyncClient() as client: response = await client.get(args["endpoint"]) if response.status_code != 200: # Trả lỗi dưới dạng tool result để Claude có thể phản ứng lại # is_error đánh dấu đây là một lệnh gọi thất bại, chứ không phải dữ liệu bất thường return { "content": [ { "type": "text", "text": f"API error: {response.status_code} {response.reason_phrase}", } ], "is_error": True, }
data = response.json() return {"content": [{"type": "text", "text": json.dumps(data, indent=2)}]} except Exception as e: # Soạn thông báo Claude đọc được. Một exception chưa xử lý sẽ đến Claude # dưới dạng str(e) trần trụi, không có ngữ cảnh return { "content": [{"type": "text", "text": f"Failed to fetch data: {str(e)}"}], "is_error": True, }import { tool } from "@anthropic-ai/claude-agent-sdk";import { z } from "zod";
tool( "fetch_data", "Fetch data from an API", { endpoint: z.string().url().describe("API endpoint URL") }, async (args) => { try { const response = await fetch(args.endpoint);
if (!response.ok) { // Trả lỗi dưới dạng tool result để Claude có thể phản ứng lại // isError đánh dấu đây là một lệnh gọi thất bại, chứ không phải dữ liệu bất thường return { content: [ { type: "text", text: `API error: ${response.status} ${response.statusText}` } ], isError: true }; }
const data = await response.json(); return { content: [ { type: "text", text: JSON.stringify(data, null, 2) } ] }; } catch (error) { // Soạn thông báo Claude đọc được. Một throw chưa xử lý sẽ đến Claude // dưới dạng thông báo lỗi trần trụi, không có ngữ cảnh return { content: [ { type: "text", text: `Failed to fetch data: ${error instanceof Error ? error.message : String(error)}` } ], isError: true }; } });Trả về hình ảnh và resource
Phần tiêu đề “Trả về hình ảnh và resource”Mảng content trong kết quả của tool chấp nhận các block text, image, audio, resource, và resource_link. Bạn có thể trộn nhiều loại trong cùng một response. Trong TypeScript, SDK lưu block audio xuống đĩa và Claude nhận được một block text chứa đường dẫn file đã lưu; trong Python, SDK loại bỏ block audio khỏi kết quả tool và ghi log cảnh báo. SDK chuyển các block resource link thành một block text chứa tên, URI, và mô tả của link.
Hình ảnh
Phần tiêu đề “Hình ảnh”Một block image mang theo bytes hình ảnh được mã hóa base64 ngay trong nội dung. Không có trường URL. Để trả về một hình ảnh nằm ở một URL, hãy fetch nó trong handler, đọc bytes của response, và mã hóa base64 trước khi trả về. Kết quả được xử lý như input hình ảnh.
| Trường | Kiểu | Ghi chú |
|---|---|---|
type | "image" | |
data | string | Bytes mã hóa base64. Chỉ base64 thuần, không có tiền tố data:image/...;base64, |
mimeType | string | Bắt buộc. Ví dụ image/png, image/jpeg, image/webp, image/gif |
import base64import httpxfrom claude_agent_sdk import tool
# Định nghĩa một tool fetch hình ảnh từ URL và trả về cho Claude@tool("fetch_image", "Fetch an image from a URL and return it to Claude", {"url": str})async def fetch_image(args): async with httpx.AsyncClient() as client: # Fetch bytes hình ảnh response = await client.get(args["url"])
return { "content": [ { "type": "image", "data": base64.b64encode(response.content).decode( "ascii" ), # Mã hóa base64 bytes thô "mimeType": response.headers.get( "content-type", "image/png" ), # Đọc MIME type từ response } ] }import { tool } from "@anthropic-ai/claude-agent-sdk";import { z } from "zod";
tool( "fetch_image", "Fetch an image from a URL and return it to Claude", { url: z.string().url() }, async (args) => { const response = await fetch(args.url); // Fetch bytes hình ảnh const buffer = Buffer.from(await response.arrayBuffer()); // Đọc vào Buffer để mã hóa base64 const mimeType = response.headers.get("content-type") ?? "image/png";
return { content: [ { type: "image", data: buffer.toString("base64"), // Mã hóa base64 bytes thô mimeType } ] }; });Resource
Phần tiêu đề “Resource”Một block resource nhúng một mẩu nội dung được định danh bởi URI. URI là một nhãn để Claude tham chiếu; nội dung thực nằm trong trường text hoặc blob của block. Dùng khi tool của bạn tạo ra thứ gì đó nên được gọi bằng tên sau này, như một file đã sinh ra hoặc một bản ghi từ hệ thống bên ngoài.
| Trường | Kiểu | Ghi chú |
|---|---|---|
type | "resource" | |
resource.uri | string | Định danh cho nội dung. Bất kỳ scheme URI nào |
resource.text | string | Nội dung, nếu là văn bản. Cung cấp trường này hoặc blob, không cả hai |
resource.blob | string | Nội dung mã hóa base64, nếu là nhị phân. Chỉ TypeScript: Python SDK loại bỏ resource nhị phân khỏi kết quả tool và ghi log cảnh báo |
resource.mimeType | string | Tùy chọn |
Ví dụ dưới đây minh họa một block resource được trả về từ trong handler của tool. URI file:///tmp/report.md là một nhãn Claude có thể tham chiếu sau này; SDK không đọc từ đường dẫn đó.
return { content: [ { type: "resource", resource: { uri: "file:///tmp/report.md", // Nhãn để Claude tham chiếu, không phải đường dẫn SDK đọc mimeType: "text/markdown", text: "# Report\n..." // Nội dung thực, đặt trực tiếp } } ]};return { "content": [ { "type": "resource", "resource": { "uri": "file:///tmp/report.md", # Nhãn để Claude tham chiếu, không phải đường dẫn SDK đọc "mimeType": "text/markdown", "text": "# Report\n...", # Nội dung thực, đặt trực tiếp }, } ]}Các dạng block này bắt nguồn từ kiểu CallToolResult của MCP. Xem đặc tả MCP để biết định nghĩa đầy đủ.
Trả về dữ liệu có cấu trúc
Phần tiêu đề “Trả về dữ liệu có cấu trúc”structuredContent là một object JSON tùy chọn trong kết quả, tách biệt với mảng content. Dùng để trả về các giá trị thô mà Claude có thể đọc như các trường chính xác thay vì phải phân tích chúng từ một chuỗi văn bản hay hình ảnh.
Khi structuredContent được đặt, Claude nhận được JSON đó cộng với bất kỳ block image hoặc resource nào trong content. Block text trong content không được chuyển tiếp, vì được giả định là trùng lặp với dữ liệu có cấu trúc. Ví dụ dưới đây render một biểu đồ dưới dạng block image và trả về các điểm dữ liệu đằng sau nó trong structuredContent từ cùng một handler. Trong đoạn code, chartPngBuffer là một Buffer chứa bytes PNG đã render.
return { content: [ { type: "image", data: chartPngBuffer.toString("base64"), mimeType: "image/png" } ], structuredContent: { series: "temperature_2m", unit: "fahrenheit", points: [62.1, 63.4, 65.0, 64.2] }};Ví dụ: bộ chuyển đổi đơn vị
Phần tiêu đề “Ví dụ: bộ chuyển đổi đơn vị”Tool này chuyển đổi giá trị giữa các đơn vị đo chiều dài, nhiệt độ, và trọng lượng. Người dùng có thể hỏi “convert 100 kilometers to miles” hoặc “what is 72°F in Celsius,” và Claude sẽ chọn đúng loại đơn vị và đơn vị cụ thể từ yêu cầu.
Nó minh họa hai mẫu:
- Enum schema:
unit_typebị giới hạn trong một tập giá trị cố định. Trong TypeScript, dùngz.enum(). Trong Python, dict schema không hỗ trợ enum, nên cần dùng dict JSON Schema đầy đủ. - Xử lý input không được hỗ trợ: khi không tìm thấy cặp chuyển đổi, handler trả về
isError: trueđể Claude có thể báo cho người dùng biết điều gì đã sai, thay vì coi lỗi như một kết quả bình thường.
from typing import Anyfrom claude_agent_sdk import tool, create_sdk_mcp_server
# z.enum() trong TypeScript trở thành ràng buộc "enum" trong JSON Schema.# Dict schema không có tương đương, nên cần JSON Schema đầy đủ.@tool( "convert_units", "Convert a value from one unit to another", { "type": "object", "properties": { "unit_type": { "type": "string", "enum": ["length", "temperature", "weight"], "description": "Category of unit", }, "from_unit": { "type": "string", "description": "Unit to convert from, e.g. kilometers, fahrenheit, pounds", }, "to_unit": {"type": "string", "description": "Unit to convert to"}, "value": {"type": "number", "description": "Value to convert"}, }, "required": ["unit_type", "from_unit", "to_unit", "value"], },)async def convert_units(args: dict[str, Any]) -> dict[str, Any]: conversions = { "length": { "kilometers_to_miles": lambda v: v * 0.621371, "miles_to_kilometers": lambda v: v * 1.60934, "meters_to_feet": lambda v: v * 3.28084, "feet_to_meters": lambda v: v * 0.3048, }, "temperature": { "celsius_to_fahrenheit": lambda v: (v * 9) / 5 + 32, "fahrenheit_to_celsius": lambda v: (v - 32) * 5 / 9, "celsius_to_kelvin": lambda v: v + 273.15, "kelvin_to_celsius": lambda v: v - 273.15, }, "weight": { "kilograms_to_pounds": lambda v: v * 2.20462, "pounds_to_kilograms": lambda v: v * 0.453592, "grams_to_ounces": lambda v: v * 0.035274, "ounces_to_grams": lambda v: v * 28.3495, }, }
key = f"{args['from_unit']}_to_{args['to_unit']}" fn = conversions.get(args["unit_type"], {}).get(key)
if not fn: return { "content": [ { "type": "text", "text": f"Unsupported conversion: {args['from_unit']} to {args['to_unit']}", } ], "is_error": True, }
result = fn(args["value"]) return { "content": [ { "type": "text", "text": f"{args['value']} {args['from_unit']} = {result:.4f} {args['to_unit']}", } ] }
converter_server = create_sdk_mcp_server( name="converter", version="1.0.0", tools=[convert_units],)import { tool, createSdkMcpServer } from "@anthropic-ai/claude-agent-sdk";import { z } from "zod";
const convert = tool( "convert_units", "Convert a value from one unit to another", { unit_type: z.enum(["length", "temperature", "weight"]).describe("Category of unit"), from_unit: z .string() .describe("Unit to convert from, e.g. kilometers, fahrenheit, pounds"), to_unit: z.string().describe("Unit to convert to"), value: z.number().describe("Value to convert") }, async (args) => { type Conversions = Record<string, Record<string, (v: number) => number>>;
const conversions: Conversions = { length: { kilometers_to_miles: (v) => v * 0.621371, miles_to_kilometers: (v) => v * 1.60934, meters_to_feet: (v) => v * 3.28084, feet_to_meters: (v) => v * 0.3048 }, temperature: { celsius_to_fahrenheit: (v) => (v * 9) / 5 + 32, fahrenheit_to_celsius: (v) => ((v - 32) * 5) / 9, celsius_to_kelvin: (v) => v + 273.15, kelvin_to_celsius: (v) => v - 273.15 }, weight: { kilograms_to_pounds: (v) => v * 2.20462, pounds_to_kilograms: (v) => v * 0.453592, grams_to_ounces: (v) => v * 0.035274, ounces_to_grams: (v) => v * 28.3495 } };
const key = `${args.from_unit}_to_${args.to_unit}`; const fn = conversions[args.unit_type]?.[key];
if (!fn) { return { content: [ { type: "text", text: `Unsupported conversion: ${args.from_unit} to ${args.to_unit}` } ], isError: true }; }
const result = fn(args.value); return { content: [ { type: "text", text: `${args.value} ${args.from_unit} = ${result.toFixed(4)} ${args.to_unit}` } ] }; });
const converterServer = createSdkMcpServer({ name: "converter", version: "1.0.0", tools: [convert]});Sau khi định nghĩa server, truyền nó vào query giống như ví dụ thời tiết. Ví dụ này gửi ba prompt khác nhau trong một vòng lặp để cho thấy cùng một tool xử lý các loại đơn vị khác nhau. Với mỗi response, nó kiểm tra các object AssistantMessage (chứa các tool call Claude thực hiện trong lượt đó) và in ra từng ToolUseBlock trước khi in văn bản ResultMessage cuối cùng. Cách này cho phép bạn thấy khi nào Claude đang dùng tool so với khi nào nó trả lời từ kiến thức riêng.
Vì tool search được bật mặc định, kết quả có thể bao gồm cả một lệnh gọi ToolSearch khi Claude nạp schema của tool đã bị trì hoãn.
import asynciofrom claude_agent_sdk import ( query, ClaudeAgentOptions, ResultMessage, AssistantMessage, ToolUseBlock,)
async def main(): options = ClaudeAgentOptions( mcp_servers={"converter": converter_server}, allowed_tools=["mcp__converter__convert_units"], )
prompts = [ "Convert 100 kilometers to miles.", "What is 72°F in Celsius?", "How many pounds is 5 kilograms?", ]
for prompt in prompts: try: async for message in query(prompt=prompt, options=options): if isinstance(message, AssistantMessage): for block in message.content: if isinstance(block, ToolUseBlock): print(f"[tool call] {block.name}({block.input})") elif isinstance(message, ResultMessage) and message.subtype == "success": print(f"Q: {prompt}\nA: {message.result}\n") except Exception as error: # query() một lượt sẽ raise sau khi trả về một kết quả lỗi. Chỉ các kết quả # success mới được in ở trên, nên xử lý lỗi ở đây và tiếp tục với prompt tiếp theo. print(f"Call failed: {error}")
asyncio.run(main())import { query } from "@anthropic-ai/claude-agent-sdk";
const prompts = [ "Convert 100 kilometers to miles.", "What is 72°F in Celsius?", "How many pounds is 5 kilograms?"];
for (const prompt of prompts) { try { for await (const message of query({ prompt, options: { mcpServers: { converter: converterServer }, allowedTools: ["mcp__converter__convert_units"] } })) { if (message.type === "assistant") { for (const block of message.message.content) { if (block.type === "tool_use") { console.log(`[tool call] ${block.name}`, block.input); } } } else if (message.type === "result" && message.subtype === "success") { console.log(`Q: ${prompt}\nA: ${message.result}\n`); } } } catch (error) { // query() một lượt sẽ throw sau khi trả về một kết quả lỗi. Chỉ các kết quả // success mới được log ở trên, nên xử lý lỗi ở đây và tiếp tục với prompt tiếp theo. console.error(`Call failed: ${error}`); }}Bước tiếp theo
Phần tiêu đề “Bước tiếp theo”Tool tùy chỉnh bọc các hàm bất đồng bộ trong một giao diện chuẩn. Bạn có thể trộn các mẫu trong trang này trong cùng một server: một server duy nhất có thể chứa một tool cơ sở dữ liệu, một tool gateway API, và một tool render hình ảnh cạnh nhau.
Từ đây, bạn có thể:
- Nếu server của bạn phát triển lên hàng chục tool, xem tool search để trì hoãn việc nạp chúng cho đến khi Claude cần.
- Để kết nối tới các MCP server bên ngoài (filesystem, GitHub, Slack) thay vì tự xây dựng, xem Kết nối MCP server.
- Để kiểm soát tool nào chạy tự động so với tool cần duyệt, xem Cấu hình quyền hạn.
lượt xem