Инструменты
Model Context Protocol (MCP) позволяет серверам предоставлять инструменты, которые могут вызываться языковыми моделями. Инструменты позволяют моделям взаимодействовать с внешними системами: делать запросы к базам данных, вызывать API, выполнять вычисления. Каждый инструмент уникально идентифицируется по имени и содержит метаданные с описанием его схемы.
В главе Основы мы научились объявлять простой инструмент:
use neva::prelude::*;
#[tool(descr = "A simple 'say hello' tool")]
async fn hello(name: String) -> String {
format!("Hello, {name}!")
}
Того же результата можно добиться без использования процедурного макроса:
use neva::prelude::*;
async fn hello(name: String) -> String {
format!("Hello, {name}!")
}
#[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_tool("hello", hello)
.with_description("A simple 'say hello' tool");
mcp_server.run().await;
}
В примере выше имя инструмента должно быть задано явно.
При использовании атрибутного макроса #[tool] имя инструмента автоматически выводится из имени функции.
Все остальные параметры инструмента, доступные в атрибутном макросе, можно настроить с помощью методов with_* (например, with_description()).
Метод map_tool() регистрирует обработчик инструмента под указанным именем и возвращает изменяемую ссылку на зарегистрированный инструмент.
Схема входных данных
Для инструмента можно явно задать схему входных данных. Если схема не указана, Neva автоматически генерирует её на основе сигнатуры функции-обработчика.
Для переопределения сгенерированной схемы укажите её в виде JSON-строки:
#[tool(
descr = "A simple 'say hello' tool",
input_schema = r#"{
"properties": {
"name": {
"type": "string",
"description": "The name to greet"
}
},
"required": ["name"]
}"#
)]
async fn hello(name: String) -> String {
format!("Hello, {name}!")
}
Схема выходных данных
Если инструмент возвращает структурированные данные (например, JSON-объект), Neva автоматически генерирует схему выходных данных на основе возвращаемого типа.
Как и в случае схемы входных данных, её можно переопределить вручную:
#[tool(
descr = "A 'say hello' tool with structured output",
output_schema = r#"{
"properties": {
"message": {
"type": "string",
"description": "The generated greeting message"
}
},
"required": ["message"]
}"#
)]
async fn hello(say: String, name: String) -> Json<Results> {
let result = Results {
message: format!("{say}, {name}!")
};
result.into()
}
MCP-контекст
В более сложных сценариях — например, когда инструменту нужен доступ к ресурсам, объявленным на том же MCP-сервере, — можно внедрить Context в обработчик инструмента:
#[tool(descr = "Fetches resource metadata")]
async fn read_resource(ctx: Context, res: Uri) -> Result<Content, Error> {
let result = ctx.resource(res).await?;
let resource = result.contents
.into_iter()
.next()
.expect("No resource contents");
Ok(Content::resource(resource))
}
Обучение на примерах
Полный пример доступен здесь.