---
id: a1fad13de24c90924cfa9e25e9f986cb
author: snowmerak
title: MCP-host eens nader bekijken
description: Dit is een tekst die bijdraagt aan het begrip van het MCP-protocol van Anthropic en de MCP-host (mcphost) die in Go is geïmplementeerd.
language: nl
date: 2025-04-08T00:59:42.416582136Z
path: /blog/posts/a-little-understanding-of-mcp-host-zb952aae0
category: ai
---

## Wat is MCP

[MCP](https://modelcontextprotocol.io/introduction) is een protocol dat door Anthropic is ontwikkeld voor Claude. MCP is de afkorting van Model Context Protocol en stelt een LLM in staat om actief acties of resources bij de buitenwereld op te vragen. Omdat MCP letterlijk niets meer is dan een protocol voor het versturen van verzoeken en antwoorden, moeten het proces en de uitvoering door de ontwikkelaar zelf worden verzorgd.

### Over de interne werking

Voordat ik de interne werking uiteenzet, wil ik kort ingaan op [Gemini Function Calling](https://ai.google.dev/gemini-api/docs/function-calling). Gemini Function Calling stelt, net als MCP, een LLM in staat om op eigen initiatief externe acties aan te roepen. Je zult zich afvragen waarom Function Calling hier eigenlijk wordt besproken. De reden is dat Function Calling eerder verscheen dan MCP en dat het, doordat beide een OpenAPI-schema gebruiken, compatibel is; daarom veronderstel ik dat de werking van beide sterk op elkaar lijkt. Omdat de uitleg van Gemini Function Calling relatief uitgebreid is, lijkt het nuttig om die hier te behandelen.

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

De algemene stroom verloopt als volgt.

1. Definieer de functie.
2. Stuur de functiedefinitie samen met de prompt naar 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. Gemini vraagt om een functieaanroep indien nodig.
   1. Indien Gemini dit nodig acht, ontvangt de aanroeper de naam en parameters voor de functieaanroep.
   2. De aanroeper kan bepalen of hij de functie wel of niet uitvoert.
      1. De functie aanroepen en een geldige waarde teruggeven
      2. De functie niet aanroepen maar wel gegevens teruggeven alsof dit wel is gebeurd
      3. Het verzoek simpelweg negeren
4. Gemini kan in het bovenstaande proces meerdere functies tegelijk aanroepen, of na het bekijken van het resultaat een functie opnieuw aanroepen, en voert dergelijke acties uit en vraagt deze op.
5. Wanneer er uiteindelijk een geordend antwoord is, eindigt het proces.

Deze stroom komt over het algemeen overeen met MCP. Dit wordt ook op vergelijkbare wijze beschreven in de [MCP-tutorial](https://modelcontextprotocol.io/tutorials/building-mcp-with-llms). Ook ollama tools werkt op vergelijkbare wijze.

En gelukkig, werkelijk een grote meevaller, delen deze drie hulpmiddelen, ollama tools, MCP en Gemini Function Calling, nagenoeg dezelfde schemastructuur, zodat je door alleen MCP te implementeren het op alle drie kunt gebruiken.

Ach, en er is ook een nadeel dat alle drie delen. Omdat het uiteindelijk het model is dat de functies uitvoert, kan een model in een slechte staat functies niet aanroepen, ze onjuist aanroepen, of een DoS-aanval op de MCP-server veroorzaken.

## Een MCP-host in Go

### mark3lab's mcphost

In Go bestaat er [mcphost](https://github.com/mark3labs/mcphost), dat wordt ontwikkeld door de organisatie mark3lab.

Het gebruik is erg eenvoudig.

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

Maak na de installatie het bestand `$HOME/.mcp.json` aan en schrijf het als volgt.

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

Voer vervolgens het volgende uit met een ollama-model.  
Indien nodig haalt u daarvoor eerst het model op met `ollama pull mistral-small`.

> Over het algemeen raad ik claude of qwen2.5 aan, maar op dit moment raad ik mistral-small aan.

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

Als je het zo uitvoert, kun je het echter alleen gebruiken als vraag-en-antwoordsysteem in een CLI-omgeving.  
Daarom gaan we de code van deze `mcphost` aanpassen zodat het op een meer programmeerbare manier kan werken.

### Fork van mcphost

Zoals we al hebben gezien, bevat `mcphost` de functionaliteit om via MCP metadata op te halen en functies aan te roepen. Daarom is er een onderdeel nodig voor het aanroepen van de LLM, het omgaan met de MCP-server en het beheren van de berichtengeschiedenis.

Het onderdeel dat hiervoor is overgenomen, is de `Runner` uit het volgende pakket.

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

De interne declaraties van dit onderdeel bespreek ik niet afzonderlijk. Ze hebben echter vrijwel dezelfde namen als wat ze representeren.

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

Voor de `mcpClients` en `tools` die hier worden gebruikt, raadpleegt u [dit bestand](https://github.com/snowmerak/mcphost/blob/main/internal/runner/from.go).  
Voor `provider` gebruiken we die van ollama, dus raadpleegt u [dit bestand](https://github.com/snowmerak/mcphost/blob/main/pkg/llm/ollama/provider.go).

Het belangrijkste onderdeel is de methode `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
}
```

De code zelf is samengesteld uit delen van [dit bestand](https://github.com/snowmerak/mcphost/blob/main/cmd/mcp.go).

De inhoud is grofweg als volgt.

1. Stuur de lijst met tools samen met de prompt om te vragen of er uitgevoerd moet worden, of om een antwoord te genereren.
2. Zodra een antwoord is gegenereerd, stopt de recursie en wordt het geretourneerd.
3. Als de LLM een verzoek tot uitvoering van een tool achterlaat, roept de host de MCP-server aan.
4. Voeg het resultaat toe aan de geschiedenis en keer terug naar stap 1.

## Tot slot

> Al klaar?

Eigenlijk heb ik niet zoveel te zeggen. Dit artikel is grofweg bedoeld om u inzicht te geven in hoe een MCP-server werkt. Ik hoop dat dit artikel u enigszins heeft geholpen om de werking van een MCP-host te begrijpen.
