MCP-инструменты
Как LLM вызывают ваш плагин через Plugin Gateway.
MCP (Model Context Protocol) — это то, как LLM в Adapstory обнаруживают и вызывают capabilities вашего плагина. Plugin Gateway (BC-01) сидит между LLM и вашим кодом.
Анатомия вызова инструмента
LLM ──▶ Plugin Gateway (BC-01) ──▶ Ваш плагин (/mcp/<tool>)
│
├─ auth: service-account JWT со scope (tenant, plugin)
├─ guardrails: content filter, prompt-injection check
├─ budget: token + per-call cost check
└─ telemetry: OpenTelemetry span стартует здесьПлагин получает обычный HTTP-запрос с:
Authorization: Bearer <jwt>— идентичность вызывающего пользователяX-Tenant-Id: <tenant>— контекст тенантаX-Correlation-Id: <uuid>— связывает трейсX-Llm-Session: <id>— conversation, в которой идёт вызов- JSON body соответствующий вашей
schema
Написание handler'а
Держите handler'ы маленькими. Паттерн:
- Валидируйте аргументы по JSON-Schema (SDK делает это автоматически).
- Вызовите core-API или LLM Gateway для реальной работы.
- Верните JSON-результат соответствующий
returnSchema(если объявлен).
# src/hello_learner/tools.py
from adapstory_plugin_sdk import mcp_tool, LlmGateway, Context
@mcp_tool(name="hello_learner")
async def hello_learner(
learner_name: str,
*,
ctx: Context,
gateway: LlmGateway,
) -> dict:
ctx.logger.info("greeting", learner=learner_name)
response = await gateway.chat(
model=ctx.plugin.defaultModel,
messages=[
{"role": "system", "content": "Be brief and encouraging."},
{"role": "user", "content": f"Say hi to {learner_name}."},
],
)
return {"message": response.text}Стриминг
Для инструментов, отдающих частичные результаты (summaries, генерации), поставьте streaming: true в манифесте и верните Server-Sent Events:
@mcp_tool(name="summarize_course", streaming=True)
async def summarize_course(course_id: str, *, gateway: LlmGateway):
async for chunk in gateway.stream(
model="claude-sonnet-4-6",
messages=[...],
):
yield { "delta": chunk.text }Gateway пробрасывает SSE в LLM-клиент с соответствующей буферизацией.
Cancellation
Gateway шлёт заголовок X-Llm-Cancel: <correlation-id>, когда апстрим-LLM отваливается. Уважайте его:
async for chunk in gateway.stream(...):
if ctx.cancelled:
break
yield { "delta": chunk.text }SDK пробрасывает cancellation в in-flight Gateway-вызовы.
Бюджеты и back-pressure
Если BC-01 возвращает 429 Too Many Requests с X-LLM-Budget-Reset — не retry. Верните деградированный результат (кэш, эвристика, пусто) и дайте клиенту принять решение.
Per-tool rate limits (из manifest.rateLimit) обеспечиваются Gateway. Вам не нужно их реализовывать.
Контракт ошибок
Возвращайте структурированные ошибки, чтобы LLM могла восстановиться:
from adapstory_plugin_sdk import ToolError
raise ToolError(
code="learner_not_found",
message="No learner with that ID in this tenant.",
retryable=False,
suggestion="Check that the learner has been enrolled.",
)Gateway конвертирует это в JSON-ответ, о котором LLM может рассуждать.
Контракт observability
Каждый handler, декорированный @mcp_tool, автоматически:
- Стартует OpenTelemetry span
plugin.{name}.tool.{tool_name}с атрибутамиtenant.id,user.id,llm.session.id. - Эмитит лог-строку INFO на entry + exit.
- Репортит длительность + outcome в Prometheus через OTLP-коллектор.
Вам не нужно инструментировать это самостоятельно. Добавьте только ctx.logger.info(...) с бизнес-контекстом.
Далее: Жизненный цикл.