golang 如何写一个mcp server

TwoAdmin 2025-9-17 109 9/17

如何使用 Go (Golang) 编写一个 Model Context Protocol (MCP) 服务器。

MCP 是一种允许工具(如 Claude AI)与外部数据源、应用程序或服务安全交互的协议。编写一个 MCP Server 就是创建一个遵循该协议的进程,通过 STDIN/STDOUT 与客户端进行 JSON-RPC 通信。


核心概念

一个 MCP 服务器需要实现以下核心组件:

  1. 传输层 (Transport): 通过 STDIN/STDOUT 进行 JSON-RPC 2.0 通信。

  2. 资源 (Resources): 提供可读取的数据流(如文件、数据库内容、API 数据)。

  3. 工具 (Tools): 提供可执行的操作(如执行命令、写入数据、调用外部API)。

  4. 提示词模板 (Prompts): 提供预定义的提示词片段。


实现步骤

我们将使用官方提供的 Go SDK github.com/modelcontextprotocol/sdk-go 来简化开发。

第 1 步:初始化项目并安装依赖

bash

mkdir my-mcp-server
cd my-mcp-server
go mod init my-mcp-server
go get github.com/modelcontextprotocol/sdk-go

第 2 步:创建主文件 main.go

以下是创建一个简单 MCP 服务器的完整示例,它包含资源、工具和提示词模板。

go
package main

import (
	"context"
	"fmt"
	"log"
	"strings"

	mcp "github.com/modelcontextprotocol/sdk-go"
)

func main() {
	// 1. 创建服务器实例
	server := mcp.NewServer(
		"my-file-server", // 服务器名称
		"1.0.0",          // 版本号
	)

	// 2. 添加资源:提供一个虚拟的 "notes" 目录
	server.AddResourceHandler("notes", func(ctx context.Context, method string, params map[string]any) (mcp.ResourceResult, error) {
		// method 可以是 "read" 或 "list"
		name, _ := params["name"].(string)

		if method == "list" {
			// 返回可用笔记列表
			return mcp.ResourceResult{
				Contents: []mcp.ResourceContent{
					{
						URI: "notes:///todo.txt",
						// 在 list 操作中,content 是可选的,通常用于预览
					},
					{
						URI: "notes:///ideas.txt",
					},
				},
			}, nil
		}

		// method == "read"
		// 根据请求的 URI 返回不同的内容
		switch name {
		case "notes:///todo.txt":
			return mcp.ResourceResult{
				Contents: []mcp.ResourceContent{
					{
						URI:     "notes:///todo.txt",
						Content: "1. Learn MCP\n2. Build a server\n3. Integrate with Claude",
						MimeType: "text/plain",
					},
				},
			}, nil
		case "notes:///ideas.txt":
			return mcp.ResourceResult{
				Contents: []mcp.ResourceContent{
					{
						URI:     "notes:///ideas.txt",
						Content: "Project Idea: AI-powered task manager\nAnother Idea: Automated code reviewer",
						MimeType: "text/plain",
					},
				},
			}, nil
		default:
			return mcp.ResourceResult{}, fmt.Errorf("note not found: %s", name)
		}
	})

	// 3. 添加工具:一个简单的字符串处理工具
	server.AddToolHandler("reverse", func(ctx context.Context, params map[string]any) (mcp.ToolResult, error) {
		input, ok := params["input"].(string)
		if !ok {
			return mcp.ToolResult{}, fmt.Errorf("input parameter is required and must be a string")
		}

		// 反转字符串
		runes := []rune(input)
		for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
			runes[i], runes[j] = runes[j], runes[i]
		}
		result := string(runes)

		return mcp.ToolResult{
			Content: []mcp.Content{
				{
					Type: "text",
					Text: fmt.Sprintf("Original: %s\nReversed: %s", input, result),
				},
			},
		}, nil
	}, mcp.ParamSchema{
		Type: mcp.JSONSchemaObject,
		Properties: map[string]mcp.ParamSchema{
			"input": {
				Type:        mcp.JSONSchemaString,
				Description: "The string to reverse",
			},
		},
		Required: []string{"input"},
	})

	// 4. 添加提示词模板
	server.AddPromptHandler("greeting", func(ctx context.Context, params map[string]any) (mcp.PromptResult, error) {
		name, _ := params["name"].(string)
		if name == "" {
			name = "there"
		}

		return mcp.PromptResult{
			Messages: []mcp.PromptMessage{
				{
					Role: "user",
					Content: []mcp.Content{
						{
							Type: "text",
							Text: fmt.Sprintf("Hello %s! I'm your MCP server assistant. How can I help you today?", name),
						},
					},
				},
			},
		}, nil
	}, mcp.ParamSchema{
		Type: mcp.JSONSchemaObject,
		Properties: map[string]mcp.ParamSchema{
			"name": {
				Type:        mcp.JSONSchemaString,
				Description: "Name of the person to greet",
			},
		},
	})

	// 5. 启动服务器
	if err := server.Run(); err != nil {
		log.Fatalf("Server error: %v", err)
	}
}

