Przejdź do treści

Jak zbudować własny MCP server w Pythonie od zera

Schemat MCP - klient Claude Desktop łączy się przez stdio z serwerem Python wystawiającym tools i resources

W zeszłym roku zbudowałem dla siebie pięć MCP serwerów - Garmina, kalendarz, taski, własną bazę notatek i jeden pod konkretnego klienta, który chciał spiąć Claude'a ze swoim CRM-em w Laravelu. Z każdym kolejnym projektem czas od pomysłu do działającego prototypu spadał - z dwóch dni na pierwszym do dwóch godzin na ostatnim. Nie dlatego, że MCP jest skomplikowane. Dlatego, że oficjalne tutoriale pokazują "hello world", a w realnym setupie wywalają się rzeczy, których w hello worldzie nie ma.

Ten wpis to tutorial, który chciałbym znaleźć rok temu. Krok po kroku, z działającym kodem, w Pythonie 3.10+, z FastMCP 3 (wyszedł w lutym 2026 i mocno uprościł API), z podłączeniem do Claude Desktop i debugiem w MCP Inspectorze. Bez teorii, której nie potrzebujesz, żeby zacząć - jeśli chcesz teorii, mam osobny wpis o MCP.

Przykładem przewodnim będzie serwer pogodowy spinający Open-Meteo (darmowe, bez klucza API). Po przejściu tego tutoriala będziesz mieć działający serwer, który Claude może wywoływać, i będziesz wiedzieć, co zmienić, żeby zamiast pogody serwować dane z własnego systemu.

Co to jest MCP w dwie minuty

Model Context Protocol to standard otwarty przez Anthropic w listopadzie 2024, który pozwala modelom (Claude, ale też inne) gadać ze światem zewnętrznym przez ujednolicony interfejs. Zamiast pisać własny adapter do każdej integracji, wystawiasz MCP server i każdy klient zgodny z protokołem (Claude Desktop, Claude Code, IDE pluginy, własne aplikacje) potrafi z niego skorzystać.

Server eksponuje trzy rzeczy:

  • Tools - funkcje, które model może wywołać (np. get_weather(city))
  • Resources - dane do pobrania (pliki, dokumenty, wyniki query)
  • Prompts - parametryzowane szablony promptów

W 2026 publicznych MCP serwerów jest już ponad 7 tysięcy. Anthropic, Google, OpenAI - wszyscy dorobili się klientów. Standard się przyjął i nie zniknie.

Wymagania

  • Python 3.10 lub nowszy (FastMCP wymaga, type hints na pełen gaz)
  • Klient MCP - polecam Claude Desktop (najłatwiejszy do testów) albo Claude Code
  • Edytor tekstu, terminal
  • 10-15 minut

Zakładam, że umiesz Pythona na poziomie podstawowym i radzisz sobie z pip, virtualenv, plikami JSON konfiguracyjnymi.

Krok 1: instalacja FastMCP

FastMCP to wrapper na oficjalny modelcontextprotocol/python-sdk Anthropic, który redukuje boilerplate o jakieś 80%. Zamiast definiować schematy JSON ręcznie, używasz dekoratorów na funkcjach Pythona. Type hinty same się tłumaczą na schematy.

mkdir mcp-pogoda
cd mcp-pogoda
python -m venv .venv
source .venv/bin/activate  

httpx dorzucam, bo będę robił async HTTP do Open-Meteo. Standardowo używam go zamiast requests w nowoczesnych projektach.

Krok 2: pierwszy tool

Tworzę plik server.py:

from fastmcp import FastMCP
import httpx

