Co to jest MCP i jak napisać własny tool w Pythonie

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.

Zostaw komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Przewijanie do góry