Podłączanie Claude Code do narzędzi wewnętrznych: integracja systemów firmowych przez MCP

Podłącz wewnętrzne systemy firmowe do Claude Code przez MCP — od generycznego serwera fetch po własny MCP Server, obejmując platformy wdrożeniowe, monitoring, systemy zgłoszeń i zasady projektowania narzędzi.


W poprzednim artykule podłączaliśmy bazę danych. Bazy danych mają standardowe protokoły, więc integracja była stosunkowo prosta. Ale w codziennej pracy większość zespołów zależy od całej masy wewnętrznych systemów: platform deploymentu, paneli monitoringu, systemów ticketów, wewnętrznych API, centrów konfiguracji.

Te systemy zazwyczaj nie mają gotowych serwerów MCP, ale niemal wszystkie udostępniają HTTP API. W tym artykule pokażemy, jak za pomocą MCP podłączyć narzędzia wewnętrzne do Claude Code, żeby mógł bezpośrednio sprawdzać monitoring, podglądać status deploymentu czy operować na ticketach.

Dwa podejścia

Narzędzia wewnętrzne można podłączyć na dwa sposoby:

Sposób pierwszy: uniwersalny serwer HTTP MCP
W społeczności istnieją uniwersalne serwery MCP, które opakowują dowolne REST API w narzędzia MCP. Opisujesz API w pliku konfiguracyjnym, a serwer zamienia je na narzędzia dostępne dla Claude. Sprawdza się przy prostych API, bez skomplikowanej logiki.

Sposób drugi: napisać własny serwer MCP
Przy użyciu MCP SDK w TypeScript lub Pythonie można napisać dedykowany serwer z pełną kontrolą nad definicjami narzędzi, walidacją parametrów i obsługą błędów. Sprawdza się, gdy trzeba łączyć kilka API, transformować dane lub dodać logikę biznesową.

W tym artykule omówimy oba sposoby, zaczynając od prostszego.

Sposób pierwszy: szybkie podłączenie przez mcp-server-fetch

Najprostsze rozwiązanie to oficjalny @anthropic-ai/mcp-server-fetch, który pozwala Claude bezpośrednio wysyłać zapytania HTTP. Konfiguracja jest minimalna:

{
  "mcpServers": {
    "fetch": {
      "command": "npx",
      "args": ["-y", "@anthropic-ai/mcp-server-fetch"]
    }
  }
}

Po skonfigurowaniu Claude może bezpośrednio odpytywać wewnętrzne API:

Sprawdź aktualny status na platformie deploymentu https://deploy.internal.com/api/v1/services/user-service

Claude wyśle zapytanie GET, odbierze odpowiedź i pokaże wynik.

Ale to podejście ma wyraźne ograniczenia:

  • Za każdym razem trzeba podać Claude pełny URL i format zapytania
  • Brak walidacji parametrów — Claude może pomylić ścieżkę
  • Dane uwierzytelniające trzeba przekazywać za każdym razem albo wpisywać w prompcie (niebezpieczne)
  • Nie ma możliwości łączenia kilku wywołań API

Nadaje się do jednorazowych zadań, ale nie do stałego użytku.

Sposób drugi: piszemy dedykowany serwer MCP

Gdy regularnie korzystasz z jakiegoś wewnętrznego systemu, lepiej napisać dedykowany serwer MCP. Pokażemy to na konkretnym przykładzie — podłączenie do firmowej platformy deploymentu.

Załóżmy, że platforma deploymentu udostępnia następujące API:

  • GET /api/v1/services — lista wszystkich serwisów
  • GET /api/v1/services/:name/status — status serwisu
  • POST /api/v1/services/:name/deploy — uruchomienie deploymentu
  • GET /api/v1/services/:name/logs — ostatnie logi deploymentu

Piszemy serwer MCP w TypeScript

Najpierw inicjalizujemy projekt:

mkdir mcp-deploy && cd mcp-deploy
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
npx tsc --init

Główny kod 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",
});

// Lista wszystkich serwisów
server.tool("list_services", "列出部署平台上的所有服务及其状态", {}, async () => {
  const data = await api("/api/v1/services");
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
});

// Status pojedynczego serwisu
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) }] };
  }
);

// Logi deploymentu
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) }] };
  }
);

