MCP dla agentów LangGraph, krok po kroku: serwery, transport i most adaptera
MCP dla agentów LangGraph, krok po kroku: role hosta, klienta i serwera, serwer FastMCP, transporty stdio vs HTTP, adaptery langchain-mcp-adapters oraz pułapki.
Źródła: MCP for LangGraph Developers: From Basics to Production
Wymagania wstępne są niewielkie: znajomość struktury projektu LangGraph oraz koncepcji funkcji ozdobionej tagiem @tool. Model Context Protocol to nowa część infrastruktury, a nie nowy sposób myślenia o grafach, dlatego wyjaśnienia zaczynają się od zera od samego MCP, a do LangGraph wracamy dopiero wtedy, gdy koncepcja jest już dobrze zrozumiała.
Problem skalowania przy zwykłych narzędziach
Narzędzie w LangGraph zazwyczaj zaczyna się w ten sposób:
@tool
def search_web(query: str) -> str:
"""Search the web for current information."""
return f"Search results for: {query}"
To jest w porządku, dopóki nie jest potrzebna skalowalność. Załóżmy, że pięciu agentów w pięciu projektach potrzebuje narzędzia do „szukania w bazie danych firmy”. Zgodnie z powyższym wzorem to samo narzędzie musi zostać napisane pięć razy, w pięciu bazach kodu, być może w pięciu językach. Zmiana schematu oznacza pięć edycji. Udostępnienie go koleżance, która buduje inny rodzaj aplikacji AI, być może wcale nie opartej na LangGraph, kończy się niepowodzeniem, ponieważ ta funkcja @tool należy do świata Pythona w LangChain.
Nazwa tego problemu to problem integracji N×M. N narzędzi (bazy danych, API, systemy plikowe) musi połączyć się z M aplikacjami AI (agentem LangGraph, dostosowanym agentem kolegi, Claude Desktop, asystentem IDE). Bez wspólnego standardu powstają indywidualne integracje typu N×M.
Model Context Protocol (MCP) stanowi otwarty, uniwersalny standard umożliwiający łączenie aplikacji AI z narzędziami i danymi. Narzędzie stworzone raz jako serwer MCP może być używane przez każdą aplikację kompatybilną z MCP – czy to agenta LangGraph, Claude Desktop, Cursor czy własnego agenta – bez konieczności pisania specjalnego kodu integracyjnego. Często porównuje się to do portu USB-C: jeden standard złącza, dowolne kompatybilne urządzenie po obu stronach.
Przejście od pytania „co to jest MCP?” do serwera klasy produkcyjnej podłączonego do rzeczywistego agenta LangGraph odbywa się przez pięć poziomów, z których każdy dodaje nową informację.
Poziom 1: czym jest MCP
Najpierw umieść zdjęcie, zanim dodasz jakikolwiek kod. Konserjer hotelowy osobiście nie wie, jak zarezerwować stolik w restauracji, wezwać taksówkę czy kupić bilety do teatru. Konserjer posiada listę zaufanych lokalnych usług, z których każda jest specjalistą w swojej dziedzinie. Gdy gość chce zarezerwować kolację, konserjer łączy się z dedykowaną linią do usługi rezerwacyjnej, składa prośbę w formacie zrozumiałym dla obu stron i przekazuje odpowiedź.
MCP to właśnie ta standardowa linia łączności oraz standardowy format prośby. Aplikacja AI pełni rolę konserjera. Serwer MCP to specjalistyczna usługa, taka jak system rezerwacji restauracji, firma taksówkowa lub kasa biletowa. Sam MCP stanowi wspólny język i schemat połączeń, dzięki czemu każdy konserjer może komunikować się z dowolną usługą bez konieczności nauki sposobu funkcjonowania jej własnej centrali telefonicznej.
Trzy role
Każdy system MCP składa się dokładnie z trzech uczestników, a wszystko inne opiera się na nich.
- Host. Aplikacja AI, której faktycznie używa użytkownik końcowy: w tym przypadku agent LangGraph, gdzie indziej Claude Desktop, Cursor lub VS Code z copilotem. To ona prowadzi rozmowę, decyduje, kiedy potrzebne jest narzędzie, i pokazuje wyniki.
- Client. Znajduje się wewnątrz hosta, a każdy client utrzymuje dedykowane, jedno-do-jednego połączenie z dokładnie jednym serwerem. Host połączony z trzema serwerami uruchamia wewnętrznie trzy klienty. To jedno-do-jednego mapowanie stanowi celową granicę bezpieczeństwa, dzięki czemu client przeznaczony do serwera systemu plików nie może przypadkowo wyciec danych na serwer bazodanowy.
Format komunikacji: JSON-RPC 2.0
Komunikaty pomiędzy klientem a serwerem używają formatu JSON-RPC 2.0 – prostego i powszechnie stosowanego formatu z trzema typami wiadomości.
Prośba polega na żądaniu wykonania określonej czynności i zawiera unikalny id, dzięki czemu można dopasować odpowiedź:
{"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "search_db", "arguments": {"query": "Q3 revenue"}}}
Odpowiedź odpowiada na żądanie. Zawiera albo result w przypadku sukcesu, albo error w przypadku niepowodzenia, przy tym ma takie samo id jak żądanie:
{"jsonrpc": "2.0", "id": 1, "result": {"content": [{"type": "text", "text": "Q3 revenue was $4.2M"}]}}
Wiadomość to wiadomość jednokierunkowa, która nie wymaga odpowiedzi, na przykład w celu poinformowania o zmianie listy dostępnych narzędzi.
Surowy JSON-RPC rzadko jest pisany ręcznie, ponieważ zajmują się tym SDK-y. Mimo to znajomość jego zasady pomaga zrozumieć wiele aspektów, podobnie jak znajomość protokołu HTTP sprawia, że ktoś staje się lepszym deweloperem REST, nawet jeśli nigdy nie pisze surowych zapytań HTTP.
Poziom 2: trzy prymitywy
Server oferuje swoje możliwości za pośrednictwem dokładnie trzech rodzajów prymitywów, a niemal każda decyzja projektowa przy tworzeniu takiego serwera polega na wyborze jednego z nich.
Narzędzia to działania, które model może wykonać. Narzędzie to funkcja, którą LLM może zdecydować się wywołać podczas rozumowania, podobnie jak funkcja ozdobiona w zwykłym LangGraph. Różnica polega na tym, gdzie się znajduje: wewnątrz serwera MCP, a nie w kodzie LangGraph, gdzie jest odkrywane i wywoływane przez protokół. Użyj narzędzia wtedy, gdy coś musi się wydarzyć: dokonać obliczenia, zadać zapytanie do bazy danych, wysłać e-mail lub edytować plik.
Zasoby dostarczają jedynie treści do odczytu. Aplikacja może pobrać zasób, aby przekazać modelowi materiały pomocnicze, bez konieczności, by model sam zdecydował się go wywołać, tak jak robiłoby to z narzędziem. Zasoby są adresowane za pomocą URI, podobnie jak strona internetowa jest adresowana za pomocą URL. Używaj ich do materiałów dostępnych tylko do odczytu, takich jak plik konfiguracyjny, dokument lub lista źródeł danych, o których model może chcieć wiedzieć przed wybraniem tego, co ma wyszukać.
Wskazówki to szablony wielokrotnego użycia. Są to z góry przygotowane struktury, które uruchamiają określony proces dla użytkownika lub hosta. W odróżnieniu od narzędzi, wskazówki są zazwyczaj wywoływane przez użytkownika, a nie przez model, więc przypominają polecenia z użyciem średnika. Nadają się do częstych, powtarzalnych zadań, takich jak „streszcz ten dokument” lub „sprawdź ten kod pod kątem problemów bezpieczeństwa”, gdzie spójne, dobrze zaprojektowane instrukcje są lepsze niż pozostawienie sformułowania użytkownikowi.
Gdy nie jesteś pewien, co wybrać, zapytaj, czy dana czynność coś zmienia, czy tylko dostarcza informacji. Jeśli coś tworzy, aktualizuje, usuwa, wysyła lub wykonuje, jest to narzędzie. Jeśli są to dane tylko do odczytu, których model może potrzebować jako tło, jest to zasób. Jeśli jest to szablon instrukcji, który użytkownik wywołuje wyraźnie, jest to wskazówka.
Poziom 3: pierwszy serwer z FastMCP
FastMCP jest dominującym frameworkiem w Pythonie do tworzenia serwerów MCP. Przekształca złożoną strukturę tego protokołu (obsługa zapytań, generowanie schematów, cykl życia) w kilka dekoratorów, w tym samym duchu co @tool dla funkcji LangChain. Instalacja:
pip install fastmcp
Pełny serwer z jednym narzędziem mieści się w krótkim pliku:
# ── math_server.py ───────────────────────────────────────────
from fastmcp import FastMCP
# Create the server - this object is the container for everything below
mcp = FastMCP(name="Math Server")
@mcp.tool
def add(a: int, b: int) -> int:
"""Add two numbers together."""
return a + b
@mcp.tool
def multiply(a: int, b: int) -> int:
"""Multiply two numbers together."""
return a * b
if __name__ == "__main__":
mcp.run() # Defaults to stdio transport
To jest ważny, działający serwer MCP. FastMCP czyta wskazówki typowe funkcji, aby stworzyć schemat wejścia, oraz czyta dokumentację, aby wygenerować opis, który widzi model LLM. Obowiązuje ta sama zasada co w przypadku zwykłych narzędzi, tylko z większymi konsekwencjami: niejasna dokumentacja sprawia, że model nie wie, kiedy powinien użyć narzędzia, a ta niejasność wpływa na każde zastosowanie, które się łączy, a nie tylko na jeden agent.
Aby dodać zasób, potrzebny jest dekorator z wybranym URI:
# ── Adding to math_server.py ─────────────────────────────────
@mcp.resource("config://server-info")
def server_info() -> dict:
"""Provides metadata about this server's capabilities."""
return {"version": "1.0", "operations": ["add", "multiply"]}
Gdy klient żąda config://server-info, FastMCP wywołuje tę funkcję i zwraca wynik. Działa ona wyłącznie na żądanie, co stanowi ocenę opóźnioną, więc nie oblicza się niczego, czego model nigdy nie użyje.
Czasami zasób powinien zależeć od argumentu, np. od konkretnej rekordu, a nie od stałego bloku konfiguracji:
@mcp.resource("history://{operation_id}")
def get_calculation(operation_id: str) -> dict:
"""Retrieve a past calculation by its ID."""
# In a real server, this would look up a database record
return {"id": operation_id, "result": "..."}
Placownik {operation_id} w URI sprawia, że jest to szablon zasobu. Prośba o adres history://abc123 sprawia, że FastMCP wyodrębnia abc123 i automatycznie przekazuje go do funkcji.
Wskazówka jest definiowana w ten sam sposób:
@mcp.prompt
def explain_calculation(operation: str, result: str) -> str:
"""Generate a request to explain a calculation in plain English."""
return f"Explain how the operation '{operation}' produced the result '{result}', in simple terms a beginner could follow."
Aby uruchomić serwer:
python math_server.py
Zanim połączysz rzeczywistą aplikację AI, sprawdź ją za pomocą MCP Inspector, narzędzia do debugowania opartego na przeglądarce, które jest dostarczane wraz z tym ekosystemem. Pokazuje ono dostępne narzędzia, uruchamia je ręcznie i sprawdza zasoby bez udziału modelu LLM:
fastmcp dev math_server.py
To otwiera lokalną interfejs internetową, zazwyczaj pod adresem http://127.0.0.1:6274, gdzie można przeglądać dostępne narzędzia, wprowadzać argumenty testowe oraz obserwować wiadomości JSON-RPC przekazywane w obie strony. Najpierw przetestuj to tutaj. Naprawa uszkodzonego narzędzia jest znacznie trudniejsza poprzez zachowanie LLM przy wywoływaniu narzędzi niż bezpośrednio w inspektorze.
Poziom 4: transporty
Do tej pory serwer działał z użyciem domyślnego transportu. Wybór ma znaczenie, ponieważ określa, w jaki sposób agent LangGraph będzie się łączył.
Traktuj transport jak fizyczny przewód, a JSON-RPC jako język przekazywany przez niego. Język angielski funkcjonuje zarówno przez telefon, jak i przez radio – jeden język, dwa przewody. Serwery MCP używają tego samego JSON-RPC 2.0 niezależnie od transportu, ale dane przemieszczają się w inny sposób.
stdio, dla narzędzi lokalnych
To jest domyślny wybór, a także to, co robi mcp.run(), gdy jest wywoływany bez argumentów:
if __name__ == "__main__":
mcp.run() # stdio is the default
Dzięki stdio (standardowe wejście i wyjście) host uruchamia serwer jako podproces i komunikuje się z nim poprzez zapisywanie danych do jego stdin oraz odczytywanie z jego stdout. Nie jest potrzebna sieć – to komunikacja między procesami na jednej maszynie. Nadaje się to do serwerów działających obok hosta, takich jak narzędzia do systemu plików, lokalne narzędzia do wykonywania skryptów lub osobiste utility, i stanowi najprostsze oraz najczęściej stosowane rozwiązanie w celach rozwojowych.
Jedno ograniczenie ma ogromne znaczenie. W modelu stdio stdout serwera jest wyłącznie przeznaczony na wiadomości protokołowe, więc przypadkowy wywołanie funkcji print() w celach debugowania psuje strumień danych. Logowanie musi odbywać się do stderr.
Streamable HTTP, dla narzędzi zdalnych
Dla serwera sieciowego należy wyraźnie określić środek transportu:
if __name__ == "__main__":
mcp.run(transport="streamable-http", host="0.0.0.0", port=8000)
Tutaj serwer działa jako samodzielna usługa internetowa. Klienci łączą się przez sieć za pomocą zapytań HTTP POST, z opcjonalnymi Server-Sent Events do przesyłania odpowiedzi w formie strumienia. Dzięki temu serwer jest naprawdę zdalny: może znajdować się na innej maszynie, w chmurze lub za load balancerem, i jednocześnie obsługiwać kilka aplikacji. Należy go używać wtedy, gdy serwer musi być udostępniany całemu zespołowi, wdrażany centralnie lub dostępny z innych maszyn, w tym z agenta LangGraph działającego na serwerze i komunikującego się z serwerem MCP umożliwiającym dostęp do bazy danych.
Zasada ogólna: stdio dla narzędzia lokalnego, jednego użytkownika i prostego ustawienia, typowego dla rozwoju i użytku osobistego; Streamable HTTP dla narzędzia udostępnianego, wielu użytkowników lub aplikacji, wdrażania w środowisku produkcyjnym oraz potrzeb autoryzacji. To właśnie wybór, który należy określić dla każdego serwera w konfiguracji LangGraph.
Poziom 5: łączenie z LangGraph
Mostem jest pakiet o nazwie langchain-mcp-adapters, utrzymywany przez zespół LangChain w celu umożliwienia używania narzędzi MCP w agentach LangGraph. Robi on dla MCP to samo, co dekorator @tool robił dla zwykłych funkcji: przekształca zewnętrzne narzędzie MCP w obiekt BaseTool, który można bezpośrednio umieścić w ToolNode LangGraph bez konieczności specjalnego traktowania w grafie. Po konwersji agent nie wie ani nie obchodzi go, czy narzędzie pochodzi z serwera MCP, czy zostało napisane lokalnie.
Zainstaluj go za pomocą:
pip install langchain-mcp-adapters langgraph "langchain[openai]"
Podstawowym obiektem jest MultiServerMCPClient, który może łączyć się z kilkoma serwerami MCP jednocześnie i swobodnie łączyć różne metody transmisji:
# ── MODULE 1 (extended): MCP CLIENT CONFIGURATION ───────────
from langchain_mcp_adapters.client import MultiServerMCPClient
client = MultiServerMCPClient({
# A local server, launched as a subprocess via stdio
"math": {
"command": "python",
"args": ["/absolute/path/to/math_server.py"],
"transport": "stdio",
},
# A remote server, already running, connected over HTTP
"company_data": {
"url": "http://localhost:8000/mcp",
"transport": "http",
},
})
Każda pozycja słownika to jeden serwer. Klucz ("math" lub "company_data") jest jedynie etykietą, która pomaga w uporządkowaniu narzędzi z różnych serwerów wewnątrz systemu. Pozycja ta odzwierciedla decyzję dotyczącą sposobu transmisji danych: serwery stdio przyjmują command i args, natomiast serwery HTTP przyjmują url.
Ładowanie narzędzi wymaga jednego wywołania:
# This single call discovers every tool, on every configured server,
# and converts each one into a LangChain-compatible BaseTool
tools = await client.get_tools()
print([t.name for t in tools])
# ['add', 'multiply', 'search_company_db', ...]
To jest cała integracja. client.get_tools() wysyła żądanie MCP tools/list do każdego skonfigurowanego serwera, pobiera zwrócone schematy i otacza każdy z nich obiektem narzędzia, który z punktu widzenia grafu nie różni się od ręcznie napisanej funkcji @tool.
Pozostała część konfiguracji wygląda tak samo jak w dowolnym agencie LangGraph. MCP zmienia źródło narzędzi, ale nie wpływa na sposób budowania grafu. Pełny szablon:
# ============================================================
# LANGGRAPH + MCP AGENT — COMPLETE TEMPLATE
# Extends: Part 1 (core structure)
# ============================================================
# ── MODULE 1: IMPORTS & CONFIGURATION ───────────────────────
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage
from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode, tools_condition
from langgraph.checkpoint.memory import MemorySaver
llm = init_chat_model("openai:gpt-4o")
client = MultiServerMCPClient({
"math": {
"command": "python",
"args": ["./math_server.py"],
"transport": "stdio",
},
"company_data": {
"url": "http://localhost:8000/mcp",
"transport": "http",
},
})
# ── MODULE 2: STATE ──────────────────────────────────────────
class State(MessagesState):
pass
# ── MODULE 4: NODES ──────────────────────────────────────────
# (Module 3, Tools, is now sourced from MCP rather than defined here)
def make_agent_node(llm_with_tools):
"""Factory pattern: tools are loaded asynchronously, so the node
closure is built after the async tool-loading step completes."""
def agent_node(state: State) -> dict:
response = llm_with_tools.invoke(state["messages"])
return {"messages": [response]}
return agent_node
# ── MODULE 6: GRAPH ASSEMBLY (inside an async function) ──────
async def build_graph():
tools = await client.get_tools()
llm_with_tools = llm.bind_tools(tools)
graph_builder = StateGraph(State)
graph_builder.add_node("agent", make_agent_node(llm_with_tools))
graph_builder.add_node("tools", ToolNode(tools))
graph_builder.add_edge(START, "agent")
graph_builder.add_conditional_edges("agent", tools_condition)
graph_builder.add_edge("tools", "agent")
return graph_builder.compile(checkpointer=MemorySaver())
# ── MODULE 7: ENTRYPOINT ──────────────────────────────────────
async def main():
graph = await build_graph()
config = {"configurable": {"thread_id": "session-001"}}
response = await graph.ainvoke(
{"messages": [HumanMessage(content="What's (3 + 5) times 12?")]},
config=config
)
print(response["messages"][-1].content)
if __name__ == "__main__":
asyncio.run(main())
Wszystko staje się asynchroniczne
MCP wprowadza jedną istotną zmianę. Komunikacja jest z natury asynchroniczna, ponieważ każde wezwanie narzędzia stanowi ruch w sieci lub pomiędzy podprocesami, nawet w przypadku stdio. Dlatego client.get_tools() musi być oczekiwany, budowa grafu zazwyczaj odbywa się w funkcji async def, a skompilowany graf jest wywoływany za pomocą ainvoke, a nie invoke. Wszystko inne – StateGraph, MessagesState, tools_condition oraz MemorySaver – pozostaje bez zmian.
Znów gotowy router
W tym szablonie routowanie w praktyce znika. Zamiast niestandardowego routera używa tools_condition, czyli gotowego rozwiązania: jeśli ostatnia wiadomość zawiera wywołania narzędzi, przechodzi się do "tools", w przeciwnym razie proces kończy się. Router nie potrafi odróżnić narzędzi MCP od tych napisanych ręcznie, ponieważ oba są obiektami BaseTool w momencie, gdy są widoczne dla grafu.
Poziom 6: kwestie związane z produkcją
Istnieje kilka czynników, które odróżniają działającą demonstrację od systemu gotowego do wdrożenia, i to one najczęściej występują w rzeczywistych konfiguracjach MCP i LangGraph.
Utrzymuj połączenie z klientem aktywne. MultiServerMCPClient domyślnie nie przechowuje stanu: każde wezwanie narzędzia otwiera nowe połączenie, je wykonywa i następnie zamyka. To wystarcza przy jednej prośbie. W aplikacji obsługującej wiele użytkowników otwieranie nowego podprocesu lub połączenia HTTP przy każdym wezwaniu powoduje znaczną opóźnienie. W środowisku produkcyjnym należy otworzyć klienta raz, gdy proces się uruchamia, i używać go przez cały czas trwania aplikacji:
# Hold the connection open across the application's lifetime,
# rather than opening/closing per request
async with MultiServerMCPClient({...}) as client:
tools = await client.get_tools()
graph = await build_graph_with(tools)
# ... serve many requests using this same graph ...
Unikaj użycia stdio w serwerze internetowym. Stdio zostało zaprojektowane do zastosowań lokalnych, jednoosobowych, na tym samym komputerze, takich jak aplikacja desktopowa uruchamiająca narzędzie pomocnicze. Gdy agent LangGraph jest wdrażany jako usługa internetowa dla wielu użytkowników, serwery stdio stają się obciążeniem: podprocesy są tworzone przy każdej prośbie, a nie ma naturalnego sposobu na udostępnianie stanu ani skalowanie. W przypadku dowolnego wdrożenia po stronie serwera należy preferować Streamable HTTP i szczerze ocenić, czy w ogóle potrzebny jest oddzielny proces MCP. Istnieją sytuacje, gdy zwykła funkcja @tool wywołująca bezpośrednio wewnętrzną bibliotekę przewyższa pod względem prostoty i szybkości dodatkowy etap przetwarzania przez protokół.
Dodaj ludzką aprobatę dla narzędzi wrażliwych. Narzędzia MCP stają się zwykłymi narzędziami po dotarciu do grafu, więc obowiązują wobec nich wszystkie istniejące zasady. Jeśli serwer udostępnia coś wrażliwego, takiego jak wysyłanie e-maili, modyfikacja bazy danych lub wydawanie pieniędzy, należy to otoczyć tym samym krokiem weryfikacji interrupt() używanym przy każdej innej wywołaniu narzędzia. MCP umożliwia odkrywanie i standaryzację, ale domyślnie nie zapewnia bezpieczeństwa. To nadal jest zadaniem programisty.
Serwery nie mogą widzieć stanu grafu. Serwery MCP działają jako oddzielne procesy. Nie mogą uzyskać dostępu do state LangGraph, do mechanizmu checkpointer ani do żadnego obiektu Pythona w pamięci. Gdy konieczna jest personalizacja wywołania, na przykład na podstawie zapisanej preferencji użytkownika, wartość musi zostać przekazana wyraźnie jako argument narzędzia lub dostarczona za pośrednictwem interceptorów – zaawansowanego mechanizmu oferowanego przez langchain-mcp-adapters, który umożliwia inspekcję i modyfikację wywołania przed jego wysłaniem, korzystając z kontekstu działania grafu. Przekazywanie wartości jako argumentów wystarcza dla większości agentów i jest znacznie prostsze.
Poziom 7: MCP w systemie wieloagentowym
MCP łączy się z wzorcem supervisor. Kilka specjalistycznych agentów otrzymuje swoje narzędzia z dedykowanego dla nich serwera:
# ── Specialist agents, each pulling tools from a DIFFERENT MCP server ──
async def build_multi_agent_system():
client = MultiServerMCPClient({
"research": {"url": "http://localhost:8001/mcp", "transport": "http"},
"code": {"url": "http://localhost:8002/mcp", "transport": "http"},
})
research_tools = await client.get_tools(server_name="research")
code_tools = await client.get_tools(server_name="code")
researcher_llm = llm.bind_tools(research_tools)
coder_llm = llm.bind_tools(code_tools)
# From here, wire researcher_node, coder_node, and a supervisor_node
# exactly as shown in Part 4 - the rest of the supervisor pattern
# is completely unchanged.
...
MCP nie wprowadza tutaj konkurencyjnej architektury. Specjalista wspierany przez serwer MCP jest strukturalnie identyczny z tym wspieranym przez ręcznie pisane narzędzia: nadzorca kieruje komunikacją w ten sam sposób, a specjaliści przekazują informacje w ten sam sposób. MCP zmienia jedynie miejsce przechowywania definicji narzędzi oraz liczbę aplikacji, które mogą je ponownie wykorzystać.
Szybki przewodnik
- Architektura. Host to aplikacja AI, z którą ma kontakt użytkownik (w tym przypadku agent LangGraph). Client znajduje się w hostzie, po jednym na każdym połączonym serwerze, i zarządza sesją jeden-do-jednego. Server to samodzielny program udostępniający narzędzia, zasoby i polecenia, który nie wie, z jakiego hosta nawiązuje połączenie. JSON-RPC 2.0 to format wiadomości używany we wszystkich wymianach: żądaniach, odpowiedziach i powiadomieniach.
- Prymitywy. Tool to wykonywalna akcja, którą model decyduje się wywołać, tworzona za pomocą
@mcp.tool. Resource to dane tylko do odczytu adresowane przez URI, kontrolowane przez aplikację lub użytkownika, tworzone za pomocą@mcp.resource("uri://..."). Prompt to szablon instrukcji wielokrotnie używalny, wywoływany przez użytkownika, tworzony za pomocą@mcp.prompt. - FastMCP.
FastMCP(name="...")tworzy serwer, który jest kontenerem dla wszystkiego.@mcp.tooludostępnia funkcję, która odczytuje wskazówki typów dotyczące schematu oraz dokumentację opisową.@mcp.resource("scheme://{param}")deklaruje dane tylko do odczytu, przy czym{param}służy do tworzenia szablonu.@mcp.promptdeklaruje szablon.mcp.run()uruchamia serwer – domyślnie przez stdio, lub z użyciemtransport="streamable-http"do pracy w sieci.fastmcp dev server.pyotwiera narzędzie inspekcji do testowania bez użycia LLM. - Transporty.
stdioopiera się na podprocesach i działa lokalnie, przy czym serwer jest uruchamiany przez hosta, a wyniki debugowania nigdy nie powinny trafiać do stdout.streamable-httpjest sieciowy, obsługuje wiele jednoczesnych hostów i stanowi standard w środowiskach produkcyjnych na serwerach współdzielonych. - Integracja z LangGraph.
MultiServerMCPClient({...})z modułulangchain_mcp_adapters.clientłączy się z jednym lub więcej serwerami wykorzystującymi różne transporty w ramach jednego słownika. Funkcjaawait client.get_tools()odkrywa wszystkie narzędzia i przekształca je w obiekty typuBaseTool.tools_conditionpełni rolę wbudowanego routera i działa identycznie zarówno dla narzędzi stworzonych ręcznie, jak i tych pochodzących z MCP. Wszystko odbywa się asynchronicznie:get_tools(), budowa grafu orazainvokewymagają użyciaawait.
Czy tutaj potrzebny jest MCP?
- Jedno narzędzie, jeden agent, jedna baza kodu. Nie potrzeba MCP. Zwykła funkcja
@tooljest prostsza i nie wiąże się z żadnym obciążeniem protokołowym. - Narzędzie wykorzystywane w różnych agentach lub projektach, lub udostępniane kolegom pracującym z innymi frameworkami. Stwórz je jako serwer MCP. To jest jego główna wartość.
- Chcesz zamiast ręcznie pisanego kodu integracyjnego mieć setki istniejących serwerów społecznościowych (GitHub, Slack, Postgres, Google Drive, Stripe i inne). Połącz się za pomocą
MultiServerMCPClienti pomiń pisanie kodu integracyjnego, co często stanowi największą oszczędność czasu oferowaną przez MCP. - Narzędzie znajdujące się na innej maszynie niż agent, lub udostępniane jednocześnie przez kilka hostów. Użyj Streamable HTTP jako samodzielnej usługi.
Standard, a nie zamiennik
Uczenie się nowego protokołu rodzi obawę, że zastąpi to, co już jest znane. MCP tak nie robi. Pozostawia narzędzia, węzły i grafy LangGraph nietknięte. Zmienia się jedynie pochodzenie narzędzi oraz liczba aplikacji, które mogą nimi korzystać. Gdy narzędzie MCP dotrze do ToolNode, będzie zachowywać się jak każde ręcznie napisane narzędzie, będzie akceptowane przez te same wzorce interrupt() i będzie pasować do tych samych architektur nadzorczych.
Postęp odbywał się krok po kroku: koncepcje i role, trzy elementy podstawowe, działający serwer, wybór środka transportu, most LangGraph, wzmocnienie aplikacji w środowisku produkcyjnym oraz kompozycja z systemami wielu agentów. Każdy poziom dodawał jedną ideę do już istniejącej, stabilnej bazy. W połączeniu z kanoniczną strukturą grafu, zarządzaniem pamięcią, zabezpieczeniami zapewniającymi udział człowieka, orkiestracją i wyszukiwaniem w środowisku wielu agentów, to wszystko obejmuje większość elementów niezbędnych do stworzenia poważnej aplikacji LangGraph w środowisku produkcyjnym, a MCP umożliwia rozszerzenie narzędzi poza jeden kod źródłowy.
Literatura pokrewna
- Praktyczne notatki: Dlaczego nikt nie mówi o LangChain, LangGraph czy AutoAgent — Szczegółowy przewodnik po Praktycznych notatkach: Dlaczego nikt nie mówi o LangChain, LangGraph czy AutoAgent: kontrakty, sprawdzania oraz gotowe elementy kodu dla zespołów wdrażających ten wzorzec.
- ReAct w LangGraph: Od ręcznie budowanej pętli do ToolNode i StateGraph — Stwórz agenta ReAct dwa razy: najpierw jako ręczną pętlę asynchroniczną, a następnie przy użyciu ToolNode, tools_condition oraz StateGraph z LangGraph, które automatycznie obsługują łańcuchowe wywołania narzędzi.