Model Context Protocol: vznikající standard pro aplikace AI a LLM

1. 4. 2025
Doba čtení: 28 minut

Sdílet

Dvě humanoidní postavy propojují svou mysl
Autor: Root.cz s využitím Zoner AI
Současně s vývojem aplikací, které do nějaké míry využívají umělou inteligenci a/nebo velké jazykové modely se ukázala potřeba existence standardního mechanismu pro komunikaci mezi jednotlivými moduly těchto aplikací.

Obsah

1. Model Context Protocol: vznikající standard pro potřeby aplikací využívajících AI a LLM

2. Základní vlastnosti MCP

3. Instalace balíčku mcp

4. Instalace nástrojů nutných pro spuštění MCP Inspectoru

5. Implementace jednoduchého MCP serveru s jediným definovaným zdrojem

6. Spuštění serveru s jeho otestováním pomocí MCP Inspectoru

7. MCP server s definovaným dynamickým zdrojem (resource template)

8. Otestování nové varianty MCP serveru

9. Zdroj vracející strukturovaná data

10. Server s definicí nástroje (tool)

11. Otestování MCP serveru s voláním nástroje

12. Složitější MCP servery s několika zdroji a/nebo nástroji

13. Jak MCP Inspector spouští MCP server?

14. Přímé spuštění MCP serveru z příkazové řádky s transportem STDIO

15. Přímé spuštění MCP serveru z příkazové řádky s transportem SSE

16. MCP klient spouštějící server s komunikací přes STDIO

17. MCP klient s komunikací přes SSE

18. Klient, který přečte zdroj z MCP serveru

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Model Context Protocol: vznikající standard pro potřeby aplikací využívajících AI a LLM

V posledním roce je možné sledovat velmi rychlý vývoj aplikací, které nějakým způsobem využívají umělou inteligenci (AI) popř. velké jazykové modely (LLM). Takové aplikace nebo služby se mohou skládat z většího množství komponent, které spolu musí nějakým způsobem komunikovat – a to většinou oboustranně a asynchronně. Samozřejmě již existuje několik více či méně standardních způsobů takové komunikace – REST API, GraphQL, RPC, do jisté míry i komunikační protokol Apache Kafky. Ovšem každému zmíněnému komunikačnímu mechanismu něco chybí. REST API je mechanismus pro práci se zdroji (i když ho lze různými způsoby ohýbat), GraphQL je určen především pro provádění dotazů, RPC naopak pro volání vzdáleného kódu a protokol Apache Kafky pro realizaci komunikačních strategií pub-sub, push-pull a streamingu.

Poznámka: pochopitelně existují další desítky či spíše stovky dalších mechanismů.

2. Základní vlastnosti MCP

Řešením by mohl být Model Context Protocol neboli MCP. V něm dochází ke kombinaci několika způsobů komunikace. Lze pracovat se zdroji (resources), což do značné míry odpovídá klasickému REST API, lze volat takzvané nástroje (tools), což odpovídá RPC a navíc je umožněna oboustranná a asynchronní komunikace mezi klienty a servery. V současnosti jsou definovány dva takzvané transporty, což je způsob realizace komunikace mezi klientem a serverem na nižší vrstvě. Buď se využívá STDIO, což vlastně není nic jiného než přímá komunikace mezi klientem a serverem přes standardní vstupy a výstupu (a tedy komunikace probíhá lokálně, navíc si klient může server přímo spustit). A druhým transportem je SSE neboli Server Sent Events; komunikace poté probíhá přes HTTP/HTTPS.

Poznámka: samotný MCP se neustále vyvíjí a vyvíjí se i balíčky, které ho umožňují využívat v různých programovacích jazycích. Například teprve nedávno byla přidána podpora pro OAuth atd., není vyřešeno verzování, správa API, samotné balíčky mají (přiznejme si to) dosti mizernou dokumentaci atd. I přesto se začíná MCP poměrně výrazným způsobem v oblastech AI a LLM prosazovat, takže si ukážeme jeho základní způsoby využití a později i možnosti jeho nasazení v složitějším „osobním asistentovi“ založeném na LLM.

3. Instalace balíčku mcp

MCP je možné využívat mnoha různými způsoby. Je například podporován některými frameworky určenými pro tvorbu aplikací založených na velkých jazykových modelech atd. Ovšem pro začátek je dobré zjistit, jaké jsou vlastně základní vlastnosti MCP a z tohoto důvodu si naprogramujeme vlastní MCP server (resp. hned několik jeho variant) a taktéž jednoduché MCP klienty. V současnosti je možné aplikace používající MCP naprogramovat v mnoha jazycích; my se pro jednoduchost zaměříme na Python. Pro ekosystém Pythonu vznikl balíček nazvaný příhodně mcp, který si nainstalujeme příkazem:

$ pip install --user mcp

Resp. na systémech rozlišujících Python 2 a Python 3:

$ pip3 install --user mcp

Balíček mcp má sice několik závislostí (například celý Pydantic a Uvicorn), ovšem i tak by měla být jeho instalace prakticky okamžitá:

Collecting mcp
  Downloading mcp-1.5.0-py3-none-any.whl.metadata (20 kB)
Requirement already satisfied: anyio>4.5 in /home/ptisnovs/.local/lib/python3.12/site-packages (from mcp) (4.6.2.post1)
Collecting httpx-sse>0.4 (from mcp)
  Downloading httpx_sse-0.4.0-py3-none-any.whl.metadata (9.0 kB)
Requirement already satisfied: httpx>0.27 in /home/ptisnovs/.local/lib/python3.12/site-packages (from mcp) (0.28.0)
Collecting pydantic-settings>2.5.2 (from mcp)
  Downloading pydantic_settings-2.8.1-py3-none-any.whl.metadata (3.5 kB)
