Перейти к содержанию

Ресурсы

Машинный перевод

Эта страница переведена с английской документации автоматически, и основной версией остаётся английская страница. Если что-то читается неправильно, на странице Переводы объясняется, как об этом сообщить.

Ресурс — это данные, которые вы открываете приложению для чтения.

В этом вся разница. Инструмент — это то, что решает вызвать модель. Ресурс — это то, что решает загрузить приложение (файл конфигурации, запись, документ) и передать модели в качестве контекста.

Чтобы объявить ресурс, поставьте @mcp.resource(uri) над обычной функцией Python.

Первый ресурс

server.py
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 плейсхолдер, а в функцию — соответствующий параметр:

server.py
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 и возвращайте то, что подходит:

server.py
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=.
  • Инструменты нужны модели, чтобы действовать. Ресурсы нужны приложению, чтобы читать.

Третий примитив, тот, что человек выбирает из меню, — это Промпты.