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

Типы содержимого

Обработчики инструментов и ресурсов MCP могут возвращать различные виды содержимого — не только обычный текст. На этой странице описаны все доступные типы содержимого и случаи их применения.

Типы ответов инструментов

Обработчики инструментов могут возвращать любой тип, конвертируемый в CallToolResponse. Наиболее распространённые:

Текст

Возврат String или &str из обработчика #[tool] автоматически создаёт текстовый ответ:

#[tool(descr = "Greets a user")]
async fn greet(name: String) -> String {
format!("Hello, {name}!")
}

Структурированный JSON

Для возврата сериализуемого типа в виде структурированного вывода оберните его в Json<T> и используйте как возвращаемый тип:

use neva::prelude::*;
use serde::Serialize;

#[derive(Serialize)]
struct WeatherReport {
city: String,
temperature_c: f64,
condition: String,
}

#[tool(descr = "Returns weather for a city")]
async fn get_weather(city: String) -> Json<WeatherReport> {
WeatherReport {
city,
temperature_c: 22.5,
condition: "Sunny".into(),
}
.into()
}

Json<T> автоматически преобразуется в CallToolResponse со структурированным содержимым. Также можно явно сконструировать с помощью Json(value) или вызвать CallToolResponse::json(value) при ручном формировании ответа.

Изображения

Возвращайте бинарные данные изображения с помощью Content::image(). Данные должны быть в формате base64:

use neva::prelude::*;

#[tool(descr = "Returns a chart image")]
async fn generate_chart(data: String) -> CallToolResponse {
let png_bytes = render_chart(&data); // ваша логика рендеринга

let image = Content::image(png_bytes)
.with_mime("image/png");

CallToolResponse::from(image)
}

MIME-тип по умолчанию — image/jpg. Используйте .with_mime() для указания другого типа.

Аудио

Возвращайте аудиоданные аналогичным образом, используя Content::audio():

#[tool(descr = "Returns synthesized speech")]
async fn synthesize(text: String) -> CallToolResponse {
let wav_bytes = text_to_speech(&text); // ваша TTS-логика

let audio = Content::audio(wav_bytes)
.with_mime("audio/wav");

CallToolResponse::from(audio)
}

MIME-тип по умолчанию — audio/wav.

Несколько элементов содержимого

Используйте CallToolResponse::array() для возврата нескольких элементов содержимого в одном ответе:

use neva::prelude::*;

#[tool(descr = "Returns analysis with a chart")]
async fn analyze(data: String) -> CallToolResponse {
let summary = Content::text(format!("Analysis of: {data}"));
let chart = Content::image(render_chart(&data)).with_mime("image/png");

CallToolResponse::array([summary, chart])
}

Ссылки на ресурсы

Ссылка на внешний ресурс без его встраивания:

use neva::prelude::*;

#[tool(descr = "Returns a link to the report")]
async fn get_report_link(id: String) -> CallToolResponse {
let link = Content::from(
ResourceLink::from(format!("res://reports/{id}"))
);
CallToolResponse::from(link)
}

Встроенные ресурсы

Встраивание ресурса непосредственно в ответ инструмента:

use neva::prelude::*;

#[tool(descr = "Returns report contents inline")]
async fn get_report_inline(id: String) -> CallToolResponse {
let uri: Uri = format!("res://reports/{id}").parse().unwrap();
let contents = ResourceContents::new(uri.clone())
.with_text("Report contents here...");

let embedded = Content::from(EmbeddedResource::from(contents));
CallToolResponse::from(embedded)
}

Ответы с ошибками

Сигнализируйте об ошибке на уровне инструмента, которую модель может прочитать и проанализировать. В отличие от ошибок Rust (прерывающих запрос), ошибки инструментов возвращаются как структурированное содержимое:

#[tool(descr = "Fetches a record")]
async fn fetch_record(id: String) -> CallToolResponse {
match load_from_db(&id).await {
Ok(record) => CallToolResponse::json(record),
Err(e) => CallToolResponse::error(e.to_string()),
}
}

Различие между ошибками инструментов и ошибками уровня запроса описано в разделе Обработка ошибок.


Типы содержимого ресурсов

Обработчики ресурсов возвращают ResourceContents, которое может быть текстом, JSON или бинарными данными.

Текст

#[resource(uri = "note://{id}", title = "Read note")]
async fn get_note(uri: Uri, id: String) -> ResourceContents {
let text = load_note(&id).await;
ResourceContents::new(uri).with_text(text)
}

JSON

use serde_json::json;

#[resource(uri = "config://{key}", title = "Read config")]
async fn get_config(uri: Uri, key: String) -> ResourceContents {
let value = json!({ "key": key, "enabled": true });
ResourceContents::new(uri).with_json(value)
}

Бинарные данные (Blob)

#[resource(uri = "file://{path}", title = "Read file", mime = "application/octet-stream")]
async fn get_file(uri: Uri, path: String) -> ResourceContents {
let bytes = std::fs::read(&path).unwrap_or_default();
ResourceContents::new(uri).with_blob(bytes)
}

Сокращённые возвращаемые типы

Neva реализует Into<CallToolResponse> для многих распространённых типов, поэтому зачастую не нужно явно конструировать CallToolResponse:

Возвращаемый типЭквивалентно
StringCallToolResponse::new(text)
&strCallToolResponse::new(text)
Json<T>CallToolResponse::json(value)
(String, String)Текстовый кортеж из двух полей (uri, body)
Vec<Content>CallToolResponse::array(items)
ContentОтвет с одним элементом

Аналогично для ResourceContents:

Возвращаемый типЭквивалентно
StringResourceContents::new(uri).with_text(s)
(String, String)URI + текстовое тело
serde_json::ValueResourceContents::new(uri).with_json(v)