Przejdź do treści
AI w praktyce Aktualizacja: 10 min czytania

Serwer MCP w TypeScript dla Claude Code, Codex i Copilot

Serwer MCP w TypeScript: narzędzie z Zod, test w MCP Inspector i ten sam serwer w Claude Code, Codex i Copilot w VS Code.

Spis treści

Agent AI w edytorze zna Twój kod, ale nie zna Twojej aplikacji. Nie wie, jak utworzyć w niej rekord ani gdzie sprawdzić ranking. Serwer MCP (Model Context Protocol) to mały program, który mu to umożliwia, bo udostępnia funkcje aplikacji jako narzędzia z opisem i schematem argumentów. Pokażę Ci taki serwer w TypeScript na przykładzie aplikacji z quizami z kursu DevChallenge. Potem podłączymy go do Claude Code, Codex i Copilot w VS Code, bez zmieniania w nim ani jednej linijki.

Co to jest serwer MCP i jak działa

Serwer MCP pośredniczy między agentem AI a Twoją aplikacją. Agent pobiera od niego listę narzędzi, a potem wywołuje te, których potrzebuje, z argumentami zgodnymi ze schematem. W MCP są trzy role. Host to aplikacja, w której rozmawiasz z modelem, na przykład Claude Code. Klient to część hosta, która utrzymuje połączenie z jednym serwerem. Serwer to Twój program z narzędziami.

Najprostszy transport to stdio. Host uruchamia serwer jako osobny proces i wymienia z nim wiadomości JSON-RPC przez standardowe wejście i wyjście. Wynika z tego jedna praktyczna zasada: stdout należy do protokołu. Każdy console.log w serwerze może zepsuć komunikację, dlatego logi wypisujesz na stderr przez console.error.

Taki serwer nie potrzebuje publicznego adresu, certyfikatu ani logowania. Działa na Twoim komputerze i kończy pracę razem z sesją agenta.

Narzędzie MCP w TypeScript

Serwer z kursu korzysta z oficjalnego SDK MCP w wersji 2, czyli z pakietu @modelcontextprotocol/server, i z Zod. Poniżej jest skrócona wersja jego narzędzia, które tworzy i publikuje quiz:

import { randomUUID } from 'node:crypto'
import { McpServer } from '@modelcontextprotocol/server'
import { serveStdio } from '@modelcontextprotocol/server/stdio'
import { z } from 'zod'

const apiUrl = new URL(process.env.DEVCHALLENGE_API_URL ?? 'http://localhost:3000')

const postJson = async (path: string, payload: object) => {
  const response = await fetch(new URL(path, apiUrl), {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', Origin: apiUrl.origin },
    body: JSON.stringify(payload),
    signal: AbortSignal.timeout(75_000),
  })
  if (!response.ok) throw new Error(`Żądanie ${path} zwróciło status ${response.status}.`)
  return response.json()
}

const server = new McpServer({ name: 'devchallenge', version: '1.0.0' })

server.registerTool(
  'create-challenge',
  {
    title: 'Stwórz challenge DevChallenge',
    description:
      'Generuje quiz przez AI i publikuje go pod publicznym linkiem. ' +
      'Wywołanie kosztuje generację AI, więc potwierdź je w hoście.',
    inputSchema: z.object({
      topic: z.string().trim().min(1).max(200),
      level: z.enum(['easy', 'medium', 'hard']),
      questionCount: z.number().int().min(3).max(10),
      typeMode: z.enum(['single', 'trueFalse', 'multiple']).default('single'),
    }),
    annotations: { readOnlyHint: false, destructiveHint: false, openWorldHint: true },
  },
  async ({ topic, level, questionCount, typeMode }) => {
    try {
      const challenge = await postJson('/api/challenges/generate', {
        topic,
        level,
        questionCount,
        typeMode,
        documentId: null,
        documentToken: null,
        hasAiConsent: false,
      })
      const saved = await postJson('/api/challenges', { publicationId: randomUUID(), challenge })
      return { content: [{ type: 'text', text: `Challenge gotowy: ${apiUrl.origin}/c/${saved.id}` }] }
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Nie udało się utworzyć quizu.'
      return { content: [{ type: 'text', text: message }], isError: true }
    }
  },
)

serveStdio(() => server)
console.error('DevChallenge MCP działa, stdout należy do protokołu.')

