Powrot do bloga
23 sierpnia 2026
published

MCP od zera. Jak podłączyć AI do własnych narzędzi

Model Context Protocol to jednocześnie najnudniejsza i najbardziej praktyczna rzecz, jaka wydarzyła się wokół AI w ostatnim czasie. Nudna, bo to zwykły...

Okladka: MCP od zera. Jak podłączyć AI do własnych narzędzi

Model Context Protocol to jednocześnie najnudniejsza i najbardziej praktyczna rzecz, jaka wydarzyła się wokół AI w ostatnim czasie. Nudna, bo to zwykły protokół komunikacyjny. Praktyczna, bo bez niej model językowy jest bardzo elokwentnym więźniem odciętym od twoich danych.

Pokażę, jak MCP działa od środka, jak w kilkanaście minut podpiąć pierwszy serwer i jak napisać własny w Pythonie. Bez zakładania, że wiesz cokolwiek o protokole.

Czytanie: ~15 min

Dlaczego w ogóle ktoś wymyślił kolejny protokół

Zacznijmy od problemu, bo bez niego MCP wygląda na wymysł architektów, którzy nie mieli co robić.

Model językowy sam z siebie nie umie nic poza generowaniem tekstu. Nie zajrzy do twojej bazy, nie przeczyta pliku na dysku, nie wystawi faktury w systemie. Żeby cokolwiek zrobił, ktoś musi napisać kawałek kodu, który tłumaczy „chcę listę faktur z czerwca" na konkretne zapytanie do konkretnego API, a potem wynik wraca do modelu.

Ten kawałek kodu nazywamy narzędziem (tool). I tu zaczyna się bałagan. Napisałem integrację z bazą PostgreSQL dla ChatGPT? Świetnie, tylko że dla Claude'a wygląda inaczej. Dla agenta w n8n jeszcze inaczej. Dla własnej aplikacji w .NET znowu inaczej. Cztery aplikacje, pięć źródeł danych, dwadzieścia integracji do napisania i utrzymania.

To klasyczny problem N razy M. Anthropic w listopadzie 2024 wypuściło MCP właśnie po to, żeby zamienić go na N plus M: każdy klient AI gada jednym protokołem, każde źródło danych wystawia jeden serwer, a reszta się dogaduje. Trochę jak USB dla portów, tylko dla kontekstu.

Że pomysł chwycił, widać po tym, kto go przyjął. W 2025 wsparcie ogłosiło OpenAI, potem Google. Dziś MCP obsługuje Claude Desktop, Claude Code, Cursor, VS Code z Copilotem, JetBrains, Windsurf i kilkadziesiąt mniejszych narzędzi. Rzadko się zdarza, żeby konkurenci zgodzili się na wspólny format czegokolwiek.

Trzy elementy, które musisz rozróżnić

MCP opiera się na trzech rolach i mylenie ich to najczęstsza przyczyna frustracji na starcie.

Host to aplikacja, z którą rozmawiasz. Claude Desktop, Claude Code, Cursor, twoja własna apka z modelem w środku. Host zarządza modelem i decyduje, co model może zrobić.

Klient siedzi wewnątrz hosta i utrzymuje połączenie z jednym serwerem. Jeden serwer, jeden klient. Podpinasz pięć serwerów, host tworzy pięć klientów. W praktyce nigdy tego nie dotykasz, bo host robi to za ciebie.

Serwer to program, który wystawia możliwości: dostęp do plików, zapytania do bazy, wywołania API. Serwer nie wie nic o modelu i nie obchodzi go, kto go używa. Wystawia funkcje i czeka.

Kluczowa rzecz do zapamiętania: serwer MCP to nie jest serwer w rozumieniu maszyny w chmurze. W najprostszej wersji to skrypt w Pythonie, który uruchamia się lokalnie na twoim laptopie i gada z hostem przez standardowe wejście i wyjście. Zero infrastruktury, zero deploymentu.

Co serwer może wystawić

Protokół definiuje trzy typy rzeczy, które serwer udostępnia. W praktyce 90% serwerów, jakie zobaczysz, używa tylko pierwszego.

