使用Golang开发mcp-server服务

1. MCP协议基础知识

什么是MCP?

模型上下文协议(Model Context Protocol,简称 MCP)是一种开放标准,旨在标准化大型语言模型(LLM)与外部数据源和工具之间的交互方式。 由 Anthropic 于 2024 年 11 月推出,MCP 通过定义统一的接口,使 AI 应用能够安全、灵活地访问和操作本地及远程数据资源,提升模型的功能性和可扩展性。

MCP 的通用架构

MCP 遵循 CS 架构(client-server),具体包含的组件如下:

Host 主机:发起连接 LLM 的应用程序,例如 Claude for Desktop 或其他的 AI 应用。

MCP Client 客户端:运行在主机里的客户端,与 MCP Server 服务器保持 1:1 连接,负责协议通信。

MCP Server 服务器:负责向客户端提供 资源、提示 和 工具 的服务器。

三大核心组件

Resources(资源)

Resources(资源)是 MCP 协议中的核心原语之一,服务器通过它可以向客户端提供可读的数据或内容,用作 LLM 交互的上下文信息。每个资源通过 URI 进行标识,格式如下:

[协议]://[主机]/[路径]

示例:

file:///home/www/documents/go.pdf
postgres://database/www/schema
screen://localhost/www/display1

示例:

// 静态资源示例
resource := mcp.NewResource(
    "config://settings",
    "应用配置信息",
    mcp.WithResourceDescription("获取当前应用的配置参数"),
    mcp.WithMIMEType("application/json"),
)

// 动态资源示例 
userProfileTemplate := mcp.NewResourceTemplate(
    "user://profile/{userId}",
    "用户资料",
    mcp.WithTemplateDescription("获取指定用户的详细信息"),
)

资源类型:

**文本类型:**包含 UTF_8 编码的文本数据,例如:文本、代码、日志等等

**二进制资源:**包含 Base64 编码的原始二进制数据,例如:图片、PDF文件、音视频等等

客户端可以通过调用服务器的 resources/list 获取资源列表:

{
  "uri": "string",             // 资源唯一标识符
  "name": "string",            // 资源名称(人类可读)

  "description": "string",     // 可选,资源描述
  "mimeType": "string"         // 可选,资源 MIME 类型
}

客户还可通过 resources/read 接口读取资源内容,只需传入资源的 URI。返回结构如下:

{
  contents: [
    {
      uri: string;        // 资源唯一标识符
      mimeType?: string;  // 可选,资源 MIME 类型

      // 以下二选一
      text?: string;      // 文本资源
      blob?: string;      // 二进制资源
    }
  ]
}

Tools(工具)

    Tools(工具) 是 MCP 协议中的一项关键原语,服务器可通过它向客户端暴露可执行功能,供 LLM 使用(通常需要用户批准,确保人类参与决策)。Tools 的核心概念包括:

  • 发现(Discovery):客户端可通过 tools/list 接口获取可用工具列表。

  • 调用(Invocation):客户端可通过 tools/call接口发起工具调用请求,由服务器执行具体操作并返回结果。

  • 灵活性(Flexibility):工具既可以是简单的计算函数,也可以是复杂的 API 集成。

tools结构定义:

{
  "name": "string",                // 工具唯一标识符
  "description": "string",         // 可选,工具描述

  "inputSchema": {                 // 工具参数的 JSON Schema
    "type": "object",

    "properties": { ... }          // 工具参数定义
  }
}

使用go代码实例化定义:

// 计算工具
calculatorTool := mcp.NewTool("calculate",
    mcp.WithDescription("执行数学计算"),
    mcp.WithString("expression", mcp.Required(), 
        mcp.Description("数学表达式,如 '2+3*4'")),
    mcp.WithString("format", 
        mcp.Description("结果格式: decimal, fraction, scientific")),
)

// 文件操作工具
fileWriteTool := mcp.NewTool("write_file",
    mcp.WithDescription("写入文件内容"),
    mcp.WithString("path", mcp.Required()),
    mcp.WithString("content", mcp.Required()),
    mcp.WithBoolean("append", mcp.Description("是否追加模式")),
)

Prompts(提示词)

提示词 是服务器定义可复用的提示词模板和工作流,客户端可以轻松将这些模板呈现给用户或 LLM

结构:

{
  "name": "string",               // 提示词唯一标识符
  "description": "string",        // 可选,人类可读的描述
  "arguments": [                  // 可选参数列表
    {
      "name": "string",           // 参数标识符
      "description": "string",    // 可选,参数描述
      "required": "boolean"       // 是否为必填参数
    }
  ]
}

使用go代码实例化定义:

codeReviewPrompt := mcp.NewPrompt("code_review",
    mcp.WithPromptDescription("代码审查模板"),
    mcp.WithPromptArgument("language", "编程语言"),
    mcp.WithPromptArgument("code", "要审查的代码"),
)

客户端可以通过调用 prompts/list 获取可用的提示词列表:

{
  "prompts": [
    {
      "name": "code_review",                       // 提示词唯一标识符
      "description": "代码审查模板",   // 提示词描述
      "arguments": [
        {
          "name": "language",                       // 参数名称
          "description": "编程语言",                // 参数描述
          "required": true                          // 是否必填
        }
      ]
    }
  ]
}

综合说明

Prompts(提示词)、工具(Tool)、资源(Resource)形成互补

  • Tool 解决“执行动作”的问题;

  • Resource 提供“静态/动态文件”或长文本;

  • Prompt 则专注于“对话内容模板”

