Obsah
1. Realizace MCP klientů i serverů v jazyku Go
2. Instalace knihovny mcp-golang
3. Realizace MCP serveru v jazyce Go
4. Úplný zdrojový kód MCP serveru naprogramovaného v jazyce Go
5. Komunikace s MCP serverem z MCP klienta naprogramovaného v Pythonu
6. Komunikace s MCP serverem z MCP klienta naprogramovaného v jazyce Go
7. Úplný zdrojový kód jednoduchého MCP klienta, který server spustí a přečte z něj vybraný zdroj
8. Vylepšení struktury MCP serveru
9. Nástroje (tools) poskytované MCP serverem
10. Realizace serveru s definovaným nástrojem (tool)
11. Zavolání nástroje poskytovaného MCP serverem z Pythonu
12. Zavolání nástroje poskytovaného MCP serverem z jazyka Go
13. Podpora pro výzvy (prompt)
14. Jednoduchý MCP server s výzvou (prompt)
15. Úplný zdrojový kód serveru nabízejícího výzvu
16. Komunikace s MCP serverem přes nabízenou výzvu
17. Úplný zdrojový kód klienta, který komunikuje přes výzvu
18. Omezení a chyby knihovny mcp-golang
19. Repositář s demonstračními příklady
1. Realizace MCP klientů i serverů v jazyku Go
V úvodní dvojici článků [1] [2] jsme se seznámili se základními vlastnostmi MCP neboli Model Context Protocolu. Připomeňme si, že se jedná o poměrně novou (a neustále vyvíjenou) specifikaci protokolu používaného pro komunikaci mezi moduly systémů nasazovaných v oblasti umělé inteligence (AI) a velkých jazykových modelů (LLM).
Na tuto dvojici článků dnes navážeme. Prozatím jsme si ukázali tvorbu MCP klientů i serverů v jazyce Python s využitím oficiální MCP knihovny. Naproti tomu se dnes zaměříme na programovací jazyk Go, pro který podobná oficiální knihovna prozatím není k dispozici. Namísto toho vznikly minimálně tři knihovny s různou implementací MCP protokolu, přičemž každá z těchto knihoven má své dobré vlastnosti, ale i stinné stránky a dokonce i chyby. Dnes si popíšeme vlastnosti knihovny mcp-golang, kterou lze využít zejména pro volání nástrojů (tools) popř. výzev (prompts). Kupodivu jsou však velmi špatně podporovány zdroje (resources), které jsou vlastně po stránce implementace nejjednodušší.
2. Instalace knihovny mcp-golang
Prvním krokem, který musíme udělat ještě před vývojem MCP klientů a MCP serverů v jazyce Go, je instalace knihovny mcp-golang. Nejprve si v novém adresáři necháme vygenerovat kostru projektu v Go příkazem go mod init:
$ go mod init mcp-tests go: creating new go.mod: module mcp-tests
Výsledkem by měl být soubor go.mod.
Ve druhém kroku ze stejného adresáře spustíme instalaci knihovny mcp-golang (postačuje go get a nikoli go install):
$ go get github.com/metoro-io/mcp-golang go: downloading github.com/metoro-io/mcp-golang v0.8.0 go: downloading github.com/tidwall/sjson v1.2.5 go: downloading github.com/pkg/errors v0.9.1 go: downloading github.com/invopop/jsonschema v0.12.0 go: downloading github.com/wk8/go-ordered-map/v2 v2.1.8 go: downloading github.com/tidwall/gjson v1.18.0 go: downloading gopkg.in/yaml.v3 v3.0.1 go: downloading github.com/buger/jsonparser v1.1.1 go: downloading github.com/bahlo/generic-list-go v0.2.0 go: downloading github.com/mailru/easyjson v0.7.7 go: downloading github.com/tidwall/pretty v1.2.1 go: downloading github.com/tidwall/match v1.1.1 go: added github.com/bahlo/generic-list-go v0.2.0 go: added github.com/buger/jsonparser v1.1.1 go: added github.com/invopop/jsonschema v0.12.0 go: added github.com/mailru/easyjson v0.7.7 go: added github.com/metoro-io/mcp-golang v0.8.0 go: added github.com/pkg/errors v0.9.1 go: added github.com/tidwall/gjson v1.18.0 go: added github.com/tidwall/match v1.1.1 go: added github.com/tidwall/pretty v1.2.1 go: added github.com/tidwall/sjson v1.2.5 go: added github.com/wk8/go-ordered-map/v2 v2.1.8 go: added gopkg.in/yaml.v3 v3.0.1
Projektový soubor go.mod by nyní měl obsahovat přímé i nepřímé závislosti a mohl by vypadat takto:
module mcp-tests go 1.22.1 require ( github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect github.com/invopop/jsonschema v0.12.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/metoro-io/mcp-golang v0.8.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/tidwall/gjson v1.18.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/sjson v1.2.5 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect )
3. Realizace MCP serveru v jazyce Go
Pro realizaci jednoduchého MCP serveru v jazyce Go použijeme v první řadě funkci/konstruktor nazvanou NewServer. Její hlavičku lze zjistit z dokumentace k právě nainstalovanému balíčku mcp-golang:
$ go doc mcp-golang.NewServer package mcp_golang // import "github.com/metoro-io/mcp-golang" func NewServer(transport transport.Transport, options ...ServerOptions) *Server
Tato funkce po svém zavolání zkonstruuje novou strukturu typu Server. Musíme specifikovat především informace o tom, jaký transport (STDIO nebo SSE) se bude využívat, tj. jak se budou k serveru připojovat jednotliví klienti:
server := mcp_golang.NewServer(stdio.NewStdioServerTransport())
Samotná struktura (záznam) Server obsahuje (pro nás) neviditelné atributy, a je pro ni definováno hned několik metod. I tyto informace lze snadno získat z dokumentace:
$ go doc mcp-golang.Server package mcp_golang // import "github.com/metoro-io/mcp-golang" type Server struct { // Has unexported fields. } func NewServer(transport transport.Transport, options ...ServerOptions) *Server func (s *Server) CheckPromptRegistered(name string) bool func (s *Server) CheckResourceRegistered(uri string) bool func (s *Server) CheckToolRegistered(name string) bool func (s *Server) DeregisterPrompt(name string) error func (s *Server) DeregisterResource(uri string) error func (s *Server) DeregisterTool(name string) error func (s *Server) RegisterPrompt(name string, description string, handler any) error func (s *Server) RegisterResource(uri string, name string, description string, mimeType string, handler any) error func (s *Server) RegisterTool(name string, description string, handler any) error func (s *Server) Serve() error
V našem prvním serveru bude realizován jen jediný zdroj (resource), který musí být zaregistrován metodou:
func (s *Server) RegisterResource(uri string, name string, description string, mimeType string, handler any) error
Z hlavičky této metody je zřejmé, jaké informace se mají předávat. Jediným nejasným parametrem je handler, tj. funkce, která je volaná pro realizaci odpovědi serveru klientovi. Prozatím budeme realizovat velmi jednoduchý handler. Ten vrací strukturu (resp. ukazatel na strukturu) ResourceResponse, ve které je klientovi vrácena zpráva „Hello, dear client“:
func resourceHandler() (*mcp_golang.ResourceResponse, error) { resource := mcp_golang.NewTextEmbeddedResource( "pozdrav://", "Hello, dear client", "text/plain") response := mcp_golang.NewResourceResponse(resource) return response, nil }
4. Úplný zdrojový kód MCP serveru naprogramovaného v jazyce Go
Úplný zdrojový kód první varianty MCP serveru naprogramovaného v jazyce Go, vypadá následovně:
// Jednoduchý MCP server s jediným zdrojem package main import ( mcp_golang "github.com/metoro-io/mcp-golang" "github.com/metoro-io/mcp-golang/transport/stdio" ) func resourceHandler() (*mcp_golang.ResourceResponse, error) { resource := mcp_golang.NewTextEmbeddedResource( "pozdrav://", "Hello, dear client", "text/plain") response := mcp_golang.NewResourceResponse(resource) return response, nil } func main() { done := make(chan struct{}) server := mcp_golang.NewServer(stdio.NewStdioServerTransport()) err := server.RegisterResource( "pozdrav://", "resource_test", "This is a test resource", "text/plain", resourceHandler) if err != nil { panic(err) } err = server.Serve() if err != nil { panic(err) } <-done }
Teoreticky je možné tento server přeložit spustit z příkazové řádky příkazem:
$ go run mcp_server_1.go
Ovšem komunikace s tímto procesem by měla probíhat přes standardní vstup a výstup. Bez podrobných znalostí MCP protokolu je to však složité (a v praxi to ani není zapotřebí), a proto ke komunikaci využijeme MCP klienty.
5. Komunikace s MCP serverem z MCP klienta naprogramovaného v Pythonu
MCP je podporován celou řadou programovacích jazyků a pochopitelně umožňuje komunikaci mezi procesy naprogramovanými v různých jazycích. To si ověříme, a to konkrétně takovým způsobem, že výše uvedený MCP server vytvořený v jazyku Go inicializujeme a zavoláme z Pythonu. Dokonce je možné, aby MCP klient naprogramovaný v Pythonu nejdříve MCP server přeložil a teprve poté spustil. Pro tento účel se používá již výše zmíněný příkaz go run mcp_server1.go, takže tento příkaz zadáme do parametrů MCP serveru (je nutné rozdělit samotný příkaz od jeho parametrů):
# parametry pro spuštění MCP serveru server_params = StdioServerParameters( command="go", # spustí se tento příkaz args=["run", "./mcp_server_1.go"], # a předají se mu následující parametry env=None, # lze definovat i proměnné prostředí )
V implementaci MCP klienta nejdříve server spustíme, dále s ním zahájíme komunikaci přes STDIO (standardní vstup a výstup) a nakonec ze serveru přečteme obsah zdroje (resource) s identifikátorem pozdrav://. Samotný zdrojový kód by měl být po přečtení předchozí dvojice článků čtenářům snadno pochopitelný:
"""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="go", # spustí se tento příkaz args=["run", "./mcp_server_1.go"], # 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)) text = data.contents[0].text print("Text:", text) # přímé spuštění klienta if __name__ == "__main__": import asyncio asyncio.run(run())
Komunikaci mezi MCP klientem a MCP serverem si snadno ověříme:
$ python mcp_client_1.py Data returned: meta=None contents=[TextResourceContents(uri=AnyUrl('pozdrav://'), mimeType='text/plain', text='Hello, dear client')] Type: <class 'mcp.types.ReadResourceResult'> Text: Hello, dear client
6. Komunikace s MCP serverem z MCP klienta naprogramovaného v jazyce Go
Jak bude vlastně naprogramován MCP klient v jazyce Go? Teoreticky se jedná o stejný postup, jako v případě klienta napsaného v Pythonu, ovšem realizace je nepatrně odlišná, takže si ji popíšeme po jednotlivých krocích.
Nejprve necháme klienta inicializovat spuštění MCP serveru, což je v Go triviální:
cmd := exec.Command("go", "run", "./mcp_server_1.go")
Následně získáme proudy pro standardní vstup a výstup právě spuštěného MCP serveru. Se serverem totiž budeme komunikovat právě přes STDIO:
stdin, err := cmd.StdinPipe() if err != nil { log.Fatalf("Failed to get stdin pipe: %v", err) } stdout, err := cmd.StdoutPipe() if err != nil { log.Fatalf("Failed to get stdout pipe: %v", err) }
Následuje spuštění MCP serveru:
if err := cmd.Start(); err != nil { log.Fatalf("Failed to start server: %v", err) } defer cmd.Process.Kill()
Nyní tedy MCP server běží, takže můžeme inicializovat vlastní MCP protokol s transportem nastaveným na STDIO:
transport := stdio.NewStdioServerTransportWithIO(stdout, stdin) client := mcp.NewClient(transport) if _, err := client.Initialize(context.Background()); err != nil { log.Fatalf("Failed to initialize: %v", err) }
V této chvíli spolu již MCP klient i server komunikují, takže se můžeme pokusit o přečtení zdroje pozdrav://:
resource, err := client.ReadResource(context.Background(), "pozdrav://") if err != nil { log.Printf("Failed to read resource: %v", err) }
Pokud se čtení podaří, vypíšeme si podrobnější informace o přečteném zdroji:
if resource != nil { log.Printf("Resource content: %s", resource) }
7. Úplný zdrojový kód jednoduchého MCP klienta, který server spustí a přečte z něj vybraný zdroj
Úplný zdrojový kód MCP klienta naprogramovaného v jazyce Go vypadá takto:
package main import ( "context" "log" "os/exec" mcp "github.com/metoro-io/mcp-golang" "github.com/metoro-io/mcp-golang/transport/stdio" ) func main() { cmd := exec.Command("go", "run", "./mcp_server_1.go") stdin, err := cmd.StdinPipe() if err != nil { log.Fatalf("Failed to get stdin pipe: %v", err) } stdout, err := cmd.StdoutPipe() if err != nil { log.Fatalf("Failed to get stdout pipe: %v", err) } if err := cmd.Start(); err != nil { log.Fatalf("Failed to start server: %v", err) } defer cmd.Process.Kill() transport := stdio.NewStdioServerTransportWithIO(stdout, stdin) client := mcp.NewClient(transport) if _, err := client.Initialize(context.Background()); err != nil { log.Fatalf("Failed to initialize: %v", err) } resource, err := client.ReadResource(context.Background(), "pozdrav://") if err != nil { log.Printf("Failed to read resource: %v", err) } if resource != nil { log.Printf("Resource content: %s", resource) } }
8. Vylepšení struktury MCP serveru
Původní verze MCP serveru nebyla příliš čitelná, protože se ve zdrojovém kódu mísila volání knihovny mcp-golang s konfigurací zdroje. Z tohoto důvodu si server nepatrně vylepšíme, a to tak, že konfiguraci zdroje přeneseme do konstant definovaných na začátku zdrojového kódu (resp. přesněji řečeno za importy):
// Jednoduchý MCP server s jediným zdrojem package main import ( mcp_golang "github.com/metoro-io/mcp-golang" "github.com/metoro-io/mcp-golang/transport/stdio" ) const resourceURI = "pozdrav://" const resourcename = "greeting" const resourceDescription = "Odpověď s pozdravem" const resourceMimeType = "text/plain" func resourceHandler() (*mcp_golang.ResourceResponse, error) { resource := mcp_golang.NewTextEmbeddedResource( resourceURI, "Hello, dear client", resourceMimeType) response := mcp_golang.NewResourceResponse(resource) return response, nil } func main() { done := make(chan struct{}) server := mcp_golang.NewServer(stdio.NewStdioServerTransport()) err := server.RegisterResource( resourceURI, resourceName, resourceDescription, resourceMimeType, resourceHandler) if err != nil { panic(err) } err = server.Serve() if err != nil { panic(err) } <-done }
Tuto variantu serveru si otestujeme z Pythonu:
"""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="go", # spustí se tento příkaz args=["run", "./mcp_server_2.go"], # 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)) text = data.contents[0].text print("Text:", text) # přímé spuštění klienta if __name__ == "__main__": import asyncio asyncio.run(run())
9. Nástroje (tools) poskytované MCP serverem
V další variantě MCP serveru, kterou si dnes ukážeme, bude definován jeden nástroj (tool). Připomeňme si, že v předchozích článcích tento nástroj počítal faktoriál pro předané nezáporné číslo. Zkusme si tedy realizaci právě tohoto nástroje přepsat do jazyka Go. Nejdříve je nutné definovat záznam s popisem struktury dat předávaných MCP klientem do MCP serveru. Tento záznam bude obsahovat hodnotu n, pro kterou se má faktoriál počítat. V jazyce Go je zřejmé, že interně se budou předávat data ve formátu JSON (v Pythonu jsme od těchto detailů byli odstíněni):
type FactorialArguments struct { N int `json:"n" jsonschema:"required,description=input value for factorial computation"` }
Naprogramování výpočtu faktoriálu může vypadat následovně. Povšimněte si, jakým způsobem přečteme z parametru typu FactorialArguments hodnotu n i to, jak zabalíme výsledek do struktury typu ToolResponse (opět platí, že v Pythonu to bylo sice jednodušší, ale mnoho funkcionality zůstalo skryto):
func factorialHandler(arguments FactorialArguments) (*mcp_golang.ToolResponse, error) { fact := 1 for i := range arguments.N { fact *= 1 + i } toolResults := mcp_golang.NewTextContent(strconv.Itoa(fact)) response := mcp_golang.NewToolResponse(toolResults) return response, nil }
Nyní nám již zbývá spustit MCP server a nástroj factorial v něm zaregistrovat. To je snadné a především přímočaré:
server := mcp_golang.NewServer(stdio.NewStdioServerTransport()) err := server.RegisterTool("factorial", "Výpočet faktoriálu", factorialHandler) if err != nil { panic(err) } err = server.Serve() if err != nil { panic(err) }
10. Realizace serveru s definovaným nástrojem (tool)
Opět se podívejme na způsob realizace celého MCP serveru, ve kterém je definován a nabízen jediný nástroj (tool). Tento nástroj bude počítat hodnotu faktoriálu pro zadaný parametr n, který mu pochopitelně musí klient poslat:
// Jednoduchý MCP server s jediným nástrojem package main import ( "strconv" mcp_golang "github.com/metoro-io/mcp-golang" "github.com/metoro-io/mcp-golang/transport/stdio" ) type FactorialArguments struct { N int `json:"n" jsonschema:"required,description=input value for factorial computation"` } func factorialHandler(arguments FactorialArguments) (*mcp_golang.ToolResponse, error) { fact := 1 for i := range arguments.N { fact *= 1 + i } toolResults := mcp_golang.NewTextContent(strconv.Itoa(fact)) response := mcp_golang.NewToolResponse(toolResults) return response, nil } func main() { done := make(chan struct{}) server := mcp_golang.NewServer(stdio.NewStdioServerTransport()) err := server.RegisterTool("factorial", "Výpočet faktoriálu", factorialHandler) if err != nil { panic(err) } err = server.Serve() if err != nil { panic(err) } <-done }
11. Zavolání nástroje poskytovaného MCP serverem z Pythonu
Výše popsaný nástroj zavoláme z MCP klienta, který je naprogramován v Pythonu. Opět se pro nás nejedná o žádnou novinku a všechny části tohoto klienta by měly být zřejmé, a to včetně způsobu zavolání nástroje a předání parametru n.
"""MCP klient, který zavolá nástroj.""" from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client # parametry pro spuštění MCP serveru server_params = StdioServerParameters( command="go", # spustí se tento příkaz args=["run", "./mcp_server_3.go"], # 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() for n in range(11): # zavolání nástroje data = await session.call_tool("factorial", arguments={"n": n}) factorial = data.content[0].text print(n, factorial) # přímé spuštění klienta if __name__ == "__main__": import asyncio asyncio.run(run())
Samozřejmě si komunikaci otestujeme:
$ python mcp_client_3.py
Výsledky vypsané na terminál by měly vypadat takto:
0 1 1 1 2 2 3 6 4 24 5 120 6 720 7 5040 8 40320 9 362880 10 3628800
12. Zavolání nástroje poskytovaného MCP serverem z jazyka Go
Nyní prakticky stejný postup, tj. připojení k MCP serveru, zavolání nástroje a získání výsledku z tohoto nástroje, budeme realizovat v jazyce Go. MCP klient se vlastně nijak zásadně neliší od prvního klienta popsaného v šesté kapitole, pouze namísto přečtení zdroje (resource) zavoláme nástroj (tool), takže jen v krátkosti:
package main import ( "context" "log" "os/exec" mcp "github.com/metoro-io/mcp-golang" "github.com/metoro-io/mcp-golang/transport/stdio" ) // Define type-safe arguments type CalculateArgs struct { N int `json:"n"` } func main() { cmd := exec.Command("go", "run", "./mcp_server_3.go") stdin, err := cmd.StdinPipe() if err != nil { log.Fatalf("Failed to get stdin pipe: %v", err) } stdout, err := cmd.StdoutPipe() if err != nil { log.Fatalf("Failed to get stdout pipe: %v", err) } if err := cmd.Start(); err != nil { log.Fatalf("Failed to start server: %v", err) } defer cmd.Process.Kill() transport := stdio.NewStdioServerTransportWithIO(stdout, stdin) client := mcp.NewClient(transport) if _, err := client.Initialize(context.Background()); err != nil { log.Fatalf("Failed to initialize: %v", err) } for n := range 11 { args := CalculateArgs{N: n} response, err := client.CallTool( context.Background(), "factorial", args) if err != nil { log.Fatalf("Failed to call tool: %v", err) } if response != nil && len(response.Content) > 0 { factorial := response.Content[0].TextContent.Text log.Printf("%d! = %s", n, factorial) } } }
V dalším kroku otestujeme funkčnost komunikace mezi MCP klientem a MCP serverem:
$ go run mcp_client_3.go 2025/04/08 13:55:27 0! = 1 2025/04/08 13:55:27 1! = 1 2025/04/08 13:55:27 2! = 2 2025/04/08 13:55:27 3! = 6 2025/04/08 13:55:27 4! = 24 2025/04/08 13:55:27 5! = 120 2025/04/08 13:55:27 6! = 720 2025/04/08 13:55:27 7! = 5040 2025/04/08 13:55:27 8! = 40320 2025/04/08 13:55:27 9! = 362880 2025/04/08 13:55:27 10! = 3628800
13. Podpora pro výzvy (prompt)
Mnoho v současnosti vyvíjených aplikací, které nějakým způsobem využívají umělou inteligenci, je založeno na použití velkých jazykových modelů (LLM – Large Language Model). Tyto modely nabízí různá komunikační rozhraní, která do značné míry závisí na provozovateli tohoto modelu (providers). Mnohé modely+provozovatelé například podporují OpenAI, modely od IBM pak komunikují po rozhraní definovaném ve WatsonX atd. Ovšem stejně tak je možné vlastní volání LLM modelu „uzavřít“ do MCP serveru s tím, že všichni klienti budou komunikovat právě s tímto MCP serverem s využitím Model Context Protocolu. A právě pro tento účel slouží výzvy (prompt), které je možné považovat za specifickou skupinu nástrojů (zde již poněkud zjednodušuji – záleží totiž více na sémantice, i když způsob práce s výzvou je do značné míry podobná práci s nástrojem). U každé výzvy nabízené MCP serverem je definováno její jméno, popis a argumenty (jméno+popis+informace, zda je argument povinný či nikoli).
14. Jednoduchý MCP server s výzvou (prompt)
Popišme si nyní krok za krokem způsob implementace MCP serveru, který s klienty dokáže komunikovat přes výzvy (prompt. Základní struktura tohoto serveru se podobá již oběma výše popsaným serverům. Nejdříve si necháme zkonstruovat objekt (záznam), který představuje vlastní MCP server:
server := mcp_golang.NewServer(stdio.NewStdioServerTransport())
Následně zaregistrujeme handler pro výzvu pojmenovanou například prompt_test. V této chvíli již překladač programovacího jazyka Go musí handler znát, ale my si ho ukážeme až o několik řádků níže:
err := server.RegisterPrompt("prompt_test", "This is a test prompt", promptHandler)
Server spustíme (pochopitelně s kontrolou případné chyby, která může v průběhu inicializace nastat):
err = server.Serve()
Samotný handler pro výzvu má podobnou strukturu jako handler pro nástroj. Musíme tedy nejdříve definovat strukturu dat předávaných do handleru. Interně je přenos prováděn přes formát JSON. Podívejme se na definici typu záznamu (struktury) s dvojicí atributů Title a Description. Současně je specifikováno, jakým způsobem bude prováděna serializace do formátu JSON:
type Content struct { Title string `json:"title" jsonschema:"required,description=The title to submit"` Description string `json:"description" jsonschema:"description=The description to submit"` }
Samotný handler přečte z předaných dat atribut Title a vytvoří na jeho základě odpověď. Ta bude například pro vstupní data Title=Pavel vypadat takto: „Hello, Pavel!“. Musíme pouze explicitně vlastní řetězec, který obsahuje odpověď, „zabalit“ do struktury typu PromptMessage a tu následně do struktury PromptResponse. To sice zní složitě, ale vlastní implementace je snadná:
func promptHandler(arguments Content) (*mcp_golang.PromptResponse, error) { text := mcp_golang.NewTextContent(fmt.Sprintf("Hello, %s!", arguments.Title)) message := mcp_golang.NewPromptMessage(text, mcp_golang.RoleUser) response := mcp_golang.NewPromptResponse("description", message) return response, nil }
15. Úplný zdrojový kód serveru nabízejícího výzvu
Úplný zdrojový kód výše popsaného MCP serveru s výzvou (prompt) naprogramovaného v jazyce Go vypadá následovně:
// Jednoduchý MCP server s výzvou (prompt) package main import ( "fmt" mcp_golang "github.com/metoro-io/mcp-golang" "github.com/metoro-io/mcp-golang/transport/stdio" ) type Content struct { Title string `json:"title" jsonschema:"required,description=The title to submit"` Description string `json:"description" jsonschema:"description=The description to submit"` } func promptHandler(arguments Content) (*mcp_golang.PromptResponse, error) { text := mcp_golang.NewTextContent(fmt.Sprintf("Hello, %s!", arguments.Title)) message := mcp_golang.NewPromptMessage(text, mcp_golang.RoleUser) response := mcp_golang.NewPromptResponse("description", message) return response, nil } func main() { done := make(chan struct{}) server := mcp_golang.NewServer(stdio.NewStdioServerTransport()) err := server.RegisterPrompt("prompt_test", "This is a test prompt", promptHandler) err = server.Serve() if err != nil { panic(err) } <-done }
16. Komunikace s MCP serverem přes nabízenou výzvu
Jak bude realizována komunikace MCP klienta s MCP serverem přes nabízenou výzvu? Se znalostmi, které již máme, je to vlastně snadné. Nejdříve MCP server spustíme (nevyužívám zde SSE, ale STDIO):
cmd := exec.Command("go", "run", "./mcp_server_4.go")
Navážeme propojení mezi oběma procesy přes STDIO, což již dobře známe:
stdin, err := cmd.StdinPipe() if err != nil { log.Fatalf("Failed to get stdin pipe: %v", err) } stdout, err := cmd.StdoutPipe() if err != nil { log.Fatalf("Failed to get stdout pipe: %v", err) }
Nakonec server inicializujeme a připojíme se k němu:
if err := cmd.Start(); err != nil { log.Fatalf("Failed to start server: %v", err) } transport := stdio.NewStdioServerTransportWithIO(stdout, stdin) client := mcp.NewClient(transport)
Následně si opět definujeme strukturu používanou pro komunikaci mezi klientem a serverem. Povšimněte si, že druhý atribut je definován formou ukazatele a nemusí být inicializován (není „required“):
type Content struct { Title string `json:"title" jsonschema:"required,description=The title to submit"` Description *string `json:"description" jsonschema:"description=The description to submit"` }
Poslání zprávy výzvě a získání odpovědí (obecně většího množství zpráv):
content := Content{Title: "Foo"} response, err := client.GetPrompt(ctx, "prompt_test", content) if err != nil { log.Fatalf("Failed to get prompt: %v", err) }
Pokud nedošlo k chybě, zkusíme získané zprávy přečíst (jednu po druhé) a vypsat je do logu:
if response != nil { messages := response.Messages for i, message := range messages { text := *message.Content.TextContent log.Println(i, text.Text) } }
Vše si otestujeme spuštěním klienta:
$ go run mcp_client_4.go
Do logu by se měla vypsat odpověď v tomto tvaru (samozřejmě, že časové razítko bude odlišné). Jméno „Foo“ jsme předali do výzvy, takže je zřejmé, že komunikace probíhá podle předpokladů:
2025/04/09 17:09:16 0 Hello, Foo!
17. Úplný zdrojový kód klienta, který komunikuje přes výzvu
Posledním zdrojovým kódem, který si dnes ukážeme, je MCP klient naprogramovaný v Go, který s MCP klientem komunikuje přes výzvu (prompt):
// MCP klient, který komunikuje přes prompt package main import ( "context" "log" "os/exec" mcp "github.com/metoro-io/mcp-golang" "github.com/metoro-io/mcp-golang/transport/stdio" ) func main() { cmd := exec.Command("go", "run", "./mcp_server_4.go") stdin, err := cmd.StdinPipe() if err != nil { log.Fatalf("Failed to get stdin pipe: %v", err) } stdout, err := cmd.StdoutPipe() if err != nil { log.Fatalf("Failed to get stdout pipe: %v", err) } if err := cmd.Start(); err != nil { log.Fatalf("Failed to start server: %v", err) } defer cmd.Process.Kill() transport := stdio.NewStdioServerTransportWithIO(stdout, stdin) client := mcp.NewClient(transport) ctx := context.Background() if _, err := client.Initialize(ctx); err != nil { log.Fatalf("Failed to initialize: %v", err) } type Content struct { Title string `json:"title" jsonschema:"required,description=The title to submit"` Description *string `json:"description" jsonschema:"description=The description to submit"` } content := Content{Title: "Foo"} response, err := client.GetPrompt(ctx, "prompt_test", content) if err != nil { log.Fatalf("Failed to get prompt: %v", err) } if response != nil { messages := response.Messages for i, message := range messages { text := *message.Content.TextContent log.Println(i, text.Text) } } }
18. Omezení a chyby knihovny mcp-golang
Knihovna mcp-golang, jejíž základní vlastnosti jsme si popsali v dnešním článku, dokáže bez problémů pracovat s nástroji a s výzvami. Kupodivu je problematická práce se zdroji, což je zvláštní, protože z implementačního hlediska se jedná o tu jednodušší část MCP. Další nevýhodou této knihovny je fakt, že i pro realizaci jednoduchého nástroje nebo výzvy je nutné pracovat s mnoha strukturami, které se do sebe „zabalují“: EmbeddedResource, ResourceResponse, TextContent, ToolResponse, PromptMessage, PromptResponse atd. Na jednu stranu se jedná o logické a navíc i poměrně univerzální řešení, ovšem na stranu druhou nelze popřít, že implementace MCP serveru v Pythonu je mnohem jednodušší – a zejména „AI systémy“ jsou v současnosti mnohdy vyvíjeny co nejrychleji s tím, že se celé velké části těchto systémů stejně dříve či později zahodí (což je však téma na zcela odlišný článek).
19. Repositář s demonstračními příklady
Zdrojové kódy demonstračních příkladů, které byly popsány v dnešním článku, jsou uloženy do repositáře https://github.com/tisnik/most-popular-python-libs a naleznete je na následujících adresách:
Zdrojové kódy všech minule a předminule popsaných demonstračních příkladů určených pro Python a balíček mcp byly uloženy do stejného 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:
20. Odkazy na Internetu
- MCP Python SDK
https://github.com/modelcontextprotocol/python-sdk?tab=readme-ov-file#running-your-server - MCP protocol: Resources
https://modelcontextprotocol.info/docs/concepts/resources/ - Example Servers
https://modelcontextprotocol.io/examples - Core architecture
https://modelcontextprotocol.io/docs/concepts/architecture - Unleashing the Power of Model Context Protocol (MCP): A Game-Changer in AI Integration
https://techcommunity.microsoft.com/blog/educatordeveloperblog/unleashing-the-power-of-model-context-protocol-mcp-a-game-changer-in-ai-integrat/4397564 - MPC inspector
https://github.com/modelcontextprotocol/inspector - Model Context Protocol servers
https://github.com/modelcontextprotocol/servers - python-sdk na GitHubu
https://github.com/modelcontextprotocol/python-sdk - typescript-sdk na GitHubu
https://github.com/modelcontextprotocol/typescript-sdk - mcp-golang
https://github.com/metoro-io/mcp-golang - 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/ - 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 - 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 - RFC 6570: URI Template
https://datatracker.ietf.org/doc/html/rfc6570 - Return resources as structured JSON instead of text?
https://github.com/modelcontextprotocol/python-sdk/issues/279 - Python standard library: pprint
https://docs.python.org/3/library/pprint.html - Python standard library: json — JSON encoder and decoder¶
https://docs.python.org/3/library/json.html - Calling MCP Servers the Hard Way
https://deadprogrammersociety.com/2025/03/calling-mcp-servers-the-hard-way.html - mcptools
https://github.com/f/mcptools - Server-sent events
https://en.wikipedia.org/wiki/Server-sent_events - Model context protocol (MCP)
https://openai.github.io/openai-agents-python/mcp/ - A Clear Intro to MCP (Model Context Protocol) with Code Examples
https://towardsdatascience.com/clear-intro-to-mcp/ - A Developer's Guide to the MCP
https://www.getzep.com/ai-agents/developer-guide-to-mcp - MCP: Flash in the Pan or Future Standard?
https://blog.langchain.dev/mcp-fad-or-fixture/ - MCP yeah you know me: A complete guide and review of Model Context Protocol (MCP)
https://ebi.ai/blog/model-context-protocol-guide/ - Pillow documentation
https://pillow.readthedocs.io/en/stable/handbook/tutorial.html - Pillow: Python Imaging Library (Fork)
https://pypi.org/project/pillow/ - How to use Pillow, a fork of PIL
https://www.pythonforbeginners.com/gui/how-to-use-pillow - PNG is Not GIF
https://www.root.cz/clanky/png-is-not-gif/