如何使用golang实现一个MCP-Client

之前的几篇文章中,主要跟大家分享了如何创建一个MCP-Server,以及如何调试MCP-Server。今天,我们就写一个简单MCP-Client来与之前写的pgsql-mcp-server的交互示例,来看看MCP-Client是如何和MCP-Server进行通信的。

1. 如何编写一个MCP-Client

在官方For Client Developers的快速开始中,提供了几种编程语言的示例代码,但是示例代码写死了mcp服务的提供要么是js文件要么是py文件,所以分享一个通用的可以连接任意代码编写的MCP-Client的是有点价值的,这里我仅示例客户端和mcp-server的通信,和LLM大模型的交互后面再做分享。

1.1 读取MCP-Server的配置文件

我们看到许多MCP Host(例如cline、Claude for Desktop等)都是提供一个配置文件,供使用者将其要用到的MCP-Server进行配置连接,所以,我们先来读取配置文件。

这里仿照cline的配置,给出示例代码:

go 复制代码
type MCPConfig struct {
	McpServers map[string]MCPServer `json:"mcpServers"`
}

type MCPServer struct {
	Command     string            `json:"command"`
	Args        []string          `json:"args"`
	Env         map[string]string `json:"env"`
	Disabled    bool              `json:"disabled"`
	AutoApprove []string          `json:"autoApprove"`
}

这里忽略掉读取文件的代码,直接给出要解析的配置文件:

go 复制代码
	configData := `{
		"mcpServers": {
		  "pgsql-mcp-server": {
			"command": "C:\\Users\\Administrator\\go\\bin\\sql-mcp-server.exe",
			"args": [],
			"env": {
			  "GOPATH": "C:\\Users\\Administrator\\go",
			  "GOMODCACHE": "C:\\Users\\Administrator\\go\\pkg\\mod",
			  // 省略其他环境变量
			  ...
			  "DB_SSLMODE": "disable"
			},
			"disabled": false,
			"autoApprove": []
		  }
		}
	  }`

	// 解析配置文件
	var config MCPConfig
	if err := json.Unmarshal([]byte(configData), &config); err != nil {
		fmt.Printf("Failed to parse config file: %v\n", err)
		return
	}

1.2 初始化MCP-Client

通过上面的步骤,我们以及知道了待连接的pgsql-mcp-server配置信息,我们现在可以初始化一个MCP-Client,将其配置传入到MCP-Client中。

go 复制代码
	c, err := client.NewStdioMCPClient(
		mcpServerConfig.Command,
		envSlice,
		mcpServerConfig.Args...,
	)
	if err != nil {
		fmt.Printf("Failed to createa new stdio-based MCP client: %v\n", err)
		return
	}
	defer c.Close()

1.3 连接MCP-Server

现在可以与pgsql-mcp-server 进行通信,这里我们使用MCP-Client的Initialize方法进行初始化,并打印出pgsql-mcp-server的版本信息。

go 复制代码
	initRequest := mcp.InitializeRequest{}
	initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
	initRequest.Params.ClientInfo = mcp.Implementation{
		Name:    "mcp-client",
		Version: "1.0.0",
	}

	initResult, err := c.Initialize(ctx, initRequest)
	if err != nil {
		log.Fatalf("Failed to initialize: %v", err)
	}
    	fmt.Printf(
		"Initialized with server: %s %s\n\n",
		initResult.ServerInfo.Name,
		initResult.ServerInfo.Version,
	)

如果没有问题的话,将会打印出如下内容:

vbscript 复制代码
Initializing client...
Initialized with server: sql-mcp-server 🚀 1.0.0

2. 交互

MCP-Client主要和MCP-Server交互Resources,Prompts Tools,Sampling以及Roots,这里由于我上次写的pgsql-mcp-server只提供了Tools,所以下面仅说明List Tools和Call Tools的两种场景。

2.1 List Tools