因此在一个常见工作流中,可以:

  • 通过 Tool 计算、抓取或生成某些数据;

  • 把数据保存为文件或直接包装成 ResourceContents 由资源回调返回;

  • 让 Prompt 引用这个资源 URI,把内容作为上下文发给模型。

传输类型

MCP 协议内置了两种标准传输方式:标准输入/输出(stdio) 和 Server-Sent Events(SSE) 、Streamable HTTP

标准输入/输出(stdio)

stdio 传输通过 标准输入输出流 实现客户端与服务器之间的通信,适用于本地集成与命令行工具。

推荐在以下场景使用 stdio

  • 构建命令行工具

  • 本地系统集成

  • 简单进程间通信

  • 与 shell 脚本协作

Go server 示例:

s := server.NewMCPServer(
    "My Server", // Server name
    "1.0.0",     // Version
)
if err := server.ServeStdio(s); err != nil {
    log.Fatalf("Server error: %v", err)
}

Server-Sent Events (SSE)

SSE 传输 通过 HTTP POST 请求实现 客户端到服务器通信,同时支持 服务器到客户端流式传输 。

推荐在以下场景使用 SSE

  • 仅需要服务器到客户端的流式通信

  • 运行在受限网络环境

  • 实现简单的推送更新

Go server 示例:

s := server.NewMCPServer(
    "My Server", // Server name
    "1.0.0",     // Version
)
sseServer := server.NewSSEServer(s)
err := sseServer.Start(":8080")
if err != nil {
    panic(err)
}

Streamable HTTP

2025 年 3 月 26 日,MCP引入了一项关键更新:用 Streamable HTTP 替代原先的 HTTP + SSE 作为默认传输方式。这一变更在解决原有方案中连接不可恢复、服务端长连接压力大等问题的同时,依然保留了 SSE 带来的流式响应优势。

      Streamable HTTP 并不是传统意义上的 流式 HTTP(Streaming HTTP),它指的是一种 兼具以下特性的传输机制:

  • 以普通 HTTP 请求为基础,客户端用 POST/GET 发请求;

  • 服务器可选地将响应升级为 SSE 流,实现 流式传输 的能力(当需要时);

  • 去中心化、无强制要求持续连接,支持 stateless 模式;

  • 客户端和服务端之间的消息传输更加灵活,比如同一个 /message 端点可用于发起请求和接收 SSE 流;

  • 不再需要单独的 /sse 端点,一切通过统一的 /message 协议层处理。

Streamable HTTP优势:

  • 支持无状态服务器实现

  • 向后兼容现有HTTP+SSE方案

  • 灵活的传输方式选择

  • 更好的基础设施集成

原有问题:

  • SSE连接断线无法恢复

  • 服务器需维持高可用长连接

  • 单向被动响应,无法主动推送


2. MCP vs Function Calling 对比

Function Calling的局限性

传统的Function Calling存在以下核心问题:

紧耦合架构

// 传统Function Calling - 工具必须硬编码在应用中
func handleFunctionCall(name string, args map[string]interface{}) error {
    switch name {
    case "calculator":
        return handleCalculator(args)
    case "weather":
        return handleWeather(args)
    case "database_query":
        return handleDatabaseQuery(args)
    // 每添加新工具都需要修改这里
    default:
        return fmt.Errorf("unknown function: %s", name)
    }
}

无状态限制

// Function Calling - 每次调用都是独立的

func calculateSum(numbers [ ]float64) float64 {

    // 无法记住之前的计算历史
    // 无法维护计算上下文
    return sum(numbers)
}

部署和维护问题

  • 添加新工具需要重新编译整个应用

  • 工具错误可能导致整个应用崩溃

  • 资源隔离困难,内存泄漏影响全局

Function calling 和MCP对比

特性维度 Function Calling MCP
部署方式 嵌入式,需重新编译 独立服务,热插拔
错误隔离 共享进程,相互影响 进程隔离,独立恢复
资源管理 共享内存,难以控制 独立资源配额
扩展性 线性增长复杂度 水平扩展,负载均衡
语言限制 单一语言栈 跨语言生态
调试难度 复杂,影响主程序 独立调试,不影响其他组件
版本管理 强耦合版本依赖 独立版本控制

3. 开发环境准备

基础依赖配置

可用的go-mcp库:

https://github.com/mark3labs/mcp-go

https://github.com/modelcontextprotocol/go-sdk

// go.mod
module golang-mcp-server

go 1.21

require (
    github.com/mark3labs/mcp-go v0.36.0
)

require (
    github.com/google/uuid v1.6.0          // UUID生成
    github.com/spf13/cast v1.7.1           // 类型转换
    gopkg.in/yaml.v3 v3.0.1               // 配置文件解析
)


4. 示例演示

Hello World 服务器

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/mark3labs/mcp-go/mcp"
	"github.com/mark3labs/mcp-go/server"
)

func main() {
	// 创建MCP服务器实例
	s := server.NewMCPServer(
		"go-mcp-server",                   // 服务器名称
		"1.0.0",                           // 版本号
		server.WithToolCapabilities(true), // 启用工具功能
		server.WithRecovery(),             // 启用错误恢复
	)

	// 定义问候工具
	helloTool := mcp.NewTool("say_hello",
		mcp.WithDescription("向指定的人问好"),
		mcp.WithString("name",
			mcp.Required(),
			mcp.Description("要问候的人的姓名"),
		),
		mcp.WithString("language",
			mcp.Description("问候语言:zh, en, ja"),
		),
	)

	// 注册工具处理函数
	s.AddTool(helloTool, handleHello)

	// 启动服务器
	if err := server.ServeStdio(s); err != nil {
		log.Fatalf("服务器启动失败: %v", err)
	}
}

