Jak zbudować serwer MCP w Pythonie krok po kroku
Jak zbudować serwer MCP? Piszemy własne narzędzia w Pythonie z SDK 2.0, testujemy je w MCP Inspector i podpinamy do Claude Code i Claude Desktop. Krok po kroku.
Ten artykuł w innych językach:العربيةDeutschEnglishEspañolFrançaisItaliano日本語简体中文
W tym artykule
- Co będzie potrzebne?
- Krok 1: jak przygotować projekt?
- Krok 2: jak zbudować serwer MCP z pierwszym narzędziem?
- Krok 3: dlaczego nie wolno używać print()?
- Krok 4: jak przetestować serwer w MCP Inspector?
- Krok 5: jak podpiąć serwer do Claude Code?
- Krok 6: jak dodać serwer do Claude Desktop?
- Krok 7: jak uruchomić serwer MCP przez HTTP?
- Na co zwrócić uwagę?
- FAQ
Jak zbudować serwer MCP? W Pythonie wystarczy jeden plik i oficjalne MCP Python SDK: piszemy zwykłą funkcję z typami i docstringiem, dodajemy dekorator @mcp.tool(), a SDK samo zamienia ją w narzędzie, które może wywołać Claude, ChatGPT czy Cursor. Cały serwer z przykładu w dokumentacji ma kilkanaście linijek.
W tym poradniku piszemy serwer od zera, testujemy go w MCP Inspector i podpinamy do Claude Code oraz Claude Desktop. Komendy i kod pochodzą z oficjalnego poradnika „Build an MCP server”, README SDK i dokumentacji Claude Code.
Co będzie potrzebne?
Zakładam, że wiemy już, czym jest MCP, czyli host, klient, serwer i narzędzia (jeśli nie, zacznijmy od poradnika Co to jest MCP). Do pracy potrzebujemy:
- Pythona 3.11+ (tyle wymaga README aktualnego SDK),
- menedżera pakietów uv, którego używa oficjalny poradnik,
- Node.js 22.19.0 lub nowszego, jeśli chcemy testować serwer w MCP Inspector,
- hosta MCP do sprawdzenia całości, np. Claude Code albo Claude Desktop.
Jedna ważna uwaga na start: w wersji 2.0 SDK zmieniła się nazwa głównej klasy. Według opisu zmian w v2 dawne from mcp.server.fastmcp import FastMCP to teraz from mcp.server import MCPServer. Sporo poradników w sieci pokazuje jeszcze starą wersję, a oficjalny poradnik wymaga SDK 2.0.0 lub nowszego.
Krok 1: jak przygotować projekt?
Najpierw instalujemy uv (macOS i Linux):
curl -LsSf https://astral.sh/uv/install.sh | sh
Na Windowsie w PowerShellu:
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
Po instalacji restartujemy terminal, żeby komenda uv była widoczna. Potem tworzymy projekt i instalujemy SDK:
uv init moj-serwer
cd moj-serwer
uv venv
source .venv/bin/activate
uv add "mcp[cli]"
touch server.py
Dodatek [cli] instaluje komendę mcp (z poleceniami mcp dev, mcp run i mcp install), która przyda się do testów (na Windowsie środowisko aktywujemy przez .venv\Scripts\activate).
Krok 2: jak zbudować serwer MCP z pierwszym narzędziem?
Do pliku server.py wpisujemy przykład z README SDK i dopisujemy na końcu uruchomienie serwera, tak jak w oficjalnym poradniku:
from mcp.server import MCPServer
mcp = MCPServer("Demo")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
@mcp.resource("greeting://{name}")
def greeting(name: str) -> str:
"""Greet someone by name."""
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.run(transport="stdio")
To kompletny serwer MCP: jedno narzędzie (add) i jeden zasób z szablonem adresu (greeting://{name}). Co ciekawe, nie piszemy żadnego JSON Schema: według README sygnatura a: int, b: int jest schematem, a docstring staje się opisem narzędzia, który czyta model. SDK samo zajmuje się parsowaniem zapytań, walidacją i protokołem.
Dlatego warto dbać o docstringi. Model wybiera narzędzie na podstawie nazwy i opisu, więc „Add two numbers.” to minimum, a przy prawdziwych narzędziach opisujemy też argumenty, tak jak w oficjalnym przykładzie z pogodą:
@mcp.tool()
async def get_forecast(latitude: float, longitude: float) -> str:
"""Get weather forecast for a location.
Args:
latitude: Latitude of the location
longitude: Longitude of the location
"""
(ten przykład korzysta z API amerykańskiej służby pogodowej NWS, więc działa tylko dla lokalizacji w USA)
Krok 3: dlaczego nie wolno używać print()?
To najczęstszy błąd przy pierwszym serwerze. Lokalny serwer komunikuje się z hostem przez standardowe wejście i wyjście (stdio), a wiadomości mają format JSON-RPC. Dokumentacja ostrzega wprost: zapis do stdout psuje wiadomości JSON-RPC i wyłącza serwer, a print() domyślnie pisze właśnie na stdout.
Zamiast tego używamy modułu logging, który pisze na stderr:
import logging
logger = logging.getLogger(__name__)
logger.info("Processing request") # trafia na stderr
Przy serwerach HTTP ten problem nie występuje, bo logi na stdout nie mieszają się z odpowiedziami HTTP.
Krok 4: jak przetestować serwer w MCP Inspector?
MCP Inspector to oficjalne narzędzie do testowania serwerów. Z SDK uruchamiamy je jedną komendą:
uv run mcp dev server.py
Otworzy się przeglądarka z Inspectorem. Według README wywołanie add z argumentami a=1 i b=2 zwraca 3. W tym samym miejscu widać listę narzędzi i zasobów oraz surowe wiadomości JSON-RPC, co bardzo pomaga, gdy coś nie działa.
Inspector ma też wersję w terminalu (--tui) i tryb do skryptów (--cli). Ten drugi wypisuje np. listę narzędzi i kończy działanie, więc nadaje się do CI:
npx @modelcontextprotocol/inspector --cli uv run server.py --method tools/list
(sprawdź wersję Node.js komendą node --version, Inspector wymaga 22.19.0 lub nowszej)
Krok 5: jak podpiąć serwer do Claude Code?
Lokalny serwer dodajemy jako serwer typu stdio. Wszystko po -- to komenda, którą Claude Code uruchomi:
claude mcp add --transport stdio demo -- uv --directory /PELNA/SCIEZKA/moj-serwer run server.py
Ścieżka musi być bezwzględna (sprawdzimy ją komendą pwd w katalogu projektu). Status połączenia pokaże claude mcp get demo, a w sesji Claude Code komenda /mcp. Teraz możemy poprosić np. „dodaj 1234 i 5678 narzędziem add” i zatwierdzić wywołanie.
Dwie rzeczy z dokumentacji Claude Code przydają się od razu:
- czas startu serwera zmienimy zmienną
MCP_TIMEOUT, np.MCP_TIMEOUT=10000 claudedaje serwerowi 10 sekund, - Claude Code ostrzega, gdy wynik narzędzia przekracza 10 000 tokenów, a domyślnie ucina go na 25 000 tokenach (limit zmienia zmienna
MAX_MCP_OUTPUT_TOKENS), więc narzędzia nie powinny zwracać całych baz danych.
Krok 6: jak dodać serwer do Claude Desktop?
W Claude Desktop serwery lokalne dopisujemy do pliku konfiguracyjnego claude_desktop_config.json. Leży on w ~/Library/Application Support/Claude/ na macOS, w ~/.config/Claude/ na Linuksie i w %AppData%\Claude\ na Windowsie (jeśli go nie ma, tworzymy go). Wpis wygląda tak:
{
"mcpServers": {
"demo": {
"command": "uv",
"args": [
"--directory",
"/PELNA/SCIEZKA/moj-serwer",
"run",
"server.py"
]
}
}
}
Potem całkowicie zamykamy aplikację (na macOS Cmd+Q, samo zamknięcie okna nie wystarczy) i uruchamiamy ją ponownie. Serwer powinien pojawić się w menu „Connectors” pod ikoną plusa. Jeśli go nie ma, dokumentacja radzi sprawdzić składnię JSON, bezwzględność ścieżki i w polu command wpisać pełną ścieżkę do uv (pokaże ją which uv). Logi znajdziemy w ~/Library/Logs/Claude (macOS) albo ~/.config/Claude/logs/ (Linux), w plikach mcp.log i mcp-server-NAZWA.log.
Krok 7: jak uruchomić serwer MCP przez HTTP?
Serwer stdio działa tylko na naszym komputerze. Żeby wystawić go jako serwer zdalny, uruchamiamy go przez Streamable HTTP, czyli transport, który według README SDK wdraża się na produkcji:
uv run mcp run server.py --transport streamable-http
Serwer nasłuchuje wtedy pod adresem http://localhost:8000/mcp, a w Claude Code dodajemy go jak każdy serwer zdalny:
claude mcp add --transport http demo-http http://localhost:8000/mcp
Żeby taki serwer działał w aplikacji Claude w przeglądarce jako tzw. „custom connector”, musi być dostępny z internetu, bo Claude łączy się z nim z chmury Anthropic. Wtedy przyda się też logowanie: dokumentacja MCP opisuje autoryzację serwerów przez OAuth 2.1.
Na co zwrócić uwagę?
Po pierwsze, na to, co narzędzie pozwala zrobić. Serwer MCP daje modelowi realne możliwości działania na naszym komputerze, więc narzędzia piszmy możliwie wąsko: zamiast „uruchom dowolną komendę” lepiej „odczytaj plik z tego katalogu”.
Po drugie, na dane z zewnątrz. Jeśli narzędzie pobiera treści z internetu albo z maili, może przynieść ze sobą prompt injection, czyli ukryte instrukcje dla modelu. Więcej o tym pisałem w poradniku Bezpieczeństwo agentów AI.
Po trzecie, na sekrety. Klucze API przekazujmy przez zmienne środowiskowe (--env w Claude Code), a nie w kodzie. Według dokumentacji Inspector zapisuje tokeny w systemowym pęku kluczy, a gdy go nie ma (np. w kontenerze), w niezaszyfrowanym pliku ~/.mcp-inspector/secrets.json.
Taki serwer możemy potem podpiąć też do własnego agenta, co pokazuję w poradniku Jak zbudować agenta AI w Pythonie.
FAQ
Jak zbudować serwer MCP najprościej?
Najprostszy serwer z README SDK ma kilkanaście linijek: obiekt MCPServer, jedna funkcja z dekoratorem @mcp.tool() i mcp.run(). Resztę, czyli schemat argumentów i obsługę protokołu, robi SDK.
Czym jest FastMCP?
To dawna nazwa głównej klasy serwera w MCP Python SDK. W wersji 2.0 zmieniono ją na MCPServer, a import to from mcp.server import MCPServer.
Czy serwer MCP można napisać w innym języku niż Python?
Tak. Oficjalny poradnik ma wersje m.in. dla TypeScriptu, Javy, Kotlina, C#, Ruby, Rusta i Go.
Czy serwer MCP działa tylko z Claude?
Nie. MCP to otwarty standard, więc ten sam serwer zadziała w każdym hoście, który obsługuje protokół, np. w ChatGPT, Visual Studio Code czy Cursorze.