Подключите внутренние системы компании к Claude Code через MCP — от универсального fetch-сервера до написания выделенного MCP Server, включая платформы деплоя, мониторинг, тикет-системы и принципы проектирования инструментов.
В прошлой статье мы подключали базу данных. У баз данных есть стандартные протоколы, поэтому интеграция была относительно простой. Но в повседневной работе большинство команд зависят от кучи внутренних систем: платформы деплоя, панели мониторинга, тикет-системы, внутренние API, центры конфигурации.
Для этих систем обычно нет готовых MCP-серверов, но почти все они предоставляют HTTP API. В этой статье разберём, как с помощью MCP подключить внутренние инструменты к Claude Code, чтобы он мог напрямую проверять мониторинг, смотреть статус деплоя, работать с тикетами.
Подключить внутренние инструменты можно двумя способами:
Способ первый: универсальный HTTP MCP-сервер
В сообществе есть универсальные MCP-серверы, которые оборачивают произвольный REST API в MCP-инструменты. Вы описываете API в файле конфигурации, а сервер превращает его в инструменты, доступные Claude. Подходит для простых API без сложной логики.
Способ второй: написать свой MCP-сервер
С помощью MCP SDK на TypeScript или Python можно написать специализированный сервер с полным контролем над определениями инструментов, валидацией параметров и обработкой ошибок. Подходит, когда нужно комбинировать несколько API, трансформировать данные или добавить бизнес-логику.
В этой статье рассмотрим оба способа, начнём с простого.
Самый лёгкий вариант — использовать официальный @anthropic-ai/mcp-server-fetch, который позволяет Claude напрямую отправлять HTTP-запросы. Конфигурация минимальна:
{
"mcpServers": {
"fetch": {
"command": "npx",
"args": ["-y", "@anthropic-ai/mcp-server-fetch"]
}
}
}
После настройки Claude сможет обращаться к внутренним API напрямую:
Проверь текущий статус на платформе деплоя https://deploy.internal.com/api/v1/services/user-service
Claude отправит GET-запрос, получит ответ и покажет результат.
Но у этого подхода есть очевидные ограничения:
Годится для разовых задач, но не для постоянного использования.
Если вам нужно регулярно работать с какой-то внутренней системой, лучше написать специализированный MCP-сервер. Рассмотрим на реальном примере — подключение к корпоративной платформе деплоя.
Допустим, ваша платформа деплоя предоставляет следующие API:
GET /api/v1/services — список всех сервисовGET /api/v1/services/:name/status — статус сервисаPOST /api/v1/services/:name/deploy — запуск деплояGET /api/v1/services/:name/logs — последние логи деплояСначала инициализируем проект:
mkdir mcp-deploy && cd mcp-deploy
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
npx tsc --init
Основной код src/index.ts:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const API_BASE = process.env.DEPLOY_API_URL!;
const API_TOKEN = process.env.DEPLOY_API_TOKEN!;
async function api(path: string, method = "GET", body?: unknown) {
const res = await fetch(`${API_BASE}${path}`, {
method,
headers: {
Authorization: `Bearer ${API_TOKEN}`,
"Content-Type": "application/json",
},
body: body ? JSON.stringify(body) : undefined,
});
if (!res.ok) {
throw new Error(`API error: ${res.status} ${await res.text()}`);
}
return res.json();
}
const server = new McpServer({
name: "deploy-platform",
version: "1.0.0",
});
// Список всех сервисов
server.tool("list_services", "列出部署平台上的所有服务及其状态", {}, async () => {
const data = await api("/api/v1/services");
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
});
// Статус отдельного сервиса
server.tool(
"service_status",
"查看指定服务的当前部署状态、版本号和健康检查结果",
{ name: z.string().describe("服务名称,如 user-service") },
async ({ name }) => {
const data = await api(`/api/v1/services/${name}/status`);
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
);
// Логи деплоя
server.tool(
"deploy_logs",
"查看指定服务最近的部署日志",
{
name: z.string().describe("服务名称"),
limit: z.number().optional().default(10).describe("返回条数,默认 10"),
},
async ({ name, limit }) => {
const data = await api(`/api/v1/services/${name}/logs?limit=${limit}`);
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
);
// Запуск деплоя
server.tool(
"trigger_deploy",
"触发指定服务的部署。这是一个写操作,会实际影响生产环境",
{
name: z.string().describe("服务名称"),
version: z.string().describe("要部署的版本号或 git ref"),
},
async ({ name, version }) => {
const data = await api(`/api/v1/services/${name}/deploy`, "POST", {
version,
});
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
);
const transport = new StdioServerTransport();
server.connect(transport);
Компиляция:
npx tsc
{
"mcpServers": {
"deploy": {
"command": "node",
"args": ["/path/to/mcp-deploy/dist/index.js"],
"env": {
"DEPLOY_API_URL": "https://deploy.internal.com",
"DEPLOY_API_TOKEN": "your-api-token"
}
}
}
}
Токен кладём в .claude/settings.local.json (не коммитим в git), URL — в .claude/settings.json (коммитим, чтобы использовать в команде).
После настройки диалог становится естественным:
Какой сейчас статус у user-service?
→ Claude вызывает service_status("user-service")
→ Ответ: работает, версия v2.3.1, последний деплой 2 часа назад, все проверки здоровья пройдены
Были ли неудачные деплои за последнее время?
→ Claude вызывает deploy_logs("user-service", 20)
→ Анализирует логи и сообщает, что третий деплой был откачен из-за таймаута проверки здоровья
Задеплой user-service на v2.3.2
→ Claude вызывает trigger_deploy("user-service", "v2.3.2")
→ Поскольку в описании инструмента указано «операция записи», Claude сначала попросит подтверждение
Это вопрос, который стоит обдумать серьёзно.
Операции чтения — подключайте смело. Проверка статусов, просмотр логов, поиск тикетов — эти операции не имеют побочных эффектов, и ошибка Claude ни к чему не приведёт.
Операции записи — зависит от ситуации:
Низкорисковые операции записи подключать можно, но нужно чётко описать их в инструменте. Claude автоматически запрашивает подтверждение пользователя для операций с побочными эффектами. Например: создание тикета, отправка сообщения, обновление конфигурации.
Высокорисковые операции записи лучше не подключать. Удаление ресурсов, откат, изменение прав доступа — последствия серьёзные и необратимые, безопаснее делать это вручную.
Если всё же подключаете операции записи, сделайте как минимум две вещи:
| Система | Доступные инструменты | Примечания |
|---|---|---|
| Платформа деплоя (K8s / Kamal) | Статус сервисов, логи, запуск деплоя | Операции записи — с подтверждением |
| Мониторинг (Grafana / Datadog) | Просмотр метрик, история алертов | Ограничивайте временной диапазон запросов |
| Тикет-система (Jira / Linear) | Поиск тикетов, создание, обновление | Создание тикета — запись, но низкий риск |
| Внутренняя документация (Notion / Confluence) | Поиск документов, чтение страниц | Следите за пагинацией, не запрашивайте слишком много |
| Центр конфигурации (Consul / etcd) | Чтение конфигурации, сравнение окружений | Только чтение, запись не подключайте |
| CI/CD (GitHub Actions / Jenkins) | Статус сборок, запуск сборки | Запуск сборки — операция записи среднего риска |
Написание MCP-инструментов — не то же самое, что написание API. API предназначен для программистов, инструменты — для ИИ. Несколько принципов, которые стоит учитывать:
Имена инструментов должны быть понятными
✗ get_svc_stat — Claude может неправильно угадать сокращение
✓ service_status — сразу ясно, что делает
Описания пишите для ИИ
Описание инструмента — это не документация для человека, а основа, по которой Claude решает, когда вызывать этот инструмент. В описании нужно указать: что делает инструмент, что возвращает, когда его использовать.
✗ "Получить статус сервиса"
✓ "Просмотр текущего статуса деплоя, версии и результатов проверки здоровья указанного сервиса. Используйте, когда пользователь спрашивает, работает ли сервис"
Параметры описывайте с помощью zod
Только параметры с .describe() дают Claude понять, что именно нужно передать. Без описания Claude будет угадывать по имени.
Возвращайте структурированные данные
MCP-инструменты возвращают текст, но старайтесь отдавать форматированный JSON. Claude обрабатывает структурированные данные гораздо точнее, чем обычный текст.
Соблюдайте правильную гранулярность
Не запихивайте сложный процесс в один инструмент. Но и не разбивайте простой запрос на три инструмента. Принцип: один инструмент — одна самостоятельная осмысленная операция.
Код MCP-сервера можно разместить в нескольких местах:
В репозитории проекта (рекомендуется для начала)
your-project/
├── .claude/settings.json
├── mcp-servers/
│ └── deploy/
│ ├── src/index.ts
│ ├── package.json
│ └── tsconfig.json
└── ...
Преимущество: код и конфигурация рядом, команда клонирует репозиторий, устанавливает зависимости — и всё работает.
Отдельный репозиторий
Когда MCP-сервер используется в нескольких проектах, разместите его в отдельном репозитории и опубликуйте как npm-пакет или Docker-образ.
{
"mcpServers": {
"deploy": {
"command": "npx",
"args": ["-y", "@yourcompany/mcp-deploy-server"]
}
}
}
Глобальная установка
Для MCP-серверов, общих для всей компании (например, подключение к единой аутентификации или централизованной платформе логов), установите глобально и пропишите в ~/.claude/settings.json.
Самые частые проблемы при разработке MCP-серверов — «Claude не вызывает мой инструмент» или «вызывает, но с ошибкой».
Убедитесь, что сервер запустился
После перезапуска Claude Code введите /mcp, чтобы увидеть список подключённых MCP-серверов. Если вашего сервера нет в списке — проверьте command и args.
Тестируйте сервер отдельно
MCP-сервер общается через stdio, его можно протестировать прямо в терминале:
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | node dist/index.js
Если вернулся список инструментов — сам сервер работает нормально.
Смотрите логи вызовов
Claude Code показывает входные и выходные данные каждого вызова инструмента. Если параметры переданы неправильно, скорее всего, описание инструмента или определение параметров недостаточно чёткое, и Claude понял их неверно.
Рассмотрим реальный пример от начала до конца. Допустим, нужно подключить Sentry к Claude Code, чтобы он мог напрямую искать ошибки на продакшене.
server.tool(
"search_errors",
"在 Sentry 中搜索最近的错误。用于排查线上问题、查看错误趋势",
{
query: z.string().describe("搜索关键词,如错误信息、函数名"),
hours: z.number().optional().default(24).describe("查看最近多少小时的错误"),
},
async ({ query, hours }) => {
const since = new Date(Date.now() - hours * 3600000).toISOString();
const data = await api(
`/api/0/projects/${ORG}/${PROJECT}/issues/?query=${encodeURIComponent(query)}&start=${since}&sort=date`
);
const summary = data.map((issue: any) => ({
title: issue.title,
count: issue.count,
firstSeen: issue.firstSeen,
lastSeen: issue.lastSeen,
link: issue.permalink,
}));
return {
content: [{ type: "text", text: JSON.stringify(summary, null, 2) }],
};
}
);
server.tool(
"error_details",
"查看 Sentry 中某个错误的详细信息,包括堆栈和最近一次事件",
{ issueId: z.string().describe("Sentry issue ID") },
async ({ issueId }) => {
const [issue, latest] = await Promise.all([
api(`/api/0/issues/${issueId}/`),
api(`/api/0/issues/${issueId}/events/latest/`),
]);
return {
content: [
{
type: "text",
text: JSON.stringify(
{
title: issue.title,
count: issue.count,
users: issue.userCount,
stacktrace: latest.entries?.find(
(e: any) => e.type === "exception"
),
},
null,
2
),
},
],
};
}
);
После подключения расследование продакшен-проблем выглядит так:
Были ли новые ошибки 500 за последние 4 часа?
→ Claude ищет в Sentry
→ Находит 3 новых issue, самый серьёзный затронул 120 пользователей
→ Автоматически подтягивает стектрейс, определяет, что это NullPointerException
→ Находит соответствующее место в коде и предлагает исправление
Весь процесс — от обнаружения проблемы до локализации в коде — происходит в одном диалоге.
В этой статье мы разобрали, как подключить внутренние инструменты через MCP. Основная идея: у внутренней системы есть HTTP API → пишем MCP-сервер-обёртку → Claude может пользоваться напрямую.
Все примеры в этой статье оборачивали существующие API — у платформы деплоя и Sentry уже есть интерфейсы, а MCP-сервер лишь выступал прослойкой. В следующей статье рассмотрим другой сценарий: когда нужных возможностей в готовом API просто нет, и нужно с нуля создать MCP-сервер с собственной логикой, управлением состоянием и сложным многошаговым взаимодействием.