Strona główna / Artykuły / MCP od zera, część 3: Połączenie klienta z twoim serwerem

MCP od zera, część 3: Połączenie klienta z twoim serwerem

Stwórz klienta MCP stdio, który uruchamia hr_server.py, wywołuje funkcję search_employee i zwraca wyniki — bez konieczności ręcznego uruchamiania serwera.

804 słów

W poprzedniej części pozostawiono serwer MCP z jedną funkcją: umożliwienie wyszukiwania w dziedzinie HR.

HR MCP Server
      ↓
search_employee

Proces ten rozpoczął się od:

python hr_server.py

Pozostało jedno pytanie: kto faktycznie komunikuje się z serwerem? Ta rola przypada klientowi.

Client
   ↓
MCP
   ↓
HR MCP Server

Rozpatrujmy te pojęcia w prosty sposób. Serwer oferuje możliwości, a klient się do niego łączy i je wykorzystuje.

Stwórzmy naszego klienta

Oprócz pliku hr_server.py dodajmy kolejny moduł:

hr_client.py

Struktura projektu wygląda teraz tak:

mcp-hr
│
├── hr_server.py
│
└── hr_client.py

Ponieważ serwer został już napisany, uwaga skupia się na klientie.

Połączmy się z naszym serwerem

Wplątajmy poniższy kod do pliku hr_client.py:

import asyncio

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    server = StdioServerParameters(
        command="python",
        args=["hr_server.py"]
    )
    async with stdio_client(server) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()

asyncio.run(main())

Pierwszy rzut oka może sprawiać wrażenie, że treść jest gęsta. Decydującym fragmentem jest:

server = StdioServerParameters(
    command="python",
    args=["hr_server.py"]
)

Ty parametry informują klienta o uruchomieniu i połączeniu się z hr_server.py. Klient uruchamia proces serwera i otwiera sesję stdio z nim.

A teraz wezwijmy nasze narzędzie

Część 2 opisała narzędzie o nazwie:

search_employee

Klient może wyszukiwać Johna w ten sposób:

await session.initialize()
result = await session.call_tool(
    "search_employee",
    {"name": "John"}
)

print(result.content[0].text)

Mówiąc prościej, klient prosi sesję o uruchomienie funkcji search_employee z imieniem John. Pełny skrypt klienta jest następujący:

import asyncio

from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client

async def main():
    server = StdioServerParameters(
        command="python3",
        args=["hr_server.py"]
    )
    async with stdio_client(server) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
            result = await session.call_tool(
                "search_employee",
                {"name": "John"}
            )
            print(result.content[0].text)

asyncio.run(main())

To plik stanowi kompletną stronę klienta w tym demo.

Załóżmy, że to uruchomimy

Z terminala należy wykonać:

python hr_client.py

Ciekawy szczegół: hr_server.py nie wymaga osobistego uruchomienia ręcznie. Klient uruchamia je poprzez:

command="python",
args=["hr_server.py"]

a następnie łączy się z biegnącym procesem.

Co się dzieje?

Klient wysyła żądanie narzędzia w takiej formie:

Tool:
  search_employee
Name:
  John

Serwer otrzymuje to i wykonuje:

search_employee("John")

Rozwiązuje to w następujący sposób:

John → Finance

i zwraca ten wynik do wywołującego. W całości ścieżka wygląda tak:

hr_client.py
     │
     │  search_employee("John")
     ↓
hr_server.py
     │
     ↓
Search employee list
     │
     ↓
John → Finance
     │
     ↓
hr_client.py

Oba strony protokołu przeprowadzają teraz rzeczywistą podróż w obie strony.

Z powrotem do naszego przykładu z USB

Połączenie klawiatury z komputerem stanowi przydatne porównanie.

Keyboard
   ↓
USB
   ↓
Computer

Oba końce zgadzają się co do wspólnego protokołu kablowego. Tutaj klient używa MCP, a serwer również MCP. To wspólne umówienie umożliwia im współpracę.

Ale czy wybrał on odpowiedni narzędzie?

Jedna z ograniczeń łatwo umknąć uwadze. Rozważmy ponownie miejsce wywołania:

session.call_tool(
    "search_employee",
    {"name": "John"}
)

Kto wybrał search_employee? To autor aplikacji, poprzez umieszczenie nazwy narzędzia w kodzie źródłowym. Klient nigdy nie czytał pytania w języku naturalnym, takiego jak „Czy John pracuje w dziale finansów?”, i nie decydował na podstawie tego, które funkcje uruchomić. Kolejnym brakującym elementem jest automatyczny wybór narzędzia.

Co dalej?

Załóżmy, że serwer posiada kilka dodatkowych narzędzi:

search_employee
create_employee
get_leave_balance
list_departments

Gdy użytkownik pyta, do którego działu należy John, coś musi określić, że właściwą funkcją jest:

search_employee

a nie jakieś inne narzędzie z tej samej grupy. Ta decyzja zależy od nazw narzędzi, ich opisów oraz dostępnego katalogu. W następnej części omówimy, w jaki sposób aplikacja wybiera spośród narzędzi MCP, gdy jest ich wiele dostępnych.

Aż do tego momentu istotna lekcja płynąca z tego kroku jest czysto mechaniczna: klient stdio może uruchomić serwer, zainicjować sesję, wywołać określone narzędzie z argumentami oraz wydrukować strukturyzowaną odpowiedź — bez konieczności ręcznego uruchamiania oddzielnego procesu serwera.