Praktyczne MCP - tworzymy serwer z narzędziami

Modele językowe w wersji podstawowej, nie są w stanie zrobić nic, oprócz przetwarzania informacji. W dzisiejszych czasach, jednak wymagamy aby systemy potrafiły same reagować na zdarzenia i same sięgać po informacje z różnych źródeł. Do tego celu powstał protokół MCP (więcej informacji pod tym linkiem).

W dzisiejszym praktycznym przykładzie skupimy się na utworzeniu usługi dla administratorów, która będzie w stanie sprawdzić (gdy zapytamy ją o to w języku naturalnym), czy dany komputer jest w naszej sieci. Przykład może jest trywialny, ale uważam że na takich najlepiej się uczyć rozumieć trudne zagadnienia.


Środowisko wirtualne

Zacznijmy od podstaw. Utwórzmy nasze odizolowane środowisko i zainstalujmy zależności:
python -m uv init
uv add mcp pythonping fastmcp
.venv\Scripts\activate

Import bibliotek

Jedną z pierwszych trudności które napotkamy, jest wybór odpowiedniej wersji. Biblioteki FastMCP w wersjach 1.0 i 2.0 są między sobą niekompatybilne już na poziomie definiowania importu.

Dla wersji FastMCP 1.0 użyjemy:

from mcp.server import FastMCP

Zaś dla wersji FastMCP 2.0 skorzystamy z (ją wybieramy w poniższym przykładzie):

from fastmcp import FastMCP

Definicja loggera

Każdy nasz większy projekt, powinniśmy zacząć od zdefiniowania loggera. Pozwoli nam na zbieranie pełnych informacji, co się dzieje w danym momencie w aplikacji.

import logging

logging.basicConfig(
level=logging.INFO,
format="%(levelname)s: %(asctime)s \t%(message)s",
datefmt="%H:%M:%S"
)

logger = logging.getLogger(__name__)

Narzędzia

Przejdźmy teraz do właściwego kodu. Utwórzmy naszą funkcję, która sprawdzi czy dany serwer jest dostępny w sieci - czyli czy odpowiada na ping.

from pythonping import ping as py_ping

@mcp.tool()
def ping(host: str) -> str:
logger.info(f'"PING {host}"')
try:
response = py_ping(host, count=1, timeout=2)
if response.success():
msg = f'"PING {host}" 200 OK (czas: {response.rtt_avg_ms:.1f} ms)'
logger.debug(msg)
return "online"
else:
msg = f'"PING {host}" 408 TIMEOUT'
logger.warning(msg)
return "offline"
except Exception as e:
msg = f'"PING {host}" 500 ERROR ({e})'
logger.error(msg)
return "offline"

Rejestracja narzędzi

Każde zdefiniowane przez nas narzędzie, należy podpiąć pod naszą instancję obiektu klasy "FastMCP". Pozwoli ona na późniejsze wywołanie tych metod po przez API.

def register_tools(mcp):

@mcp.tool()
def ping(host: str) -> str:
pass

mcp = FastMCP()
register_tools(mcp)

Testowanie serwera

Zanim przejdziemy do wywołania naszych akcji, należy uruchomić inspektor. Dzięki niemu będziemy mogli podejrzeć i w łatwy sposób przetestować wszystkie akcje. Należy w konsoli uruchomić:

npx @modelcontextprotocol/inspector

i otworzyć w przeglądarce adres url:

Open inspector with token pre-filled:
http://localhost:6274/?MCP_PROXY_AUTH_TOKEN=f5544537f23039d2119aaa0a92f6610ed27c1a99cc5758e5e19de8d590ae16bd

Następnie w zależności od preferowanej metody połączenia, dobieramy odpowiednie opcje:

1. Stdio - komunikacja przez standardowe operacje wejścia/wyjścia

W niej, program uruchamiamy po przez:

mcp.run(transport="stdio")
Wymaga ustawienia w inspektorze:


2. Streamable - komunikacja przez protokół http

W nim, program uruchamiamy po przez metodę:

mcp.run(transport="streamable-http", host="0.0.0.0", port=8000, path="/mcp")

Wymaga ustawienia w inspektorze:


Teraz pozostało tylko uruchomić nasz plik z serwerem:

python server.py

I kliknąć connect w inspektorze.

*Po poprawnym połączeniu, powinna się zapalić zielona kontrolka oraz pojawić się nam panel zarządzania.

Testy 

Z poziomu samego "MCP inspektora", możemy wywołać nasze narzędzia ręcznie. Dzięki temu mamy możliwość zasymulowania bezpośredniego wykonania akcji, bez wywołania jej pośrednio przez LLM.

Dla testu, możesz w zakładce tools, spróbować wykonać polecenie "ping" do innego urządzenia.


Po rozwinięciu zakładki "historia", mamy dostęp do wyników naszych poleceń.


Pełny kod:

import logging
from fastmcp import FastMCP
from pythonping import ping as py_ping

logging.basicConfig(
level=logging.INFO,
format="%(levelname)s: %(asctime)s \t%(message)s",
datefmt="%H:%M:%S"
)

logger = logging.getLogger(__name__)

def register_tools(mcp: FastMCP) -> None:
"""
Registers available tools within the given MCP server instance.

Args:
mcp (FastMCP): The FastMCP server instance to which the tools will be added.

Returns:
None
"""

@mcp.tool()
def ping(host: str) -> str:
"""
Sends an ICMP echo (ping) request to the specified host and returns its network status.

Args:
host (str): The target hostname or IP address to check (e.g., "8.8.8.8" or "example.com").

Returns:
str: One of the following status values:
- `"online"` – if the host responded successfully to the ping request
- `"offline"` – if the host did not respond or a timeout/error occurred
"""
logger.info(f'"PING {host}"')
try:
response = py_ping(host, count=1, timeout=2)
if response.success():
msg = f'"PING {host}" 200 OK (czas: {response.rtt_avg_ms:.1f} ms)'
logger.debug(msg)
return "online"
else:
msg = f'"PING {host}" 408 TIMEOUT'
logger.warning(msg)
return "offline"
except Exception as e:
msg = f'"PING {host}" 500 ERROR ({e})'
logger.error(msg)
return "offline"

def main() -> None:
"""
Initializes the MCP server, registers tools, and starts the HTTP service.
"""
server = FastMCP()
register_tools(server)
logger.info("Starting MCP Ping Server on http://127.0.0.1:8000/mcp")
server.run(transport="streamable-http", host="0.0.0.0", port=8000, path="/mcp")


if __name__ == "__main__":
main()

W części drugiej, zajmiemy się naszym klientem. Będzie się odwoływał do naszego serwera, aby skorzystać z jego narzędzi.

Kamil Mirończuk

I kiedy czegoś gorąco pragniesz, to cały wszechświat sprzyja potajemnie twojemu pragnieniu
~Paulo Coelho

Komentarze

Zostaw komentarz

Twój adres mailowy NIE zostanie opublikowany. W razie otrzymania zapytania, otrzymasz na niego odpowiedź.
Wymagane pola są oznaczone jako *