// Uruchomienie deploymentu
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);

Kompilacja:

npx tsc

Konfiguracja 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"
      }
    }
  }
}

Token umieszczamy w .claude/settings.local.json (nie commitujemy do gita), URL — w .claude/settings.json (commitujemy, żeby współdzielić w zespole).

Jak to wygląda w praktyce

Po skonfigurowaniu rozmowa staje się naturalna:

Jaki jest aktualny status user-service?

→ Claude wywołuje service_status("user-service")
→ Odpowiedź: działa, wersja v2.3.1, ostatni deployment 2 godziny temu, wszystkie health checki przeszły
Czy ostatnio były nieudane deploymenty?

→ Claude wywołuje deploy_logs("user-service", 20)
→ Analizuje logi i informuje, że trzeci deployment został wycofany z powodu timeoutu health checku
Zdeployuj user-service na v2.3.2

→ Claude wywołuje trigger_deploy("user-service", "v2.3.2")
→ Ponieważ w opisie narzędzia zaznaczono „operacja zapisu", Claude najpierw poprosi o potwierdzenie

Czy podłączać operacje zapisu

To pytanie, nad którym warto się poważnie zastanowić.

Operacje odczytu — podłączaj śmiało. Sprawdzanie statusów, przeglądanie logów, wyszukiwanie ticketów — te operacje nie mają skutków ubocznych, a pomyłka Claude niczego nie zepsuje.

Operacje zapisu — to zależy:

Niskoriskykowne operacje zapisu można podłączyć, ale trzeba je wyraźnie opisać w narzędziu. Claude automatycznie prosi o potwierdzenie użytkownika przy operacjach ze skutkami ubocznymi. Na przykład: tworzenie ticketu, wysyłanie wiadomości, aktualizacja konfiguracji.

Wysokoryzykownych operacji zapisu lepiej nie podłączać. Usuwanie zasobów, rollback, zmiana uprawnień — konsekwencje są poważne i nieodwracalne, bezpieczniej wykonywać je ręcznie.

