Підключаємо 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-сервер із власною логікою, керуванням станом та складною багатокроковою взаємодією.