Tools to funkcje, które model może wywołać, żeby coś zrobić. wyslij_maila, pobierz_faktury, utworz_zadanie. Model sam decyduje, kiedy ich użyć, na podstawie opisu, który podajesz. To sprawia, że opis narzędzia jest ważniejszy niż jego kod. Napiszesz „pobiera dane" i model nigdy tego nie użyje we właściwym momencie. Napiszesz „pobiera listę faktur sprzedażowych z systemu Enova dla wskazanego zakresu dat, zwraca numer, kontrahenta, kwotę netto i status płatności" i nagle wszystko działa.

Resources to dane tylko do odczytu, które host może wciągnąć do kontekstu. Plik, dokument, wynik zapytania. Różnica względem tools jest taka, że resources zwykle wybiera użytkownik albo aplikacja, nie model.

Prompts to gotowe szablony instrukcji, które serwer podsuwa użytkownikowi. Coś jak makra. W Claude Code pojawiają się jako komendy ze slashem.

Zacznij od tools. Do resources i prompts wrócisz, gdy będą ci potrzebne, a często nigdy nie będą.

Jak to gada: stdio kontra HTTP

Dwa sposoby transportu, wybór zależy od tego, gdzie serwer żyje.

stdio to lokalny serwer uruchamiany jako proces potomny hosta. Komunikacja idzie przez standardowe wejście i wyjście, formatem jest JSON-RPC 2.0. Ten wariant wybierasz zawsze, gdy serwer działa na tej samej maszynie co host. Jest szybki, nie wymaga otwierania portów i nie ma problemu z uwierzytelnianiem, bo proces dziedziczy twoje uprawnienia.

Streamable HTTP to wariant zdalny. Serwer stoi pod adresem URL, host łączy się przez HTTP, odpowiedzi mogą przychodzić strumieniowo przez Server-Sent Events. Tego używasz, gdy serwer ma obsłużyć wielu użytkowników albo siedzi w chmurze i wymaga autoryzacji przez OAuth. Starsze materiały mówią o transporcie „SSE" jako osobnym trybie, ale został zastąpiony przez Streamable HTTP w wersji specyfikacji z marca 2025.

Na start i tak będziesz robił wszystko przez stdio.

Podłącz pierwszy serwer i zobacz, że to działa

Teoria wystarczy. Najszybsza droga do zrozumienia MCP to podpięcie gotowego serwera i zobaczenie, jak model nagle zaczyna robić rzeczy, których wcześniej nie umiał.

Potrzebujesz Node.js albo Pythona z narzędziem uv. Większość publicznych serwerów odpala się jednym poleceniem przez npx lub uvx, bez instalowania czegokolwiek na stałe.

W Claude Code najprościej dodać serwer komendą:

# serwer dający dostęp do wskazanego katalogu
claude mcp add filesystem -- npx -y @modelcontextprotocol/server-filesystem C:/projekty

W Claude Desktop robisz to samo ręcznie w pliku konfiguracyjnym. Na Windowsie leży w %APPDATA%\Claude\claude_desktop_config.json, na macOS w ~/Library/Application Support/Claude/.

{
  "mcpServers": {
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "C:/projekty"
      ]
    }
  }
}

Restart aplikacji i tyle. Model widzi teraz narzędzia do czytania, pisania i przeszukiwania plików w katalogu C:/projekty. Możesz poprosić „przejrzyj wszystkie pliki .cs w tym folderze i powiedz, gdzie mam zduplikowaną logikę walidacji" i to zadziała.

Kilka serwerów, od których warto zacząć:

  • filesystem - dostęp do wskazanych katalogów, najlepszy do pierwszych testów
  • github - issues, pull requesty, kod, wyszukiwanie w repo
  • postgres albo sqlite - zapytania do bazy w trybie tylko do odczytu
  • playwright - sterowanie przeglądarką, przydatne przy testach i scrapowaniu
  • memory - prosty graf wiedzy, który przeżywa między sesjami

