# Napisz serwer MCP

Gdy dodajesz serwer MCP do Speedwave, piszesz worker integracyjny: izolowany proces, który wystawia jedną usługę (GitLab, Slack, system operacyjny) jako narzędzia MCP. Claude nigdy nie rozmawia z twoim workerem bezpośrednio. Zapytania przechodzą przez [Tool Gateway](/pl/docs/under-the-hood/tool-gateway/), jedyny serwer MCP, który widzi Claude. Gateway nie przechowuje żadnych tokenów i kieruje każde wywołanie przez wewnętrzny DNS Dockera do workera, który jest właścicielem danych uwierzytelniających, pod adresem `http://mcp-{name}:{port}` (albo `http://host.docker.internal:{port}` dla workera systemu operacyjnego). Jeśli chcesz udostępnić worker innym instalacjom, spakuj go jako wtyczkę. Zobacz [Napisz wtyczkę](/pl/docs/plugins/writing-a-plugin/).

Trzymaj otwarty `mcp-servers/gitlab` podczas czytania tej strony: to referencyjna implementacja wszystkiego, co opisujemy niżej.

## Zaimportuj wspólny pakiet

Nie implementuj od nowa uruchamiania, walidacji, formatowania błędów ani helperów wynikowych. `@speedwave/mcp-shared` już to wszystko ma: `bootWorker`, typ `Tool`, wrappery `withResultValidation` i `withClientValidation`, wczytywanie tokenów oraz sanitizer logów `sanitize`.

## Uruchom swój worker

Wywołaj `bootWorker()` z `main()`. Oto cały punkt wejścia workera GitLab:

```typescript title="mcp-servers/gitlab/src/index.ts"
import { bootWorker, ts, makeStandardHealthCheck } from '@speedwave/mcp-shared';
import { initializeGitLabClient, type GitLabClient } from './client.js';
import { createToolDefinitions } from './tools/index.js';

bootWorker<GitLabClient>({
  serverName: 'mcp-gitlab',
  version: '1.0.0',
  displayName: 'GitLab',
  authTokenEnv: 'MCP_GITLAB_AUTH_TOKEN',
  host: '0.0.0.0',
  initClient: initializeGitLabClient,
  makeTools: (client) => createToolDefinitions(client),
  makeHealthCheck: (client) => /* ... */,
}).catch((error) => {
  console.error(`${ts()} Fatal error:`, error);
  process.exit(1);
});
```

`PORT` jest wczytywany automatycznie, do tego dochodzą ponowienia dla `initClient` z backoffem i wyjście z procesu przy brakującym `authTokenEnv`. `onNotConfigured` decyduje, co się stanie, gdy klient wróci nieskonfigurowany: `'warn'` (domyślnie: loguje i mimo to startuje) albo `'fail'` (kończy proces). Pomiń `initClient` dla workera bez danych uwierzytelniających, tak jak robi to office.

## Zamontuj swoje dane uwierzytelniające

[Montuj wyłącznie dane uwierzytelniające własnej usługi](/pl/docs/security/credentials/), w `/tokens`, w trybie tylko do odczytu. Rozwiąż ścieżkę przez `tokensDir()` i wczytaj plik przez `loadTokenFile(name)`. Klient GitLab czyta dwa pliki:

- /tokens/
  - token        token dostępu GitLab
  - host_url     opcjonalny; nadpisuje GITLAB_URL, domyślnie https://gitlab.com
Jeśli wczytywanie się nie powiedzie, złap błąd i zwróć `null` z funkcji inicjującej zamiast pozwolić na crash: serwer i tak wystartuje, a każde wywołanie narzędzia zwróci błąd „nieskonfigurowany”. Dla usługi opartej na OAuth użyj `authedRequest`, `authedSdkCall` i `RefreshLock` zamiast statycznego tokenu: odświeżają się przy błędzie uwierzytelniania i serializują równoległe odświeżenia.

## Zadeklaruj narzędzie

Narzędzie to obiekt zgodny z interfejsem `Tool`. Oto `listBranches` z workera GitLab:

```typescript title="mcp-servers/gitlab/src/tools/branch-tools.ts"
const listBranchesTool: Tool = {
  name: 'listBranches',
  description: 'List branches in a project',
  annotations: READ_ONLY_ANNOTATIONS,
  _meta: { deferLoading: true },
  keywords: ['gitlab', 'branches', 'list', 'git', 'refs'],
  example: 'const branches = await gitlab.listBranches({ project_id: "speedwave/core" })',
  inputSchema: {
    type: 'object',
    properties: {
      project_id: { type: ['string', 'number'], description: 'Project ID or path' },
      search: { type: 'string', description: 'Search by branch name' },
      limit: { type: 'number', description: 'Max results (default 20)' },
    },
    required: ['project_id'],
  },
  outputSchema: { /* JSON Schema for the result */ },
  inputExamples: [
    { description: 'List all branches', input: { project_id: 'my-group/my-project' } },
  ],
};
```

Nadaj mu `name` zgodną z `^[a-zA-Z0-9_-]+$`, długości od 1 do 99 znaków, oraz `keywords`, żeby wykrywanie na żądanie mogło je znaleźć. Ustaw `_meta.deferLoading` na `true` dla narzędzia, które ładuje się na żądanie, albo na `false`, żeby trzymać je stale załadowane. Wybierz stałą adnotacji odpowiednio do tego, co narzędzie robi z usługą:

| Stała | `readOnlyHint` | `destructiveHint` | Przykład |
| --- | --- | --- | --- |
| `READ_ONLY_ANNOTATIONS` | `true` | `false` | `listBranches`, `getBranch` |
| `WRITE_ANNOTATIONS` | `false` | `false` | `createBranch` |
| `DESTRUCTIVE_ANNOTATIONS` | `false` | `true` | `deleteBranch` |

## Opakuj swój handler

Połącz każde narzędzie z handlerem w parę `{ tool, handler }`, zgrupuj takie pary w fabryki takie jak `createBranchTools(client)`, a potem zbierz wszystkie fabryki na listę narzędzi swojego workera.

Jeśli twój handler przyjmuje klienta, który nie jest nullem, i rzuca wyjątek przy błędzie, użyj `withClientValidation`: sprawdza, czy klient nie jest nullem, i mapuje rzucony błąd na komunikat czytelny dla użytkownika, tak jak robi to GitLab. Jeśli twój handler zwraca już wynik w formie `{ success, data?, error? }`, użyj zamiast tego `withResultValidation`, tak jak robi to Slack. Tak czy inaczej, błędy braku konfiguracji kieruj do `notConfiguredMessage(service)` i mapuj kody statusu swojej usługi na zrozumiałe komunikaty.

Przepuszczaj wszystko, co logujesz, przez `sanitize()`, żeby żaden sekret nie trafił do logu.

## Przetestuj swoje narzędzia

Wywołaj fabrykę narzędzi z mockiem albo z `null` jako klientem, a potem znajdź każdy handler po nazwie i wywołaj go bezpośrednio. Sprawdź:

- Metadane: nazwę, opis, adnotacje, słowa kluczowe, schematy.
- Ścieżkę udanego wykonania na zamockowanej metodzie klienta.
- Walidację parametrów dla brakujących albo falsy wartości wejściowych.
- Obsługę błędów dla rzuconych wyjątków oraz ścieżkę dla nieskonfigurowanego klienta.

Uruchom `make test-mcp` dla całego zestawu testów i `make coverage-mcp` dla progów pokrycia. `mcp-servers/gitlab/src/tools/` zawiera referencyjne testy, z których możesz kopiować.