// 问候工具处理函数
func handleHello(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	// 提取必需参数
	name, err := request.RequireString("name")
	if err != nil {
		return mcp.NewToolResultError("缺少name参数"), nil
	}

	// 提取可选参数
	language := request.GetString("language", "zh")

	// 根据语言生成问候语
	var greeting string
	switch language {
	case "en":
		greeting = fmt.Sprintf("Hello, %s! Welcome to MCP!", name)
	case "ja":
		greeting = fmt.Sprintf("こんにちは、%sさん!MCPへようこそ!", name)
	default:
		greeting = fmt.Sprintf("你好, %s! 欢迎使用MCP!", name)
	}

	return mcp.NewToolResultText(greeting), nil
}

计算器工具集

基于仓库中的实际代码,扩展计算器功能:

package main

import (
	"context"
	"fmt"
	"github.com/mark3labs/mcp-go/mcp"
	"github.com/mark3labs/mcp-go/server"
	"math"
)

func main() {
	// 创建MCP服务器实例
	s := server.NewMCPServer(
		"go-mcp-server",                   // 服务器名称
		"1.0.0",                           // 版本号
		server.WithToolCapabilities(true), // 启用工具功能
		server.WithRecovery(),             // 启用错误恢复
	)
	// 基础四则运算
	addTool := mcp.NewTool("add",
		mcp.WithDescription("执行加法运算"),
		mcp.WithNumber("a", mcp.Required(), mcp.Description("第一个数字")),
		mcp.WithNumber("b", mcp.Required(), mcp.Description("第二个数字")),
	)

	subtractTool := mcp.NewTool("subtract",
		mcp.WithDescription("执行减法运算"),
		mcp.WithNumber("a", mcp.Required(), mcp.Description("被减数")),
		mcp.WithNumber("b", mcp.Required(), mcp.Description("减数")),
	)

	multiplyTool := mcp.NewTool("multiply",
		mcp.WithDescription("执行乘法运算"),
		mcp.WithNumber("a", mcp.Required(), mcp.Description("第一个因数")),
		mcp.WithNumber("b", mcp.Required(), mcp.Description("第二个因数")),
	)

	divideTool := mcp.NewTool("divide",
		mcp.WithDescription("执行除法运算"),
		mcp.WithNumber("a", mcp.Required(), mcp.Description("被除数")),
		mcp.WithNumber("b", mcp.Required(), mcp.Description("除数")),
	)

	// 高级数学函数
	powerTool := mcp.NewTool("power",
		mcp.WithDescription("计算幂运算"),
		mcp.WithNumber("base", mcp.Required(), mcp.Description("底数")),
		mcp.WithNumber("exponent", mcp.Required(), mcp.Description("指数")),
	)

	sqrtTool := mcp.NewTool("sqrt",
		mcp.WithDescription("计算平方根"),
		mcp.WithNumber("number", mcp.Required(), mcp.Description("要计算平方根的数")),
	)

	// 注册所有工具
	s.AddTool(addTool, handleAdd)
	s.AddTool(subtractTool, handleSubtract)
	s.AddTool(multiplyTool, handleMultiply)
	s.AddTool(divideTool, handleDivide)
	s.AddTool(powerTool, handlePower)
	s.AddTool(sqrtTool, handleSqrt)

	if err := server.ServeStdio(s); err != nil {
		fmt.Printf("Server error: %v\n", err)
	}
}

// 基础运算处理函数
func handleAdd(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	a, err := request.RequireFloat("a")
	if err != nil {
		return mcp.NewToolResultError("参数a必须是数字"), nil
	}

	b, err := request.RequireFloat("b")
	if err != nil {
		return mcp.NewToolResultError("参数b必须是数字"), nil
	}

	result := a + b
	return mcp.NewToolResultText(fmt.Sprintf("%.2f + %.2f = %.2f", a, b, result)), nil
}

func handleSubtract(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	a, err := request.RequireFloat("a")
	if err != nil {
		return mcp.NewToolResultError("参数a必须是数字"), nil
	}

	b, err := request.RequireFloat("b")
	if err != nil {
		return mcp.NewToolResultError("参数b必须是数字"), nil
	}

	result := a - b
	return mcp.NewToolResultText(fmt.Sprintf("%.2f - %.2f = %.2f", a, b, result)), nil
}

func handleMultiply(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	a, err := request.RequireFloat("a")
	if err != nil {
		return mcp.NewToolResultError("参数a必须是数字"), nil
	}

	b, err := request.RequireFloat("b")
	if err != nil {
		return mcp.NewToolResultError("参数b必须是数字"), nil
	}

	result := a * b
	return mcp.NewToolResultText(fmt.Sprintf("%.2f × %.2f = %.2f", a, b, result)), nil
}

