Свой MCP-сервер
Принципы
- Модель видит только ваши бизнес-инструменты.
- EVA не вызывает на старте чата никаких служебных инструментов и не выдаёт серверу доступ к файлам.
- Тенант передаётся только при подключении: заголовками HTTP или переменными окружения stdio.
- Файлы ходят по ссылкам: сервер выдаёт ссылку, EVA сама скачивает или загружает файл.
Соединение
На каждый чат и каждый источник EVA открывает отдельное соединение. Параметры тенанта фиксируются при подключении и действуют всё время его жизни.
Тенанты
| Транспорт | Основной тенант | Все разрешённые |
|---|---|---|
| Streamable HTTP, SSE | X-Eva-Tenant-Id | X-Eva-Tenant-Ids через запятую |
| stdio | EVA_TENANT_ID | EVA_TENANT_IDS через запятую |
- Основной тенант всегда входит в список разрешённых.
- Сервер обязан сам проверять доступ по списку и не выходить за него.
- Если сервер не поддерживает тенанты, EVA ничего не передаёт.
Каталог тенантов
Чтобы администратор мог выбирать тенанты для групп, объявите инструмент с фиксированным именем list_eva_tenants без аргументов. EVA вызывает его только при проверке источника, модель его не видит.
{
"tenants": [
{ "id": "main", "name": "Основной", "isDefault": true },
{ "id": "demo", "name": "Демо", "isDefault": false }
]
}Ровно один тенант должен быть isDefault.
Файлы
| Направление | Сервер отдаёт | EVA вызывает |
|---|---|---|
| Файл к модели | downloadUrl | download_url |
| Файл от модели | uploadUrl, метод, кодировку, имя поля | upload_url |
Для временной загрузки на ваш сервер используйте POST multipart/form-data:
{
"uploadUrl": "https://mcp.example.com/api/v1/uploads/<token>",
"method": "POST",
"encoding": "multipart",
"fieldName": "file",
"fileName": "report.pdf"
}Для pre-signed ссылок объектного хранилища используйте PUT с encoding: "raw".
Правила:
- не передавайте файл в аргументах инструментов, ни в base64, ни иначе;
- принимайте загрузку потоком на диск, не читая тело целиком в память;
- если пришёл заголовок
X-Public-Base-Url, стройте ссылки от этого origin. Иначе используйте адрес из своих настроек.
Долгие задачи оформляйте как задание: инструмент запуска возвращает jobId и uploadUrl, инструмент статуса возвращает прогресс и в конце downloadUrl. Так устроены Paleograph и Transcript.
Правила для инструментов
- Принимайте от модели только бизнес-поля: период, формат, запрос.
- Не принимайте в аргументах
tenantId, токены и пароли. - Не рассчитывайте на состояние, выданное платформой на чат.
- Пишите понятные описания: EVA проверяет их на промпт-инъекции, а модель выбирает инструмент по описанию.
Пример на Python
import os
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("eva-demo")
PRIMARY = os.environ.get("EVA_TENANT_ID")
ALLOWED = [t for t in os.environ.get("EVA_TENANT_IDS", "").split(",") if t]
@mcp.tool(name="list_eva_tenants", description="Каталог тенантов для администратора EVA.")
def list_eva_tenants() -> dict:
return {"tenants": [{"id": "main", "name": "Основной", "isDefault": True}]}
@mcp.tool(description="Отчёт за период.")
def get_report(period: str) -> dict:
if ALLOWED and PRIMARY not in ALLOWED:
raise RuntimeError("tenant is not allowed")
return {"tenant": PRIMARY, "period": period}
if __name__ == "__main__":
mcp.run()Чек-лист
- Инструменты не требуют тенант и учётные данные в аргументах.
- Мультитенантный сервер читает тенанты из заголовков или окружения и не выходит за список.
- Объявлен
list_eva_tenantsс одним тенантом по умолчанию, если нужны тенанты. - Файлы передаются по ссылкам, временная загрузка через
POST multipart. - Учитывается
X-Public-Base-Url.
Как подключить готовый сервер: Источники MCP.