第 3 步:构建和测试

bash


# 构建
go build -o my-mcp-server

# 测试 STDIN/STDOUT 通信(可选,手动测试)
echo '{"jsonrpc":"2.0","id":1,"method":"listResources"}' | ./my-mcp-server

第 4 步:配置客户端(以 Claude AI 为例)

创建配置文件 ~/.config/claude/claude_desktop_config.json

json
{
  "mcpServers": {
    "my-file-server": {
      "command": "/absolute/path/to/your/my-mcp-server"
    }
  }
}

或者在项目根目录创建 mcp.json

json
{
  "mcpServers": {
    "my-file-server": {
      "command": "go",
      "args": ["run", "/absolute/path/to/your/main.go"]
    }
  }
}

更高级的示例:文件系统资源

以下是一个更实用的示例,提供对真实文件系统的访问:

go
package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"path/filepath"

	mcp "github.com/modelcontextprotocol/sdk-go"
)

type FileServer struct {
	baseDir string
}

func NewFileServer(baseDir string) *FileServer {
	return &FileServer{baseDir: baseDir}
}

func (s *FileServer) HandleResource(ctx context.Context, method string, params map[string]any) (mcp.ResourceResult, error) {
	uri, ok := params["name"].(string)
	if !ok {
		return mcp.ResourceResult{}, fmt.Errorf("name parameter is required")
	}

	// 将 URI 转换为本地文件路径
	localPath := filepath.Join(s.baseDir, strings.TrimPrefix(uri, "file:///"))

	if method == "list" {
		return s.listFiles(localPath)
	}
	
	// method == "read"
	return s.readFile(localPath, uri)
}

func (s *FileServer) listFiles(dirPath string) (mcp.ResourceResult, error) {
	entries, err := os.ReadDir(dirPath)
	if err != nil {
		return mcp.ResourceResult{}, err
	}

	contents := []mcp.ResourceContent{}
	for _, entry := range entries {
		uri := "file:///" + entry.Name()
		if entry.IsDir() {
			uri += "/"
		}
		contents = append(contents, mcp.ResourceContent{
			URI:  uri,
			Name: entry.Name(),
		})
	}

	return mcp.ResourceResult{Contents: contents}, nil
}

func (s *FileServer) readFile(filePath, uri string) (mcp.ResourceResult, error) {
	content, err := os.ReadFile(filePath)
	if err != nil {
		return mcp.ResourceResult{}, err
	}

	return mcp.ResourceResult{
		Contents: []mcp.ResourceContent{
			{
				URI:      uri,
				Content:  string(content),
				MimeType: "text/plain", // 可以根据文件扩展名设置正确的 MIME 类型
			},
		},
	}, nil
}

func main() {
	server := mcp.NewServer("file-server", "1.0.0")
	
	fileServer := NewFileServer("/tmp") // 提供 /tmp 目录的访问
	
	server.AddResourceHandler("files", fileServer.HandleResource)
	
	if err := server.Run(); err != nil {
		log.Fatalf("Server error: %v", err)
	}
}


关键要点

  1. JSON-RPC 协议: 所有通信都通过 STDIN/STDOUT 使用 JSON-RPC 2.0。

  2. 错误处理: 始终返回适当的错误信息。

  3. 模式定义: 为工具和提示词定义清晰的参数模式。

  4. 资源URI: 使用合适的 URI 方案(如 file:///, notes:///)。

  5. 内容类型: 正确设置 MIME 类型以便客户端正确处理内容。

调试技巧

  1. 日志记录: 使用 log.Printf() 输出调试信息(客户端通常会捕获 STDERR)。

  2. 手动测试: 通过 echo 命令手动发送 JSON-RPC 请求来测试服务器。

  3. 客户端日志: 查看 Claude 或其他客户端的日志来了解通信详情。

这个基础框架应该能帮助你开始构建自己的 MCP 服务器。根据你的具体需求,你可以实现更复杂的资源、工具和提示词模板。

- THE END -
Tag:

TwoAdmin

9月17日17:19

最后修改:2025年9月17日
0

非特殊说明,本博所有文章均为博主原创。