func handleDivide(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	a, err := request.RequireFloat("a")
	if err != nil {
		return mcp.NewToolResultError("参数a必须是数字"), nil
	}

	b, err := request.RequireFloat("b")
	if err != nil {
		return mcp.NewToolResultError("参数b必须是数字"), nil
	}

	if b == 0 {
		return mcp.NewToolResultError("除数不能为零"), nil
	}

	result := a / b
	return mcp.NewToolResultText(fmt.Sprintf("%.2f ÷ %.2f = %.2f", a, b, result)), nil
}

// 高级运算处理函数
func handlePower(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	base, err := request.RequireFloat("base")
	if err != nil {
		return mcp.NewToolResultError("底数必须是数字"), nil
	}

	exponent, err := request.RequireFloat("exponent")
	if err != nil {
		return mcp.NewToolResultError("指数必须是数字"), nil
	}

	result := math.Pow(base, exponent)
	return mcp.NewToolResultText(fmt.Sprintf("%.2f ^ %.2f = %.2f", base, exponent, result)), nil
}

func handleSqrt(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	number, err := request.RequireFloat("number")
	if err != nil {
		return mcp.NewToolResultError("参数必须是数字"), nil
	}

	if number < 0 {
		return mcp.NewToolResultError("不能计算负数的平方根"), nil
	}

	result := math.Sqrt(number)
	return mcp.NewToolResultText(fmt.Sprintf("√%.2f = %.2f", number, result)), nil
}

Google搜索工具

基于仓库中现有的搜索功能,集成到MCP服务器:

func main() {
	// 创建 MCP Server 实例
	s := server.NewMCPServer(
		"Google Search MCP Server",
		"1.0.0",
	)

	// 定义工具元数据
	googleSearchTool := mcp.NewTool(
		"google_search",
		mcp.WithDescription("使用 Google(Serper) 搜索查询信息,联网搜索、查询、获取信息"),
		mcp.WithString("query",
			mcp.Required(),
			mcp.Description("要搜索的查询内容"),
		),
	)

	// 注册工具及其处理函数
	s.AddTool(googleSearchTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		// 解析参数
		type searchParams struct {
			Query string `json:"query"`
			Num   int    `json:"num"`
		}
		var params searchParams

		paramsJSON, err := json.Marshal(request.Params.Arguments)
		if err != nil {
			return nil, fmt.Errorf("failed to marshal params: %w", err)
		}
		if err := json.Unmarshal(paramsJSON, &params); err != nil {
			return nil, fmt.Errorf("failed to unmarshal params: %w", err)
		}
		if params.Query == "" {
			return nil, fmt.Errorf("query parameter is required")
		}

		// 调用搜索逻辑
		searchResp, err := search.GoogleSearch(params.Query)
		if err != nil {
			return nil, fmt.Errorf("search failed: %w", err)
		}

		// 格式化响应,只保留答案框和前 5 条有机搜索结果
		var results []map[string]interface{}
		if searchResp.AnswerBox.Answer != "" {
			results = append(results, map[string]interface{}{
				"type":   "answer",
				"answer": searchResp.AnswerBox.Answer,
			})
		}
		for i, organic := range searchResp.Organic {
			if i >= 5 {
				break
			}
			results = append(results, map[string]interface{}{
				"type":    "organic",
				"title":   organic.Title,
				"link":    organic.Link,
				"snippet": organic.Snippet,
			})
		}

		resultJSON, err := json.Marshal(map[string]interface{}{
			"query":   params.Query,
			"results": results,
		})
		if err != nil {
			return nil, fmt.Errorf("failed to marshal results: %w", err)
		}

		return &mcp.CallToolResult{
			Content: []mcp.Content{
				mcp.NewTextContent(string(resultJSON)),
			},
		}, nil
	})

	// -------- HTTP & SSE 服务 --------
	addr := ":8080"
	mux := http.NewServeMux()
	mux.HandleFunc("/search", func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		query := r.URL.Query().Get("query")
		log.Printf("[HTTP] /search query=%s from %s", query, r.RemoteAddr)
		if query == "" {
			http.Error(w, "query parameter is required", http.StatusBadRequest)
			return
		}

		results, err := doSearch(query)
		if err != nil {
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		}

		w.Header().Set("Content-Type", "application/json")
		if err := json.NewEncoder(w).Encode(map[string]any{
			"query":   query,
			"results": results,
		}); err != nil {
			log.Printf("encode error: %v", err)
		} else {
			log.Printf("[HTTP] /search query=%s completed in %v", query, time.Since(start))
		}
	})

	// SSE 端点
	mux.HandleFunc("/sse", func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()
		log.Printf("[SSE] connection from %s", r.RemoteAddr)
		f, ok := w.(http.Flusher)
		if !ok {
			http.Error(w, "streaming unsupported", http.StatusInternalServerError)
			return
		}
		w.Header().Set("Content-Type", "text/event-stream")
		w.Header().Set("Cache-Control", "no-cache")
		w.Header().Set("Connection", "keep-alive")

		query := r.URL.Query().Get("query")
		if query == "" {
			fmt.Fprintf(w, "data: %s\n\n", "{\"error\":\"query parameter is required\"}")
			f.Flush()
			return
		}

		results, err := doSearch(query)
		if err != nil {
			fmt.Fprintf(w, "data: %s\n\n", fmt.Sprintf("{\"error\":\"%s\"}", err.Error()))
			f.Flush()
			return
		}

		for _, item := range results {
			b, _ := json.Marshal(item)
			fmt.Fprintf(w, "data: %s\n\n", b)
			f.Flush()
		}

		log.Printf("[SSE] query=%s completed in %v", query, time.Since(start))
	})

	// 启动 stdio MCP 服务器
	go func() {
		if err := server.ServeStdio(s); err != nil {
			log.Printf("MCP stdio server error: %v", err)
		}
	}()

	// 构造 HTTP 服务器
	httpSrv := &http.Server{
		Addr:    addr,
		Handler: mux,
	}

	go func() {
		log.Printf("[GoogleSearch HTTP] 监听 %s", addr)
		if err := httpSrv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Printf("HTTP server error: %v", err)
		}
	}()

	// -------- 退出 --------
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
	<-quit

	ctxShutdown, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := httpSrv.Shutdown(ctxShutdown); err != nil {
		log.Printf("HTTP shutdown error: %v", err)
	}

}

