---
id: a1fad13de24c90924cfa9e25e9f986cb
author: snowmerak
title: Pochopení hostitele MCP
description: Tento článek slouží k lepšímu pochopení protokolu MCP společnosti Anthropic a hostitele MCP (mcphost) implementovaného v jazyce Go.
language: cs
date: 2025-04-08T00:59:42.416582136Z
path: /blog/posts/a-little-understanding-of-mcp-host-zb952aae0
category: ai
---

## Co je MCP

[MCP](https://modelcontextprotocol.io/introduction) je protokol, který vyvinula společnost Anthropic pro Claude. MCP je zkratka za Model Context Protocol a umožňuje LLM aktivně žádat externí systém o provedení akce nebo poskytnutí zdroje. MCP je doslova pouze protokol pro zasílání žádostí a odpovědí, a proto musí samotný proces a jeho provedení zajistit vývojář.

### O vnitřní funkci

Než vysvětlím vnitřní fungování, rád bych se krátce zastavil u [Gemini Function Calling](https://ai.google.dev/gemini-api/docs/function-calling). Gemini Function Calling, stejně jako MCP, umožňuje LLM iniciativně volat externí akce. Možná vás napadne, proč jsem Function Calling vůbec zmínil. Důvodem je, že Function Calling se objevil dříve než MCP a oba používají schéma OpenAPI, takže jsou vzájemně kompatibilní a lze předpokládat, že se jejich chování bude podobat. Dokumentace Gemini Function Calling je navíc podrobnější, a proto se může ukázat jako užitečná.

![FunctionCalling](/assets/images/mcp_with_csharp/function-calling-overview.png)

Celkový postup vypadá takto.

1. Definujte funkci.
2. Odešlete definici funkce spolu s promptem do Gemini.
   1. "Send user prompt along with the function declaration(s) to the model. It analyzes the request and determines if a function call would be helpful. If so, it responds with a structured JSON object."
3. Pokud Gemini potřebuje, požádá o volání funkce.
   1. Pokud Gemini potřebuje volání funkce, volající obdrží název a parametry pro toto volání.
   2. Volající se může rozhodnout, zda volání provede.
      1. Zda funkci skutečně zavolá a vrátí platnou hodnotu.
      2. Zda funkci nezavolá, ale vrátí data, jako by ji zavolal.
      3. Zda ji jednoduše ignoruje.
4. Gemini v rámci tohoto procesu může najednou požadovat volání několika funkcí nebo může po zhodnocení výsledku volání další funkce provést a požádat o ně.
5. Jakmile je výsledkem srozumitelná odpověď, proces končí.

Tento postup se v zásadě shoduje s MCP. Podobný popis najdete také v [tutoriálu MCP](https://modelcontextprotocol.io/tutorials/building-mcp-with-llms). Podobně funguje i ollama tools.

A co je opravdu šťastné, tyto tři nástroje, ollama tools, MCP a Gemini Function Calling, sdílejí téměř stejnou strukturu schématu, takže implementací pouze MCP lze využít všechny tři.

Všichni sdílejí i jednu nevýhodu. Volání skutečně provádí model, takže pokud je model ve špatném stavu, může funkce nevolat, volat je nesprávně nebo posílat MCP serveru DoS útok a způsobit další chybné chování.

## MCP host v Go

### mark3lab's mcphost

V Go existuje [mcphost](https://github.com/mark3labs/mcphost), který vyvíjí organizace mark3lab.

Použití je velmi jednoduché.

```sh
go install github.com/mark3labs/mcphost@latest
```

Po instalaci vytvořte soubor `$HOME/.mcp.json` a zapište do něj následující obsah.

```json
{
  "mcpServers": {
    "sqlite": {
      "command": "uvx",
      "args": [
        "mcp-server-sqlite",
        "--db-path",
        "/tmp/foo.db"
      ]
    },
    "filesystem": {
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-filesystem",
        "/tmp"
      ]
    }
  }
}
```

Poté spusťte následující příkaz s modelem ollama.  
Pokud je to nutné, nejprve stáhněte model příkazem `ollama pull mistral-small`.

> Obecně doporučuji Claude nebo qwen2.5, ale v současné době doporučuji mistral-small.

```sh
mcphost -m ollama:mistral-small
```

Při tomto spuštění lze však používat pouze formu otázek a odpovědí v CLI prostředí.  
Proto upravíme kód `mcphost` tak, aby se dalo chovat programovatelněji.

### Fork mcphost

Jak jsme již viděli, `mcphost` obsahuje funkci, která pomocí MCP extrahuje metadata a volá funkce. Potřebujeme tedy část pro volání LLM, část pro práci s MCP serverem a část pro správu historie zpráv.

Příslušnou část představuje `Runner` z následujícího balíčku.

```go
package runner

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"strings"
	"time"

	mcpclient "github.com/mark3labs/mcp-go/client"
	"github.com/mark3labs/mcp-go/mcp"

	"github.com/mark3labs/mcphost/pkg/history"
	"github.com/mark3labs/mcphost/pkg/llm"
)

type Runner struct {
	provider   llm.Provider
	mcpClients map[string]*mcpclient.StdioMCPClient
	tools      []llm.Tool

	messages []history.HistoryMessage
}
```

Vnitřní deklarace této části zde podrobně nerozebírám. Jsou však téměř shodné s jejich názvy.

```go
func NewRunner(systemPrompt string, provider llm.Provider, mcpClients map[string]*mcpclient.StdioMCPClient, tools []llm.Tool) *Runner {
	return &Runner{
		provider:   provider,
		mcpClients: mcpClients,
		tools:      tools,
		messages: []history.HistoryMessage{
			{
				Role: "system",
				Content: []history.ContentBlock{{
					Type: "text",
					Text: systemPrompt,
				}},
			},
		},
	}
}
```

Informace o `mcpClients` a `tools`, které se zde používají, najdete v [příslušném souboru](https://github.com/snowmerak/mcphost/blob/main/internal/runner/from.go).  
Pro `provider` použijeme verzi pro ollama, takže se podívejte do [příslušného souboru](https://github.com/snowmerak/mcphost/blob/main/pkg/llm/ollama/provider.go).

Hlavním jádrem je metoda `Run`.

```go
func (r *Runner) Run(ctx context.Context, prompt string) (string, error) {
	if len(prompt) != 0 {
		r.messages = append(r.messages, history.HistoryMessage{
			Role: "user",
			Content: []history.ContentBlock{{
				Type: "text",
				Text: prompt,
			}},
		})
	}

	llmMessages := make([]llm.Message, len(r.messages))
	for i := range r.messages {
		llmMessages[i] = &r.messages[i]
	}

	const initialBackoff = 1 * time.Second
	const maxRetries int = 5
	const maxBackoff = 30 * time.Second

	var message llm.Message
	var err error
	backoff := initialBackoff
	retries := 0
	for {
		message, err = r.provider.CreateMessage(
			context.Background(),
			prompt,
			llmMessages,
			r.tools,
		)
		if err != nil {
			if strings.Contains(err.Error(), "overloaded_error") {
				if retries >= maxRetries {
					return "", fmt.Errorf(
						"claude is currently overloaded. please wait a few minutes and try again",
					)
				}

				time.Sleep(backoff)
				backoff *= 2
				if backoff > maxBackoff {
					backoff = maxBackoff
				}
				retries++
				continue
			}

			return "", err
		}

		break
	}

	var messageContent []history.ContentBlock

	var toolResults []history.ContentBlock
	messageContent = []history.ContentBlock{}

	if message.GetContent() != "" {
		messageContent = append(messageContent, history.ContentBlock{
			Type: "text",
			Text: message.GetContent(),
		})
	}

	for _, toolCall := range message.GetToolCalls() {
		input, _ := json.Marshal(toolCall.GetArguments())
		messageContent = append(messageContent, history.ContentBlock{
			Type:  "tool_use",
			ID:    toolCall.GetID(),
			Name:  toolCall.GetName(),
			Input: input,
		})

		parts := strings.Split(toolCall.GetName(), "__")

		serverName, toolName := parts[0], parts[1]
		mcpClient, ok := r.mcpClients[serverName]
		if !ok {
			continue
		}

		var toolArgs map[string]interface{}
		if err := json.Unmarshal(input, &toolArgs); err != nil {
			continue
		}

		var toolResultPtr *mcp.CallToolResult
		req := mcp.CallToolRequest{}
		req.Params.Name = toolName
		req.Params.Arguments = toolArgs
		toolResultPtr, err = mcpClient.CallTool(
			context.Background(),
			req,
		)

		if err != nil {
			errMsg := fmt.Sprintf(
				"Error calling tool %s: %v",
				toolName,
				err,
			)
			log.Printf("Error calling tool %s: %v", toolName, err)

			toolResults = append(toolResults, history.ContentBlock{
				Type:      "tool_result",
				ToolUseID: toolCall.GetID(),
				Content: []history.ContentBlock{{
					Type: "text",
					Text: errMsg,
				}},
			})

			continue
		}

		toolResult := *toolResultPtr

		if toolResult.Content != nil {
			resultBlock := history.ContentBlock{
				Type:      "tool_result",
				ToolUseID: toolCall.GetID(),
				Content:   toolResult.Content,
			}

			var resultText string
			for _, item := range toolResult.Content {
				if contentMap, ok := item.(map[string]interface{}); ok {
					if text, ok := contentMap["text"]; ok {
						resultText += fmt.Sprintf("%v ", text)
					}
				}
			}

			resultBlock.Text = strings.TrimSpace(resultText)

			toolResults = append(toolResults, resultBlock)
		}
	}

	r.messages = append(r.messages, history.HistoryMessage{
		Role:    message.GetRole(),
		Content: messageContent,
	})

	if len(toolResults) > 0 {
		r.messages = append(r.messages, history.HistoryMessage{
			Role:    "user",
			Content: toolResults,
		})

		return r.Run(ctx, "")
	}

	return message.GetContent(), nil
}
```

Kód samotný je sestaven z částí [příslušného souboru](https://github.com/snowmerak/mcphost/blob/main/cmd/mcp.go).

Obsah lze shrnout takto.

1. Spolu s promptem odešlete seznam nástrojů a požádejte o rozhodnutí, zda se mají spustit, nebo o vygenerování odpovědi.
2. Pokud je odpověď vygenerována, ukončete rekurzi a vraťte ji.
3. Pokud LLM požádá o spuštění nástroje, host zavolá MCP Server.
4. Přidejte odpověď do historie a vraťte se ke kroku 1.

## Závěrem

> Už konec?

Ve skutečnosti toho moc říct nemám. Tento článek vznikl hlavně proto, abych vám pomohl pochopit, jak MCP Server funguje. Doufám, že vám alespoň trochu pomohl porozumět tomu, jak funguje MCP host.