Jedna uwaga praktyczna, bo sam się na tym przejechałem. Konfiguracja z npx na Windowsie potrafi się wywalić, gdy ścieżka do Node'a nie jest w PATH procesu, który uruchamia Claude Desktop. Objaw: serwer po prostu nie pojawia się na liście, bez komunikatu błędu. Logi znajdziesz w %APPDATA%\Claude\logs\mcp*.log i tam zwykle widać wprost, że polecenie nie zostało znalezione. Podanie pełnej ścieżki do npx.cmd rozwiązuje sprawę.

Własny serwer w Pythonie, krok po kroku

Gotowe serwery są fajne, ale wartość zaczyna się tam, gdzie masz coś swojego: wewnętrzne API, dziwną bazę, system, którego nikt poza twoją firmą nie używa. Napiszmy taki serwer.

Użyję SDK Pythona z warstwą FastMCP, bo skraca kod do minimum. Zaczynamy od projektu:

uv init serwer-faktury
cd serwer-faktury
uv add "mcp[cli]" httpx

Teraz plik server.py. Serwer wystawi dwa narzędzia: listę faktur i szczegóły jednej faktury.

from mcp.server.fastmcp import FastMCP
import httpx

mcp = FastMCP("faktury")

API_URL = "https://api.mojafirma.pl/v1"


@mcp.tool()
async def lista_faktur(od: str, do: str, status: str = "wszystkie") -> str:
    """Zwraca listę faktur sprzedażowych z podanego zakresu dat.

    Args:
        od: data początkowa w formacie RRRR-MM-DD
        do: data końcowa w formacie RRRR-MM-DD
        status: filtr statusu - "oplacone", "nieoplacone" albo "wszystkie"
    """
    async with httpx.AsyncClient() as client:
        r = await client.get(
            f"{API_URL}/invoices",
            params={"from": od, "to": do, "status": status},
            timeout=20.0,
        )
        r.raise_for_status()
        faktury = r.json()

    if not faktury:
        return "Brak faktur w podanym zakresie."

    # zwracamy tekst, nie surowy JSON - model czyta to lepiej i taniej
    linie = [
        f"{f['number']} | {f['contractor']} | {f['net']} zł | {f['status']}"
        for f in faktury
    ]
    return "\n".join(linie)


@mcp.tool()
async def szczegoly_faktury(numer: str) -> str:
    """Zwraca pełne dane jednej faktury wraz z pozycjami.

    Args:
        numer: numer faktury, np. "FV/2026/07/014"
    """
    async with httpx.AsyncClient() as client:
        r = await client.get(f"{API_URL}/invoices/{numer}", timeout=20.0)
        if r.status_code == 404:
            return f"Nie znaleziono faktury {numer}."
        r.raise_for_status()
        return r.text


if __name__ == "__main__":
    mcp.run(transport="stdio")

To wszystko. Dekorator @mcp.tool() rejestruje funkcję jako narzędzie, a FastMCP sam buduje schemat parametrów na podstawie sygnatury i typów. Docstring staje się opisem, który zobaczy model.

Zwróć uwagę na dwie rzeczy, które robią większą różnicę niż sam kod.

Po pierwsze, docstring. To jest twój interfejs do modelu, nie do programisty. Opisz, co narzędzie robi, w jakich sytuacjach ma sens i jaki dokładnie format mają argumenty. Jeśli model regularnie wywołuje narzędzie ze złymi parametrami, problem prawie zawsze leży w opisie, nie w modelu.

Po drugie, format zwracanych danych. Kuszące jest oddanie surowego JSON-a z API, bo tak jest najszybciej. Tylko że JSON z zagnieżdżonymi obiektami i dwudziestoma polami na rekord potrafi zjeść kilka tysięcy tokenów przy liście czterdziestu faktur. Spłaszczenie do jednej linii na rekord obcina to kilkukrotnie i model i tak rozumie wszystko, czego potrzebuje. Szczegóły dokładasz osobnym narzędziem, gdy naprawdę będą potrzebne.

Podpięcie serwera do Claude Code:

claude mcp add faktury -- uv --directory C:/projekty/serwer-faktury run server.py

Albo do Claude Desktop przez config:

{
  "mcpServers": {
    "faktury": {
      "command": "uv",
      "args": [
        "--directory",
        "C:/projekty/serwer-faktury",
        "run",
        "server.py"
      ]
    }
  }
}