聚合MCP服务器

将所有功能整合到一个完整的服务器中:

package main

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"golang-mcp/search"
	"log"
	"os"
	"os/signal"
	"syscall"

	"github.com/mark3labs/mcp-go/mcp"
	"github.com/mark3labs/mcp-go/server"
)

func main() {
	// 聚合服务器
	s := server.NewMCPServer(
		"go-mcp-server",
		"1.0.0",
		server.WithResourceCapabilities(true, true), //启用资源功能
		server.WithToolCapabilities(true),           //启用工具功能
	)

	// 注册tools 资源 prompt
	registerCalculateTool(s)
	registerSearchTool(s)
	registerGreetingPrompt(s)
	registerResources(s)

	// ----------- 启动MCP SSE服务器 ----------
	sseServer := server.NewSSEServer(s)
	go func() {
		log.Println("启动MCP SSE服务器在端口 :8080")
		if err := sseServer.Start(":8080"); err != nil {
			log.Printf("MCP SSE server error: %v", err)
		}
	}()

	// ----------- 启动Streamable HTTP服务器 ----------
	go func() {
		streamableServer := server.NewStreamableHTTPServer(s)
		log.Println("启动MCP Streamable HTTP服务器在端口 :8081")
		if err := streamableServer.Start(":8081"); err != nil {
			log.Printf("Streamable HTTP server error: %v", err)
		}
	}()

	// ----------- 启动 stdio ----------
	go func() {
		if err := server.ServeStdio(s); err != nil {
			log.Printf("stdio server error: %v", err)
		}
	}()

	// ----------- 处理退出 ----------
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, os.Interrupt, syscall.SIGTERM)

	log.Println("MCP Server is running. Press Ctrl+C to stop.")
	log.Printf("可用传输模式:")
	log.Printf("  SSE: http://localhost:8080/sse (兼容模式)")
	log.Printf("  Streamable HTTP: http://localhost:8081/mcp (推荐)")
	log.Printf("  stdio: 标准输入输出")
	log.Printf("----可用功能:-----")
	log.Printf("   工具: calculate, google_search")
	log.Printf("   资源: docs://readme")
	log.Printf("   提示: greeting")

	<-quit
	log.Println("shutting down…")
}

// 公共搜索封装
func doSearch(query string) ([]map[string]any, error) {
	resp, err := search.GoogleSearch(query)
	if err != nil {
		return nil, err
	}
	var results []map[string]any
	if resp.AnswerBox.Answer != "" {
		results = append(results, map[string]any{"type": "answer", "answer": resp.AnswerBox.Answer})
	}
	for i, o := range resp.Organic {
		if i >= 5 {
			break
		}
		results = append(results, map[string]any{"type": "organic", "title": o.Title, "link": o.Link, "snippet": o.Snippet})
	}
	return results, nil
}

// registerGreetingPrompt 提供简单问候 prompt
func registerGreetingPrompt(s *server.MCPServer) {

	s.AddPrompt(mcp.NewPrompt("greeting",
		mcp.WithPromptDescription("一个友好的问候提示"),
		mcp.WithArgument("name",
			mcp.ArgumentDescription("要问候的人的名字"),
		),
		mcp.WithArgument("language",
			mcp.ArgumentDescription("问候语言:zh, en, ja"),
		),
	), func(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) {
		type greetingParams struct {
			Name     string `json:"name"`
			Language string `json:"language"`
		}
		var params greetingParams
		paramsJSON, err := json.Marshal(request.Params.Arguments)
		if err != nil {
			return nil, fmt.Errorf("failed to marshal params: %w", err)
		}
		if err := json.Unmarshal(paramsJSON, &params); err != nil {
			return nil, fmt.Errorf("failed to unmarshal params: %w", err)
		}
		name := params.Name
		if name == "" {
			name = "朋友"
		}
		// 提取可选参数
		language := params.Name
		if language == "" {
			language = "zh"
		}

		// 根据语言生成问候语
		var greeting string
		switch language {
		case "en":
			greeting = fmt.Sprintf("Hello, %s! Welcome to MCP!", name)
		case "ja":
			greeting = fmt.Sprintf("こんにちは、%sさん!MCPへようこそ!", name)
		default:
			greeting = fmt.Sprintf("你好, %s! 欢迎使用MCP!", name)
		}
		return mcp.NewGetPromptResult(
			"友好的问候",
			[]mcp.PromptMessage{
				mcp.NewPromptMessage(
					mcp.RoleAssistant,
					mcp.NewTextContent(greeting),
				),
			},
		), nil
	})
}

