Czym jest MCP?
MCP, czyli Model Context Protocol, to otwarty standard stworzony przez Anthropic, który definiuje jak modele językowe mogą komunikować się z zewnętrznymi narzędziami i źródłami danych. Zamiast każdemu dostawcy AI wymyślać własny sposób integracji, MCP proponuje jeden ujednolicony protokół.
Prosta analogia: MCP to jak USB dla AI. Zanim USB istniało, każde urządzenie miało inny wtyk. MCP robi to samo dla narzędzi — jeden standard, który pasuje do każdego modelu.
Jak to działa pod spodem?
Architektura MCP opiera się na trzech elementach:
- Host — aplikacja AI (np. Claude Desktop), która inicjuje połączenie
- Klient — część hosta zarządzająca połączeniem z serwerem MCP
- Serwer MCP — Twój program, który udostępnia narzędzia modelowi
Komunikacja wygląda tak: model decyduje że chce użyć narzędzia → wysyła żądanie do serwera MCP → serwer wykonuje kod → zwraca wynik → model dostaje odpowiedź i kontynuuje.
Ważna rzecz: model sam decyduje kiedy i jak użyć narzędzia. Ty tylko definiujesz co narzędzie robi i jakie przyjmuje parametry.
Claude Desktop + lokalny serwer MCP w Pythonie
W tym artykule opisuję konkretny setup: Claude Desktop na Macu lub PC z lokalnie działającym serwerem MCP napisanym w Pythonie. To najprostszy i najpopularniejszy sposób na start.
Claude Desktop uruchamia serwer MCP jako subprocess — Twój skrypt Python startuje razem z aplikacją i komunikuje się przez stdio. Wygląda to tak:
Claude Desktop (Twój Mac/PC)
└── serwer MCP — lokalny skrypt Python
└── SSH / API / HTTP
└── serwer docelowy / usługa zewnętrzna
Serwer MCP jest mostem między modelem a światem zewnętrznym. Możesz w nim zrobić cokolwiek — połączyć się przez SSH, wywołać API, odczytać plik. Model tego nie widzi — dostaje tylko wynik.
Ważne: po każdej zmianie w claude_desktop_config.json musisz zresetować Claude Desktop — aplikacja wczytuje konfigurację tylko przy starcie. Najszybciej: Cmd+Q (Mac) lub File → Quit, potem uruchom ponownie.
Praktyczny przykład — tool SSH
Zamiast sztucznego przykładu pokażę realny tool którego używam do zarządzania serwerami. Łączy się przez SSH z dowolnym hostem i wykonuje komendy.
Instalacja:
pip install fastmcp paramiko
Kod (server.py):
import os
import paramiko
from fastmcp import FastMCP
mcp = FastMCP("SSH Tools")
def _get_client(host: str, port: int = 22) -> paramiko.SSHClient:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
key_path = os.getenv("SSH_KEY_PATH", os.path.expanduser("~/.ssh/id_rsa"))
ssh_user = os.getenv("SSH_USER", "root")
ssh_pass = os.getenv("SSH_PASSWORD")
if os.path.exists(key_path) and not ssh_pass:
client.connect(host, port=port, username=ssh_user, key_filename=key_path)
else:
client.connect(host, port=port, username=ssh_user, password=ssh_pass)
return client
@mcp.tool()
def ssh_exec(host: str, command: str, port: int = 22) -> dict:
"""
Wykonuje komendę SSH na zdalnym hoście.
Zwraca stdout, stderr i exit code.
"""
client = _get_client(host, port)
try:
stdin, stdout, stderr = client.exec_command(command)
exit_code = stdout.channel.recv_exit_status()
return {
"stdout": stdout.read().decode(),
"stderr": stderr.read().decode(),
"exit_code": exit_code,
"success": exit_code == 0,
}
finally:
client.close()
if __name__ == "__main__":
mcp.run()
Konfiguracja w Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"ssh-tools": {
"command": "python",
"args": ["/sciezka/do/server.py"],
"env": {
"SSH_USER": "username",
"SSH_KEY_PATH": "/sciezka/do/klucza/ssh"
}
}
}
}
Teraz możesz powiedzieć Claude: „sprawdź użycie dysku na 10.1.10.51” i model sam wywoła ssh_exec z odpowiednimi argumentami.
Co podajesz w definicji toola — zasady typów
Argumenty funkcji toola muszą być prostymi typami — model musi być w stanie wygenerować je jako JSON.
Co możesz podać:
# OK — proste typy
def mój_tool(host: str, port: int, debug: bool, timeout: float) -> dict:
pass
# OK — optional z domyślną wartością
def mój_tool(host: str, port: int = 22) -> dict:
pass
# OK — lista stringów
def mój_tool(hosts: list[str]) -> dict:
pass
Czego nie podajesz:
# ŹLE — obiekt własnej klasy
def mój_tool(client: paramiko.SSHClient) -> dict:
pass
# ŹLE — słownik bez schematu (model nie wie co wpisać)
def mój_tool(config: dict) -> dict:
pass
Jeśli potrzebujesz złożonej konfiguracji — rozwiąż to wewnątrz funkcji lub przez zmienne środowiskowe, tak jak _get_client() w przykładzie wyżej.
Bezpieczeństwo — co widzi model, a czego nie
Model w kontekście MCP zachowuje się jak skrypt wykonujący funkcje. Ma dostęp tylko do:
- argumentów które sam przekazuje do funkcji
- wartości zwróconej przez funkcję
- docstringa (opisu) narzędzia
Model nie ma dostępu do:
- zmiennych środowiskowych — to czyta tylko Twój kod
- plików lokalnych jeśli ich nie zwrócisz
- kodu źródłowego serwera MCP
Dlatego hasła i klucze trzymasz w env w konfiguracji — model widzi że tool działa, ale nie wie jak się autentykuje. Gdy Claude wywołuje ssh_exec(host="10.1.10.51", command="uptime") — nie zna ani użytkownika SSH ani ścieżki do klucza. Zna tylko wynik.
Praktyczna zasada: nigdy nie zwracaj w odpowiedzi toola danych które model nie powinien widzieć — credentiali, tokenów, kluczy prywatnych. To co zwróci funkcja, model zobaczy w całości.
Co dalej?
MCP to jeden z wielu sposobów w jakich AI wchodzi do codziennej pracy devopsa. Na blogu będę opisywał kolejne przykłady użycia — zarówno praktyczne implementacje MCP, jak i szersze zastosowania modeli językowych w automatyzacji infrastruktury, monitoringu i codziennej pracy z serwerami.