Model Context Protocol (MCP) w Pythonie – serwer narzędzi AI krok po kroku

Praktyczny poradnik budowania serwera MCP w Pythonie z FastMCP – od prostego kalkulatora po integrację z agentami LangGraph. Działający kod, transport HTTP i wskazówki bezpieczeństwa.

Czym jest Model Context Protocol i dlaczego zmienia zasady gry

Jeśli czytałeś nasze poprzednie artykuły o systemach wieloagentowych i Agentic RAG, to wiesz już, jak budować inteligentne pipeline'y z LangGraph i jak orkiestrować zespoły agentów z CrewAI. Pewnie zauważyłeś jednak jeden powtarzający się problem – każdy agent potrzebuje dostępu do zewnętrznych narzędzi: baz danych, API, systemów plików. I za każdym razem trzeba pisać od zera niestandardowe integracje. Trochę to męczące, prawda?

Model Context Protocol (MCP) rozwiązuje dokładnie ten ból. To otwarty standard stworzony przez Anthropic pod koniec 2024 roku, który standaryzuje sposób komunikacji modeli językowych (LLM) z zewnętrznymi narzędziami i źródłami danych. Najprościej: wyobraź sobie USB-C dla aplikacji AI – jeden uniwersalny port zamiast dziesiątek różnych kabli.

Liczby mówią same za siebie. W 2026 roku ekosystem MCP dosłownie eksplodował: ponad 97 milionów pobrań SDK miesięcznie, ponad 5800 dostępnych serwerów i adopcja przez wszystkich głównych graczy – OpenAI, Google, Microsoft. W grudniu 2025 Anthropic przekazał MCP pod opiekę Agentic AI Foundation przy Linux Foundation, co definitywnie zamknęło dyskusję o vendor lock-in. To już nie jest „standard jednej firmy" – to branżowy fundament.

W tym poradniku zbudujemy kompletny serwer MCP w Pythonie od zera, przetestujemy go, a potem podłączymy do agenta LangGraph. Każdy krok z działającym kodem, który możesz skopiować i odpalić u siebie.

Architektura MCP – trzy role, trzy prymitywy

Zanim napiszemy choćby linijkę kodu, musimy ogarnąć architekturę. MCP składa się z trzech ról komunikacyjnych:

  • Host – aplikacja AI, która potrzebuje zewnętrznego kontekstu (np. Claude Desktop, IDE z asystentem AI, albo Twoja własna aplikacja agentowa)
  • Client – warstwa pośrednia rozumiejąca protokół MCP. Łączy hosta z serwerami, zarządza odkrywaniem dostępnych narzędzi, wywoływaniem ich i obsługą odpowiedzi
  • Server – udostępnia konkretne możliwości: narzędzia, dane, szablony promptów. Jeden serwer może obsługiwać wielu klientów jednocześnie

Komunikacja opiera się na JSON-RPC 2.0 i odbywa się przez dwa główne transporty:

  • stdio (standard input/output) – szybka komunikacja lokalna, idealna do narzędzi uruchamianych na tej samej maszynie
  • Streamable HTTP – do zdalnych serwerów, obsługuje Server-Sent Events (SSE) do streamingu odpowiedzi w czasie rzeczywistym

Każdy serwer MCP eksponuje swoje możliwości przez trzy prymitywy. Warto je poznać, bo wrócimy do nich za chwilę.

Resources – dane do odczytu

Resources to odpowiednik endpointów GET w API REST. Dostarczają dane tylko do odczytu: konfiguracje, wpisy z bazy wiedzy, metadane, dokumentację. Agent ładuje je do kontekstu bez wykonywania żadnej logiki biznesowej. Proste i bezpieczne.

Tools – wykonywalne funkcje

Tools to odpowiednik endpointów POST. Umożliwiają wykonywanie akcji: wywołania API, obliczenia, zapytania do baz danych, uruchamianie workflow. Przyjmują parametry wejściowe i zwracają strukturalne odpowiedzi. Szczerze mówiąc, to najczęściej wykorzystywany prymityw w praktyce – zdecydowana większość serwerów MCP to głównie kolekcje narzędzi.

Prompts – szablony interakcji

Prompts to wielokrotnego użytku szablony komunikacji z LLM. Definiują wzorce interakcji, które klienci mogą odkrywać i wykorzystywać. Przydają się, gdy chcesz wystandaryzować sposób, w jaki agent rozmawia z określonymi typami danych.

Przygotowanie środowiska