Debugowanie, czyli MCP Inspector

Zanim podepniesz serwer do hosta, przetestuj go osobno. Inspector to webowe narzędzie, które udaje klienta MCP i pozwala wyklikać każde narzędzie ręcznie.

npx @modelcontextprotocol/inspector uv --directory C:/projekty/serwer-faktury run server.py

Otwiera się przeglądarka, widzisz listę narzędzi, wypełniasz parametry, klikasz i dostajesz odpowiedź. Bez modelu w środku, bez zgadywania, czy problem leży w kodzie czy w tym, jak model wywołuje funkcję.

Druga zasada debugowania stdio: nigdy nie pisz na standardowe wyjście. Zwykły print() w serwerze stdio wsadza śmieci w strumień JSON-RPC i psuje całe połączenie. Logi kieruj na stderr albo przez moduł logging z odpowiednim handlerem. Zgubiłem na tym godzinę przy pierwszym serwerze i był to najbardziej frustrujący błąd w całej zabawie, bo objawem był po prostu serwer, który „nie działa".

Gdzie MCP realnie się zwraca

Teraz część, którą trudno znaleźć w dokumentacji, bo wynika z używania, nie z czytania.

Największa wartość MCP nie leży w tym, że model może coś zrobić. Leży w tym, że model może połączyć rzeczy z różnych źródeł w jednej rozmowie. Jedno narzędzie to ciekawostka. Cztery narzędzia z różnych systemów to nowa jakość.

U mnie najlepiej sprawdziły się cztery zastosowania.

Baza w trybie read-only jako lupa do danych. Podpięty serwer do PostgreSQL z użytkownikiem mającym wyłącznie prawa odczytu. Zamiast pisać zapytanie diagnostyczne, opisuję, czego szukam. Przy debugowaniu produkcyjnego problemu skraca to pętlę z kilkunastu minut do jednej. Podkreślam: użytkownik tylko do odczytu, na osobnym koncie. To nie jest przesada, tylko minimum.

Dokumentacja wewnętrzna jako serwer. Firmowe procedury, opisy procesów, schematy integracji. Zamiast wklejać model kontekst za każdym razem, wystawiam go raz jako serwer i model sięga po to, czego potrzebuje. Przy dużej dokumentacji to jedyna sensowna droga, bo wrzucenie wszystkiego do kontekstu jest nie tylko drogie, ale też pogarsza jakość odpowiedzi.

Własny vault z notatkami. Cały mój system w Obsidianie jest dostępny przez serwer filesystem. Model zna moje projekty, zadania i notatki ze spotkań, więc nie muszę mu tłumaczyć kontekstu od nowa przy każdej rozmowie.

Automatyzacja końcówek procesów. Tam, gdzie przez lata siedziały makra VBA i ręczne przeklikiwanie. Model zbiera dane z trzech miejsc, składa raport i odkłada go tam, gdzie ma być. Wcześniej wymagało to napisania sztywnego skryptu pod jeden konkretny przypadek. Teraz przypadek opisuję zdaniem.

Wspólny mianownik: MCP wygrywa tam, gdzie zadanie jest powtarzalne co do rodzaju, ale zmienne co do szczegółu. Jeśli coś robisz dokładnie tak samo za każdym razem, napisz zwykły skrypt. Będzie tańszy, szybszy i przewidywalny.

Gdzie MCP boli

Nie kupuj tego bezkrytycznie, bo problemy są realne.

Bezpieczeństwo jest twoje, nie protokołu. Serwer MCP działa z uprawnieniami procesu, który go uruchomił. Podpinasz serwer do bazy z kontem administratora i dajesz modelowi możliwość skasowania tabeli. Protokół tego nie pilnuje, bo nie taka jest jego rola. Odpowiedzialność leży po twojej stronie i to jest największa pułapka dla osób, które podpinają wszystko, co znajdą na GitHubie.

Prompt injection to nie teoria. Jeśli serwer zwraca treść pochodzącą z zewnątrz, na przykład maila albo strony internetowej, ta treść może zawierać instrukcje dla modelu. Badacze bezpieczeństwa pokazywali już działające ataki tego typu na konfiguracje MCP, w tym takie, które prowadziły do wycieku danych z prywatnych repozytoriów. Przy narzędziach modyfikujących cokolwiek trzymaj potwierdzenie użytkownika przed wykonaniem.

