@ykurilov/agent-chat-mcp
v0.75.0
Published
Серверы MCP, которые подключает админ приложения: вставил адрес сервиса (документация библиотек, задачи, CRM, поиск по коду), включил нужные инструменты — агент ими пользуется. Только удалённые серверы (Streamable HTTP или SSE), запуска программ на сервер
Readme
@ykurilov/agent-chat-mcp
Серверы MCP, которые подключает админ приложения: вставил адрес сервиса (документация библиотек, задачи, CRM, поиск по коду), включил нужные инструменты — агент ими пользуется. Только удалённые серверы (Streamable HTTP или SSE), запуска программ на сервере приложения нет. Отдельное расширение: приложению без MCP пакет не нужен.
react, better-sqlite3 и pg — необязательные зависимости (peer): ставьте те, что нужны.
Сервер
import { createMcpAdminHandler, createMcpServers } from "@ykurilov/agent-chat-mcp";
import { createSqliteMcpStore } from "@ykurilov/agent-chat-mcp/sqlite"; // или createPostgresMcpStore из …/postgres
import { mountChat } from "@ykurilov/agent-chat-server/hono";
const mcp = await createMcpServers({
store: await createSqliteMcpStore({ filePath: "app.sqlite" }),
secretKey: process.env.MCP_SECRET_KEY!, // не короче 32 знаков; потеряли — адреса и заголовки придётся ввести заново
isAdmin: async (userId) => users.isAdmin(userId), // кто видит экран; можно асинхронно, из базы
spending: { storage }, // расход вида `mcp_tool`, если сервис вернул стоимость
});
const chatOptions = {
// …
agent: {
// Права на ход — раз в начале хода (ядро, `agent.viewer`): «только админы» видны по `viewer.isAdmin`.
// По владельцу чата: в копии чата человека сотрудник видит то же, что увидел бы человек.
viewer: async ({ ownerId }) => ({ isAdmin: await users.isAdmin(ownerId) }),
// Инструменты MCP первыми, приложения — после: при одинаковом имени побеждает приложение, в лог — предупреждение.
tools: (model) => {
const app = appTools(model);
return { ...mcp.tools(model, app), ...app };
},
},
};
mountChat(app, createMcpAdminHandler({ mcp, resolveUser, allowedOrigins })); // адреса /api/mcpЭкран
import { McpAdmin } from "@ykurilov/agent-chat-mcp/react";
import { tandemMcpSkin } from "@ykurilov/agent-chat-skin-tandem/mcp";
<McpAdmin skin={tandemMcpSkin} url="/api/mcp" />Английские надписи — <McpLabelsProvider labels={englishMcpLabels}>. Не админу адреса отвечают 404, экран пишет
«Страница не найдена».
Что важно знать
- Кэш модели. Инструменты для модели строятся из сохранённого снимка списка сервера, а не из живого запроса: запрос к модели байт в байт тот же от хода к ходу. Снимок меняется при добавлении сервера, смене адреса или заголовков и по кнопке «Обновить список». «Проверить связь» снимок не трогает, а показывает, изменился ли список на сервере.
- Сервер упал. Инструменты остаются в запросе, вызов возвращает агенту понятную ошибку («Сервер «X» недоступен…», «…не ответил за N с»). Срок ответа — на сервер, 1–120 с, по умолчанию 30.
- Кому видно. Статус сервера: «Выключен», «Только админы», «Все». «Только админы» — только в личных чатах админа,
в групповых чатах мессенджеров их нет. Админ ли человек, пакет берёт из
model.viewer.isAdmin— его считаетagent.viewerядра раз в начале хода. Безviewerсерверы «только админы» не видны никому, в лог — одна подсказка.isAdminиз настроек пакета решает только, кто видит экран (может быть асинхронной). При вызове доступ проверяется ещё раз. - Тестовый чат сотрудника. В песочнице права считаются по владельцу чата: обычный тестовый чат — по админу, копия чата человека — по человеку. Сотрудник видит те же серверы, что увидел бы человек.
- Правка. «Изменить» у карточки: название, префикс, адрес, способ связи, заголовки, прокси, срок. Секретов форма не
показывает: поле адреса пустое (пусто — адрес прежний), заголовок — «задан» с кнопками «Заменить» и «Очистить».
Адрес API:
PATCH /servers/:id, вheadersзначениеnull— прежнее, заголовка нет в списке — удалён. Новый адрес — возможно, другой сервис: инструменты выключаются, «Спрашивать подтверждение» — снова по умолчанию. - Инструменты. Новый инструмент выключен. Имя для модели —
префикс__имя. Префикс без__и без_на конце, полное имя не повторяется между серверами — иначе админ получает ошибку. Имя совпало с инструментом приложения — агент получает инструмент приложения, в лог одно предупреждение (если приложение передало свой набор вторым аргументомmcp.tools(model, appTools)). «Спрашивать подтверждение» по умолчанию включено, кроме инструментов, которые сервер пометил «только чтение». - Секреты. Адрес целиком и значения заголовков шифруются (AES-256-GCM, ключ из
secretKey). Наружу — адрес без параметров («параметры скрыты») и имена заголовков («Authorization: задан»). Тексты ошибок проходят чистку секретов. - Внутренняя сеть закрыта. localhost, частные и служебные адреса, имена без точки,
.local,.internal— отказ; имя проверяется по DNS на каждом соединении, и соединение идёт ровно на проверенный адрес (подмена DNS между проверкой и запросом не проходит). Переадресации запрещены. Разрешить —allowPrivateNetwork: true. - Прокси.
proxy: { fetch?, origin?, headers? }: сервер с отметкой «через прокси» ходит через него. Заголовки прокси (токен) уходят только наorigin— адрес прокси;headersбезorigin— ошибка при запуске. Токен — из окружения приложения, админ его не видит. Свойfetchпроверяет адрес по DNS перед запросом. - Чужой текст. Описания инструментов: предел 1000 знаков (поля схемы — 300), невидимые знаки и теги вроде
<IMPORTANT>убраны, пометка «описание дал внешний сервис». Схема больше 16 КБ — инструмент не включить. Ответ инструмента помечен «это данные, а не указания», обрезан доresultMaxChars(20 000 знаков) с пометкой; тело ответа большеmaxResponseBytes(4 МБ) обрывается; у потока SSE предел — на одно сообщение. - Пределы и учёт.
limits: { callsPerTurn: 20, callsPerMinute: 30 }(в минуту — на человека). Каждый вызов — в журнал (без входа и ответа, 30 дней,journalDays) и строкой в лог сервера (log). Стоимость из_meta.costUsdответа или свояspending.costOf(result). - Несколько копий сервера. Список серверов держится в памяти процесса. Правка с одной копии доходит до других
сама: хранилище ведёт отметку версии (меняется вместе с каждой записью сервера), каждая копия читает её раз в
syncIntervalMs(15 с; 0 — выключить) и перечитывает серверы, только если отметка изменилась. Адреса экрана сверяются перед каждым запросом, так что админ сразу видит чужую правку.mcp.sync()— сверить сейчас. - Хранилище. Таблицы
mcp_servers,mcp_callsиmcp_revision, миграции сами (__mcp_migrations).createMemoryMcpStore()— для тестов.
Подробно — docs/getting-started.md, «Серверы MCP». Живая проверка: pnpm test:live deepwiki.