Do budowy serwera MCP potrzebujemy Pythona w wersji 3.10 lub wyższej (na niższych nie zadziała) oraz oficjalnego SDK. Rekomendowany menedżer pakietów to uv – jest znacznie szybszy od pip i lepiej zarządza zależnościami.

# Instalacja uv (jeśli jeszcze nie masz)
curl -LsSf https://astral.sh/uv/install.sh | sh

# Inicjalizacja projektu
uv init mcp-narzedzia-demo
cd mcp-narzedzia-demo

# Dodanie zależności MCP SDK
uv add "mcp[cli]"

Alternatywnie, z pip:

python -m pip install "mcp[cli]"

W momencie pisania tego artykułu (marzec 2026) najnowsza wersja Python MCP SDK to 1.2.x. Użyj co najmniej wersji 1.2.0 – to ona wprowadza pełne wsparcie dla transportu Streamable HTTP.

Krok 1 – Pierwszy serwer MCP z FastMCP

No dobra, czas na kod. FastMCP to wysokopoziomowy interfejs w oficjalnym SDK, który automatycznie generuje definicje narzędzi na podstawie type hintów i docstringów. Jeśli potrafisz napisać zwykłą funkcję Pythona, potrafisz zbudować serwer MCP. Serio, to aż takie proste.

Zacznijmy od czegoś prostego – serwer z dwoma narzędziami obliczeniowymi:

# server.py
from mcp.server.fastmcp import FastMCP

# Tworzenie instancji serwera
mcp = FastMCP("Kalkulator Demo")

@mcp.tool()
def dodaj(a: float, b: float) -> float:
    """Dodaje dwie liczby i zwraca wynik."""
    return a + b

@mcp.tool()
def pomnoz(a: float, b: float) -> float:
    """Mnoży dwie liczby i zwraca wynik."""
    return a * b

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

To wszystko. Trzy dekoratory, dwie funkcje, serwer gotowy do działania. FastMCP automatycznie wygeneruje schematy JSON z type hintów (a: float, b: float) i opisów z docstringów. Klient MCP odkryje te narzędzia i będzie wiedział, jak je wywołać – bez żadnej dodatkowej konfiguracji.

Krok 2 – Dodajemy zasoby i zaawansowane narzędzia

Prosty kalkulator to fajne demo, ale powiedzmy sobie szczerze – w produkcji potrzebujemy czegoś bardziej mięsistego. Zbudujmy serwer, który łączy się z bazą danych SQLite i udostępnia zarówno dane (resources), jak i operacje (tools):

# db_server.py
import sqlite3
import json
import logging
import sys
from mcp.server.fastmcp import FastMCP

# Konfiguracja logowania – WAŻNE: w trybie stdio
# NIGDY nie pisz do stdout, bo zepsujesz komunikację JSON-RPC
logging.basicConfig(
    level=logging.INFO,
    stream=sys.stderr  # Logowanie TYLKO do stderr
)
logger = logging.getLogger(__name__)

DB_PATH = "produkty.db"

mcp = FastMCP("Baza Produktów", json_response=True)


def _get_connection():
    """Tworzy połączenie z bazą SQLite."""
    conn = sqlite3.connect(DB_PATH)
    conn.row_factory = sqlite3.Row
    return conn


# === RESOURCES (dane do odczytu) ===

@mcp.resource("db://schema")
def get_schema() -> str:
    """Zwraca schemat bazy danych produktów."""
    conn = _get_connection()
    cursor = conn.execute(
        "SELECT sql FROM sqlite_master WHERE type='table'"
    )
    schemas = [row[0] for row in cursor.fetchall()]
    conn.close()
    return "\n\n".join(schemas)


@mcp.resource("db://products/stats")
def get_stats() -> str:
    """Zwraca statystyki bazy produktów."""
    conn = _get_connection()
    cursor = conn.execute(
        "SELECT COUNT(*) as total, "
        "AVG(price) as avg_price, "
        "MIN(price) as min_price, "
        "MAX(price) as max_price "
        "FROM products"
    )
    row = cursor.fetchone()
    conn.close()
    return json.dumps(dict(row), ensure_ascii=False)


# === TOOLS (operacje) ===