W kursie serwer sprawdza jeszcze odpowiedź API schematem Zod, zanim cokolwiek opublikuje. Tutaj pominąłem ten krok, żeby kod zmieścił się na jednym ekranie. Kilka decyzji warto omówić osobno:

  • Opis mówi o koszcie. Czyta go model, więc od tego opisu zależy, czy agent zapyta Cię o zgodę, zanim wywoła narzędzie.
  • Adnotacje informują hosta o skutkach. readOnlyHint: false i openWorldHint: true oznaczają, że narzędzie zmienia stan i sięga poza Twój komputer. Host może więc poprosić Cię o potwierdzenie, ale adnotacja to tylko podpowiedź, a nie zabezpieczenie.
  • Błąd wraca jako wynik. Zamiast rzucać wyjątek, narzędzie zwraca treść błędu z isError: true. Model zobaczy wtedy na przykład status 429 przy limicie i przekaże Ci go, zamiast zgadywać.
  • Serwer zna tylko adres API. Klucze OpenAI i dane dostępu do bazy danych zostają w aplikacji.

Node.js 24 uruchamia pliki .ts bezpośrednio, więc serwer nie potrzebuje osobnego kroku budowania. W repozytorium kursu wystarczy polecenie node mcp-server/src/index.ts.

Najpierw sprawdź serwer w MCP Inspector

Zanim podłączysz agenta, sprawdź serwer w darmowym MCP Inspector. Uruchom aplikację w jednym terminalu, a w drugim pobierz listę narzędzi. W poleceniu wpisz własną ścieżkę do serwera:

npx @modelcontextprotocol/inspector --cli \
  -e DEVCHALLENGE_API_URL=http://127.0.0.1:3000 \
  node /ABSOLUTNA/SCIEZKA/mcp-server/src/index.ts \
  --method tools/list

Wynik powinien zawierać create-challenge razem ze schematem argumentów. Lista narzędzi niczego nie generuje, więc ten krok nic nie kosztuje. Bez --cli Inspector otworzy interfejs w przeglądarce, w którym wywołasz narzędzie ręcznie.

Ten sam serwer w Claude Code, Codex i Copilot

Każdy agent potrzebuje tej samej informacji: jakim poleceniem uruchomić serwer i z jakimi zmiennymi środowiskowymi. Różni się tylko miejsce, w którym ją zapisujesz. Agenci zmieniają się szybko, więc przy błędzie zajrzyj do ich aktualnej dokumentacji.

Claude Code

claude mcp add devchallenge \
  --transport stdio \
  --scope local \
  --env DEVCHALLENGE_API_URL=http://127.0.0.1:3000 \
  -- node /ABSOLUTNA/SCIEZKA/mcp-server/src/index.ts

Nazwa serwera stoi zaraz po add, bo opcja --env przyjmuje kilka wartości i wzięłaby nazwę za kolejną zmienną. W sesji Claude Code polecenie /mcp pokaże, czy serwer jest połączony. Jeśli agent uruchomi starszy Node niż 24, wpisz zamiast node pełną ścieżkę z polecenia node -p "process.execPath".

Codex

codex mcp add devchallenge \
  --env DEVCHALLENGE_API_URL=http://127.0.0.1:3000 \
  -- node /ABSOLUTNA/SCIEZKA/mcp-server/src/index.ts
codex mcp list

Codex zapisuje ten wpis w swojej konfiguracji, w pliku ~/.codex/config.toml. Drugie polecenie pokazuje listę serwerów, więc od razu sprawdzisz, czy wpis się zapisał.

Copilot w VS Code

W katalogu projektu utwórz plik .vscode/mcp.json. Główny klucz to servers:

{
  "servers": {
    "devchallenge": {
      "type": "stdio",
      "command": "node",
      "args": ["${workspaceFolder}/mcp-server/src/index.ts"],
      "env": { "DEVCHALLENGE_API_URL": "http://127.0.0.1:3000" }
    }
  }
}

Potem otwórz Copilot Chat w trybie agenta i sprawdź na liście narzędzi, czy jest tam create-challenge. Jeśli korzystasz z GitHub Copilot na koncie firmowym, administrator organizacji może mieć wyłączone serwery MCP. Kiedy narzędzi nie widać, sprawdź najpierw to ustawienie.

Skill w Claude Code i Codex: kiedy agent ma zapytać o zgodę

Konfiguracja mówi agentowi, jak uruchomić serwer, ale nie mówi, kiedy z niego korzystać. Do tego służy skill, czyli plik SKILL.md z krótką instrukcją i opisem, po którym agent rozpoznaje, że skill pasuje do zadania. Przykładowy skill dla tej aplikacji może wyglądać tak:

---
name: devchallenge-quiz
description: Tworzy quiz w lokalnej aplikacji DevChallenge przez narzędzie MCP create-challenge. Użyj, gdy ktoś prosi o quiz na wybrany temat.
---

