Перейти к основному содержимому

Ресурсы

Model Context Protocol (MCP) предоставляет стандартизированный способ для серверов предоставлять клиентам ресурсы. Ресурсы позволяют серверам передавать данные, обеспечивающие контекст для языковых моделей: файлы, схемы баз данных или специфичная для приложения информация. Каждый ресурс уникально идентифицируется по URI.

В главе Основы мы научились объявлять простой динамический ресурс:

use neva::prelude::*;

#[resource(
uri = "res://{name}",
title = "Read resource",
descr = "Some details about resource",
mime = "application/octet-stream",
annotations = r#"{
"audience": ["user"],
"priority": 1.0
}"#
)]
async fn get_res(uri: Uri, name: String) -> ResourceContents {
let data = "some file contents"; // Читаем ресурс из источника

ResourceContents::new(uri)
.with_title(name)
.with_blob(data)
}

Того же результата можно добиться без использования процедурного макроса:

use neva::prelude::*;

async fn get_res(uri: Uri, name: String) -> ResourceContents {
let data = "some file contents"; // Читаем ресурс из источника

ResourceContents::new(uri)
.with_title(name)
.with_blob(data)
}

#[tokio::main]
async fn main() {
let mut mcp_server = App::new()
.with_options(|opt| opt
.with_stdio()
.with_name("Sample MCP Server")
.with_version("1.0.0"));

mcp_server
.map_resource("res://{name}", "get_res", hello)
.with_description("Some details about resource")
.with_title("Read resource")
.with_mime("application/octet-stream")
.with_annotations(|anotations| anotations
.with_audience("user")
.with_priority(1.0));

mcp_server.run().await;
}

В примере выше имя шаблона ресурса должно быть задано явно. При использовании атрибутного макроса #[resource] имя шаблона ресурса автоматически выводится из имени функции.

Все остальные параметры шаблона ресурса, доступные в атрибутном макросе, можно настроить с помощью методов with_* (например, with_description()).

Метод map_resource() регистрирует обработчик шаблона ресурса под указанным именем и возвращает изменяемую ссылку на зарегистрированный шаблон ресурса.

Содержимое

Существует несколько способов вернуть результат ресурса. Наиболее удобный — использование перечисления ResourceContents:

let resource = ResourceContents::new("res://text")
.with_title("Text resource")
.with_text("Some text content");

Конкретный тип содержимого задаётся вспомогательными методами:

Каждый из этих методов задаёт как содержимое, так и соответствующий MIME-тип. При необходимости MIME-тип можно переопределить с помощью with_mime().

Также можно использовать специализированные структуры напрямую:

Можно также вернуть массив или Vec из ResourceContents. Все они автоматически преобразуются в ReadResourceResult.

Статические ресурсы

Выше рассматривались динамические ресурсы, однако можно также определить обработчик статического ресурса, например:

#[resource(uri = "res://static_resource")]
async fn get_res(uri: Uri) -> ResourceContents {
TextResourceContents::new(uri, "some file contents")
}

или с помощью метода add_resource():

let mut mcp_server = App::new()
.with_options(|opt| opt
.with_stdio()
.with_name("Sample MCP Server")
.with_version("1.0.0"));

mcp_server
.add_resource("res://static_resource", "Some static resource");

mcp_server.run().await;

Обработка list_resources

С помощью атрибутного макроса #[resources] можно переопределить функцию, предоставляющую список ресурсов, и при необходимости реализовать пагинацию:

use neva::prelude::*;

#[resources]
async fn list_resources(_: ListResourcesRequestParams) -> Vec<Resource> {
// Читаем список ресурсов из источника
let resources = vec![
Resource::new("res://res1", "resource 1")
.with_descr("A test resource 1")
.with_mime("text/plain"),
Resource::new("res://res2", "resource 2")
.with_descr("A test resource 2")
.with_mime("text/plain"),
];
resources
}

Также можно использовать метод map_resources():

let mut mcp_server = App::new()
.with_options(|opt| opt
.with_stdio()
.with_name("Sample MCP Server")
.with_version("1.0.0"));

mcp_server.map_resources(|_: ListResourcesRequestParams| async {
// Читаем список ресурсов из источника
let resources = vec![
Resource::new("res://res1", "resource 1")
.with_descr("A test resource 1")
.with_mime("text/plain"),
Resource::new("res://res2", "resource 2")
.with_descr("A test resource 2")
.with_mime("text/plain"),
];
resources
});

mcp_server.run().await;

Обновление ресурсов

Помимо чтения ресурсов, инструменты MCP-сервера также могут добавлять, обновлять или удалять их:

use neva::prelude::*;

/// Добавление нового ресурса
#[tool]
async fn add_resource(mut ctx: Context, uri: Uri) -> Result<(), Error> {
let resource = Resource::from(uri); // Создаём новый ресурс
ctx.add_resource(resource).await
}

/// Удаление ресурса
#[tool]
async fn remove_resource(mut ctx: Context, uri: Uri) -> Result<(), Error> {
ctx.remove_resource(uri).await
}

/// Обновление существующего ресурса
#[tool]
async fn update_resource(mut ctx: Context, uri: Uri) -> Result<(), Error> {
// Читаем и обновляем ресурс с указанным URI
// ...

ctx.resource_updated(uri).await
}

Каждая из этих операций автоматически уведомляет клиента, если он подписан на соответствующие события:

  • notifications/resources/list_changed — при добавлении или удалении ресурса
  • notifications/resources/updated — при обновлении ресурса

Обучение на примерах

Полный пример доступен здесь.

Дополнительные примеры