---
id: a1fad13de24c90924cfa9e25e9f986cb
author: snowmerak
title: Pochopenie hostiteľa MCP
description: Tento článok má za cieľ napomôcť pochopeniu protokolu MCP od spoločnosti Anthropic a hostiteľa MCP (mcphost) implementovaného v jazyku Go.
language: sk
date: 2025-04-08T00:59:42.416582136Z
path: /blog/posts/a-little-understanding-of-mcp-host-zb952aae0
category: ai
---

## Čo je MCP

[MCP](https://modelcontextprotocol.io/introduction) je protokol, ktorý vyvinula spoločnosť Anthropic pre claude. MCP je skratka pre Model Context Protocol a je to protokol, ktorý umožňuje LLM aktívne žiadať外部 o akcie alebo zdroje. MCP je doslova len protokol na odosielanie žiadostí a prijímanie odpovedí, a preto musí samotný proces a vykonávanie zabezpečiť vývojár.

### O vnútornej činnosti

Skôr než vysvetlíme vnútornú činnosť, sa krátko zastavme pri [Gemini Function Calling](https://ai.google.dev/gemini-api/docs/function-calling). Gemini Function Calling rovnako ako MCP umožňuje LLM iniciatívne volať externé akcie. Možno sa čitateľ spýta, prečo sme Function Calling vôbec spomenuli. Dôvod je ten, že Function Calling sa objavil skôr ako MCP a keďže oba využívajú schému OpenAPI, sú vzájomne kompatibilné, a preto sme predpokladali, že ich vzájomné správanie bude podobné. Keďže opis Gemini Function Calling je relatívne podrobnejší, predpokladáme, že bude užitočný, a preto sme ho uviedli.

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

Celkový tok je nasledovný.

1. Definujte funkciu.
2. Spolu s promptom odošlite definíciu funkcie 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. Ak Gemini považuje za potrebné, požiada o volanie funkcie.
   1. Ak Gemini považuje za potrebné volanie funkcie, volajúci dostane názov a parametre na volanie.
   2. Volajúci môže rozhodnúť, či sa má vykonať:
      1. zavolať funkciu a vrátiť správnu hodnotu,
      2. nevolať funkciu, ale vrátiť údaje, akoby bola zavolaná,
      3. alebo ju jednoducho ignorovať.
4. Gemini v rámci uvedeného procesu môže naraz požadovať volanie viacerých funkcií alebo po vyhodnotení výsledku funkcie požadovať ďalšie volania.
5. Keď je výsledkom upravená odpoveď, proces sa ukončí.

Tento tok je vo všeobecnosti v súlade s MCP. Podobne ho opisuje [tutoriál MCP](https://modelcontextprotocol.io/tutorials/building-mcp-with-llms). Podobne funguje aj ollama tools.

A to je naozaj šťastie, že tieto tri nástroje, ollama tools, MCP a Gemini Function Calling, zdieľajú takmer rovnakú štruktúru schémy, takže implementáciou iba MCP ju možno použiť na všetky tri miesta.

Ešte jedna nevýhoda je spoločná pre všetky. Keďže volanie vykonáva model, ak je váš model v zlom stave, môže funkcie nevolať, volať ich nesprávne alebo poslať MCP serveru DOS útok a podobne, čiže správať sa nesprávne.

## MCP hostiteľ v Go

### mcphost od mark3lab

V Go existuje [mcphost](https://github.com/mark3labs/mcphost), ktorý vyvíja organizácia mark3lab.

Použitie je veľmi jednoduché.

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

Po inštalácii vytvorte súbor `$HOME/.mcp.json` a zapíšte doňho nasledovné.

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

Potom spustite s modelom ollama nasledovne.  
Samozrejme, ak je to potrebné, predtým stiahnite model príkazom `ollama pull mistral-small`.

> Všeobecne odporúčame claude alebo qwen2.5, no momentálne odporúčam mistral-small.

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

Ak takto spustíte program, dá sa používať iba formou otázok a odpovedí v prostredí CLI.  
Preto teraz upravíme kód `mcphost` tak, aby sa dal ovládať programovo.

### Fork mcphost

Ako sme už videli, `mcphost` obsahuje funkciu, ktorá využíva MCP na extrakciu metadát a volanie funkcií. Preto sú potrebné časti, ktoré volajú LLM, pracujú so serverom MCP a spravujú históriu správ.

Časť, ktorá obsahuje tieto funkcie, je `Runner` z nasledujúceho balíka.

```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
}
```

Vnútorné deklarácie tejto časti si podrobne neprezeráme. Takmer všetky však zodpovedajú svojim názvom.

```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,
				}},
			},
		},
	}
}
```

Ohľadom `mcpClients` a `tools`, ktoré sa tu používajú, si prosím pozrite [príslušný súbor](https://github.com/snowmerak/mcphost/blob/main/internal/runner/from.go).  
Pre `provider` použijeme ten od ollama, preto si prosím pozrite [príslušný súbor](https://github.com/snowmerak/mcphost/blob/main/pkg/llm/ollama/provider.go).

Hlavným jedlom je metóda `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
}
```

Samotný kód je zostavený z časti [príslušného súboru](https://github.com/snowmerak/mcphost/blob/main/cmd/mcp.go).

Obsah je približne nasledovný.

1. Spolu s promptom odošlite zoznam nástrojov a požiadajte o rozhodnutie, či sa majú vykonať, alebo o vygenerovanie odpovede.
2. Keď sa odpoveď vygeneruje, ukončite rekurziu a vráťte ju.
3. Ak LLM zanechá požiadavku na vykonanie nástroja, hostiteľ zavolá MCP Server.
4. Pridajte odpoveď do histórie a vráťte sa na bod 1.

## Záver

> Už koniec?

Vlastne toho veľa povedať nemám. Tento text vznikol predovšetkým preto, aby pomohol pochopiť, ako funguje MCP Server. Dúfam, že vám aspoň trochu pomohol pochopiť činnosť MCP hostiteľa.
