Skip to main content
Glama
README.md
# toolchain-mcp

MCP server (stdio) dla Claude Code integrujący toolchain: GitLab,
Jira, Redmine, Mattermost i Harbor — plus composite tools (`xref_*`)
łączące dane między systemami (np. "co wiadomo o tickecie X" jednym
zapytaniem zamiast czterech). Connector danego serwisu włącza się automatycznie
gdy w środowisku ustawiony jest komplet jego zmiennych — brak konfiguracji
= zero toolí tego serwisu, zero szumu w liście dostępnych narzędzi.

## Connectory i toole (~32)

Aktualną listę (z podziałem R/W) daje `node scripts/list-tools.mjs` po
zbudowaniu projektu i ustawieniu env — patrz [Smoke test](#smoke-test).
Stan na dziś:

| Connector | Toole |
|---|---|
| **gitlab** (13.8.4 CE) | `gitlab_list_mrs`, `gitlab_get_mr`, `gitlab_get_mr_diff`, `gitlab_list_mr_discussions`, `gitlab_reply_mr_discussion` **[W]**, `gitlab_list_pipelines`, `gitlab_get_job_log`, `gitlab_get_file`, `gitlab_search` |
| **jira** (7.7.1 Server) | `jira_search`, `jira_get_issue`, `jira_add_comment` **[W]**, `jira_list_transitions`, `jira_find_user`, `jira_list_projects` |
| **redmine** (redmine.example.com) | `redmine_list_issues`, `redmine_get_issue`, `redmine_add_note` **[W]**, `redmine_list_projects`, `redmine_search`, `redmine_whoami` |
| **mattermost** (mattermost.example.com, 10.11.18) | `mm_search_posts`, `mm_get_channel_posts`, `mm_get_thread`, `mm_post_message` **[W]**, `mm_find_channel`, `mm_find_user` |
| **harbor** (harbor.example.com) | `harbor_list_repositories`, `harbor_list_artifacts`, `harbor_get_vulnerabilities` |
| **xref** (composite, differentiator) | `xref_ticket` (Jira ∨ Redmine → agregacja z GitLab/MM), `xref_find_user` (login → lookup w Jira/GitLab/MM) |

**[W]** = write tool, wyłączany przez `TOOLCHAIN_MCP_READ_ONLY=1` (patrz niżej).

## Wymagania

- Node.js **≥ 20**
- Dostęp sieciowy do serwisów, które integrujesz. Jeśli któraś instancja jest
  w sieci wewnętrznej / za VPN (typowo GitLab, Jira, Harbor), zestaw połączenie
  zanim odpalisz serwer. Instancje publiczne nie wymagają VPN-a.

## Instalacja

```bash
git clone ssh://git@gitlab.kodownik.co:2424/kodowniki/toolchain-mcp.git
cd toolchain-mcp
npm ci
npm run build
```

Build kompiluje TypeScript do `dist/` (`tsc -p tsconfig.build.json`) —
serwer, `scripts/list-tools.mjs` i `scripts/smoke.mjs` importują z `dist/`,
nie z `src/`.

## Konfiguracja

Wszystkie zmienne mają prefiks `TOOLCHAIN_MCP_` (gołe `GITLAB_URL` itp. kolidują
ze zmiennymi środowiska deweloperów). Connector jest aktywny **tylko** gdy
ustawiony jest komplet jego zmiennych credentialowych — URL ma sensowny
default i nie musi być podawany.

| Zmienna | Default | Uwagi |
|---|---|---|
| `TOOLCHAIN_MCP_GITLAB_URL` | `https://gitlab.example.com` | |
| `TOOLCHAIN_MCP_GITLAB_TOKEN` | — | PAT scope `api` lub `read_api` |
| `TOOLCHAIN_MCP_JIRA_URL` | `https://jira.example.com` | |
| `TOOLCHAIN_MCP_JIRA_USERNAME` / `TOOLCHAIN_MCP_JIRA_PASSWORD` | — | basic auth (7.7.1 bez PAT) |
| `TOOLCHAIN_MCP_REDMINE_URL` | `https://redmine.example.com` | |
| `TOOLCHAIN_MCP_REDMINE_API_KEY` | — | z `/my/account` |
| `TOOLCHAIN_MCP_MM_URL` | `https://mattermost.example.com` | |
| `TOOLCHAIN_MCP_MM_TOKEN` | — | bot token LUB session token — najłatwiej `npm run mm-login` (GitLab-SSO-only, patrz niżej) |
| `TOOLCHAIN_MCP_MM_TEAM` | — | default team (nazwa-handle) |
| `TOOLCHAIN_MCP_HARBOR_URL` | `https://harbor.example.com` | |
| `TOOLCHAIN_MCP_HARBOR_USER` / `TOOLCHAIN_MCP_HARBOR_PASSWORD` | — | robot account (`robot$...` — uwaga na `$` w shellu) |
| `TOOLCHAIN_MCP_READ_ONLY` | `0` | `1` = write-toole nie są rejestrowane |
| `TOOLCHAIN_MCP_CA_FILE` | — | dodatkowy CA (alternatywa: `NODE_EXTRA_CA_CERTS`) |
| `TOOLCHAIN_MCP_<SVC>_INSECURE_TLS` | `0` | per-serwis opt-out z weryfikacji TLS (NIGDY globalny `NODE_TLS_REJECT_UNAUTHORIZED=0`) |
| `TOOLCHAIN_MCP_TIMEOUT_MS` | `15000` | timeout HTTP |

## Jak zdobyć każdy token

**GitLab (gitlab.example.com)** — zalogowany w GitLab → *Settings → Access Tokens*
→ nowy Personal Access Token, scope `read_api` (albo `api` jeśli chcesz też
`gitlab_reply_mr_discussion`). Wklej do `TOOLCHAIN_MCP_GITLAB_TOKEN`.

**Jira (jira.example.com, wersja 7.7.1)** — **PAT-ów tu nie ma** (weszły
dopiero w Jirze 8.14+). Auth to zwykłe login+hasło domenowe AD, więc
`TOOLCHAIN_MCP_JIRA_USERNAME` / `TOOLCHAIN_MCP_JIRA_PASSWORD` to Twój login i hasło
domenowe.

> ⚠️ **Uwaga na rotację hasła.** Gdy zmienisz hasło domenowe , serwer z
> nadal starym hasłem w env zacznie dostawać 401 — kilka prób ze złym
> hasłem może zablokować konto AD i/lub włączyć CAPTCHA-blokadę logowania
> w Jirze. Po każdej rotacji hasła **natychmiast** zaktualizuj
> `TOOLCHAIN_MCP_JIRA_PASSWORD` (i zrestartuj serwer MCP w Claude Code).

**Redmine (redmine.example.com)** — zaloguj się w przeglądarce → *Moje konto*
→ *Klucz dostępu API* (jeśli sekcji nie widać, REST API nie jest włączone
dla Twojego konta — poproś admina o włączenie). Wklej do
`TOOLCHAIN_MCP_REDMINE_API_KEY`.

**Mattermost (mattermost.example.com)** — logowanie na tym serwerze jest **wyłącznie
przez GitLab SSO** (OAuth do osobnej instancji `mattermost.example.com`), więc
zwykłego tokenu API nie da się wygenerować z ustawień konta. Trzy ścieżki
(od najwygodniejszej):
- *Helper (zalecane):* uruchom `npm run mm-login` — otworzy się okno
  przeglądarki (systemowy Chrome), zalogujesz się **raz** jak zwykle
  (GitLab SSO + ewentualne 2FA), a skrypt wyłuska token sesji `MMAUTHTOKEN`
  i wypisze `export TOOLCHAIN_MCP_MM_TOKEN='…'`. Dodaj `--write`, by od razu
  zapisał go do `~/.secrets` (plik dostaje `chmod 600`). Jeśli chcesz
  `eval` wyniku, wołaj `node scripts/mm-gitlab-login.mjs` bezpośrednio
  (nie `npm run …`, bo npm dokłada baner na stdout). Profil przeglądarki jest trwały
  (`~/.cache/toolchain-mcp/mm-profile`), więc przy odświeżaniu wygasłego tokenu
  zwykle wystarczy jedno kliknięcie. Zero haseł w plikach, zero ryzyka
  lockoutu konta (login robi prawdziwa przeglądarka). Wymaga
  devDependency `playwright` + systemowego Chrome (lub
  `npx playwright install chromium`).
- *Docelowo (bez wygasania):* poproś admina Mattermost o **bot account** —
  token bota **nie wygasa**, w przeciwieństwie do tokenu sesji.
- *Ręcznie:* zaloguj się w przeglądarce na `mattermost.example.com`, DevTools →
  *Application* → *Cookies* → skopiuj wartość ciasteczka **`MMAUTHTOKEN`**.
  To token sesji — **wygasa**; po wygaśnięciu `mm_*` zwracają 401.

**Harbor (harbor.example.com)** — poproś admina projektu `demo` w Harborze o
**robot account** (`robot$<nazwa>` + hasło). Uwaga: nazwa robota zawiera
znak `$` — w shellu i w `.env` cytuj wartość w pojedynczym cudzysłowie,
żeby powłoka nie próbowała go rozwinąć jako zmienną.

## Podpięcie do Claude Code

**Wariant A — `claude mcp add`** (per-użytkownik, nie trafia do repo):

```bash
claude mcp add toolchain \
  --env TOOLCHAIN_MCP_GITLAB_TOKEN=... \
  --env TOOLCHAIN_MCP_JIRA_USERNAME=... --env TOOLCHAIN_MCP_JIRA_PASSWORD=... \
  --env TOOLCHAIN_MCP_REDMINE_API_KEY=... \
  --env TOOLCHAIN_MCP_MM_TOKEN=... --env TOOLCHAIN_MCP_MM_TEAM=myteam \
  --env TOOLCHAIN_MCP_HARBOR_USER=... --env TOOLCHAIN_MCP_HARBOR_PASSWORD=... \
  -- node /ABSOLUTNA/SCIEZKA/DO/toolchain-mcp/dist/index.js
```

Podaj tylko zmienne serwisów, z których faktycznie korzystasz — pominięty
serwis po prostu nie zarejestruje swoich toolí.

**Wariant B — `.mcp.json`** (scope project, commitowalny bez sekretów):
skopiuj [`.mcp.json.example`](./.mcp.json.example) do `.mcp.json`, popraw
ścieżkę do `dist/index.js`. Wartości `${TOOLCHAIN_MCP_*}` ekspandują się z env
Twojej powłoki w momencie startu Claude Code — same sekrety zostają w
Twoim `~/.secrets`/`.env`, nie w repo.

## Read-only mode

`TOOLCHAIN_MCP_READ_ONLY=1` — write-toole (`gitlab_reply_mr_discussion`,
`jira_add_comment`, `redmine_add_note`, `mm_post_message`) **w ogóle nie są
rejestrowane** w serwerze (twardy wycinacz, nie tylko hint w opisie). Włącz
gdy chcesz podpiąć serwer koledze/agentowi bez ryzyka przypadkowego posta
czy komentarza.

## TLS intranetu

Certyfikaty serwisów wewnętrznych bywają self-signed. Trzy sposoby
obsługi, od najbezpieczniejszego:

1. **`NODE_EXTRA_CA_CERTS=/sciezka/do/ca.pem`** — standardowa zmienna Node,
   dorzuca CA globalnie do procesu.
2. **`TOOLCHAIN_MCP_CA_FILE=/sciezka/do/ca.pem`** — analogiczne, tylko dla
   dispatcherów HTTP tego serwera.
3. **`TOOLCHAIN_MCP_<SVC>_INSECURE_TLS=1`** (np. `TOOLCHAIN_MCP_JIRA_INSECURE_TLS=1`) —
   wyłącza weryfikację TLS, ale **tylko dla wskazanego serwisu**.

**Nigdy** nie używaj globalnego `NODE_TLS_REJECT_UNAUTHORIZED=0` — wyłącza
weryfikację TLS dla całego procesu Node, łącznie z ruchem do serwisów,
których to nie dotyczy.

## Smoke test

Po `npm run build`, z ustawionym env (i VPN dla serwisów w sieci wewnętrznej):

```bash
npm run smoke
```

Odpytuje po jednym lekkim, nieszkodliwym endpoincie na serwis (`/version`,
`/myself`, `/users/current.json`, `/users/me`, `/projects?page_size=1`).
Serwis bez ustawionych zmiennych → `SKIP (brak env)`, nie liczy się jako
błąd. Exit code `0` gdy wszystkie skonfigurowane serwisy odpowiadają OK,
`1` gdy którykolwiek zwrócił błąd — użyteczne po każdej rotacji tokenu, żeby
sprawdzić od razu wszystkie na raz zamiast czekać na pierwszy failing tool
call. **Uruchamiany ręcznie, nie w CI** (wymaga żywej sieci wewnętrznej).

## Troubleshooting

- **Jira 401 / komunikat o degraded mode** — connector Jira po pierwszym
  401/403 przechodzi w tryb degraded do końca życia procesu (ochrona przed
  lockoutem konta) i przestaje wysyłać requesty. Jeśli komunikat
  wspomina CAPTCHA (`X-Authentication-Denied-Reason: CAPTCHA_CHALLENGE`) —
  zaloguj się raz ręcznie w przeglądarce na `jira.example.com`, rozwiąż
  CAPTCHĘ, potem zrestartuj serwer MCP. Jeśli to zwykłe 401 — sprawdź czy
  hasło w `TOOLCHAIN_MCP_JIRA_PASSWORD` nie jest przeterminowane po rotacji.
- **Redmine 401** — albo `TOOLCHAIN_MCP_REDMINE_API_KEY` jest nieprawidłowy/wygasł
  (wygeneruj nowy w *Moje konto*), albo REST API jest wyłączone dla Twojego
  konta po stronie admina (poproś o włączenie).
- **Mattermost 401** — najczęściej wygasł `MMAUTHTOKEN` (sesja z ciasteczka
  ma ograniczony czas życia). Wyciągnij nowy token wg instrukcji wyżej albo
  poproś admina o bot account, żeby problem zniknął na stałe.
- **401 z Jiry a toole `xref_*` dalej strzelają** — `xref_*` robi własne
  wywołania HTTP i NIE podlega degraded-mode connectora Jira (świadoma
  odchyłka — patrz komentarz w `src/xref.ts`). Przy nieaktualnym haśle
  **nie** wołaj `xref_ticket` w pętli (ryzyko lockoutu konta) — najpierw
  popraw `TOOLCHAIN_MCP_JIRA_PASSWORD`.