@mcp.tool()
def search_products(query: str, limit: int = 10) -> str:
    """Wyszukuje produkty po nazwie lub opisie.

    Args:
        query: Fraza wyszukiwania
        limit: Maksymalna liczba wyników (domyślnie 10)
    """
    conn = _get_connection()
    cursor = conn.execute(
        "SELECT id, name, price, category "
        "FROM products "
        "WHERE name LIKE ? OR description LIKE ? "
        "LIMIT ?",
        (f"%{query}%", f"%{query}%", limit)
    )
    results = [dict(row) for row in cursor.fetchall()]
    conn.close()
    logger.info(f"Wyszukiwanie '{query}' zwróciło {len(results)} wyników")
    return json.dumps(results, ensure_ascii=False)


@mcp.tool()
def get_product_details(product_id: int) -> str:
    """Pobiera szczegółowe informacje o produkcie.

    Args:
        product_id: ID produktu w bazie danych
    """
    conn = _get_connection()
    cursor = conn.execute(
        "SELECT * FROM products WHERE id = ?",
        (product_id,)
    )
    row = cursor.fetchone()
    conn.close()
    if row is None:
        return json.dumps({"error": "Produkt nie znaleziony"})
    return json.dumps(dict(row), ensure_ascii=False)


@mcp.tool()
def update_stock(product_id: int, quantity: int) -> str:
    """Aktualizuje stan magazynowy produktu.

    Args:
        product_id: ID produktu
        quantity: Nowa ilość na magazynie
    """
    conn = _get_connection()
    conn.execute(
        "UPDATE products SET stock = ? WHERE id = ?",
        (quantity, product_id)
    )
    conn.commit()
    conn.close()
    logger.info(f"Zaktualizowano stock produktu {product_id} na {quantity}")
    return json.dumps({"status": "ok", "product_id": product_id, "new_stock": quantity})


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

Zwróć uwagę na kilka kluczowych rzeczy:

  • Logowanie do stderr – w trybie stdio cała komunikacja JSON-RPC idzie przez stdout. Jeśli tam wyślesz cokolwiek innego (np. przez print()), zepsujesz protokół. To jeden z najczęstszych błędów początkujących – zawsze loguj do sys.stderr
  • Type hinty i docstringi – FastMCP generuje z nich schematy narzędzi. Im dokładniejsze opisy napiszesz, tym lepiej agent zrozumie, kiedy i jak użyć danego narzędzia
  • json_response=True – wymusza serializację odpowiedzi do JSON, co znacznie ułatwia parsowanie po stronie klienta

Krok 3 – Testowanie z MCP Inspector

MCP SDK zawiera wbudowany MCP Inspector – graficzny interfejs do testowania serwera bez potrzeby pisania klienta. Pomyśl o nim jak o Postmanie, ale dla protokołu MCP.

# Uruchomienie inspektora z serwerem
uv run mcp dev db_server.py

Inspektor otworzy się w przeglądarce i pozwoli Ci:

  • Przeglądać dostępne narzędzia i zasoby
  • Wywoływać narzędzia z dowolnymi parametrami
  • Oglądać surowe komunikaty JSON-RPC
  • Debugować problemy z serializacją

To naprawdę wygodne narzędzie – oszczędza mnóstwo czasu w porównaniu z ręcznym testowaniem przez CLI.

Gdy serwer działa poprawnie, możesz go zainstalować w Claude Desktop:

# Instalacja serwera w Claude Desktop
uv run mcp install db_server.py --name "Baza Produktów"

Po restarcie Claude Desktop zobaczysz nowe narzędzia dostępne w czacie. Claude będzie mógł przeszukiwać produkty, sprawdzać szczegóły i aktualizować stany magazynowe – wszystko przez standaryzowany protokół.

Krok 4 – Transport HTTP dla zdalnych serwerów

Transport stdio sprawdza się świetnie na localhoście, ale w środowisku produkcyjnym potrzebujesz serwera HTTP obsługującego wielu klientów jednocześnie. Na szczęście FastMCP wspiera to natywnie:

# http_server.py
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("Zdalny Serwer Produktów", json_response=True)

@mcp.tool()
def search_products(query: str, limit: int = 10) -> str:
    """Wyszukuje produkty w bazie."""
    # ... logika wyszukiwania (jak wcześniej)
    return "[]"

if __name__ == "__main__":
    # Streamable HTTP – obsługuje wielu klientów
    mcp.run(transport="streamable-http")

Serwer uruchomi się domyślnie na http://localhost:8000/mcp. Klienci łączą się przez HTTP POST (żądania) i opcjonalnie SSE (streaming odpowiedzi). To zresztą ten sam mechanizm, którego używa LangGraph API Server do eksponowania agentów jako endpointów MCP.