Requirement already satisfied: pydantic<3.0.0,>2.7.2 in /home/ptisnovs/.local/lib/python3.12/site-packages (from mcp) (2.10.5)
Collecting sse-starlette>1.6.1 (from mcp)
  Downloading sse_starlette-2.2.1-py3-none-any.whl.metadata (7.8 kB)
Requirement already satisfied: starlette>0.27 in /home/ptisnovs/.local/lib/python3.12/site-packages (from mcp) (0.41.3)
Requirement already satisfied: uvicorn>0.23.1 in /home/ptisnovs/.local/lib/python3.12/site-packages (from mcp) (0.34.0)
Requirement already satisfied: idna>2.8 in /usr/lib/python3.12/site-packages (from anyio>4.5->mcp) (3.7)
Requirement already satisfied: sniffio>1.1 in /home/ptisnovs/.local/lib/python3.12/site-packages (from anyio>4.5->mcp) (1.3.1)
Requirement already satisfied: certifi in /home/ptisnovs/.local/lib/python3.12/site-packages (from httpx>0.27->mcp) (2024.8.30)
Requirement already satisfied: httpcore==1.* in /home/ptisnovs/.local/lib/python3.12/site-packages (from httpx>0.27->mcp) (1.0.7)
Requirement already satisfied: h11<0.15,>0.13 in /home/ptisnovs/.local/lib/python3.12/site-packages (from httpcore==1.*->httpx>0.27->mcp) (0.14.0)
Requirement already satisfied: annotated-types>0.6.0 in /home/ptisnovs/.local/lib/python3.12/site-packages (from pydantic<3.0.0,>2.7.2->mcp) (0.7.0)
Requirement already satisfied: pydantic-core==2.27.2 in /home/ptisnovs/.local/lib/python3.12/site-packages (from pydantic<3.0.0,>2.7.2->mcp) (2.27.2)
Requirement already satisfied: typing-extensions>4.12.2 in /usr/lib/python3.12/site-packages (from pydantic<3.0.0,>2.7.2->mcp) (4.12.2)
Requirement already satisfied: python-dotenv>0.21.0 in /home/ptisnovs/.local/lib/python3.12/site-packages (from pydantic-settings>2.5.2->mcp) (1.0.1)
Collecting anyio>4.5 (from mcp)
  Downloading anyio-4.9.0-py3-none-any.whl.metadata (4.7 kB)