func registerResources(s *server.MCPServer) {
	// 静态资源示例 - 暴露一个 README 文件
	resource := mcp.NewResource(
		"docs://readme",
		"项目说明文档",
		mcp.WithResourceDescription("项目的 README 文件"),
		mcp.WithMIMEType("text/markdown"),
	)
	// 添加资源及其处理函数
	s.AddResource(resource, func(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) {
		content, err := os.ReadFile("README.md")
		if err != nil {
			return nil, err
		}
		return []mcp.ResourceContents{
			mcp.TextResourceContents{
				URI:      "docs://readme",
				MIMEType: "text/markdown",
				Text:     string(content),
			},
		}, nil
	})
}

// registerCalculateTool 将简单的四则运算工具添加到 server
func registerCalculateTool(s *server.MCPServer) {
	calcTool := mcp.NewTool("calculate",
		mcp.WithDescription("执行基本的算术运算"),
		mcp.WithString("operation", mcp.Required(), mcp.Description("运算类型: add/subtract/multiply/divide"), mcp.Enum("add", "subtract", "multiply", "divide")),
		mcp.WithNumber("x", mcp.Required(), mcp.Description("第一个数字")),
		mcp.WithNumber("y", mcp.Required(), mcp.Description("第二个数字")),
	)

	s.AddTool(calcTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		var params struct {
			Operation string  `json:"operation"`
			X         float64 `json:"x"`
			Y         float64 `json:"y"`
		}
		buf, _ := json.Marshal(req.Params.Arguments)
		_ = json.Unmarshal(buf, &params)

		var result float64
		switch params.Operation {
		case "add":
			result = params.X + params.Y
		case "subtract":
			result = params.X - params.Y
		case "multiply":
			result = params.X * params.Y
		case "divide":
			if params.Y == 0 {
				return nil, errors.New("不允许除以零")
			}
			result = params.X / params.Y
		default:
			return nil, fmt.Errorf("未知运算: %s", params.Operation)
		}
		return mcp.FormatNumberResult(result), nil
	})
}

// registerSearchTool 使用之前封装的 search.GoogleSearch
func registerSearchTool(s *server.MCPServer) {
	searchTool := mcp.NewTool("google_search",
		mcp.WithDescription("使用 Google(Serper) 搜索查询信息,联网搜索、查询、获取信息"),
		mcp.WithString("query", mcp.Required(), mcp.Description("要搜索的查询内容")),
	)

	s.AddTool(searchTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
		var p struct {
			Query string `json:"query"`
		}
		b, _ := json.Marshal(req.Params.Arguments)
		_ = json.Unmarshal(b, &p)
		if p.Query == "" {
			return nil, fmt.Errorf("query 不能为空")
		}
		results, err := doSearch(p.Query)
		if err != nil {
			return nil, err
		}
		out, _ := json.Marshal(map[string]any{"query": p.Query, "results": results})
		return &mcp.CallToolResult{Content: []mcp.Content{mcp.NewTextContent(string(out))}}, nil
	})
}

CURL调用

POST http://localhost:8081/message
{
    "method":"tools/call",
    "params":{
        "name":"google_search",
        "arguments":{
            "query": "golang教程"
        }
    }
}


Response
{
    "id": null,
    "jsonrpc": "2.0",
    "result": {
        "content": [
            {
                "text": "{\"query\":\"golang教程\",\"results\":[{\"link\":\"http://www.runoob.com/go/go-tutorial.html\",\"snippet\":\"Go 语言教程Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。 Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发, ...\",\"title\":\"Go 语言教程\",\"type\":\"organic\"},{\"link\":\"https://tour.go-zh.org/\",\"snippet\":\"欢迎访问Go 编程语言教程。 本教程分为几个模块,点击本页左上角的Go 语言之旅可以查看模块列表。 你也可以随时点击页面右上角的菜单来浏览目录。 本教程由一系列幻灯 ...\",\"title\":\"Go 语言之旅\",\"type\":\"organic\"},{\"link\":\"https://www.topgoer.com/\",\"snippet\":\"前景 · 开发环境 · Go的安装 · 配置GOPATH · 编辑器 · Git安装 · 第一个go程序 · Go基础 · Go语言的主要特征 · Golang内置类型和函数 · Init函数和main函数 · 命令 ...\",\"title\":\"前景· Go语言中文文档\",\"type\":\"organic\"}]}",
                "type": "text"
            }
        ]
    }
}

构建和运行

# 设置环境变量
export SERPER_API_KEY="your_serper_api_key"

# 构建项目
go build -o mcp-server main.go

# 运行服务器
./mcp-server

# 或者直接运行
go run main.go

构建MCP客户端

可以使用 mcp-go 提供的 client 模块,构建一个通过 stdio 方式连接到前面打包好的 MCP 服务器的客户端。

  • 初始化客户端并连接服务器

  • 获取提示词、资源、工具列表

  • 调用远程工具(tool)

client.go

package main

import (
	"context"
	"encoding/base64"
	"fmt"
	"os"
	"strings"
	"time"

	"github.com/mark3labs/mcp-go/client"
	"github.com/mark3labs/mcp-go/mcp"
)

