Skip to main content
Glama
mkidawa

GDG Store MCP Server

by mkidawa
README.md
# Zbuduj własny serwer MCP w TypeScript

Repozytorium do spotkania GDG. Teza przewodnia: **MCP to USB-C dla narzędzi AI** — narzędzie napisane raz działa w każdym kliencie (Claude Desktop, Cursor, Gemini CLI, VS Code, Twój własny agent). Zamiast M aplikacji × N integracji piszemy M + N.

Przechodzimy cały protokół od zera: minimalny serwer → prawdziwe narzędzia z walidacją → resources i prompts → transport HTTP → własny klient. **Do checkpointów 01–05 nie potrzebujesz żadnego klucza API** — testujemy przez MCP Inspector i własnego klienta.

## Wymagania

- Node.js 20+
- Edytor (VS Code / Cursor / cokolwiek)
- (Tylko do teasera) klucz Gemini z darmowego tieru: https://aistudio.google.com/apikey

## Architektura

Serwery MCP w tym repo nie trzymają danych — są cienkimi adapterami nad lokalnym REST API (dokładnie tak, jak produkcyjne serwery MCP opakowują istniejące systemy):

```
host (Claude/Inspector) → klient MCP → serwer MCP → backend REST (:4000) → data/*.json
```

Backend czyta pliki `data/*.json` przy każdym requeście — możesz edytować dane na żywo, bez restartu.

## Setup

```bash
git clone <adres-repo>
cd gdg-agent-workshop
npm install
```

**Terminal 1 — backend (potrzebny do checkpointów 02-05 i teasera):**

```bash
npm run api
# 🗄️ GDG Store API: http://localhost:4000
# szybki test na Windows: curl.exe http://localhost:4000/products/KB-MECH-75
```

Gotowe zapytania do skopiowania: [curl-requests.md](curl-requests.md).

Sprawdzenie, że wszystko działa (odpala serwer 01 w GUI Inspectora):

```bash
npx @modelcontextprotocol/inspector npx tsx src/01-minimal/server.ts
```

## Checkpointy

Scenariusz: budujemy serwer MCP fikcyjnego sklepu **GDG Store**. Dane serwuje lokalny backend REST (`npm run api`, dane w `data/*.json`) — w realnym projekcie w jego miejscu stałyby Medusa/WooCommerce i API InPost/DPD.

| # | Uruchomienie | Co pokazuje |
|---|---|---|
| 01 | Inspector (komenda wyżej) | Minimalny serwer: 1 narzędzie, stdio, ~25 linijek. Anatomia: nazwa + opis + schemat + handler. Jedyny checkpoint bez backendu. |
| 02 | `npx @modelcontextprotocol/inspector npx tsx src/02-store/server.ts` | 4 narzędzia sklepu jako adaptery nad REST API. Walidacja zod (regex na SKU!), błędy domenowe przez `isError` (404 z backendu ORAZ padnięty backend), annotations. |
| 03 | `npx @modelcontextprotocol/inspector npx tsx src/03-resources-prompts/server.ts` | Resources (statyczny + template z `list`) i Prompts — pozostałe 2/3 protokołu. |
| 04 | `npm run 04`, potem Inspector → Streamable HTTP → `http://localhost:3000/mcp` | Ten sam protokół zdalnie po HTTP. Tryb stateless. Komendy curl w komentarzu na dole pliku. |
| 05 | `npm run 05` | Własny klient MCP w ~50 linijkach: handshake, odkrywanie, wywołania. Zero LLM-a. |
| teaser | `npm run teaser` (wymaga klucza w `.env`) | Klient z 05 + Gemini = pętla agentowa. Temat następnego spotkania 😉 |

## Podpięcie serwera do prawdziwych hostów

Ten sam serwer (checkpoint 02), zero zmian w kodzie:

**Claude Desktop** — `claude_desktop_config.json` (Settings → Developer):

```json
{
  "mcpServers": {
    "gdg-store": {
      "command": "npx",
      "args": ["tsx", "/ABSOLUTNA/SCIEZKA/DO/repo/src/02-store/server.ts"]
    }
  }
}
```

**Gemini CLI** — `~/.gemini/settings.json`:

```json
{
  "mcpServers": {
    "gdg-store": {
      "command": "npx",
      "args": ["tsx", "/ABSOLUTNA/SCIEZKA/DO/repo/src/02-store/server.ts"]
    }
  }
}
```

**Cursor** — Settings → MCP → Add new global MCP server (ta sama struktura).

Po restarcie hosta zapytaj: *„Czy klawiatura mechaniczna 75% jest dostępna w GDG Store?”*

## Trzy prymitywy MCP (jeśli zapamiętasz jedną tabelkę)

| Prymityw | Kto decyduje o użyciu | Analogia |
|---|---|---|
| **Tools** | model | „model sam sięga po dane / wykonuje akcję” |
| **Resources** | aplikacja / user | „załącz plik do rozmowy” |
| **Prompts** | user | „slash-command przygotowany przez autora serwera” |

## Kluczowe wnioski

1. **Opis narzędzia to interfejs użytkownika dla modelu.** LLM widzi tylko nazwę, opis i schemat — jakość opisów decyduje o tym, czy narzędzie w ogóle zostanie sensownie użyte.
2. **Serwer stdio nie może pisać na stdout.** stdout to kanał protokołu; logi zawsze na stderr (`console.error`).
3. **Błąd domenowy ≠ błąd protokołu.** `isError: true` wraca do modelu jako treść, żeby mógł zareagować; wyjątek JSON-RPC to sprawa między klientem a serwerem.
4. **Klient to nie magia.** Handshake + `tools/list` + `tools/call` — Claude Desktop robi dokładnie to samo, co nasz 50-linijkowy klient z checkpointu 05.

## Co dalej

- Dodaj do backendu endpoint `GET /orders` (lista wszystkich), a potem użyj go w resource template zamiast zahardkodowanych ID (patrz komentarz w checkpoincie 03)
- Dołóż własne narzędzie do serwera 02 (np. `create_return` — zgłoszenie zwrotu; uwaga: POST do backendu i to już nie jest `readOnlyHint`!)
- Dodaj resource template `gdg-store://products/{sku}`
- Podepnij serwer 02 do swojego ulubionego hosta i używaj na co dzień
- Przeczytaj o autoryzacji zdalnych serwerów (OAuth 2.1) w spec: https://modelcontextprotocol.io
- Przyjdź na następne spotkanie: **pętla agentowa od zera** — jak hosty naprawdę używają tych narzędzi (`npm run teaser` to zapowiedź)