---
id: a1fad13de24c90924cfa9e25e9f986cb
author: snowmerak
title: Att förstå MCP-värden lite grundligare
description: Detta är en text som syftar till att ge en förståelse för Anthropics MCP-protokoll och för MCP-värden (mcphost) implementerad i Go.
language: sv
date: 2025-04-08T00:59:42.416582136Z
path: /blog/posts/a-little-understanding-of-mcp-host-zb952aae0
category: ai
---

## Vad är MCP

[MCP](https://modelcontextprotocol.io/introduction) är ett protokoll som Anthropic utvecklat för Claude. MCP är en förkortning av Model Context Protocol och är ett protokoll som gör det möjligt för LLM:er att aktivt begära åtgärder eller resurser från externa system. Eftersom MCP i bokstavlig mening endast är ett protokoll för att skicka förfrågningar och svar, måste utvecklaren själv hantera processen och exekveringen.

### Om den interna funktionen

Innan vi förklarar den interna funktionen vill jag kort behandla [Gemini Function Calling](https://ai.google.dev/gemini-api/docs/function-calling). Gemini Function Calling gör, precis som MCP, att LLM:en självständigt kan anropa externa funktioner. Man kan då undra varför Function Calling överhuvudtaget tas upp. Anledningen är att Function Calling lanserades före MCP och att den, eftersom den också använder OpenAPI-schema, är kompatibel med MCP, vilket gör att deras beteende antas vara likartat. Eftersom beskrivningen av Gemini Function Calling är relativt detaljerad, bedömer jag att den kan vara till hjälp och därför tar jag upp den här.

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

Det övergripande flödet ser ut så här.

1. Funktionen definieras.
2. Funktionsdefinitionen skickas till Gemini tillsammans med prompten.
   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 begär ett funktionsanrop om det behövs.
   1. Om Gemini behöver ett funktionsanrop tar anroparen emot namnet och parametrarna för anropet.
   2. Anroparen kan besluta om att utföra anropet eller inte.
      1. Anropa funktionen och returnera ett giltigt värde
      2. Anropa inte funktionen utan returnera data som om den hade anropats
      3. Ignorera den helt
4. Gemini kan inom denna process begära att flera funktioner anropas samtidigt, eller att en funktion anropas och därefter, med hänsyn till resultatet, att ytterligare anrop görs.
5. När ett välstrukturerat svar erhålls avslutas processen.

Detta flöde överensstämmer i allmänhet med MCP. Det beskrivs på liknande sätt i [MCP:s handledning](https://modelcontextprotocol.io/tutorials/building-mcp-with-llms). Det liknar även ollama tools.

Och lyckligtvis är dessa tre verktyg, ollama tools, MCP och Gemini Function Calling, i praktiken baserade på en gemensam schemastruktur, så det är möjligt att implementera endast MCP och använda det i alla tre miljöerna.

Det finns också en nackdel som alla delar. Eftersom det i slutändan är modellen som utför anropen, kan en modell i dålig form underlåta att anropa funktioner, anropa dem felaktigt eller till och med överbelasta MCP-servern med DoS-liknande trafik och därmed bete sig felaktigt.

## MCP-värd i Go

### mark3lab:s mcphost

I Go finns [mcphost](https://github.com/mark3labs/mcphost), som utvecklas av organisationen mark3lab.

Användningen är mycket enkel.

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

Efter installationen skapar du filen `$HOME/.mcp.json` och skriver följande i den.

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

Kör sedan följande med en ollama-modell.  
Om det behövs hämtar du dessförinnan modellen med `ollama pull mistral-small`.

> I allmänhet rekommenderas Claude eller qwen2.5, men för närvarande rekommenderar jag mistral-small.

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

Om du kör på detta sätt kan du dock endast använda det som ett frågesvarssystem i CLI-miljö.  
Därför ska vi ändra koden i `mcphost` så att det kan fungera på ett mer programmerbart sätt.

### Fork av mcphost

Som redan konstaterats innehåller `mcphost` funktioner för att extrahera metadata och anropa funktioner med hjälp av MCP. Därför behövs delar som hanterar anrop till LLM, hanterar MCP-servrar och hanterar meddelandehistoriken.

Den del som motsvarar detta är `Runner` i följande paket.

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

Jag går inte igenom de interna deklarationerna i detta avsnitt. De är dock i stort sett namngivna efter sin funktion.

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

För `mcpClients` och `tools` som används här, se [denna fil](https://github.com/snowmerak/mcphost/blob/main/internal/runner/from.go).  
För `provider` använder vi ollamas variant, så se [denna fil](https://github.com/snowmerak/mcphost/blob/main/pkg/llm/ollama/provider.go).

Huvudrätten är metoden `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
}
```

Kodens grundstruktur är en sammanslagning av delar från [denna fil](https://github.com/snowmerak/mcphost/blob/main/cmd/mcp.go).

I huvudsak fungerar det så här.

1. Verktygslistan skickas tillsammans med prompten för att begära antingen ett beslut om exekvering eller ett genererat svar.
2. När ett svar har genererats avbryts rekursionen och svaret returneras.
3. Om LLM:en begär exekvering av ett verktyg anropar värden MCP-servern.
4. Resultatet läggs till i historiken och processen återgår till steg 1.

## Avslutningsvis

> Redan slut?

Egentligen finns det inte så mycket mer att säga. Texten har skrivits för att ge en översiktlig förståelse för hur en MCP-server fungerar. Jag hoppas att den har bidragit något, om än litet, till att förstå hur en MCP-värd fungerar.