func main() {

	// 创建一个基于 stdio 的MCP客户端
	mcpClient, err := client.NewStdioMCPClient(
		"D:/mcp-golang/go-mcp-servers.exe",
		[]string{},
	)
	if err != nil {
		panic(err)
	}
	defer mcpClient.Close()

	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	fmt.Println("初始化 mcp 客户端...")
	initRequest := mcp.InitializeRequest{}
	initRequest.Params.ProtocolVersion = mcp.LATEST_PROTOCOL_VERSION
	initRequest.Params.ClientInfo = mcp.Implementation{
		Name:    "Client Demo",
		Version: "1.0.0",
	}

	// 初始化MCP客户端并连接到服务器
	initResult, err := mcpClient.Initialize(ctx, initRequest)
	if err != nil {
		panic(err)
	}
	fmt.Printf(
		"\n初始化成功,服务器信息: %s %s\n\n",
		initResult.ServerInfo.Name,
		initResult.ServerInfo.Version,
	)

	// 从服务器获取提示词列表
	fmt.Println("提示词列表:")
	promptsRequest := mcp.ListPromptsRequest{}
	prompts, err := mcpClient.ListPrompts(ctx, promptsRequest)
	if err != nil {
		panic(err)
	}
	for _, prompt := range prompts.Prompts {
		fmt.Printf("- %s: %s\n", prompt.Name, prompt.Description)
		fmt.Println("参数:", prompt.Arguments)
	}

	// 从服务器获取资源列表
	fmt.Println("资源列表:")
	resourcesRequest := mcp.ListResourcesRequest{}
	resources, err := mcpClient.ListResources(ctx, resourcesRequest)
	fmt.Println(resources)
	fmt.Println(err)
	if err != nil {
		panic(err)
	}
	// 遍历资源列表并逐个读取内容
	for _, r := range resources.Resources {
		fmt.Printf("\n--- 读取资源 %s ---\n", r.URI)
		// 1. 构造 ReadResource 请求
		readReq := mcp.ReadResourceRequest{
			Request: mcp.Request{
				Method: "resources/read",
			},
		}
		readReq.Params.URI = r.URI // 指定要读取的 URI

		// 2. 调用客户端方法
		readRes, err := mcpClient.ReadResource(ctx, readReq)
		if err != nil {
			fmt.Printf("读取 %s 失败: %v\n", r.URI, err)
			continue
		}

		// 3. 处理返回的 contents
		for _, c := range readRes.Contents {
			switch v := c.(type) {

			// ---------- 纯文本 ----------
			case mcp.TextResourceContents:
				fmt.Printf("【文本内容】(%s)\n", v.MIMEType)
				fmt.Println(v.Text)

			// ---------- 二进制(Blob)----------
			case mcp.BlobResourceContents:
				// v.Blob 是 base64 字符串
				raw, err := base64.StdEncoding.DecodeString(v.Blob)
				if err != nil {
					fmt.Println("base64 解码失败:", err)
					continue
				}
				fileName := strings.ReplaceAll(r.URI, "://", "_")
				if err := os.WriteFile(fileName, raw, 0644); err != nil {
					fmt.Printf("保存 %s 失败: %v\n", fileName, err)
				} else {
					fmt.Printf("二进制内容已写入本地文件: %s (%d bytes)\n", fileName, len(raw))
				}
			}
		}
	}

	// 从服务器获取工具列表
	fmt.Println("可用工具列表:")
	toolsRequest := mcp.ListToolsRequest{}
	tools, err := mcpClient.ListTools(ctx, toolsRequest)
	if err != nil {
		panic(err)
	}

	for _, tool := range tools.Tools {
		fmt.Printf("- %s: %s\n", tool.Name, tool.Description)
		fmt.Println("参数:", tool.InputSchema.Properties)
	}

	// 调用计算工具
	fmt.Println("调用工具: calculate")
	toolRequest := mcp.CallToolRequest{
		Request: mcp.Request{
			Method: "tools/call",
		},
	}
	toolRequest.Params.Name = "calculate"
	toolRequest.Params.Arguments = map[string]any{
		"operation": "add",
		"x":         1,
		"y":         1,
	}
	result, err := mcpClient.CallTool(ctx, toolRequest)
	if err != nil {
		panic(err)
	}
	fmt.Println("调用工具结果:", result.Content[0].(mcp.TextContent).Text)

	// 调用搜索工具
	fmt.Println("调用google_search工具: google_search")
	toolRequest = mcp.CallToolRequest{
		Request: mcp.Request{
			Method: "tools/call",
		},
	}
	toolRequest.Params.Name = "google_search"
	toolRequest.Params.Arguments = map[string]any{
		"query": "golang教程",
	}
	result, err = mcpClient.CallTool(ctx, toolRequest)
	if err != nil {
		panic(err)
	}
	fmt.Println("调用google_search工具:", result.Content[0].(mcp.TextContent).Text)
}

测试MCP服务器

创建一个简单的测试客户端:

package main

import (
	"bufio"
	"encoding/json"
	"fmt"
	"net/http"
	"strings"
	"testing"
	"time"
)

const (
	baseURL = "http://localhost:8080"
)