Requirement already satisfied: click>7.0 in /home/ptisnovs/.local/lib/python3.12/site-packages (from uvicorn>0.23.1->mcp) (8.1.8)
Downloading mcp-1.5.0-py3-none-any.whl (73 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 73.7/73.7 kB 1.6 MB/s eta 0:00:00
Downloading httpx_sse-0.4.0-py3-none-any.whl (7.8 kB)
Downloading pydantic_settings-2.8.1-py3-none-any.whl (30 kB)
Downloading sse_starlette-2.2.1-py3-none-any.whl (10 kB)
Downloading anyio-4.9.0-py3-none-any.whl (100 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.9/100.9 kB 4.4 MB/s eta 0:00:00
Installing collected packages: httpx-sse, anyio, sse-starlette, pydantic-settings, mcp
  Attempting uninstall: anyio
    Found existing installation: anyio 4.6.2.post1
    Uninstalling anyio-4.6.2.post1:
      Successfully uninstalled anyio-4.6.2.post1
Successfully installed anyio-4.9.0 httpx-sse-0.4.0 mcp-1.5.0 pydantic-settings-2.8.1 sse-starlette-2.2.1

Po instalaci by měl být k dispozici i příkaz mcp, jehož existenci a dostupnost si snadno ověříme:

 Usage: mcp [OPTIONS] COMMAND [ARGS]...
 
 MCP development tools
 
╭─ Options ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ --help          Show this message and exit.                                                                                          │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ Commands ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ version   Show the MCP version.                                                                                                      │
│ dev       Run a MCP server with the MCP Inspector.                                                                                   │
│ run       Run a MCP server.                                                                                                          │
│ install   Install a MCP server in the Claude desktop app.                                                                            │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
Poznámka: v případě, že není mcp dostupný, je možné, že adresář ~/.local/bin není přidán do proměnné prostředí PATH.

Taktéž si můžeme přímo v interaktivní smyčce Pythonu naimportovat některou třídu z tohoto balíčku (například FastMCP) a zobrazit si její nápovědu:

$ python
 
Python 3.12.9 (main, Feb  4 2025, 00:00:00) [GCC 14.2.1 20240912 (Red Hat 14.2.1-3)] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from mcp.server.fastmcp import FastMCP
>>> help(FastMCP)

S výsledkem:

Help on class FastMCP in module mcp.server.fastmcp.server:

class FastMCP(builtins.object)
 |  FastMCP(name: 'str | None' = None, instructions: 'str | None' = None, **settings: 'Any')
 |
 |  Methods defined here:
 |
 |  __init__(self, name: 'str | None' = None, instructions: 'str | None' = None, **settings: 'Any')
 |      Initialize self.  See help(type(self)) for accurate signature.
 |
 |  add_prompt(self, prompt: 'Prompt') -> 'None'
 |      Add a prompt to the server.
 |
 |      Args:
 |          prompt: A Prompt instance to add
 |
 |  add_resource(self, resource: 'Resource') -> 'None'
 |      Add a resource to the server.
 |
 |      Args:
 |          resource: A Resource instance to add
 |
 ...
 ...
 ...

4. Instalace nástrojů nutných pro spuštění MCP Inspectoru

Balíček mcp, který jsme právě nainstalovali, nabízí možnost využití takzvaného MCP Inspectoru, což je aplikace s webovým grafickým uživatelským rozhraním, kterou lze použít pro otestování MCP serverů. Tato aplikace vyžaduje instalaci npm a node.js. Distribuce Linuxu většinou tyto nástroje mají ve svých repositářích balíčků (i když možná ne nejnovější verze, což nám ale nemusí vadit), takže jejich instalace je jednoduchá. Pro distribuce založené na RPM lze použít například:

$ dnf install npm
 
$ dnf install nodejs

Pro jistotu si otestujeme, jestli jsou tyto nástroje skutečně nainstalovány:

$ npm --version
10.8.2
 
 
$ node --version
v20.18.2

V případě, že se vypsaly verze (mohou být i nepatrně starší) jak npm, tak i node.js, mělo by být možné MCP Inspector použít.

5. Implementace jednoduchého MCP serveru s jediným definovaným zdrojem

Pokusme se nyní naprogramovat nějaký velmi jednoduchý MCP server, tj. službu, která dokáže komunikovat s klienty právě přes protokol MCP. V tomto serveru je definován pouze jediný zdroj (resource) nazvaný pozdrav, jenž je implementován pomocí běžné Pythonovské funkce s dekorátorem. Jedná se o zdroj, který vrací konstantní řetězec. Samotný zdroj je popsán dekorátorem @mcp.resource a typ hodnoty, kterou zdroj vrací, je odvozena na základě typové informace (jméno typu za šipkou definuje typ návratové hodnoty funkce):

"""Jednoduchý MCP server s jediným definovaným zdrojem."""
 
from mcp.server.fastmcp import FastMCP
 
# konstrukce serveru
mcp = FastMCP("Test")
 
 
@mcp.resource("pozdrav://")
def pozdrav() -> str:
    """Odpověď s pozdravem."""
    return "Hello, dear client"
Poznámka: připomeňme si, že dekorátory jsou interně implementovány jako funkce vyššího řádu, které „obalují“ funkci, před kterou jsou zapsány – typicky před volání a za volání funkce přidávají nějaký další kód popř. zajišťují vytvoření uzávěru (closure). Pro potřeby tohoto článku nám však bude stačit jen základní znalosti o tom, jak se dekorátory zapisují.

6. Spuštění serveru s jeho otestováním pomocí MCP Inspectoru

MCP server, jehož zdrojový kód jsme si ukázali v předchozí kapitole, je možné spustit několika možnými způsoby. Nejjednodušší je použití příkazu mcp, kterému se předá jméno zdrojového kódu s implementací serveru a popř. i způsob transportu (tedy konkrétního režimu komunikace mezi klientem a serverem). Ovšem my prozatím nemáme naprogramován žádný klient, takže je nutné využít nějaký jiný nástroj pro otestování našeho serveru. Takový nástroj ve skutečnosti existuje a jmenuje se MCP Inspector (ostatně jsme instalovali jeho závislosti). Pro otestování našeho serveru spustíme následující příkaz (ten očekává, že jméno zdrojového kódu s implementací MCP serveru je mcp_server1.py):

$ mcp dev mcp_server_1.py

Při prvním spuštění (později již ne) bude mcp potřebovat doinstalovat jeden npm balíček, takže se zeptá, zda může tuto operaci spustit. Odpovíme, že ano:

Need to install the following packages:
@modelcontextprotocol/inspector@0.7.0
Ok to proceed? (y) y

Dále se vypíše informace o tom, na které adrese (včetně portu) je MCP inspektor dostupný:

Starting MCP inspector...
Proxy server listening on port 3000
 
? MCP Inspector is up and running at http://localhost:5173 ?

Ve webovém prohlížeči je nutné přejít na výše vypsanou adresu http://localhost:5173. Měla by se zobrazit stránka, která v levé části nabízí způsob připojení k serveru. Vyplňte nyní údaje tak, jak je to zobrazeno na screenshotu:

MCP

Obrázek 1: Takto může vypadat vyplněný dialog pro připojení k MCP serveru.

Po stisku tlačítka Connect by se měl server znovu spustit (to je poněkud nešťastné) a MCP Inspector by měl začít se serverem komunikovat přes standardní vstup a výstup (STDIO).

Dále v části Resources stiskněte tlačítko List Resources. Měl by se zobrazit seznam zdrojů, který obsahuje pouze jedinou položku „pozdrav://“, což je skutečně název našeho zdroje:

MCP

Obrázek 2: Seznam dostupných zdrojů, které MCP server nabízí.

Pokud tento zdroj vyberete, provede se komunikace s MCP serverem a měla by se vrátit odpověď ve formátu JSON:

{
  "contents": [
    {
      "uri": "pozdrav://",
      "mimeType": "text/plain",
      "text": "Hello, dear client"
    }
  ]
}

Tím jsme ověřili nejenom to, že MCP server nabízí příslušný zdroj, ale že je možné s ním komunikovat s využitím MCP.

7. MCP server s definovaným dynamickým zdrojem (resource template)

Druhá varianta MCP serveru, kterou vytvoříme, bude nepatrně složitější, protože je v ní definován dynamický zdroj (původní název resource template ovšem může být poněkud matoucí). Jedná se o zdroj, který má ve svém názvu měnitelnou část (selector) name. Výsledkem čtení tohoto zdroje bude řetězec ve formátu „Hello, {name}“, ve kterém se za „{name}“ doplní identifikátor ze jména zdroje. Teoreticky tedy můžeme tímto způsobem předávat MCP serveru parametry a získávat odpovědi na základě těchto parametrů, i když z hlediska sémantiky by bylo v mnoha případech vhodnější využít spíše nástroje (tools), s nimiž se seznámíme v dalších kapitolách. Povšimněte si, že nyní má funkce pozdrav s implementací zdroje parametr name, který je typu řetězec:

"""Jednoduchý MCP server s jediným definovaným dynamickým zdrojem."""
 
from mcp.server.fastmcp import FastMCP
 
# konstrukce serveru
mcp = FastMCP("Test")
 
 
@mcp.resource("pozdrav://{name}")
def pozdrav(name: str) -> str:
    """Odpověď s osobním pozdravem."""
    return f"Hello, {name}"
Poznámka: na třech řádcích zdrojového kódu jsou tedy uvedeny všechny důležité informace o zdroji, které jsou zapotřebí pro realizaci komunikace. Řádek s dekorátorem identifikuje zdroj i jeho měnitelné části (selectoru), na řádku s hlavičkou funkce je pak možné zjistit datové typy vstupů (měnitelných částí) i datový typ odpovědi. A komentář (dokumentační řetězec) samotné funkce je zobrazen v MCP Inspectoru.

8. Otestování nové varianty MCP serveru

Opět si otestujeme základní funkcionalitu druhé varianty našeho MCP serveru. Podobně jako v prvním případě i nyní spustíme MCP Inspector se zadáním jména zdrojového kódu s implementací serveru:

$ mcp dev mcp_server_2.py
 
Starting MCP inspector...
Proxy server listening on port 3000
 
? MCP Inspector is up and running at http://localhost:5173 ?

Ve webovém prohlížeči otevřeme stránku na adrese http://localhost:5173. V prvním kroku se k MCP serveru připojíme, přičemž parametry připojení mohou vypadat následovně:

MCP

Obrázek 3: Parametry připojení k MCP serveru. Zelené kolečko u „Connected“ naznačuje, že MCP Inspector skutečně navázal připojení.

Nyní ovšem zdroj definovaný v MCP serveru nenalezneme v sekci „Resources“, ale v sekci nazvané „Resource Templates“. Vypíšeme si obsah těchto zdrojů s měnitelnou částí:

MCP

Obrázek 4: Seznam zdrojů se selektory.

Vybereme (jediný) takový zdroj a zadáme potřebný selektor:

MCP

Obrázek 5: Zápis selektoru a získání zdroje z MCP serveru. Povšimněte si, že se v popisu zdroje objevuje komentář z funkce, kterou je zdroj implementován.

Výsledkem by měla být následující datová struktura. Povšimněte si, že do ní bylo skutečně doplněno předané jméno (tedy onen selektor):

{
  "contents": [
    {
      "uri": "pozdrav://Pavel",
      "mimeType": "text/plain",
      "text": "Hello, Pavel"
    }
  ]
}
MCP

Obrázek 6: Odpověď MCP serveru zobrazená přímo v MCP Inspectoru.

9. Zdroj vracející strukturovaná data

Pokusme se nyní definovat poněkud složitější zdroj. Ten bude mít dva selektory, které jsou oddělené pomlčkou (ovšem stejně dobře je možné použít lomítko – RFC 6570 umožňuje více způsobů). Ovšem zajímavější je, že tento zdroj vrací pro zadané jméno a příjmení spisovatele seznam jeho knih, tj. v pojetí jazyka Python list. Můžete být ovšem i přesnější a definovat i seznam slovníků atd. – využívají se zde totiž standardizované typové informace (type hints). Pro jednoduchost není seznam knih získáván z databáze, ale pro jakéhokoli autora vrátíme stejný seznam, takže implementace MCP serveru by mohla vypadat takto:

"""Jednoduchý MCP server s jediným definovaným dynamickým zdrojem."""
 
from mcp.server.fastmcp import FastMCP
 
# konstrukce serveru
mcp = FastMCP("Test")
 
 
@mcp.resource("author://{name}-{surname}")
def author(name: str, surname: str) -> list:
    """Knihy od vybraného autora."""
    return [
        {"name": name, "surname": surname, "title": "Foo", "year": 1900},
        {"name": name, "surname": surname, "title": "Bar", "year": 2005},
        {"name": name, "surname": surname, "title": "Baz", "year": 2025},
    ]

Po připojení k MCP serveru se pokusíme získat seznam knih pro autora se jménem John a příjmením Doe. Povšimněte si, že samotná odpověď MCP serveru je sice ve formátu JSON, ovšem to se týká strukturování odpovědi tak, že dostaneme i různá metadata. Ovšem vlastní data jsou předána v parametru text ve formě slovníku serializovaného do řetězce:

{
  "contents": [
    {
      "uri": "author://John-Doe",
      "mimeType": "text/plain",
      "text": "[{\"name\": \"John\", \"surname\": \"Doe\", \"title\": \"Foo\", \"year\": 1900}, {\"name\": \"John\", \"surname\": \"Doe\", \"title\": \"Bar\", \"year\": 2005}, {\"name\": \"John\", \"surname\": \"Doe\", \"title\": \"Baz\", \"year\": 2025}]"
    }
  ]
}

Bude výsledek odlišný v případě, že zvolíme jiný formát odpovědi, tedy odlišný mimeType? To si můžeme snadno otestovat nepatrnou změnou MCP serveru:

"""Jednoduchý MCP server s jediným definovaným dynamickým zdrojem."""
 
from mcp.server.fastmcp import FastMCP
 
# konstrukce serveru
mcp = FastMCP("Test")
 
 
@mcp.resource("author://{name}-{surname}", mime_type="application/json")
def author(name: str, surname: str) -> list:
    """Knihy od vybraného autora."""
    return [
        {"name": name, "surname": surname, "title": "Foo", "year": 1900},
        {"name": name, "surname": surname, "title": "Bar", "year": 2005},
        {"name": name, "surname": surname, "title": "Baz", "year": 2025},
    ]

Odpověď je až na odlišný mimeType totožná s předchozí implementací:

{
  "contents": [
    {
      "uri": "author://John-Doe",
      "mimeType": "application/json",
      "text": "[{\"name\": \"John\", \"surname\": \"Doe\", \"title\": \"Foo\", \"year\": 1900}, {\"name\": \"John\", \"surname\": \"Doe\", \"title\": \"Bar\", \"year\": 2005}, {\"name\": \"John\", \"surname\": \"Doe\", \"title\": \"Baz\", \"year\": 2025}]"
    }
  ]
}
Poznámka: jedná se o nedostatek knihovny mcp – viz též Return resources as structured JSON instead of text?.

10. Server s definicí nástroje (tool)

Zdroje (resources) slouží resp. měly by sloužit jako zdroj dat a jejich chování by mělo být idempotentní. To znamená, že několikeré volání toho samého zdroje se stejnými selektory by mělo vracet totožné výsledky. Ovšem v oblasti AI a LLM je nutné volat resp. spouštět složitější operace, které provádí nějakou komplikovanější činnost a obecně nejsou idempotentní (třeba nákup letenky nebo rezervace schůzky do kalendáře). Pro tento účel slouží ve světě MCP takzvané nástroje (tool), které se začaly dosti bouřlivým způsobem vyvíjet v posledních cca deseti měsících – protože právě díky nástrojům se z původních „kecálků“ typu ChatGTP stávají reálně použitelná rozhraní mezi lidmi a IT systémy (více si řekneme v samostatném článku – toto je opravdu velmi dynamická oblast).

Vytvoření MCP serveru s nástrojem může být snadné. Abychom si to ukázali, naprogramujeme server, který má kromě zdroje definován i nástroj nazvaný factorial. Tento nástroj akceptuje celé číslo poslané v dotazu a odpoví hodnotou faktoriálu tohoto čísla. Pro jednoduchost jsem zvolil tu nejsnadnější implementaci výpočtu. Nejde nám totiž o vlastní výpočet, ale o realizaci komunikace přes MCP:

"""MCP server s dynamickým zdrojem a definicí nástroje (tool)."""
 
from mcp.server.fastmcp import FastMCP
 
# konstrukce serveru
mcp = FastMCP("Test")
 
 
@mcp.tool()
def factorial(n: int) -> int:
    """Výpočet faktoriálu ve smyčce."""
    f = 1
    for x in range(1, n + 1):
        f *= x
    return f
 
 
@mcp.resource("pozdrav://{name}")
def pozdrav(name: str) -> str:
    """Odpověď s osobním pozdravem."""
    return f"Hello, {name}"

11. Otestování MCP serveru s voláním nástroje

Pro otestování nové varianty MCP serveru opět spustíme MCP Inspectora:

$ mcp dev mcp_server_3.py 
 
Starting MCP inspector...
Proxy server listening on port 3000
 
? MCP Inspector is up and running at http://localhost:5173 ?

Po otevření stránky s MCP Inspectorem a připojení k MCP serveru známým způsobem je nutné v horní části stránky se seznamem záložek vybrat záložku nazvanou Tools (ve výchozím stavu je vybrána záložka Resources, kterou jsme používali doposud). Ukáže se menu s položkou (tlačítkem) List Tools a po jeho stisku by se měl zobrazit název nástroje i jeho popis získaný z dokumentačního řetězce funkce, ve které je nástroj implementován:

MCP
Obrázek 7: Seznam nástrojů, které nabízí MCP server.

Nástroj vybereme a do zobrazeného dialogu zadáme vstupní parametr, který je poslán do nástroje, jenž na základě tohoto parametru (celé číslo) vypočítá faktoriál. Výsledek je vrácen formou jediné hodnoty, což znamená, že není nutné parsovat odpověď v JSONu:

MCP
Obrázek 8: Volání nástroje s předáním parametru. Vypočtená hodnota je
vrácena do MCP Inspectoru, kde je taktéž zobrazena.

12. Složitější MCP servery s několika zdroji a/nebo nástroji

Typicky bývá v MCP serveru realizováno větší množství zdrojů a popř. nástrojů. Ukažme si příklad, jak by mohla implementace takového serveru vypadat. Ve variantě MCP serveru zobrazené pod tímto odstavcem je definován nástroj pojmenovaný factorial, který známe. A dále se zde nachází definice dvou zdrojů, které oba začínají URI „pozdrav://“. Ovšem první z těchto zdrojů nemá selektor (URI je neměnné), zatímco u druhého zdroje je selektor specifikován a musí být použit při čtení tohoto zdroje:

"""MCP server se zdrojem,  dynamickým zdrojem a definicí nástroje (tool)."""
 
from mcp.server.fastmcp import FastMCP
 
# konstrukce serveru
mcp = FastMCP("Test")
 
 
@mcp.tool()
def factorial(n: int) -> int:
    """Výpočet faktoriálu ve smyčce."""
    f = 1
    for x in range(1, n + 1):
        f *= x
    return f
 
 
@mcp.resource("pozdrav://")
def pozdrav1() -> str:
    """Odpověď s pozdravem."""
    return "Hello, dear client"
 
 
@mcp.resource("pozdrav://{name}")
def pozdrav2(name: str) -> str:
    """Odpověď s osobním pozdravem."""
    return f"Hello, {name}"
Poznámka: pro otestování tohoto MCP serveru je pochopitelně opět možné využít MCP Inspector. Postup zůstává stále stejný, takže ho již nebudu popisovat a ani nebudou ukázány příslušné screenshoty.

13. Jak MCP Inspector spouští MCP server?

V předchozích kapitolách jsme si řekli, že MCP Inspector se dokáže sám připojit k MCP serveru. Ovšem současně jsme tento MCP server spustili i přímo příkazem mcp. Bylo by tedy zajímavé zjistit, co se děje „pod pokličkou“. Naši implementaci MCP serveru tedy upravíme do takové podoby, aby se při každém jeho spuštění vytvořil soubor, jehož jméno bude odpovídat číslu procesu. Tak zjistíme, kolikrát se vlastně MCP inicializoval. Poněkud naivní implementace tohoto MCP serveru může vypadat následovně:

"""Jednoduchý MCP server s jediným definovaným dynamickým zdrojem."""
 
import os
from mcp.server.fastmcp import FastMCP
from datetime import datetime
 
# konstrukce serveru
mcp = FastMCP("Test")
 
pid = os.getpid()
 
# při každém spuštění serveru se vytvoří nový soubor
with open(f"{pid}.txt", "w") as fout:
    fout.write(str(datetime.now()))
 
 
@mcp.resource("pozdrav://")
def pozdrav() -> str:
    """Odpověď s pozdravem."""
    return "Hello, dear client"

Opět provedeme spuštění MCP Inspectoru:

$ mcp dev mcp_server_4.py

Výsledek bude jediný soubor s PID, který značí, že server byl spuštěn, i když jsme se k němu nepřipojili:

3288345.txt

Nyní soubor smažeme, opět spustíme MCP Inspector a připojíme se k serveru (connect). Výsledkem bude dvojice souborů, protože byl server spuštěný dvakrát:

3288553.txt
3288667.txt

Ovšem získání (resp. přesněji řečeno přečtení) jednotlivých zdrojů atd. již nevede ke spuštění nového MCP serveru – vše je obslouženo jediným serverem, který bude běžet až do té doby, dokud nedojde ke vzniku nezachycené výjimky nebo pokud server explicitně neukončíme (Ctrl+C).

14. Přímé spuštění MCP serveru z příkazové řádky s transportem STDIO

Spouštění MCP serveru nepřímo přes nástroj MCP Inspector je vhodné provádět jen v průběhu jeho vývoje a ladění. Ovšem nasazení takového serveru může vypadat odlišně. V tom nejjednodušším případě můžeme server spustit přímo z interpretru Pythonu, tj. bez použití příkazu mcp. Do zdrojového kódu MCP serveru je v takovém případě pouze nutné přidat programové řádky, které ho spustí. V základní podobě se MCP serveru spustí s volbou transportu STDIO, tj. se serverem se bude komunikovat přes vstupně-výstupní proudy (standard input stream, standard output stream):

# přímé spuštění serveru v režimu STDIO
if __name__ == "__main__":
    mcp.run()

Celý zdrojový kód serveru se změní jen nepatrně – přidali jsme do něj tři výše uvedené řádky zajišťující jeho spuštění:

"""MCP server se zdrojem,  dynamickým zdrojem a definicí nástroje (tool)."""
 
from mcp.server.fastmcp import FastMCP
 
# konstrukce serveru
mcp = FastMCP("Test")
 
 
@mcp.tool()
def factorial(n: int) -> int:
    """Výpočet faktoriálu ve smyčce."""
    f = 1
    for x in range(1, n + 1):
        f *= x
    return f
 
 
@mcp.resource("pozdrav://")
def pozdrav1() -> str:
    """Odpověď s pozdravem."""
    return "Hello, dear client"
 
 
@mcp.resource("pozdrav://{name}")
def pozdrav2(name: str) -> str:
    """Odpověď s osobním pozdravem."""
    return f"Hello, {name}"
 
 
# přímé spuštění serveru v režimu STDIO
if __name__ == "__main__":
    mcp.run()
Poznámka: s takovým serverem se většinou nekomunikuje přímo interaktivním zadáváním příkazů a dotazů, i když i to je možné (používá se JSON RPC).

15. Přímé spuštění MCP serveru z příkazové řádky s transportem SSE

Druhým podporovaným transportem je SSE neboli Server Sent Events. V tomto případě MCP server naslouchá na IP portu a odpovídá na dotazy MCP klientů popř. přímo sám posílá události těmto klientům (nejedná se tedy o triviální komunikaci typu dotaz-odpověď, ale o komunikaci oboustrannou). Aby se MCP server spustil v režimu transportu SSE, je možné ho spustit tímto způsobem:

# přímé spuštění serveru v režimu SSE (Server-Sent Events)
if __name__ == "__main__":
    mcp.run(transport="sse")
Poznámka: popř. je možné specifikovat i port, na kterém má MCP server naslouchat.

Upravený zdrojový kód MCP serveru vypadá takto:

"""MCP server se zdrojem,  dynamickým zdrojem a definicí nástroje (tool)."""
 
from mcp.server.fastmcp import FastMCP
 
# konstrukce serveru
mcp = FastMCP("Test")
 
 
@mcp.tool()
def factorial(n: int) -> int:
    """Výpočet faktoriálu ve smyčce."""
    f = 1
    for x in range(1, n + 1):
        f *= x
    return f
 
 
@mcp.resource("pozdrav://")
def pozdrav1() -> str:
    """Odpověď s pozdravem."""
    return "Hello, dear client"
 
 
@mcp.resource("pozdrav://{name}")
def pozdrav2(name: str) -> str:
    """Odpověď s osobním pozdravem."""
    return f"Hello, {name}"
 
 
# přímé spuštění serveru v režimu SSE (Server-Sent Events)
if __name__ == "__main__":
    mcp.run(transport="sse")

I bez podrobnějších znalostí protokolu, který se pro komunikaci mezi MCP serverem a MCP klientem používá, si můžeme ověřit alespoň to, že server je skutečně spuštěn a naslouchá na portu. Server tedy spustíme:

$ python mcp_server_6.py

Výchozím portem je 8000, takže v dalším terminálu provedeme připojení k serveru s využitím (například) známého nástroje curl:

$ curl localhost:8000/sse

MCP server by měl odpovědět tak, že pošle zpátky ID sezení (session), které lze použít pro další komunikaci:

event: endpoint
data: /messages/?session_id=e233fdbae32d43b29c1d3fd9b58d9145

Navíc není spojení ihned ukončeno (připomínám: nejedná se o klasický systém dotaz-odpověď). Pokud nástroj curl neukončíme, bude patrné, že server po určitých časových intervalech cca 15 sekund posílá zprávy ping, aby si ověřil připojení klienta:

: ping - 2025-03-30 09:26:17.741441+00:00
 
: ping - 2025-03-30 09:26:32.743079+00:00
 
: ping - 2025-03-30 09:26:47.744446+00:00
 
: ping - 2025-03-30 09:27:02.746181+00:00
Poznámka: použití MCP na takto nízké úrovni většinou není nutné znát, protože máme k dispozici knihovny zajišťující komunikaci na vyšší úrovni abstrakce. Ovšem v případech, že se například klienti nemohou připojit nebo nevidí nějaký zdroj či nástroj, může být výhodné znát i tyto nízkoúrovňové postupy. Ostatně se k nim ještě vrátíme v navazujícím článku.

16. MCP klient spouštějící server s komunikací přes STDIO

V této chvíli již máme základní znalosti o tom, jakým způsobem se implementují MCP servery. Ovšem s těmito servery jsme komunikovali buď přes MCP Inspectora nebo z příkazové řádky (viz výše uvedený příkaz curl). V praxi se ovšem k MCP serverům takřka výhradně připojují další naprogramované služby a nástroje – MCP klienti. Ukažme si tedy, jak by mohl vypadat jednoduchý MCP klient, který po svém spuštění vypíše seznam zdrojů, nástrojů atd. nabízených MCP serverem. Přímo v kódu klienta nebudeme se serverem komunikovat na úrovni MCP protokolu, ale na vyšší úrovni abstrakce (ostatně ani v implementaci serveru jsme s protokolem přímo nepřišli do styku). MCP klient navíc server sám spustí v režimu používajícím transport STDIO:

# parametry pro spuštění MCP serveru
server_params = StdioServerParameters(
    command="python",  # spustí se tento příkaz
    args=["mcp_server_5.py"],  # a předají se mu následující parametry
    env=None,  # lze definovat i proměnné prostředí
)

Jak je ze zdrojového kódu klienta patrné, je realizován takovým způsobem, že veškeré operace jsou v něm prováděny asynchronně (v Pythonu se pro tento účel používají klíčová slova async a await). Klient získá seznamy všech zdrojů atd. a vypíše je na standardní výstup:

"""MCP klient, který spustí server, se kterým se komunikuje přes STDIO."""
 
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
 
# parametry pro spuštění MCP serveru
server_params = StdioServerParameters(
    command="python",  # spustí se tento příkaz
    args=["mcp_server_5.py"],  # a předají se mu následující parametry
    env=None,  # lze definovat i proměnné prostředí
)
 
 
async def run():
    """Realizace klienta."""
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
 
            prompts = await session.list_prompts()
            print(prompts)
 
            resources = await session.list_resources()
            print(resources)
 
            templates = await session.list_resource_templates()
            print(templates)
 
            tools = await session.list_tools()
            print(tools)
 
 
# přímé spuštění klienta
if __name__ == "__main__":
    import asyncio
 
    asyncio.run(run())

Tento klient spustíme:

$ python mcp_client_1.py

Klient po své inicializaci sám automaticky spustí příslušný MCP server (má k tomu všechny potřebné informace), získá seznam dostupných zdrojů atd. a ten vytiskne. Poté je ukončen.

Vypsané informace by měly vypadat přesně takto:

meta=None nextCursor=None prompts=[]
meta=None nextCursor=None resources=[Resource(uri=AnyUrl('pozdrav://'), name='pozdrav://', description=None, mimeType='text/plain', size=None, annotations=None)]
meta=None nextCursor=None resourceTemplates=[ResourceTemplate(uriTemplate='pozdrav://{name}', name='pozdrav2', description='Odpověď s osobním pozdravem.', mimeType=None, annotations=None)]
meta=None nextCursor=None tools=[Tool(name='factorial', description='Výpočet faktoriálu ve smyčce.', inputSchema={'properties': {'n': {'title': 'N', 'type': 'integer'}}, 'required': ['n'], 'title': 'factorialArguments', 'type': 'object'})]

17. MCP klient s komunikací přes SSE

Druhou možností komunikace mezi MCP serverem a MCP klientem je využití transportu SSE (Server-side events). V tomto případě je možné MCP server inicializovat ještě před spuštěním klienta. Jak se tato inicializace provede jsme si ukázali v patnácté kapitole. Předpokládejme tedy, že MCP server je inicializován a naslouchá na portu 8000. Klient se může k takovému serveru připojit způsobem, který lze naprogramovat následovně:

"""MCP klient, který spustí server, se kterým se komunikuje přes SSE."""
 
from mcp import ClientSession
from mcp.client.sse import sse_client
 
 
async def run():
    """Realizace klienta."""
    async with sse_client(url="http://localhost:8000/sse") as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
 
            prompts = await session.list_prompts()
            print(prompts)
 
            resources = await session.list_resources()
            print(resources)
 
            templates = await session.list_resource_templates()
            print(templates)
 
            tools = await session.list_tools()
            print(tools)
 
 
# přímé spuštění klienta
if __name__ == "__main__":
    import asyncio
 
    asyncio.run(run())

Od klienta z předchozí kapitoly se tedy tento klient odlišuje pouze odlišným kontextem:

    async with sse_client(url="http://localhost:8000/sse") as (read, write):
Poznámka: ovšem nepočítejte s tím, že MCP server dokáže současně obsloužit větší množství požadavků. Tímto problémem se budeme zabývat v navazujícím článku.

18. Klient, který přečte zdroj z MCP serveru

MCP klienti dokážou číst jakékoli zdroje nabízené MCP serverem, volat v nich definované nástroje (tools) atd. V dnešním posledním demonstračním příkladu si ukážeme způsob přečtení zdroje (pro jednoduchost prozatím zdroje bez selektoru). Komunikace s MCP serverem bude opět probíhat asynchronně:

"""MCP klient, který přečte zvolený zdroj."""
 
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
 
# parametry pro spuštění MCP serveru
server_params = StdioServerParameters(
    command="python",  # spustí se tento příkaz
    args=["mcp_server_5.py"],  # a předají se mu následující parametry
    env=None,  # lze definovat i proměnné prostředí
)
 
 
async def run():
    """Realizace klienta."""
    async with stdio_client(server_params) as (read, write):
        async with ClientSession(read, write) as session:
            await session.initialize()
 
            # přečtení zdroje
            data = await session.read_resource("pozdrav://")
            print("Data returned:", data)
            print("Type:", type(data))
 
 
# přímé spuštění klienta
if __name__ == "__main__":
    import asyncio
 
    asyncio.run(run())

Po spuštění tohoto klienta se inicializuje MCP server, provede se přečtení zdroje pozdrav:// a následně dojde k ukončení činnosti jak serveru, tak posléze i klienta:

Data returned: meta=None contents=[TextResourceContents(uri=AnyUrl('pozdrav://'), mimeType='text/plain', text='Hello, dear client')]
Type: <class 'mcp.types.ReadResourceResult'>
Poznámka: sofistikovanější příklady si ukážeme příště.

19. Repositář s demonstračními příklady

Zdrojové kódy všech prozatím popsaných demonstračních příkladů určených pro Python a balíček mcp byly uloženy do repositáře dostupného na adrese https://github.com/tisnik/most-popular-python-libs. V tabulce zobrazené níže jsou odkazy na jednotlivé příklady:

# Demonstrační příklad Stručný popis příkladu Cesta
1 mcp_server1.py jednoduchý MCP server s jediným definovaným zdrojem https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp_server1.py
2 mcp_server2.py jednoduchý MCP server s jediným definovaným dynamickým zdrojem https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp_server2.py
3 mcp_server3.py MCP server s dynamickým zdrojem a definicí nástroje (tool) https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp_server3.py
4 mcp_server4.py MCP server s jediným definovaným dynamickým zdrojem, zápis informací o spuštění https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp_server4.py
5 mcp_server5.py MCP server, který se přímo spustí v režimu STDIO https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp_server5.py
6 mcp_server6.py MCP server, který se přímo spustí v režimu SSE https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp_server6.py
7 mcp_server7.py MCP server se zdrojem vracejícím strukturovaná data https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp_server7.py
8 mcp_server8.py MCP server se zdrojem vracejícím strukturovaná data https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp_server8.py
     
9 mcp_client1.py MCP klient, který spustí server, se kterým se komunikuje přes STDIO https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp_client1.py
10 mcp_client2.py MCP klient, který spustí server, se kterým se komunikuje přes SSE https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp_client2.py
11 mcp_client3.py MCP klient, který přečte zvolený zdroj https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp_client3.py
12 mcp_client4.py MCP klient, který přečte zvolený zdroj a získá z něj data https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp_client4.py
13 mcp_client5.py MCP klient, který zavolá vybraný nástroj https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp_client5.py
14 mcp_client6.py MCP klient, který přečte a zpracuje strukturovaná data https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp_client6.py

20. Odkazy na Internetu

  1. MCP Python SDK
    https://github.com/modelcon­textprotocol/python-sdk?tab=readme-ov-file#running-your-server
  2. MCP protocol: Resources
    https://modelcontextproto­col.info/docs/concepts/re­sources/
  3. Example Servers
    https://modelcontextproto­col.io/examples
  4. Core architecture
    https://modelcontextproto­col.io/docs/concepts/archi­tecture
  5. Unleashing the Power of Model Context Protocol (MCP): A Game-Changer in AI Integration
    https://techcommunity.micro­soft.com/blog/educatordeve­loperblog/unleashing-the-power-of-model-context-protocol-mcp-a-game-changer-in-ai-integrat/4397564
  6. MPC inspector
    https://github.com/modelcon­textprotocol/inspector
  7. Model Context Protocol servers
    https://github.com/modelcon­textprotocol/servers
  8. python-sdk na GitHubu
    https://github.com/modelcon­textprotocol/python-sdk
  9. typescript-sdk na GitHubu
    https://github.com/modelcon­textprotocol/typescript-sdk
  10. mcp-golang
    https://github.com/metoro-io/mcp-golang
  11. MCP server: A step-by-step guide to building from scratch
    https://composio.dev/blog/mcp-server-step-by-step-guide-to-building-from-scrtch/
  12. How to Build an MCP Server Fast: A Step-by-Step Tutorial
    https://medium.com/@eugenesh4work/how-to-build-an-mcp-server-fast-a-step-by-step-tutorial-e09faa5f7e3b
  13. Step-by-Step Guide: Building an MCP Server using Python-SDK, AlphaVantage & Claude AI
    https://medium.com/@syed_hasan/step-by-step-guide-building-an-mcp-server-using-python-sdk-alphavantage-claude-ai-7a2bfb0c3096
  14. RFC 6570: URI Template
    https://datatracker.ietf.or­g/doc/html/rfc6570
  15. Return resources as structured JSON instead of text?
    https://github.com/modelcon­textprotocol/python-sdk/issues/279
  16. Python standard library: pprint
    https://docs.python.org/3/li­brary/pprint.html
  17. Python standard library: json — JSON encoder and decoder¶
    https://docs.python.org/3/li­brary/json.html
  18. Calling MCP Servers the Hard Way
    https://deadprogrammersoci­ety.com/2025/03/calling-mcp-servers-the-hard-way.html
  19. mcptools
    https://github.com/f/mcptools
  20. Server-sent events
    https://en.wikipedia.org/wiki/Server-sent_events

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.