Предоставьте Claude пользовательские инструменты
Определите пользовательские инструменты с помощью встроенного MCP-сервера Agent SDK, чтобы Claude мог вызывать ваши функции, обращаться к вашим API и выполнять операции, специфичные для вашей области.
Пользовательские инструменты расширяют Agent SDK, позволяя вам определять собственные функции, которые Claude может вызывать во время разговора. Используя встроенный MCP-сервер SDK, вы можете предоставить Claude доступ к базам данных, внешним API, логике, специфичной для вашей области, или любым другим возможностям, которые требует ваше приложение.
В этом руководстве рассказывается, как определять инструменты с входными схемами и обработчиками, объединять их в MCP-сервер, передавать их в query и контролировать, к каким инструментам Claude может получить доступ. Оно также охватывает обработку ошибок, аннотации инструментов и возврат нетекстового содержимого, такого как изображения.
Краткая справка
| Если вы хотите... | Сделайте это |
|---|---|
| Определить инструмент | Используйте @tool (Python) или tool() (TypeScript) с именем, описанием, схемой и обработчиком. См. Создание пользовательского инструмента. |
| Зарегистрировать инструмент с Claude | Оберните в create_sdk_mcp_server / createSdkMcpServer и передайте в mcpServers в query(). См. Вызов пользовательского инструмента. |
| Предварительно одобрить инструмент | Добавьте в разрешённые инструменты. См. Настройка разрешённых инструментов. |
| Удалить встроенный инструмент из контекста Claude | Передайте массив tools, содержащий только встроенные инструменты, которые вы хотите. См. Настройка разрешённых инструментов. |
| Позволить Claude вызывать инструменты параллельно | Установите readOnlyHint: true на инструментах без побочных эффектов. См. Добавление аннотаций инструментов. |
| Обработать ошибки без остановки цикла | Верните isError: true вместо выброса исключения. См. Обработка ошибок. |
| Вернуть изображения или файлы | Используйте блоки image или resource в массиве содержимого. См. Возврат изображений и ресурсов. |
| Вернуть результат в формате машиночитаемого JSON | Установите structuredContent на результат. См. Возврат структурированных данных. |
| Масштабировать до множества инструментов | Используйте поиск инструментов для загрузки инструментов по требованию. |
Создание пользовательского инструмента
Инструмент определяется четырьмя частями, передаваемыми в качестве аргументов вспомогательной функции tool() в TypeScript или декоратору @tool в Python:
- Имя: уникальный идентификатор, который Claude использует для вызова инструмента.
- Описание: что делает инструмент. Claude читает это, чтобы решить, когда его вызывать.
- Входная схема: аргументы, которые должен предоставить Claude. В TypeScript это всегда схема Zod, и типы
argsобработчика автоматически выводятся из неё. В Python это словарь, отображающий имена на типы, например{"latitude": float}, который SDK преобразует в JSON Schema для вас. Декоратор Python также принимает полный словарь JSON Schema непосредственно, когда вам нужны перечисления, диапазоны, необязательные поля или вложенные объекты. - Обработчик: асинхронная функция, которая запускается, когда Claude вызывает инструмент. Она получает проверенные аргументы и должна вернуть объект с:
content(обязательно): массив блоков результатов, каждый с типом"text","image"или"resource". См. Возврат изображений и ресурсов для нетекстовых блоков.structuredContent(необязательно): объект JSON, содержащий результат как машиночитаемые данные, возвращаемые вместе сcontent. См. Возврат структурированных данных.isError(необязательно): установите наtrue, чтобы сигнализировать об ошибке инструмента, чтобы Claude мог на неё реагировать. См. Обработка ошибок.
После определения инструмента оберните его в сервер с помощью createSdkMcpServer (TypeScript) или create_sdk_mcp_server (Python). Сервер работает встроенным образом внутри вашего приложения, а не как отдельный процесс.
Пример инструмента погоды
Этот пример определяет инструмент get_temperature и оборачивает его в MCP-сервер. Он только настраивает инструмент; чтобы передать его в query и запустить его, см. Вызов пользовательского инструмента ниже.
from typing import Any
import httpx
from claude_agent_sdk import tool, create_sdk_mcp_server
# Define a tool: name, description, 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()
# Return a content array - Claude sees this as the tool result
return {
"content": [
{
"type": "text",
"text": f"Temperature: {data['current']['temperature_2m']}°F",
}
]
}
# Wrap the tool in an in-process MCP server
weather_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";
// Define a tool: name, description, input schema, handler
const getTemperature = tool(
"get_temperature",
"Get the current temperature at a location",
{
latitude: z.number().describe("Latitude coordinate"), // .describe() adds a field description Claude sees
longitude: z.number().describe("Longitude coordinate")
},
async (args) => {
// args is typed from the 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();
// Return a content array - Claude sees this as the tool result
return {
content: [{ type: "text", text: `Temperature: ${data.current.temperature_2m}°F` }]
};
}
);
// Wrap the tool in an in-process MCP server
const weatherServer = createSdkMcpServer({
name: "weather",
version: "1.0.0",
tools: [getTemperature]
});
См. справку tool() TypeScript или справку @tool Python для полных деталей параметров, включая форматы входных JSON Schema и структуру возвращаемого значения.
Чтобы сделать параметр необязательным: в TypeScript добавьте .default() к полю Zod. В Python словарь схемы рассматривает каждый ключ как обязательный, поэтому оставьте параметр вне схемы, упомяните его в строке описания и читайте его с помощью args.get() в обработчике. Инструмент get_precipitation_chance ниже показывает оба паттерна.
Вызов пользовательского инструмента
Передайте созданный MCP-сервер в query через опцию mcpServers. Ключ в mcpServers становится сегментом {server_name} в полностью квалифицированном имени каждого инструмента: mcp__{server_name}__{tool_name}. Перечислите это имя в allowedTools, чтобы инструмент работал без запроса разрешения.
Эти фрагменты повторно используют weatherServer из примера выше, чтобы спросить Claude о погоде в определённом месте.
import asyncio
from 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 is the final message after all tool calls complete
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" is the final message after all tool calls complete
if (message.type === "result" && message.subtype === "success") {
console.log(message.result);
}
}
Добавление дополнительных инструментов
Сервер содержит столько инструментов, сколько вы перечислите в его массиве tools. Если на сервере более одного инструмента, вы можете перечислить каждый в allowedTools отдельно или использовать подстановочный знак mcp__weather__*, чтобы охватить каждый инструмент, который сервер предоставляет.
Пример ниже добавляет второй инструмент, get_precipitation_chance, к weatherServer из примера инструмента погоды и перестраивает его с обоими инструментами в массиве.
# Define a second tool for the same 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' isn't in the schema - read it with .get() to make it optional
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))}%",
}
]
}
# Rebuild the server with both tools in the array
weather_server = create_sdk_mcp_server(
name="weather",
version="1.0.0",
tools=[get_temperature, get_precipitation_chance],
)
// Define a second tool for the same server
const 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() makes the parameter optional
.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("%, ")}%` }]
};
}
);
// Rebuild the server with both tools in the array
const weatherServer = createSdkMcpServer({
name: "weather",
version: "1.0.0",
tools: [getTemperature, getPrecipitationChance]
});
Каждый инструмент в этом массиве потребляет пространство контекстного окна на каждом ходу. Если вы определяете десятки инструментов, см. поиск инструментов для загрузки их по требованию.
Добавление аннотаций инструментов
Аннотации инструментов — это необязательные метаданные, описывающие поведение инструмента. Передайте их в качестве пятого аргумента вспомогательной функции tool() в TypeScript или через аргумент ключевого слова annotations для декоратора @tool в Python. Все поля подсказок являются логическими значениями.
| Поле | По умолчанию | Значение |
|---|---|---|
readOnlyHint |
false |
Инструмент не изменяет свою среду. Контролирует, может ли инструмент вызываться параллельно с другими инструментами только для чтения. |
destructiveHint |
true |
Инструмент может выполнять деструктивные обновления. Только информационное. |
idempotentHint |
false |
Повторные вызовы с одинаковыми аргументами не имеют дополнительного эффекта. Только информационное. |
openWorldHint |
true |
Инструмент обращается к системам вне вашего процесса. Только информационное. |
Аннотации — это метаданные, а не принуждение. Инструмент, отмеченный как readOnlyHint: true, всё ещё может писать на диск, если это то, что делает обработчик. Держите аннотацию точной для обработчика.
Этот пример добавляет readOnlyHint к инструменту get_temperature из примера инструмента погоды.
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
), # Lets Claude batch this with other read-only calls
)
async def get_temperature(args):
return {"content": [{"type": "text", "text": "..."}]}
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 } } // Lets Claude batch this with other read-only calls
);
См. ToolAnnotations в справке TypeScript или Python.
Контроль доступа к инструментам
Пример инструмента погоды зарегистрировал сервер и перечислил инструменты в allowedTools. Этот раздел охватывает, как конструируются имена инструментов и как ограничить доступ, когда у вас есть несколько инструментов или вы хотите ограничить встроенные инструменты.
Формат имени инструмента
Когда инструменты MCP предоставляются Claude, их имена следуют определённому формату:
- Паттерн:
mcp__{server_name}__{tool_name} - Пример: инструмент с именем
get_temperatureна сервереweatherстановитсяmcp__weather__get_temperature
Настройка разрешённых инструментов
Опция tools и списки разрешённых/запрещённых инструментов работают на отдельных уровнях. tools контролирует, какие встроенные инструменты появляются в контексте Claude. Списки разрешённых и запрещённых инструментов контролируют, одобрены ли вызовы или отклонены после того, как Claude попытается их выполнить.
| Опция | Уровень | Эффект |
|---|---|---|
tools: ["Read", "Grep"] |
Доступность | Только перечисленные встроенные инструменты находятся в контексте Claude. Неперечисленные встроенные инструменты удаляются. Инструменты MCP не затрагиваются. |
tools: [] |
Доступность | Все встроенные инструменты удаляются. Claude может использовать только ваши инструменты MCP. |
| разрешённые инструменты | Разрешение | Перечисленные инструменты работают без запроса разрешения. Неперечисленные инструменты остаются доступными; вызовы проходят через поток разрешений. |
| запрещённые инструменты | Разрешение | Каждый вызов перечисленного инструмента отклоняется. Инструмент остаётся в контексте Claude, поэтому Claude может всё ещё попытаться его использовать перед отклонением вызова. |
Чтобы ограничить, какие встроенные инструменты может использовать Claude, предпочитайте tools запрещённым инструментам. Пропуск инструмента из tools удаляет его из контекста, чтобы Claude никогда не пытался его использовать; перечисление его в disallowedTools (Python: disallowed_tools) блокирует вызов, но оставляет инструмент видимым, поэтому Claude может потратить ход, пытаясь его использовать. См. Настройка разрешений для полного порядка оценки.
Обработка ошибок
То, как ваш обработчик сообщает об ошибках, определяет, продолжается ли цикл агента или останавливается:
| Что происходит | Результат |
|---|---|
| Обработчик выбрасывает неперехваченное исключение | Цикл агента останавливается. Claude никогда не видит ошибку, и вызов query завершается ошибкой. |
Обработчик перехватывает ошибку и возвращает isError: true (TS) / "is_error": True (Python) |
Цикл агента продолжается. Claude видит ошибку как данные и может повторить попытку, попробовать другой инструмент или объяснить сбой. |
Пример ниже перехватывает два вида сбоев внутри обработчика вместо того, чтобы позволить им выбросить исключение. Статус HTTP, отличный от 200, перехватывается из ответа и возвращается как результат ошибки. Ошибка сети или неверный JSON перехватываются окружающим try/except (Python) или try/catch (TypeScript) и также возвращаются как результат ошибки. В обоих случаях обработчик возвращается нормально и цикл агента продолжается.
import json
import httpx
from typing import Any
@tool(
"fetch_data",
"Fetch data from an API",
{"endpoint": str}, # Simple schema
)
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:
# Return the failure as a tool result so Claude can react to it.
# is_error marks this as a failed call rather than odd-looking data.
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:
# Catching here keeps the agent loop alive. An uncaught exception
# would end the whole query() call.
return {
"content": [{"type": "text", "text": f"Failed to fetch data: {str(e)}"}],
"is_error": True,
}
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) {
// Return the failure as a tool result so Claude can react to it.
// isError marks this as a failed call rather than odd-looking data.
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) {
// Catching here keeps the agent loop alive. An uncaught throw
// would end the whole query() call.
return {
content: [
{
type: "text",
text: `Failed to fetch data: ${error instanceof Error ? error.message : String(error)}`
}
],
isError: true
};
}
}
);
Возврат изображений и ресурсов
Массив content в результате инструмента принимает блоки text, image и resource. Вы можете смешивать их в одном ответе.
Изображения
Блок изображения содержит байты изображения встроенным образом, закодированные как base64. Нет поля URL. Чтобы вернуть изображение, которое находится по URL, получите его в обработчике, прочитайте байты ответа и закодируйте их в base64 перед возвратом. Результат обрабатывается как визуальный ввод.
| Поле | Тип | Примечания |
|---|---|---|
type |
"image" |
|
data |
string |
Байты в кодировке Base64. Только сырой base64, без префикса data:image/...;base64, |
mimeType |
string |
Обязательно. Например image/png, image/jpeg, image/webp, image/gif |
import base64
import httpx
# Define a tool that fetches an image from a URL and returns it to 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 the image bytes
response = await client.get(args["url"])
return {
"content": [
{
"type": "image",
"data": base64.b64encode(response.content).decode(
"ascii"
), # Base64-encode the raw bytes
"mimeType": response.headers.get(
"content-type", "image/png"
), # Read MIME type from the response
}
]
}
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 the image bytes
const buffer = Buffer.from(await response.arrayBuffer()); // Read into a Buffer for base64 encoding
const mimeType = response.headers.get("content-type") ?? "image/png";
return {
content: [
{
type: "image",
data: buffer.toString("base64"), // Base64-encode the raw bytes
mimeType
}
]
};
}
);
Ресурсы
Блок ресурса встраивает часть содержимого, идентифицируемую по URI. URI — это метка для Claude, чтобы ссылаться на неё; фактическое содержимое находится в поле text или blob блока. Используйте это, когда ваш инструмент производит что-то, что имеет смысл адресовать по имени позже, например сгенерированный файл или запись из внешней системы.
| Поле | Тип | Примечания |
|---|---|---|
type |
"resource" |
|
resource.uri |
string |
Идентификатор содержимого. Любая схема URI |
resource.text |
string |
Содержимое, если это текст. Предоставьте это или blob, но не оба |
resource.blob |
string |
Содержимое в кодировке base64, если это двоичное |
resource.mimeType |
string |
Необязательно |
Этот пример показывает блок ресурса, возвращаемый из обработчика инструмента. URI file:///tmp/report.md — это метка, на которую Claude может ссылаться позже; SDK не читает из этого пути.
return {
content: [
{
type: "resource",
resource: {
uri: "file:///tmp/report.md", // Label for Claude to reference, not a path the SDK reads
mimeType: "text/markdown",
text: "# Report\n..." // The actual content, inline
}
}
]
};
return {
"content": [
{
"type": "resource",
"resource": {
"uri": "file:///tmp/report.md", # Label for Claude to reference, not a path the SDK reads
"mimeType": "text/markdown",
"text": "# Report\n...", # The actual content, inline
},
}
]
}
Эти формы блоков происходят из типа MCP CallToolResult. См. спецификацию MCP для полного определения.
Возврат структурированных данных
structuredContent — это необязательный объект JSON на результате, отдельный от массива content. Используйте его для возврата сырых значений, которые Claude может читать как точные поля вместо их анализа из текстовой строки или изображения.
Когда установлен structuredContent, Claude получает JSON плюс любые блоки изображения или ресурса из content. Текстовые блоки в content не пересылаются, так как предполагается, что они дублируют структурированные данные. Пример ниже отображает диаграмму как блок изображения и возвращает точки данных позади неё в structuredContent из того же обработчика.
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]
}
};
Декоратор Python @tool пересылает только content и is_error из словаря возврата обработчика. Чтобы вернуть structuredContent из Python, запустите автономный MCP-сервер вместо встроенного сервера SDK.
Пример: конвертер единиц
Этот инструмент преобразует значения между единицами длины, температуры и веса. Пользователь может спросить "преобразовать 100 километров в мили" или "что такое 72°F в Цельсиях", и Claude выбирает правильный тип единицы и единицы из запроса.
Он демонстрирует два паттерна:
- Схемы перечисления:
unit_typeограничен фиксированным набором значений. В TypeScript используйтеz.enum(). В Python словарь схемы не поддерживает перечисления, поэтому требуется полный словарь JSON Schema. - Обработка неподдерживаемого ввода: когда пара преобразования не найдена, обработчик возвращает
isError: true, чтобы Claude мог сказать пользователю, что пошло не так, вместо того чтобы рассматривать сбой как нормальный результат.
from typing import Any
from claude_agent_sdk import tool, create_sdk_mcp_server
# z.enum() in TypeScript becomes an "enum" constraint in JSON Schema.
# The dict schema has no equivalent, so full JSON Schema is required.
@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]
});
После определения сервера передайте его в query так же, как в примере с погодой. Этот пример отправляет три разных запроса в цикле, чтобы показать, как один и тот же инструмент обрабатывает разные типы единиц. Для каждого ответа он проверяет объекты AssistantMessage (которые содержат вызовы инструментов, которые Claude сделал на этом ходу) и выводит каждый ToolUseBlock перед выводом финального текста ResultMessage. Это позволяет вам увидеть, когда Claude использует инструмент, а когда отвечает из своих собственных знаний.
import asyncio
from 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:
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")
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) {
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`);
}
}
}
Следующие шаги
Пользовательские инструменты оборачивают асинхронные функции в стандартный интерфейс. Вы можете смешивать паттерны на этой странице на одном сервере: один сервер может содержать инструмент базы данных, инструмент шлюза API и средство визуализации изображений рядом друг с другом.
Отсюда:
- Если ваш сервер растёт до десятков инструментов, см. поиск инструментов для отложенной загрузки их до того, как Claude их потребует.
- Чтобы подключиться к внешним MCP-серверам (файловая система, GitHub, Slack) вместо создания собственного, см. Подключение MCP-серверов.
- Чтобы контролировать, какие инструменты работают автоматически, а какие требуют одобрения, см. Настройка разрешений.