Tokeny znikają szybciej, niż myślisz. Każdy podpięty serwer wstrzykuje opisy swoich narzędzi do kontekstu przy każdym zapytaniu. Dziesięć serwerów po kilkanaście narzędzi każdy potrafi zjeść kilkanaście tysięcy tokenów, zanim w ogóle zaczniesz rozmowę. Płacisz za to przy każdej wiadomości, a jakość odpowiedzi spada, bo model gubi się w nadmiarze opcji. Trzymaj włączone tylko to, czego faktycznie używasz w danym projekcie.

Jakość publicznych serwerów jest bardzo różna. Rejestry pełne są serwerów napisanych w weekend i porzuconych. Zanim podepniesz cokolwiek, co dotyka firmowych danych, przeczytaj kod. To zwykle kilkaset linii, więc da się to zrobić w kwadrans.

Specyfikacja wciąż się rusza. Autoryzacja przeszła sporą przebudowę, transport SSE został zastąpiony, pojawiają się nowe elementy. Materiały sprzed roku potrafią być mylące. Jeśli coś nie działa zgodnie z tutorialem, sprawdź datę tutoriala, zanim zaczniesz szukać błędu u siebie.

Plan na pierwszy tydzień

Konkretna kolejność, żeby nie utknąć w czytaniu dokumentacji.

  1. Podepnij serwer filesystem do jednego katalogu z projektem. Pobaw się nim godzinę, zadaj kilkanaście pytań o kod. Chodzi o wyrobienie intuicji, kiedy model sięga po narzędzia, a kiedy nie.
  2. Dodaj drugi serwer, najlepiej github albo bazę w trybie odczytu. Zobacz, co się dzieje, gdy model łączy dwa źródła w jednym zadaniu.
  3. Napisz własny serwer z jednym narzędziem. Cokolwiek, byle twoje. Przetestuj przez Inspector, zanim podepniesz do hosta.
  4. Popraw docstring tego narzędzia trzy razy, obserwując, jak zmienia się zachowanie modelu. To najbardziej pouczająca część całej zabawy.
  5. Dopiero teraz zastanów się, co w twojej pracy powtarza się na tyle często, żeby warto było zbudować pod to zestaw narzędzi.

Ostatni punkt jest ważniejszy niż pierwsze cztery razem. Łatwo wpaść w budowanie serwerów dla samego budowania. Ja przez pierwsze dni napisałem trzy, z których dwa nigdy nie zostały użyte, bo rozwiązywały problem, którego nie miałem.

Co z tego zostaje

MCP nie jest przełomem technologicznym. To protokół, który standaryzuje coś, co i tak wszyscy robili, tylko każdy po swojemu. Cała jego wartość polega na tym, że raz napisany serwer działa wszędzie i przeżyje zmianę narzędzia, modelu czy dostawcy.

Bariera wejścia jest niska i to jest tu najbardziej praktyczna informacja. Podpięcie gotowego serwera zajmuje dwie minuty. Napisanie własnego z jednym sensownym narzędziem to godzina, jeśli masz API, do którego się podpinasz. To znacznie mniej niż jeden dzień pracy, a próg wejścia nie rośnie z czasem, bo protokół jest prosty z założenia.

Ryzyko też jest realne i nie znika samo. Uprawnienia, prompt injection, koszty kontekstu. Nie traktuj serwera MCP jak zwykłej biblioteki, którą wrzucasz i zapominasz. To jest kanał, przez który model dosięga twoich danych i tak należy go traktować przy każdej decyzji o tym, co podpiąć.

Zacznij od filesystem, dopisz jedno własne narzędzie, resztę zbuduj wtedy, gdy poczujesz konkretny brak.

Jeśli chcesz zbudować takie rozwiązanie u siebie albo sprawdzić, czy w twoim procesie ma to sens, napisz do mnie przez formularz na mwozniczka.net/kontakt. Robię prototypy, które da się przetestować w codziennej pracy, zanim zapadnie decyzja o większym projekcie.