Получение данных
В этом руководстве описывается, как использовать получение данных (elicitation) на стороне сервера для запроса дополнительного ввода от пользователя или выполнения внешних действий в процессе работы инструмента.
Что такое получение данных?
Получение данных позволяет серверному инструменту:
- Запрашивать структурированный ввод от пользователя (формы с валидацией по схеме)
- Просить клиент выполнить внешнее действие (например, открыть URL для оплаты)
- Приостанавливать выполнение до тех пор, пока запрос не будет принят или отклонён
Типичные сценарии применения:
- Сбор контактных данных или параметров конфигурации
- Шаги подтверждения от пользователя
- Платежи или OAuth-перенаправления
Для использования получения данных внедрите Context в обработчик инструмента и вызовите метод elicit() с нужными параметрами запроса.
Модель повторного выполнения
Получение данных — первоклассный вид input-запроса MRTR. Из этого следуют два практических правила:
elicitпринимает стабильный replay-ключ —ctx.elicit(key, params). По этому ключу ответ сопоставляется с местом вызова на следующем раунде.- Обработчик выполняется с самого начала на каждом раунде. Код выше
точки
elicitвыполнится повторно, поэтому он должен быть без побочных эффектов — либо обёрнут вctx.memo(вычислить один раз),ctx.once(выполнить один раз) илиctx.on_commit(отложить до финального результата).
Раунды прогоняет клиент внутри call_tool, поэтому вызывающий код видит
один вызов.
legacy-specПолучение данных работает как серверный push-запрос, управляемый
возможностями: ctx.elicit(params) не принимает ключ, а обработчик
приостанавливается, а не перезапускается. См.
Легаси-спецификация.
Спрашивайте только то, на что вызывающая сторона может ответить
В MCP 2026-07-28 возможности объявляются на каждый запрос, в его
_meta. Context::client_capabilities()
сообщает, что объявил вызывающий именно этого вызова, — поэтому
обработчик, который может обойтись без ввода, вправе сначала посмотреть, а
уже потом спрашивать:
use neva::{Context, error::Error, types::elicitation::ElicitRequestParams, tool};
#[tool]
async fn greet(mut ctx: Context) -> Result<String, Error> {
if ctx.client_capabilities().elicitation.is_none() {
return Ok("Hello, stranger!".to_string());
}
let params = ElicitRequestParams::form("Your name?")
.with_required("name", "string")
.into();
let res = ctx.elicit("name", params).await?;
Ok(format!("{:?}", res.content))
}
Это стоит делать, потому что спросить всё равно — не «ухудшенный сценарий»:
такой запрос завершает вызов ошибкой
MissingRequiredClientCapability (-32021).
Elicitation сообщается вплоть до режима
elicitation — не флаг, а
ElicitationModes:
спецификация описывает form и url как под-возможности внутри объекта
elicitation, и клиент, умеющий отрисовать форму, вполне может не уметь
открыть URL.
- Клиент, назвавший режимы, перечисляет то, что умеет: режим, которого в списке нет, — это режим, на который он ответить не может.
- Клиент, объявивший
elicitation, но не назвавший ни одного режима ({}), не исключил ничего:unconstrained()равноtrue, и разрешены все режимы.
allows(¶ms) отвечает на весь вопрос целиком — «можно ли отправить
этому вызывающему такие параметры» — для обеих форм записи:
use neva::prelude::*;
use neva::types::elicitation::ElicitRequestParams;
let params: ElicitRequestParams = ElicitRequestParams::url(
"https://example.com/pay",
"Please pay your bill"
).into();
match ctx.client_capabilities().elicitation {
Some(modes) if modes.allows(¶ms) => { ctx.elicit("payment", params).await?; }
// Elicitation объявлен, но не этот режим — идём другим путём.
_ => return Ok("Send an invoice instead".into()),
}
Определение формы для получения данных
Формы используют JSON-схему для определения и валидации структурированного ввода.
#[json_schema(de)]
struct Contact {
name: String,
email: String,
age: u32,
}
С помощью атрибутного макроса #[json_schema] можно управлять сериализацией/десериализацией через serde:
all— добавляетderive(serde::Serialize, serde::Deserialize).serde— добавляетderive(serde::Serialize, serde::Deserialize).ser— добавляетderive(serde::Serialize).de— добавляетderive(serde::Deserialize).
Создание и отправка запроса формы
Для создания параметров запроса формы используйте метод ElicitRequestParams::form() с последующим вызовом with_contract(), который задаёт ожидаемую JSON-схему.
#[tool]
async fn generate_business_card(mut ctx: Context) -> Result<String, Error> {
let params = ElicitRequestParams::form(
"Please provide your contact information"
)
.with_schema::<Contact>();
// "contact" — replay-ключ: по нему ответ клиента сопоставляется
// с этим местом вызова на следующем раунде.
ctx.elicit("contact", params.into())
.await?
.map(format_contact)
}
fn format_contact(c: Contact) -> String {
format!("Name: {}, Age: {}, email: {}", c.name, c.age, c.email)
}
Порядок выполнения:
- Сервер отвечает
input_required, передавая запрос формы - Клиент получает его, формирует и валидирует данные и повторяет вызов
- Обработчик выполняется с начала;
ctx.elicit("contact", …)воспроизводит ответ - Результат преобразуется в выходные данные инструмента
Защита побочных эффектов
Всё дорогое или заметное снаружи выше точки elicit нужно оборачивать в
примитив, потому что этот код выполняется на каждом раунде заново:
#[tool]
async fn place_order(mut ctx: Context) -> Result<String, Error> {
// Вычисляется один раз, дальше воспроизводится.
let quote: u32 = ctx.memo("quote", async { Ok(fetch_quote().await) }).await?;
let params = ElicitRequestParams::form(format!("Доставка стоит ${quote}. Подтвердить?"))
.with_schema::<Contact>();
let contact: Contact = ctx.elicit("contact", params.into()).await?.content()
.ok_or_else(|| Error::new(ErrorCode::InvalidParams, "отклонено"))?;
// Выполнится не более одного раза за все раунды.
ctx.once("charge", async { charge_card().await }).await?;
// Выполнится ровно один раз, когда обработчик дойдёт до финального результата.
ctx.on_commit(async { send_receipt().await });
Ok(format!("Заказ подтверждён для {}", contact.name))
}
Определение URL-запроса
URL-запросы используются, когда пользователь должен выполнить внешнее действие. Для создания ElicitRequestUrlParams используйте метод ElicitRequestParams::url().
#[tool]
async fn pay_a_bill(mut ctx: Context) -> Result<&'static str, Error> {
let params = ElicitRequestParams::url(
"https://www.paypal.com/us/webapps/mpp/paypal-payment",
"Please pay your bill using PayPal"
);
ctx.elicit("payment", params.into()).await?;
Ok("Payment successful")
}
MCP 2026-07-28 удалил notifications/elicitation/complete — сигналом
завершения является сам ответ на input-запрос.
Context::complete_elicitation, Client::on_elicitation_completed и
ElicitationCompleteParams больше не существуют.
URL-elicitation также лишился elicitationId: без серверного сигнала о
завершении нечего сопоставлять. Сервер, которому нужно отслеживать запрос
между повторами, кладёт собственный идентификатор в requestState —
например, через ctx.memo.
- Клиент подтверждает принятие; выполнение инструмента возобновляет ответ
- Полезно для платежей, SSO, внешних подтверждений
Обучение на примерах
Полный рабочий пример доступен здесь.