Realizace MCP klientů i serverů v jazyku Go

10. 4. 2025
Doba čtení: 26 minut

Sdílet

Dvě humanoidní postavy propojují svou mysl
Autor: Root.cz s využitím Zoner AI
Ukázali jsme si tvorbu MCP klientů i serverů v Pythonu s využitím oficiální MCP knihovny, zatímco dnes se zaměříme na jazyk Go, pro který oficiální knihovna prozatím není k dispozici.

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

20. Odkazy na Internetu

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)
}
Poznámka: v poslední verzi knihovny mcp-golang došlo ke změně, která neumožňuje přečtení obsahu zdroje. Tato chyba bude doufejme v další verzi opravena (patch zašlu). Už jen existence tohoto problému mnohé vypovídá o stabilitě a kvalitě knihoven pro MCP.

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
}
Poznámka: proces se serverem nemá ihned skončit a z tohoto důvodu na konci čekáme na zápis do kanálu done, který nebude nikdy proveden.

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)
                }
        }
 
}
Poznámka: nový je způsob zpracování výsledků nástroje, ovšem z programového kódu by měl být celý postup jasně patrný.

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
}
Poznámka: v Pythonu naproti tomu toto zabalení do značné míry realizovala samotná MCP knihovna.

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).

hacking_tip

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:

# Demonstrační příklad Stručný popis příkladu Cesta
1 mcp_server1.go MCP server s definicí jediného zdroje bez selektorů https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp-golang/mcp_server1.go
2 mcp_client1.py MCP klient komunikující s první verzí MCP serveru, naprogramováno v Pythonu https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp-golang/mcp_client1.py
3 mcp_client1.go MCP klient komunikující s první verzí MCP serveru, naprogramováno v jazyce Go https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp-golang/mcp_client1.go
     
4 mcp_server2.go vylepšení struktury MCP serveru https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp-golang/mcp_server2.go
5 mcp_client2.py MCP klient komunikující se druhou verzí MCP serveru, naprogramováno v Pythonu https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp-golang/mcp_client2.py
     
6 mcp_server3.go realizace serveru s definovaným nástrojem (tool) https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp-golang/mcp_server3.go
7 mcp_client3.py zavolání nástroje z Pythonu https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp-golang/mcp_client3.py
8 mcp_client3.go zavolání nástroje z jazyka Go https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp-golang/mcp_client3.go
     
9 mcp_server4.go realizace serveru s definovanou výzvou (prompt) https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp-golang/mcp_server4.go
10 mcp_client4.go komunikace přes výzvu (prompt) realizovaná v jazyce Go https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp-golang/mcp_client4.go

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:

# 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_server9.py MCP server, který dokáže poslat rastrový obrázek https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp_server9.py
10 mcp_server_A.py MCP server, který na požadavky odpovídá se zpožděním https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp_server_A.py
     
11 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
12 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
13 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
14 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
15 mcp_client5.py MCP klient, který zavolá vybraný nástroj https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp_client5.py
16 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
17 mcp_client7.py MCP klient, který dokáže načíst rastrový obrázek https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp_client7.py
18 mcp_client8.py MCP klient s měřením času odpovědí MCP serveru https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp_client8.py
19 mcp_client9.py MCP klient s měřením času odpovědí MCP serveru a více asynchronními voláními https://github.com/tisnik/most-popular-python-libs/blob/master/mcp/mcp_client9.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
  21. Model context protocol (MCP)
    https://openai.github.io/openai-agents-python/mcp/
  22. A Clear Intro to MCP (Model Context Protocol) with Code Examples
    https://towardsdatascience.com/clear-intro-to-mcp/
  23. A Developer's Guide to the MCP
    https://www.getzep.com/ai-agents/developer-guide-to-mcp
  24. MCP: Flash in the Pan or Future Standard?
    https://blog.langchain.dev/mcp-fad-or-fixture/
  25. MCP yeah you know me: A complete guide and review of Model Context Protocol (MCP)
    https://ebi.ai/blog/model-context-protocol-guide/
  26. Pillow documentation
    https://pillow.readthedoc­s.io/en/stable/handbook/tu­torial.html
  27. Pillow: Python Imaging Library (Fork)
    https://pypi.org/project/pillow/
  28. How to use Pillow, a fork of PIL
    https://www.pythonforbegin­ners.com/gui/how-to-use-pillow
  29. PNG is Not GIF
    https://www.root.cz/clanky/png-is-not-gif/

Autor článku

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