go 复制代码
toolsRequest := mcp.ListToolsRequest{}
	tools, err := c.ListTools(ctx, toolsRequest)
	if err != nil {
		log.Fatalf("Failed to list tools: %v", err)
	}
	for _, tool := range tools.Tools {
		fmt.Printf("- %s: %s\n", tool.Name, tool.Description)
	}
	fmt.Println()

上面代码成功运行后,将会打印出如下内容:

  • create_table: Create a new table in the postgres database
  • list_tables: List all user tables in the database
  • read_query: Execute a SELECT query on the postgres database
  • write_query: Execute an INSERT, UPDATE, or DELETE query on the postgres database

2.1 Call Tools

上面已经给出了四个tool,接下来就实现对应的交互代码即可

go 复制代码
// List tables
	fmt.Println("Listing tables...")
	listTableRequest := mcp.CallToolRequest{
		Request: mcp.Request{
			Method: "tools/call",
		},
	}
	listTableRequest.Params.Name = "list_tables"

	result, err := c.CallTool(ctx, listTableRequest)
	if err != nil {
		log.Fatalf("Failed to list allowed directories: %v", err)
	}
	printToolResult(result)

	// Read table
	fmt.Println("Reading table...")
	readTableRequest := mcp.CallToolRequest{}
	readTableRequest.Params.Name = "read_query"
	readTableRequest.Params.Arguments = map[string]interface{}{
		"query": "SELECT * FROM users LIMIT 10",
	}

	result, err = c.CallTool(ctx, readTableRequest)
	if err != nil {
		log.Fatalf("Failed to create directory: %v", err)
	}
	printToolResult(result)
go 复制代码
func printToolResult(result *mcp.CallToolResult) {
	for _, content := range result.Content {
		if textContent, ok := content.(mcp.TextContent); ok {
			fmt.Println(textContent.Text)
		} else {
			jsonBytes, _ := json.MarshalIndent(content, "", "  ")
			fmt.Println(string(jsonBytes))
		}
	}
}

上面代码成功运行后,将会打印出如下内容:

less 复制代码
Listing tables...
Tables: [students articles]

Creating table...
Table created successfully

Writing table...
Operation successful. Rows affected: 1

Reading table...
Query results: [map[created_at:2025-03-29 03:52:12.545937 +0000 +0000 email:john@example.com id:1 name:John Doe]]

3. 总结

ok,以上就是一个简单的MCP-Client的示例代码,通过这个示例,大家可以了解如何使用golang来编写一个MCP-Client,以及如何使用MCP-Client与MCP-Server进行通信,完整的示例代码我已经提交到github,请参考demo_for_AI。

相关推荐
lgcgkCQ4 小时前
【AI 转型实战 1】SpringAI 入门:搭建基础大模型对话能力
java·ai·agent·rag·spring ai·mcp·langchain4j
情 九7 小时前
MCP 实战 04:把自己的工具接进 Cherry Studio
mcp
念念不忘 必有回响10 小时前
从0到1:搭建你的第一个 MCP 服务器实战
agent·mcp
hourinX11 小时前
从 v1 到 v2,我重新拆了头像生成的整条链路—— gdenticon 重构日志(二)
go
情 九14 小时前
MCP 工具开发实战:参数校验、结构化输出与错误处理
mcp
FungLeo14 小时前
成为全栈·Go 后端篇·分类树与标签关系:目录变更为何需要协调
golang·go·gorm·数据库事务·分类树·成为全栈
FungLeo15 小时前
成为全栈·Go 后端篇·文章查询与展示投影:安全分页、排序和软删除
golang·go·gorm·分页查询·成为全栈·sql安全
xrlfreedom1 天前
MCP 面试高频考点:桌面客户端 stdio 连接失败,如何用 Resources、结构化输出与 OpenTelemetry 排查?
resources·opentelemetry·结构化输出·mcp
码哥字节1 天前
AI 编码工具默认给 Agent 多大权限?三家差在这三处
mcp·claude code·ai编程工具