---
id: a1fad13de24c90924cfa9e25e9f986cb
author: snowmerak
title: MCP host'unu biraz anlamak
description: Bu yazı, Anthropic'in MCP protokolü ile Go dilinde geliştirilmiş MCP ana makinesi (mcphost) hakkında anlayışı kolaylaştırmayı amaçlamaktadır.
language: tr
date: 2025-04-08T00:59:42.416582136Z
path: /blog/posts/a-little-understanding-of-mcp-host-zb952aae0
category: ai
---

## MCP Nedir

[MCP](https://modelcontextprotocol.io/introduction), Anthropic tarafından Claude için geliştirilmiş bir protokoldür. MCP, Model Context Protocol ifadesinin kısaltması olup LLM'nin dış dünyadan aktif olarak bir eylem veya kaynak talep edebilmesini sağlayan bir protokoldür. MCP, tam anlamıyla yalnızca istek ve yanıt veren bir protokol olduğu için bu sürecin ve yürütmenin geliştirici tarafından gerçekleştirilmesi gerekir.

### İç İşleyiş Hakkında

İç işleyişi açıklamadan önce [Gemini Function Calling](https://ai.google.dev/gemini-api/docs/function-calling) üzerinde durmak istiyorum. Gemini Function Calling de MCP ile aynı şekilde LLM'nin dış eylemleri öncülük ederek çağırmasına olanak tanır. Peki neden Function Calling'i ele aldığımız sorusu akla gelebilir. Bunu ele almamın nedeni, Function Calling'in MCP'den önce ortaya çıkmış olması ve her ikisinin de OpenAPI şemasını kullanması nedeniyle uyumlu olmaları, dolayısıyla birbirlerine benzer bir işleyişe sahip olabilecekleri düşüncesidir. Ayrıca Gemini Function Calling'in açıklamalarının nispeten daha ayrıntılı olması nedeniyle yardımcı olacağı düşünülerek ele alınmıştır.

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

Genel akış şu şekildedir:

1. Fonksiyonu tanımlarız.
2. Fonksiyon tanımını bir istemle birlikte Gemini'ye göndeririz.
   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 gerekirse fonksiyon çağrısı talep eder.
   1. Gemini gerekli görürse, fonksiyon çağrısı için gereken ad ve parametreleri çağırana iletilir.
   2. Çağıran taraf çalıştırıp çalıştırmamaya karar verebilir.
      1. Çağırıp geçerli bir değer döndürmek
      2. Çağırmadan, çağrılmış gibi veri döndürmek
      3. Tamamen yok saymak
4. Gemini yukarıdaki süreçte bir seferde birden fazla fonksiyonu çağırabilir veya fonksiyon çağrısının sonucuna bakarak yeniden çağrı yapabilir; bu tür işlemleri gerçekleştirir ve talep eder.
5. Sonuçta düzenli bir yanıt elde edildiğinde süreç sona erer.

Bu akış genel olarak MCP ile örtüşmektedir. Bu durum, [MCP eğitiminde](https://modelcontextprotocol.io/tutorials/building-mcp-with-llms) de benzer biçimde açıklanmaktadır. Ollama tools da benzer bir yapıya sahiptir.

Ve gerçekten şanslıyız ki bu üç araç, yani ollama tools, MCP ve Gemini Function Calling, şema yapısını neredeyse ortaklaşa kullandığından, yalnızca MCP'yi uygulayarak üçünde de kullanabilirsiniz.

Ayrıca hepsinin ortak bir dezavantajı vardır. Sonuçta çalıştırmayı model yaptığı için kullandığınız modelin durumu kötüyse, fonksiyonu hiç çağırmayabilir, yanlış çağırabilir veya MCP sunucusuna DoS saldırısı gibi yanlış davranışlarda bulunabilir.

## Go ile Yazılmış MCP Host

### mark3lab'in mcphost'u

Go dünyasında, mark3lab adlı kuruluş tarafından geliştirilmekte olan [mcphost](https://github.com/mark3labs/mcphost) bulunmaktadır.

Kullanımı oldukça basittir.

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

Kurulumdan sonra `$HOME/.mcp.json` dosyasını oluşturup aşağıdaki gibi yazarsınız.

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

Ardından aşağıdaki gibi ollama modeliyle çalıştırırsınız.  
Tabii ki bundan önce gerekirse `ollama pull mistral-small` komutuyla modeli indirirsiniz.

> Genel olarak claude veya qwen2.5 önerilir, ancak ben şimdilik mistral-small'ı öneriyorum.

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

Ancak bu şekilde çalıştırdığınızda yalnızca CLI ortamında soru-cevap biçiminde kullanabilirsiniz.  
Bu nedenle `mcphost` kodunu değiştirerek daha programlanabilir biçimde çalışabilmesini sağlayacağız.

### mcphost Forku

Zaten gördüğünüz üzere `mcphost`, MCP'yi kullanarak meta verileri çıkarma ve fonksiyon çağırma özelliklerini içermektedir. Bu nedenle LLM'yi çağıran kısım, MCP sunucusunu yöneten kısım ve mesaj geçmişini yöneten kısım gereklidir.

İlgili kısımları alarak aşağıdaki paketteki `Runner` yapısını oluşturduk.

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

Bu kısmın iç tanımlamalarını ayrıca incelemeyeceğiz. Ancak neredeyse isimleri kendisini açıklamaktadır.

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

Burada kullanılacak `mcpClients` ve `tools` hakkında [ilgili dosyaya](https://github.com/snowmerak/mcphost/blob/main/internal/runner/from.go) bakmanızı rica ederim.  
`provider` olarak ollama'nın sağlayıcısını kullanacağız, bu nedenle [ilgili dosyaya](https://github.com/snowmerak/mcphost/blob/main/pkg/llm/ollama/provider.go) bakmanızı rica ederim.

Ana yemek ise `Run` metodudur.

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

Kodun kendisi, [ilgili dosyadaki](https://github.com/snowmerak/mcphost/blob/main/cmd/mcp.go) bazı bölümlerin bir araya getirilmesiyle oluşturulmuştur.

İçeriği kabaca şöyledir:

1. Araç listesini istemle birlikte göndererek çalıştırma olup olmadığını veya yanıt üretilmesini talep ederiz.
2. Yanıt üretildiğinde özyinelemeyi durdurur ve döndürürüz.
3. LLM bir araç çalıştırma talebi bırakırsa, host MCP Server'ı çağırır.
4. Yanıtı geçmişe ekleyip tekrar 1. adıma döneriz.

## Sonuç

> Şimdiden mi bitti?

Aslında söylenecek pek çok şey yok. Bu yazı, esas olarak MCP Server'ın nasıl çalıştığına dair bir anlayış kazandırmak amacıyla hazırlanmıştır. Bu yazının, MCP host'un işleyişini anlamanızda size az da olsa yardımcı olmasını umuyorum.
