Ресурсы
Машинный перевод
Эта страница переведена с английской документации автоматически, и основной версией остаётся английская страница. Если что-то читается неправильно, на странице Переводы объясняется, как об этом сообщить.
Ресурс — это данные, которые вы открываете приложению для чтения.
В этом вся разница. Инструмент — это то, что решает вызвать модель. Ресурс — это то, что решает загрузить приложение (файл конфигурации, запись, документ) и передать модели в качестве контекста.
Чтобы объявить ресурс, поставьте @mcp.resource(uri) над обычной функцией Python.
Первый ресурс
from mcp.server import MCPServer
mcp = MCPServer("Bookshop")
@mcp.resource("config://app")
def get_config() -> str:
"""The active shop configuration."""
return "theme=dark\nlanguage=en"
По форме это то же, что инструмент, плюс одна деталь: URI. К ресурсам обращаются по адресу, а не по имени. Клиент запрашивает config://app, а не get_config.
Всё остальное SDK по-прежнему берёт из функции:
- Имя — это имя функции:
get_config. - Описание, которое видит клиент, — это docstring.
- Содержимое — это то, что вы возвращаете.
В ответ на resources/list клиент получает вот это:
{
"name": "get_config",
"uri": "config://app",
"description": "The active shop configuration.",
"mimeType": "text/plain"
}
А когда он читает config://app, выполняется ваша функция, и возвращённое значение приходит обратно как текст:
result.contents # [TextResourceContents(uri="config://app", mime_type="text/plain", text="theme=dark\nlanguage=en")]
Tip
Перечисление ничего не стоит. Ваша функция не вызывается при resources/list — только
при resources/read и только для запрошенного URI. Откройте хоть тысячу ресурсов —
платить придётся лишь за те, которые кто-то откроет.
Попробуйте сами
Запустите сервер с MCP Inspector:
uv run mcp dev server.py
Откройте URL, который он выведет, и перейдите на вкладку Resources. В списке есть config://app с описанием. Щёлкните по нему — Inspector прочитает ресурс, и вы увидите свои две строки конфигурации.
Шаблоны ресурсов
По одному URI на запись — это не масштабируется. Поместите в URI плейсхолдер, а в функцию — соответствующий параметр:
from mcp.server import MCPServer
mcp = MCPServer("Bookshop")
@mcp.resource("config://app")
def get_config() -> str:
"""The active shop configuration."""
return "theme=dark\nlanguage=en"
@mcp.resource("users://{user_id}/profile")
def get_user_profile(user_id: str) -> str:
"""A customer's profile."""
return f"User {user_id}: 12 orders since 2021."
{user_id} в URI, user_id: str у функции. Вот и весь контракт.
Теперь это шаблон ресурса, и он переезжает: исчезает из resources/list и появляется в resources/templates/list — уже как паттерн, а не адрес:
{
"name": "get_user_profile",
"uriTemplate": "users://{user_id}/profile",
"description": "A customer's profile.",
"mimeType": "text/plain"
}
Клиент подставляет значение вместо плейсхолдера и читает конкретный URI: users://42/profile, users://ada/profile. На все эти запросы отвечает одна функция, а совпавшее значение передаётся ей как user_id:
result.contents # [TextResourceContents(uri="users://42/profile", text="User 42: 12 orders since 2021.")]
Обратите внимание на uri в результате. Это конкретный URI, который запросил клиент, а не шаблон.
Check
Плейсхолдеры и параметры должны совпадать. Переименуйте параметр функции в
user, оставив в URI {user_id}, и декоратор откажется работать уже при импорте,
задолго до того, как к серверу подключится клиент:
ValueError: Mismatch between URI parameters {'user_id'} and function parameters {'user'}
Такое несовпадение может быть только ошибкой, поэтому SDK не даёт запустить с ним сервер.
Синтаксис плейсхолдеров описан в RFC 6570: {+path} для значений из нескольких сегментов, {?q,lang} для необязательных параметров запроса и многое другое. Кроме того, SDK по умолчанию проверяет извлечённые значения на безопасность путей. Полный справочник — на странице Шаблоны URI и безопасность путей.
get_user_profile может также принимать параметр с аннотацией Context. SDK внедряет его, никогда не считая параметром URI, а о том, что он даёт, рассказывает страница Объект Context.
Что возвращать
Вы не ограничены str. Задайте каждому ресурсу mime_type и возвращайте то, что подходит:
import base64
from mcp.server import MCPServer
mcp = MCPServer("Bookshop")
@mcp.resource("docs://readme", mime_type="text/markdown")
def readme() -> str:
"""How to use this server."""
return "# Bookshop\n\nSearch the catalog with the `search_books` tool."
@mcp.resource("stats://catalog", mime_type="application/json")
def catalog_stats() -> dict[str, int]:
"""Live counts for the catalog."""
return {"books": 1204, "authors": 391}
@mcp.resource("covers://placeholder", mime_type="image/gif")
def placeholder_cover() -> bytes:
"""A 1x1 transparent GIF, shown when a book has no cover."""
return base64.b64decode("R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7")
readmeвозвращаетstr, поэтому строка отправляется как есть. Это типичный случай.-
catalog_statsвозвращаетdict, и SDK сериализует его в текст JSON за вас:{ "books": 1204, "authors": 391 } -
placeholder_coverвозвращаетbytes, поэтому клиент получаетBlobResourceContentsвместоTextResourceContents— с вашими байтами в полеblob, закодированными в base64.
То же правило действует для всего остального, что сериализуется в JSON: список, модель Pydantic, dataclass. Если это не str и не bytes, оно превращается в JSON.
mime_type объявляете вы сами; по умолчанию это text/plain. SDK никогда не анализирует возвращаемое значение, чтобы угадать тип, поэтому ресурс с dict, который вы не пометили, по-прежнему объявляется как обычный текст.
Tip
@mcp.resource() принимает также name=, title= и description=, если вы не хотите
выводить их из функции. А когда функцию писать вообще не нужно, в
mcp.server.mcpserver.resources есть готовые классы Resource (TextResource,
BinaryResource, FileResource, HttpResource, DirectoryResource), которые регистрируются
через mcp.add_resource(...).
Клиент может также подписаться на ресурс и получать уведомления о его изменениях; это клиентская половина истории, и она описана на странице Клиент.
Итоги
@mcp.resource(uri)над функцией делает её ресурсом. URI — это адрес, возвращаемое значение — содержимое, docstring — описание.{placeholder}в URI превращает его в шаблон: он перечисляется вresources/templates/list, и одна функция обслуживает все подходящие URI.- Имена плейсхолдеров должны совпадать с именами параметров функции. Ошибётесь — узнаете об этом при импорте, а не в продакшене.
- Ваша функция выполняется, когда ресурс читают, а не когда его перечисляют.
strстановится текстом,bytes— blob-объектом в base64, всё остальное — текстом JSON. Пометить тип помогаетmime_type=.- Инструменты нужны модели, чтобы действовать. Ресурсы нужны приложению, чтобы читать.
Третий примитив, тот, что человек выбирает из меню, — это Промпты.