Krok 5 – Podłączenie serwera MCP do agenta LangGraph

A teraz najlepsza część – i szczerze, to moment, w którym MCP zaczyna naprawdę błyszczeć. Jeśli czytałeś nasz poradnik o Agentic RAG, wiesz już jak budować grafy agentowe. Teraz dodamy do nich narzędzia z serwera MCP.

Potrzebujemy biblioteki langchain-mcp-adapters, która konwertuje narzędzia MCP na narzędzia LangChain:

pip install langchain-mcp-adapters langgraph langchain-openai

Oto kompletny przykład agenta ReAct korzystającego z naszego serwera MCP:

# agent_with_mcp.py
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
from langchain_openai import ChatOpenAI

async def main():
    # Konfiguracja klienta MCP – łączymy się z naszym serwerem
    async with MultiServerMCPClient(
        {
            "produkty": {
                "command": "uv",
                "args": ["run", "db_server.py"],
                "transport": "stdio",
            }
        }
    ) as client:
        # Pobieranie narzędzi – automatyczna konwersja MCP → LangChain
        tools = client.get_tools()
        print(f"Odkryto {len(tools)} narzędzi MCP")

        # Tworzenie agenta ReAct z narzędziami MCP
        model = ChatOpenAI(model="gpt-4o", temperature=0)
        agent = create_react_agent(model, tools)

        # Wywołanie agenta
        response = await agent.ainvoke({
            "messages": [
                {
                    "role": "user",
                    "content": "Znajdź najtańszy laptop w bazie i sprawdź jego stan magazynowy"
                }
            ]
        })

        # Wynik
        for msg in response["messages"]:
            print(f"{msg.type}: {msg.content}")

if __name__ == "__main__":
    asyncio.run(main())

Co się tu dzieje krok po kroku:

  1. MultiServerMCPClient uruchamia nasz serwer MCP jako subprocess (transport stdio) i automatycznie odkrywa dostępne narzędzia
  2. client.get_tools() konwertuje narzędzia MCP na format LangChain – schematy parametrów, opisy, wszystko mapuje się automatycznie
  3. create_react_agent tworzy agenta ReAct, który sam decyduje, których narzędzi użyć na podstawie zapytania
  4. Agent może wywołać search_products, potem get_product_details, i złożyć spójną odpowiedź – całość autonomicznie, bez ręcznego sterowania

Co ważne, możesz połączyć się z wieloma serwerami MCP jednocześnie – wystarczy dodać kolejne wpisy do konfiguracji MultiServerMCPClient. Agent zobaczy narzędzia ze wszystkich serwerów i sam zdecyduje, z którego skorzystać.

Połączenie ze zdalnym serwerem HTTP

Jeśli Twój serwer MCP działa zdalnie (transport HTTP), konfiguracja jest bardzo podobna:

# Klient łączący się ze zdalnym serwerem MCP
async with MultiServerMCPClient(
    {
        "produkty_remote": {
            "url": "http://serwer-produkcyjny:8000/mcp",
            "transport": "streamable_http",
        }
    }
) as client:
    tools = client.get_tools()
    # ... reszta kodu identyczna

Z perspektywy agenta nie ma absolutnie żadnej różnicy – narzędzia działają tak samo, niezależnie od transportu. To właśnie siła standaryzacji MCP i powód, dla którego ten protokół tak szybko zyskał popularność.

MCP a A2A – dwa protokoły, jeden ekosystem

Skoro MCP łączy agentów z narzędziami, to jak wygląda komunikacja między samymi agentami? Tu wchodzi A2A (Agent-to-Agent Protocol) – otwarty standard ogłoszony przez Google Cloud w kwietniu 2025.

Kluczowa różnica jest prosta do zapamiętania:

  • MCP = integracja pionowa – agent komunikuje się z narzędziami, bazami danych, API (agent ↔ narzędzie)
  • A2A = koordynacja pozioma – agent komunikuje się z innymi agentami, deleguje zadania, wymienia informacje (agent ↔ agent)

Te protokoły nie konkurują ze sobą – uzupełniają się nawzajem. Wyobraź sobie system e-commerce: agent magazynowy używa MCP do sprawdzania stanów w bazie. Gdy wykryje niski stan, przez A2A komunikuje się z agentem zamówień, który kontaktuje się z agentem dostawcy. Każdy z nich korzysta z MCP do swoich narzędzi, a A2A do wzajemnej współpracy.