Jeśli mimo wszystko podłączasz operacje zapisu, zrób co najmniej dwie rzeczy:

  1. Wyraźnie napisz w opisie narzędzia: „to jest operacja zapisu, wpływa na środowisko produkcyjne"
  2. Dodaj w serwerze MCP niezbędne kontrole bezpieczeństwa (np. blokadę deploymentu do namespace'u production)

Typowe scenariusze podłączania systemów wewnętrznych

System Udostępniane narzędzia Uwagi
Platforma deploymentu (K8s / Kamal) Status serwisów, logi, uruchomienie deploymentu Operacje zapisu — z potwierdzeniem
Monitoring (Grafana / Datadog) Przeglądanie metryk, historia alertów Ogranicz zakres czasowy zapytań
System ticketów (Jira / Linear) Wyszukiwanie ticketów, tworzenie, aktualizacja Tworzenie ticketu to zapis, ale niskie ryzyko
Dokumentacja wewnętrzna (Notion / Confluence) Wyszukiwanie dokumentów, odczyt stron Pilnuj paginacji, nie pobieraj za dużo na raz
Centrum konfiguracji (Consul / etcd) Odczyt konfiguracji, porównanie środowisk Tylko odczyt, nie podłączaj zapisu
CI/CD (GitHub Actions / Jenkins) Status buildów, uruchomienie buildu Uruchomienie buildu — operacja zapisu średniego ryzyka

Zasady projektowania narzędzi

Pisanie narzędzi MCP to nie to samo co pisanie API. API jest dla programistów, narzędzia — dla AI. Kilka zasad wartych uwagi:

Nazwy narzędzi powinny być czytelne

✗ get_svc_stat     — Claude może źle odgadnąć skrót
✓ service_status   — od razu wiadomo, co robi

Opisy pisz dla AI

Opis narzędzia to nie dokumentacja dla człowieka, lecz podstawa, na której Claude decyduje, kiedy wywołać dane narzędzie. W opisie trzeba zawrzeć: co narzędzie robi, co zwraca, kiedy je używać.

✗ "Pobierz status serwisu"
✓ "Podgląd aktualnego statusu deploymentu, wersji i wyników health checku wskazanego serwisu. Używaj, gdy użytkownik pyta, czy serwis działa poprawnie"

Parametry opisuj za pomocą zod

Tylko parametry z .describe() pozwalają Claude zrozumieć, co dokładnie podać. Bez opisu Claude będzie zgadywać po nazwie.

Zwracaj dane strukturalne

Narzędzia MCP zwracają tekst, ale staraj się oddawać sformatowany JSON. Claude przetwarza dane strukturalne znacznie dokładniej niż zwykły tekst.

Dbaj o właściwą granularność

Nie upychaj skomplikowanego procesu w jednym narzędziu. Ale też nie rozbijaj prostego zapytania na trzy narzędzia. Zasada: jedno narzędzie — jedna samodzielna, sensowna operacja.

Gdzie umieścić serwer MCP

Kod serwera MCP można umieścić w kilku miejscach:

W repozytorium projektu (zalecane na start)

your-project/
├── .claude/settings.json
├── mcp-servers/
│   └── deploy/
│       ├── src/index.ts
│       ├── package.json
│       └── tsconfig.json
└── ...

Zaleta: kod i konfiguracja razem, zespół klonuje repo, instaluje zależności — i wszystko działa.

Osobne repozytorium

Gdy serwer MCP jest używany w wielu projektach, umieść go w osobnym repozytorium i opublikuj jako pakiet npm lub obraz Dockera.

{
  "mcpServers": {
    "deploy": {
      "command": "npx",
      "args": ["-y", "@yourcompany/mcp-deploy-server"]
    }
  }
}

Instalacja globalna

Dla serwerów MCP wspólnych dla całej firmy (np. połączenie z centralnym systemem uwierzytelniania czy platformą logów) zainstaluj globalnie i skonfiguruj w ~/.claude/settings.json.

Wskazówki do debugowania

Najczęstsze problemy przy tworzeniu serwerów MCP to „Claude nie wywołuje mojego narzędzia" albo „wywołuje, ale z błędem".

Upewnij się, że serwer się uruchomił

Po restarcie Claude Code wpisz /mcp, żeby zobaczyć listę podłączonych serwerów MCP. Jeśli Twojego serwera nie ma na liście — sprawdź command i args.

Testuj serwer osobno

Serwer MCP komunikuje się przez stdio, można go przetestować bezpośrednio w terminalu:

echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | node dist/index.js

Jeśli zwrócił listę narzędzi — sam serwer działa prawidłowo.

Sprawdzaj logi wywołań

Claude Code pokazuje dane wejściowe i wyjściowe każdego wywołania narzędzia. Jeśli parametry są przekazane niepoprawnie, najprawdopodobniej opis narzędzia lub definicja parametrów nie są wystarczająco precyzyjne i Claude źle je zinterpretował.

Przykład praktyczny: podłączenie Sentry

Przejdźmy przez konkretny przykład od początku do końca. Załóżmy, że chcemy podłączyć Sentry do Claude Code, żeby mógł bezpośrednio wyszukiwać błędy na produkcji.

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
          ),
        },
      ],
    };
  }
);

Po podłączeniu diagnozowanie problemów produkcyjnych wygląda tak:

Czy w ciągu ostatnich 4 godzin pojawiły się nowe błędy 500?

→ Claude przeszukuje Sentry
→ Znajduje 3 nowe issue, najpoważniejszy dotknął 120 użytkowników
→ Automatycznie pobiera stacktrace, ustala, że to NullPointerException
→ Znajduje odpowiednie miejsce w kodzie i proponuje poprawkę

Cały proces — od wykrycia problemu do zlokalizowania go w kodzie — odbywa się w jednej rozmowie.

Co dalej

W tym artykule omówiliśmy, jak podłączyć narzędzia wewnętrzne przez MCP. Główna idea: system wewnętrzny ma HTTP API → piszemy serwer MCP jako wrapper → Claude może z niego korzystać bezpośrednio.

Wszystkie przykłady w tym artykule opakowywały istniejące API — platforma deploymentu i Sentry same mają interfejsy, a serwer MCP jedynie stanowił warstwę pośrednią. W następnym artykule omówimy inny scenariusz: gdy potrzebne możliwości nie istnieją w żadnym gotowym API i trzeba od zera zbudować serwer MCP z własną logiką, zarządzaniem stanem i złożoną, wielokrokową interakcją.