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, 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ę.
Trzymaj otwarty mcp-servers/gitlab podczas czytania tej strony: to referencyjna implementacja wszystkiego, co opisujemy niżej.
Zaimportuj wspólny pakiet
Dział zatytułowany „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
Dział zatytułowany „Uruchom swój worker”Wywołaj bootWorker() z main(). Oto cały punkt wejścia workera GitLab:
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
Dział zatytułowany „Zamontuj swoje dane uwierzytelniające”Montuj wyłącznie dane uwierzytelniające własnej usługi, w /tokens, w trybie tylko do odczytu. Rozwiąż ścieżkę przez tokensDir() i wczytaj plik przez loadTokenFile(name). Klient GitLab czyta dwa pliki:
Folder/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
Dział zatytułowany „Zadeklaruj narzędzie”Narzędzie to obiekt zgodny z interfejsem Tool. Oto listBranches z workera GitLab:
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
Dział zatytułowany „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
Dział zatytułowany „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ć.