---
id: a1fad13de24c90924cfa9e25e9f986cb
author: snowmerak
title: Az MCP host működésének rövid megismerése
description: Ez a cikk az Anthropic MCP protokolljának és a Go nyelven megvalósított MCP hosztnak (mcphost) a megértését hivatott elősegíteni.
language: hu
date: 2025-04-08T00:59:42.416582136Z
path: /blog/posts/a-little-understanding-of-mcp-host-zb952aae0
category: ai
---

## Mi az MCP

Az [MCP](https://modelcontextprotocol.io/introduction) az Anthropic által a Claude számára kifejlesztett protokoll. Az MCP a Model Context Protocol rövidítése, amely lehetővé teszi, hogy a LLM aktívan kérjen műveleteket vagy erőforrásokat külső rendszerektől. Mivel az MCP szó szerint csupán kéréseket és válaszokat továbbító protokoll, a folyamatot és a végrehajtást a fejlesztőnek kell megvalósítania.

### A belső működésről

Mielőtt a belső működést ismertetnénk, röviden érintjük a [Gemini Function Calling](https://ai.google.dev/gemini-api/docs/function-calling) témakörét. A Gemini Function Calling is az MCP-hez hasonlóan lehetővé teszi, hogy a LLM kezdeményezően hívjon meg külső műveleteket. Felmerülhet a kérdés, miért hoztuk szóba éppen a Function Callingot. Azért, mert a Function Calling korábban jelent meg, mint az MCP, és mivel mindkettő OpenAPI sémát használ, kompatibilisek egymással, így működésük hasonló lehet. Mivel a Gemini Function Calling leírása részletesebb, feltételezhetően segítséget nyújt, ezért hoztuk szóba.

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

Az általános folyamat a következő:

1. Függvényt definiálunk.
2. A promptdal együtt elküldjük a függvénydefiníciót a Gemininek.
   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. A Gemini szükség esetén függvényhívást kér.
   1. Ha a Gemini szükségesnek ítéli, a hívó megkapja a függvényhíváshoz szükséges nevet és paramétereket.
   2. A hívó dönthet arról, hogy végrehajtja-e a hívást, vagy sem.
      1. Meghívja a függvényt, és érvényes értéket ad vissza.
      2. Nem hívja meg, hanem úgy ad vissza adatot, mintha meghívta volna.
      3. Egyszerűen figyelmen kívül hagyja.
4. A Gemini a fenti folyamat során egyszerre több függvényt is hívhat, vagy a függvényhívás eredményét megvizsgálva újabb hívást kezdeményezhet.
5. Végül, amikor rendezett válasz születik, a folyamat véget ér.

Ez a folyamat általánosságban összhangban van az MCP-vel. Ezt hasonlóan írja le az [MCP oktatóanyaga](https://modelcontextprotocol.io/tutorials/building-mcp-with-llms) is. Az ollama tools is hasonló módon működik.

Szerencsére a három eszköz, az ollama tools, az MCP és a Gemini Function Calling sémaszerkezete gyakorlatilag közös, ezért elegendő egyetlen MCP-t megvalósítani ahhoz, hogy mindhárom helyen használni lehessen.

Van azonban egy közös hátrány is. Mivel a végrehajtást végül a modell kezdeményezi, ha a használt modell gyenge állapotban van, előfordulhat, hogy nem hív meg függvényt, rosszul hívja meg, vagy DoS-szerű terhelést okoz az MCP szerveren.

## MCP host Go nyelven

### mark3lab mcphost

Go nyelvhez létezik a mark3lab szervezet által fejlesztett [mcphost](https://github.com/mark3labs/mcphost).

A használata rendkívül egyszerű.

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

A telepítés után hozzuk létre a `$HOME/.mcp.json` fájlt, és az alábbi módon írjuk meg:

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

Ezután futtassuk az alábbi módon, ollama modellel.  
Természetesen szükség esetén előtte letöltjük a modellt a `ollama pull mistral-small` paranccsal.

> Alapvetően a claude vagy a qwen2.5 modellt ajánlom, de jelenleg én a mistral-smallt javasolom.

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

Ilyen módon azonban csak CLI környezetben, kérdés-válasz formában használhatjuk. Ezért most módosítjuk a `mcphost` kódját, hogy programozhatóbban tudjon működni.

### Az mcphost fork

Ahogy már láttuk, az `mcphost` tartalmaz olyan funkciót, amely MCP segítségével metaadatokat nyer ki és függvényeket hív meg. Ezért szükség van a LLM hívásáért felelős részre, az MCP szerverek kezelésére és az üzenettörténet kezelésére.

Az ehhez szükséges részeket egy csomag `Runner` típusába emeltük ki:

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

A típus belső deklarációit nem vizsgáljuk külön, mert nagyjából a nevük alapján érthetők.

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

A itt használt `mcpClients` és `tools` részleteiért nézze meg a [megfelelő fájlt](https://github.com/snowmerak/mcphost/blob/main/internal/runner/from.go).  
A `provider` esetében az ollama implementációt fogjuk használni, ezért nézze meg a [megfelelő fájlt](https://github.com/snowmerak/mcphost/blob/main/pkg/llm/ollama/provider.go).

A főszereplő a `Run` metódus.

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

A kód maga a [megfelelő fájl](https://github.com/snowmerak/mcphost/blob/main/cmd/mcp.go) egyes részeinek összeillesztésével készült.

A tartalma nagyjából a következő:

1. A prompttal együtt elküldjük az eszközök listáját, és kérjük a végrehajtás eldöntését vagy válaszgenerálást.
2. Amikor létrejön a válasz, megszakítjuk a rekurziót, és visszaadjuk.
3. Ha a LLM eszközvégrehajtási kérést hagy hátra, a host meghívja az MCP Servert.
4. A választ hozzáadjuk a történethez, és visszatérünk az 1. pontra.

## Befejezés

> Máris vége?

Valójában nincs túl sok mondanivalónk. Ez az írás főként azért készült, hogy segítsen megérteni, hogyan működik az MCP Server. Remélem, hogy ez kis mértékben hozzájárult ahhoz, hogy megértsd az MCP host működését.