mcp = FastMCP(

Co tu się dzieje:

  • FastMCP("Pogoda PL") tworzy instancję serwera z nazwą widoczną dla klienta.
  • @mcp.tool() rejestruje funkcję jako tool dostępny dla modelu. FastMCP czyta type hinty (city: str, -> dict) i docstring, generuje schemat JSON automatycznie.
  • Docstring jest krytyczny. Model widzi go jako opis tool'a i decyduje na podstawie tego, czy wywołać. Pisz konkretnie - nie "pobiera dane" tylko "pobiera aktualną pogodę dla miasta".
  • async tutaj jest po to, że robię HTTP. FastMCP obsługuje sync i async tooly bez różnicy.
  • mcp.run() startuje serwer w trybie stdio (default) - klient wystartuje proces i będzie gadał przez standard input/output.

Sprawdź, czy startuje:

python

Powinien zawisnąć (czeka na klienta na stdio). Ctrl+C, idziemy dalej.

Krok 3: dodanie zasobów

Resources to dane, które model może czytać bez wywoływania funkcji. Typowy use case: model dostaje URI typu weather://warsaw/today i FastMCP zwraca treść.

Dodaję do server.py:

from datetime import datetime

Różnica między tool a resource jest taka: tool jest akcją (model decyduje, czy wywołać), resource jest danymi (klient może załadować bez decyzji modelu, np. dołączyć do kontekstu na start). W praktyce 80% projektów potrzebuje głównie tooli, resources są dodatkiem.

Krok 4: podłączenie do Claude Desktop

Claude Desktop czyta konfigurację z pliku claude_desktop_config.json. Lokalizacja:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Linux: ~/.config/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json

Dodaję serwer:

{
  

Ścieżki muszą być absolutne. Claude Desktop nie rozwiązuje ~ ani relatywnych ścieżek. Restart Claude Desktopa (pełny quit, nie tylko zamknięcie okna) i serwer powinien się pojawić w UI - ikonka młotka w prawym dolnym rogu okna chatu pokaże dostępne tooly.

Jeśli się nie pojawia, sprawdź logi:

  • macOS: ~/Library/Logs/Claude/mcp-server-pogoda.log
  • Linux: ~/.local/share/Claude/logs/mcp-server-pogoda.log

Najczęstszy błąd, który sam popełniałem: Python z venv ma inną ścieżkę niż systemowy, a w configu mam systemowy. Wtedy fastmcp nie jest zainstalowany i serwer pada na imporcie.

Krok 5: debug z MCP Inspector

MCP Inspector to oficjalne narzędzie do testowania serwerów bez podłączania do prawdziwego klienta. Zachowuje się jak interaktywny REPL - widzisz każdy request i response w surowej formie.

npx @modelcontextprotocol/inspector python /sciezka/

Otworzy się przeglądarka na http://localhost:5173. Tam widzisz listę tooli, możesz wywołać je z konkretnymi argumentami i zobaczyć surowy JSON-RPC, który leci między stronami.

Gdy jakiś tool wywala się "u Claude'a", zawsze najpierw sprawdzam w Inspectorze. Jeśli tam działa - problem jest po stronie klienta (config, format argumentów, uprawnienia). Jeśli tam pada - problem jest w samym serwerze i mogę debugować na poziomie kodu Python bez pośrednika.

Drugi tip: Inspector ma podgląd schematów. Jeśli twój type hint generuje dziwny schemat JSON (np. Optional[str] robi się {"type": ["string", "null"]} co modele czasem mylą), tutaj to wyłapiesz.

Krok 6: stdio vs HTTP

Domyślny tryb FastMCP to stdio - klient startuje proces serwera lokalnie i gada przez stdin/stdout. Działa świetnie dla osobistych integracji (jak mój Garmin/kalendarz/taski). Ograniczenie: server musi żyć na tej samej maszynie co klient.

Dla deploymentu zdalnego (np. firmowy serwer wystawiający MCP wielu pracownikom) FastMCP wspiera HTTP/SSE:

if

Klient (Claude Desktop od wersji 2026.03 wspiera remote MCP) podpina się przez URL zamiast komendy:

{
  

Trzy uwagi praktyczne, kiedy idziesz na HTTP:

  • Uwierzytelnianie. Stdio jest bezpieczne z definicji - proces lokalny, ten sam użytkownik. HTTP nie. Dodaj OAuth (FastMCP 3 ma wbudowane wsparcie) albo bearer tokeny, inaczej serwer będzie publiczny dla każdego, kto trafi na URL.
  • CORS i SSE. Klient negocjuje SSE (Server-Sent Events) dla streamingu. Jeśli stoi za reverse proxy, ustaw proxy_buffering off w nginx, inaczej klient się rozłączy po pierwszym evencie.
  • Stateless tools. Stdio może mieć stan w pamięci procesu. HTTP - z założenia - nie. Każdy request może trafić na inny worker. Trzymaj stan w bazie/Redisie.

Test: wywołanie z Claude Desktop

Po restarcie Claude Desktopa otwieram nowy chat i piszę:

Sprawdź pogodę w Krakowie.

Claude wyświetli pytanie o uprawnienie wywołania get_weather (Claude Desktop pyta domyślnie pierwszy raz dla każdego tool'a). Po zatwierdzeniu zobaczysz w UI rozwijaną sekcję z wywołaniem - argument {"city": "Kraków"} i wynik JSON z polami temperature_c, humidity, wind_kph. Model przerobi to na ludzką odpowiedź typu "W Krakowie mamy 12 stopni, wilgotność 64%, wiatr 8 km/h".

Jeśli widzisz tylko odpowiedź modelu bez wywołania tool'a - znaczy że Claude wziął odpowiedź z własnej wiedzy zamiast użyć serwera. Przyczyny:

  • Tool nie został zarejestrowany (sprawdź ikonkę młotka w UI - pokazuje listę dostępnych tooli)
  • Docstring jest zbyt ogólny - model nie skojarzył "pogoda" z funkcją get_weather. Dopisz w docstringu konkretne triggery: "wywołaj zawsze gdy użytkownik pyta o pogodę, temperaturę, wiatr w konkretnym mieście".
  • Tool ma za długi opis - model zignorował go, bo nie zmieścił się w prompcie systemowym (rzadkie, ale przy 50+ toolach realne).

Pułapki, na które wpadałem

Asynchroniczność tooli mieszana z synchroniczną. Jeden tool jest async def, drugi def, trzeci wywołuje synchroniczny kod blokujący w środku async. FastMCP sobie z tym poradzi, ale bloking calls w async tool'u zatrzymują event loop i cały serwer staje. Reguła: w async tool'u używaj async libów (httpx.AsyncClient, asyncpg), nie requests i psycopg2.

Timeouty. Domyślnie klient czeka na tool 30 sekund. Jeśli twój tool robi długie zapytanie do API, pobierając plik 100MB, klient go ubije. Buduj tool, żeby zwracał w < 10 sekund. Jeśli musi dłużej - wystaw status job'a jako resource i niech model pollu je.

Format argumentów. Model czasem podaje argumenty jako string ("True", "5"), zamiast jako bool/int. FastMCP robi coercion, ale jeśli definiujesz złożone typy (Pydantic models, dicty zagnieżdżone), tutaj się gubi. Sprawdzaj w Inspectorze, co dokładnie przychodzi.

Permissions na plikach. Jeśli MCP server czyta pliki użytkownika (RAG nad Documents), pamiętaj, że proces dziedziczy uprawnienia od klienta, który go wystartował. Claude Desktop na macOS chodzi w sandboxie - próba czytania ~/Documents bez explicit permissions z systemu wywoli pyt o dostęp lub błąd.

JSON config. Brak przecinka, niepoprawny escape backslashy w ścieżkach Windows, BOM w pliku - każdy z tych błędów objawia się tym, że Claude Desktop ignoruje config bez komunikatu. Walidacja przez jq . claude_desktop_config.json ratuje życie.

Zwracanie surowych obiektów, których FastMCP nie umie zserializować. Zwróciłem kiedyś z tool'a obiekt datetime bezpośrednio. FastMCP używa standardowego JSON serializera, który nie umie datetime - klient dostał błąd "Object of type datetime is not JSON serializable" i tool wyglądał na zepsuty. Reguła: zwracaj tylko stringi, liczby, boole, listy, dicty z tych typów. Daty konwertuj do ISO string (dt.isoformat()).

Co dalej

Powyższy serwer jest funkcjonalny, ale to dopiero szkielet. Przy produkcyjnym wdrożeniu dochodzą dodatkowe rzeczy:

  • Testy. FastMCP daje fastmcp.testing.Client - możesz wywoływać tooly w pytestach bez startowania prawdziwego serwera. Pisz testy od razu, bo bez nich dowiesz się o regresji od użytkownika.
  • Logging i observability. Loguj każde wywołanie tool'a (input, output, latency). Bez tego debugowanie problemów u klienta to zgadywanka. Ja używam structlog z JSON output i wpinam do Grafany.
  • Rate limiting. Jeśli tool uderza w zewnętrzne API z limitami, zaimplementuj backoff i circuit breaker, inaczej klient dostanie kaskadę błędów.
  • Caching. Wynik tool'a, który nie zmienia się szybko (np. lista produktów z katalogu), cache'uj. Redis z TTL 5-15 minut robi robotę.
  • Publikacja. Jeśli serwer ma sens publiczny - opublikuj na PyPI (pip install moj-mcp-server) plus dodaj do oficjalnego registru MCP. Claude Desktop ma od marca 2026 wbudowaną przeglądarkę registru, użytkownicy klikają "install" i mają.
  • Wersjonowanie tooli. Jeśli zmieniasz sygnaturę tool'a, którego ktoś już używa, dodaj nowy tool obok zamiast łamać stary. Model nie rozróżnia "to samo, ale lepsze" - widzi po prostu zmieniony schemat i może zacząć generować błędne wywołania. Stary deprecuj w docstringu, dopisując "Deprecated, use X instead".
  • Bezpieczeństwo input'u. Tool przyjmuje stringi od modelu. Jeśli wewnątrz robisz query SQL, exec shell command, czytasz pliki po path - sanityzuj. Nie zakładaj, że "model nie zrobi prompt injection". Atakujący może podsunąć użytkownikowi treść, która namówi model na wywołanie twojego tool'a w niezamierzony sposób.

Dla porównania, jak to wygląda w realnym projekcie - mój własny serwer Garmin (czyta dane treningowe, sen, HRV i podaje agentowi biegowemu) ma w sumie 800 linii Pythona, 12 tooli, własną warstwę cache i autentykację OAuth. Zajęło mi to weekend. Bez FastMCP zajęłoby tydzień. Pisałem o tym w case study osobistego asystenta, jeśli chcesz zobaczyć, jak wygląda szerszy kontekst takiego serwera w produkcji.

Drugi przykład - integracja Claude Code z lokalną bazą notatek przez MCP - opisałem w wpisie o Claude Code w codziennej pracy. Tam pokazuję, jak agent w terminalu sięga po MCP server tylko wtedy, kiedy faktycznie potrzebuje, zamiast trzymać całą wiedzę w prompcie.

Potrzebujesz MCP serwera spiętego z systemem firmy (CRM, ERP, własna baza wiedzy)? Napisz do mnie - projektuję i buduję pod konkretny use case.


KC
Kamil Czurak

Pomagam firmom wdrażać AI, które działa - od chatbotów po automatyzacje i agentów. 7 lat jako programista, z czego ostatnie 2 w AI.

Więcej o mnie →

Chcesz podobne rozwiązanie?

Wybierz termin w kalendarzu - 30 minut, zero zobowiązań.

Umów konsultację