Moja rada? Zacznij od MCP – potrzebujesz go od razu. Dodaj A2A dopiero wtedy, gdy Twój system przerośnie możliwości pojedynczego agenta. W większości przypadków to przyjdzie naturalnie.

Bezpieczeństwo w produkcji

Trzeba o tym powiedzieć wprost: MCP sam w sobie nie zapewnia uwierzytelniania, autoryzacji ani szyfrowania. To Twoja odpowiedzialność jako dewelopera. Na początku 2026 roku badacze bezpieczeństwa zidentyfikowali kilka klas ataków na serwery MCP, o których warto wiedzieć:

  • Prompt injection – złośliwe dane w odpowiedziach narzędzi mogą manipulować zachowaniem agenta
  • Tool poisoning – fałszywe serwery MCP mogą podszywać się pod zaufane narzędzia
  • Eksfiltracja danych – łączenie narzędzi w łańcuchy do wycieku wrażliwych informacji

Jak się zabezpieczyć? Oto sprawdzone praktyki:

  • Używaj OAuth 2.0 z przepływem On-Behalf-Of – agenty dziedziczą uprawnienia konkretnego użytkownika, a nie mają dostępu administratora
  • Wdrażaj serwery MCP za API Gateway z politykami autoryzacji (np. AgentGateway z Linux Foundation)
  • Waliduj wszystkie dane wejściowe i wyjściowe narzędzi – nie ufaj ślepo temu, co zwraca serwer MCP
  • Dla transportu HTTP zawsze używaj TLS (HTTPS)
  • Monitoruj sesje i loguj wywołania narzędzi do audytu

Nie traktuj tego jako opcjonalną listę. W środowisku produkcyjnym każdy z tych punktów jest naprawdę istotny.

Najczęściej zadawane pytania

Czym dokładnie jest Model Context Protocol (MCP)?

Model Context Protocol to otwarty standard komunikacji między aplikacjami AI a zewnętrznymi narzędziami i źródłami danych. Stworzony przez Anthropic w 2024 roku, działa jak uniwersalny adapter – zamiast budować niestandardowe integracje dla każdego narzędzia, piszesz jeden serwer MCP, który współpracuje z dowolnym klientem obsługującym protokół. W 2026 roku MCP stał się de facto standardem branżowym, przyjętym przez OpenAI, Google i Microsoft.

Czy MCP zastępuje function calling w modelach AI?

Nie zastępuje – rozszerza. Function calling to mechanizm wbudowany w konkretny model (np. GPT-4, Claude), pozwalający mu generować strukturalne wywołania zdefiniowanych funkcji. MCP standaryzuje warstwę poniżej: jak te funkcje są odkrywane, rejestrowane i wywoływane przez zewnętrzne serwery. Możesz traktować MCP jako protokół transportowy, a function calling jako mechanizm decyzyjny modelu.

Jaka jest różnica między MCP a zwykłym API REST?

API REST to ogólny protokół komunikacji między dowolnymi systemami. MCP został zaprojektowany specjalnie dla interakcji LLM z narzędziami. Kluczowe różnice: MCP obsługuje automatyczne odkrywanie narzędzi (agent sam dowiaduje się, co jest dostępne), dostarcza schematy parametrów w formacie zrozumiałym dla modelu, wspiera streaming przez SSE i definiuje trzy typy prymitywów (resources, tools, prompts) dopasowane do potrzeb agentów AI.

Czy do budowy serwera MCP potrzebuję LangChain lub LangGraph?

Nie, absolutnie nie. Oficjalny Python SDK (mcp[cli]) jest całkowicie niezależny od LangChain. FastMCP pozwala zbudować serwer MCP w kilku linijkach czystego Pythona. LangChain i LangGraph przydają się po stronie klienta – biblioteka langchain-mcp-adapters ułatwia podłączenie narzędzi MCP do agentów LangGraph. Ale to opcjonalny element, nie wymóg.

Jak MCP współpracuje z protokołem A2A od Google?

MCP i A2A rozwiązują różne problemy i doskonale się uzupełniają. MCP obsługuje integrację pionową – łączy agenta z narzędziami, bazami danych i API. A2A obsługuje koordynację poziomą – umożliwia agentom komunikację między sobą, delegowanie zadań i wspólną pracę. W dojrzałych systemach wieloagentowych stosuje się oba protokoły jednocześnie.

O Autorze Editorial Team

Our team of expert writers and editors.