1. Ustal temat i streść go w najwyżej 200 znakach.
2. Pokaż argumenty: temat, poziom, liczbę pytań i typ pytań.
3. Przed wywołaniem create-challenge poproś o potwierdzenie. Każde wywołanie kosztuje generację AI i publikuje quiz pod publicznym linkiem.
4. Po błędzie nie ponawiaj wywołania bez zgody. Status 429 oznacza limit generowania.

Claude Code szuka skilli w katalogu .claude/skills, Codex w .agents/skills, a Copilot w VS Code w obu tych miejscach. W repozytorium trzymasz więc skill w .claude/skills/devchallenge-quiz/SKILL.md i jego kopię w .agents/skills/devchallenge-quiz/SKILL.md. Ogólne zasady projektu, na przykład polecenia testów, zapisujesz w AGENTS.md.

Co agent naprawdę robi z Twoim kodem

Poproś agenta: „zrób quiz o tym, jak działa nasz moduł RAG”. Agent przeczyta pliki projektu, streści temat do 200 znaków i wywoła create-challenge. Narzędzie nie dostaje ani kodu, ani PDF⁠-⁠a, tylko ten krótki temat. Quiz powstaje więc z tematu i wiedzy modelu, a nie z Twojego repozytorium.

To nadal jest przydatne, bo agent trafnie nazywa temat na podstawie kodu. Warto jednak to wiedzieć, zanim komuś obiecasz „quiz z Twojego kodu”. Różnicę między takim narzędziem a quizem z dokumentu opisuję we wpisie RAG vs MCP na przykładzie jednej aplikacji.

Bezpieczeństwo serwera MCP w trzech zasadach

  1. Serwer zna tylko publiczne API. Sekrety zostają w aplikacji, a serwer ma te same limity co przeglądarka, czyli 5 quizów na godzinę z jednego adresu IP.
  2. Narzędzie, które coś kosztuje, wymaga zgody. Opis narzędzia, adnotacje i skill mówią to samo: najpierw argumenty, potem potwierdzenie. Nie wyłączaj potwierdzeń dla wszystkich narzędzi naraz.
  3. Zaczynaj od odczytu. W kursie drugie narzędzie, get-leaderboard, dopisujesz samodzielnie w zadaniu. To narzędzie tylko odczytuje ranking quizu, więc agent może z niego korzystać bez kosztów i bez zmian w danych.

Najczęstsze pytania

Czy serwer MCP musi być publicznie dostępny?

Nie. Serwer stdio działa lokalnie jako proces uruchamiany przez agenta. Publiczny adres jest potrzebny dopiero wtedy, gdy z serwera mają korzystać ChatGPT albo claude.ai. W kursie taki zdalny serwer udostępnia tylko ranking, czyli odczyt bez kosztów, więc działa bez logowania. Płatne tworzenie quizu zostaje w serwerze lokalnym, bo zdalnie wymagałoby logowania i ochrony przed kosztami.

Kiedy wystarczy skill, a kiedy potrzebny jest serwer MCP?

Skill to instrukcja dla agenta. Mówi, kiedy i jak wykonać zadanie, ale niczego nie wywołuje w aplikacji. Serwer MCP daje agentowi narzędzie, które coś robi, na przykład tworzy quiz. Do pilnowania zasad projektu wystarczy skill. Do zmian w danych aplikacji potrzebny jest serwer MCP, a skill podpowiada agentowi, kiedy z niego skorzystać.

Czy do nauki potrzebny jest płatny agent?

Nie. MCP Inspector jest darmowy i wystarcza do sprawdzenia narzędzi. Claude Code wymaga płatnego planu Claude albo konta API z płatnością za użycie, a dostęp do Codex i Copilot zależy od ich aktualnych planów.

Serwer z tego wpisu piszemy w Kursie 4 DevChallenge. Najpierw powstaje narzędzie i testy protokołu, potem łączymy je z Claude Code, Codex i Copilot, razem ze skillem projektu, automatyczną oceną jakości quizów i rankingiem w okienku czatu. Wszystkie lekcje znajdziesz w programie kursu.

Mateusz Karwowski
Mateusz Karwowski

Jestem programistą i nauczycielem, magistrem inżynierem informatyki po Politechnice Warszawskiej. Uczę programowania i matematyki na zajęciach 1:1. Wspólnie rozwiązujemy zadania, piszemy kod i wyjaśniamy kolejne kroki.

Jeden serwer MCP dla wszystkich agentów

W Kursie 4 DevChallenge napiszemy ten serwer razem, podłączymy go do Claude Code, Codex i Copilot w VS Code, a potem sprawdzimy go testami i automatyczną oceną quizów (evals).