// 测试HTTP搜索接口
func TestHTTPSearch(t *testing.T) {
	tests := []struct {
		name        string
		query       string
		expectError bool
	}{
		{"正常查询", "Go语言教程", false},
		{"英文查询", "golang programming", false},
		{"空查询", "", true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			url := fmt.Sprintf("%s/search", baseURL)
			if tt.query != "" {
				url += "?query=" + tt.query
			}

			resp, err := http.Get(url)
			if err != nil {
				t.Fatalf("请求失败: %v (请确保main程序正在运行)", err)
			}
			defer resp.Body.Close()

			if tt.expectError {
				if resp.StatusCode == http.StatusOK {
					t.Error("期望请求失败,但请求成功了")
				}
				return
			}

			if resp.StatusCode != http.StatusOK {
				t.Errorf("期望状态码 200, 得到 %d", resp.StatusCode)
				return
			}

			var result map[string]interface{}
			if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
				t.Errorf("响应解析失败: %v", err)
				return
			}

			if result["query"] != tt.query {
				t.Errorf("查询参数不匹配,期望: %s, 得到: %s", tt.query, result["query"])
			}

			if results, ok := result["results"].([]interface{}); ok {
				t.Logf("✅ HTTP搜索成功,查询: '%s', 返回 %d 个结果", tt.query, len(results))

				// 显示前2个结果
				for i, item := range results {
					if i >= 2 {
						break
					}
					if itemMap, ok := item.(map[string]interface{}); ok {
						if title, ok := itemMap["title"].(string); ok {
							t.Logf("   结果 %d: %s", i+1, title)
						}
					}
				}
			} else {
				t.Error("结果格式不正确")
			}
		})
	}
}

// 测试SSE搜索接口
func TestSSESearch(t *testing.T) {
	tests := []struct {
		name        string
		query       string
		expectError bool
	}{
		{"正常SSE查询", "Python教程", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			url := fmt.Sprintf("%s/sse", baseURL)
			if tt.query != "" {
				url += "?query=" + tt.query
			}

			resp, err := http.Get(url)
			if err != nil {
				t.Fatalf("SSE请求失败: %v (请确保main程序正在运行)", err)
			}
			defer resp.Body.Close()

			// 检查SSE头部
			contentType := resp.Header.Get("Content-Type")
			if contentType != "text/event-stream" {
				t.Errorf("期望Content-Type为text/event-stream, 得到 %s", contentType)
			}

			// 读取SSE流
			scanner := bufio.NewScanner(resp.Body)
			eventCount := 0
			hasError := false

			// 设置超时,避免长时间等待
			timeout := time.After(10 * time.Second)
			done := make(chan bool)

			go func() {
				for scanner.Scan() {
					line := scanner.Text()
					if strings.HasPrefix(line, "data: ") {
						eventCount++
						data := strings.TrimPrefix(line, "data: ")

						if strings.Contains(data, "error") {
							hasError = true
							t.Logf("SSE错误事件: %s", data)
							break
						}

						// 尝试解析数据
						var item map[string]interface{}
						if err := json.Unmarshal([]byte(data), &item); err == nil {
							if title, ok := item["title"].(string); ok {
								t.Logf("SSE数据: %s", title)
							}
						}

						if eventCount >= 3 { // 限制读取事件数量
							break
						}
					}
				}
				done <- true
			}()

			select {
			case <-timeout:
				t.Log("SSE读取超时")
			case <-done:
				break
			}

			if tt.expectError {
				if !hasError && eventCount == 0 {
					t.Error("期望有错误或没有数据")
				}
			} else {
				if hasError {
					t.Error("不期望有错误")
				} else {
					t.Logf("✅ SSE搜索成功,查询: '%s', 接收到 %d 个事件", tt.query, eventCount)
				}
			}
		})
	}
}


// 简单的端点可用性测试
func TestServerAvailability(t *testing.T) {
	resp, err := http.Get(fmt.Sprintf("%s/search?query=test", baseURL))
	if err != nil {
		t.Fatalf("服务器不可用: %v (请确保运行 go run main.go)", err)
	}
	defer resp.Body.Close()

	t.Log("✅ 服务器运行正常")
}

// 并发测试
func TestConcurrentRequests(t *testing.T) {
	queries := []string{"Go", "Python", "Java", "JavaScript", "Rust"}
	results := make(chan error, len(queries))

	// 并发发送HTTP请求
	for _, query := range queries {
		go func(q string) {
			url := fmt.Sprintf("%s/search?query=%s", baseURL, q)
			resp, err := http.Get(url)
			if err != nil {
				results <- err
				return
			}
			resp.Body.Close()

			if resp.StatusCode != http.StatusOK {
				results <- fmt.Errorf("状态码: %d", resp.StatusCode)
				return
			}
			results <- nil
		}(query)
	}

	// 收集结果
	successCount := 0
	for i := 0; i < len(queries); i++ {
		if err := <-results; err != nil {
			t.Logf("并发请求失败: %v", err)
		} else {
			successCount++
		}
	}

	if successCount == 0 {
		t.Error("所有并发请求都失败了")
	} else {
		t.Logf("✅ 并发测试完成,%d/%d 成功", successCount, len(queries))
	}
}

// 性能基准测试
func BenchmarkHTTPSearch(b *testing.B) {
	url := fmt.Sprintf("%s/search?query=benchmark", baseURL)

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		resp, err := http.Get(url)
		if err != nil {
			b.Fatalf("基准测试请求失败: %v", err)
		}
		resp.Body.Close()
	}
}

使用Cursor进行测试

mcp.json配置:

"go-mcp-servers": {
    "command": "D:\mcp-golang\go-mcp-servers.exe",
    "args": [],
    "env": {
        "GOPATH": "D:\Program Files\GoPath\go1.24.5",
        "GOMODCACHE": "D:\Program Files\GoPath\pkg\md"
    }
}

Prompt触发调用:

使用inspector测试

npx @modelcontextprotocol/inspector

Streamable HTTP:http://localhost:8081/mcp

SSe:http://localhost:8080/sse

打 赏