Подключаем Claude Code к внутренним инструментам: интеграция корпоративных систем через MCP

Подключите внутренние системы компании к 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, трансформировать данные или добавить бизнес-логику.

В этой статье рассмотрим оба способа, начнём с простого.

Способ первый: быстрое подключение через mcp-server-fetch

Самый лёгкий вариант — использовать официальный @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-запрос, получит ответ и покажет результат.

Но у этого подхода есть очевидные ограничения:

  • Каждый раз нужно указывать Claude полный URL и формат запроса
  • Нет валидации параметров — Claude может ошибиться в пути
  • Данные аутентификации приходится передавать каждый раз или прописывать в промпте (небезопасно)
  • Нет возможности комбинировать несколько API-вызовов

Годится для разовых задач, но не для постоянного использования.

Способ второй: пишем специализированный MCP-сервер

Если вам нужно регулярно работать с какой-то внутренней системой, лучше написать специализированный MCP-сервер. Рассмотрим на реальном примере — подключение к корпоративной платформе деплоя.

Допустим, ваша платформа деплоя предоставляет следующие API:

  • GET /api/v1/services — список всех сервисов
  • GET /api/v1/services/:name/status — статус сервиса
  • POST /api/v1/services/:name/deploy — запуск деплоя
  • GET /api/v1/services/:name/logs — последние логи деплоя

Пишем MCP-сервер на TypeScript

Сначала инициализируем проект:

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

Настройка Claude Code

{
  "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 автоматически запрашивает подтверждение пользователя для операций с побочными эффектами. Например: создание тикета, отправка сообщения, обновление конфигурации.

Высокорисковые операции записи лучше не подключать. Удаление ресурсов, откат, изменение прав доступа — последствия серьёзные и необратимые, безопаснее делать это вручную.

Если всё же подключаете операции записи, сделайте как минимум две вещи:

  1. Чётко укажите в описании инструмента: «это операция записи, она влияет на продакшен»
  2. Добавьте в MCP-сервер необходимые проверки безопасности (например, запрет деплоя в пространство имён production)

Типичные сценарии подключения внутренних систем

Система Доступные инструменты Примечания
Платформа деплоя (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-сервер

Код 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

Рассмотрим реальный пример от начала до конца. Допустим, нужно подключить 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-сервер с собственной логикой, управлением состоянием и сложным многошаговым взаимодействием.