使用 Go 構建 MCP Server

一、MCP 介紹 

1. 基本介紹 

MCP(Model Context Protocol,模型上下文協議)是由 Anthropic 公司(Claude 大模型的創造者)於 2024 年 11 月推出的一種開放標準協議,旨在統一大型語言模型(LLM)與外部數據源和工具之間的通信方式。MCP 的核心目標是解決當前 AI 應用開發中的數據孤島和碎片化集成問題。

2. 協議特點 

MCP 可以被理解爲 AI 大模型的 "萬能接口",類似於 USB-C 接口在硬件領域的作用,它提供了一種標準化的方法,使 AI 模型能夠與不同的數據源和工具進行無縫交互。通過 MCP,開發者可以更輕鬆地構建複雜的 AI 應用,而無需爲每個工具或數據源編寫專門的集成代碼。

  1. MCP 是一個標準協議,如同電子設備的 Type C 協議 (可以充電也可以傳輸數據),使 AI 模型能夠與不同的 API 和數據源無縫交互。

  2. MCP 旨在替換碎片化的 Agent 代碼集成,從而使 AI 系統更可靠,更有效。通過建立通用標準,服務商可以基於協議來推出它們自己服務的 AI 能力,從而支持開發者更快的構建更強大的 AI 應用。開發者也不需要重複造輪子,通過開源項目可以建立強大的 AI Agent 生態。

  3. MCP 可以在不同的應用 / 服務之間保持上下文,從而增強整體自主執行任務的能力。

可以理解爲 MCP 是將不同任務進行分層處理,每一層都提供特定的能力、描述和限制。而 MCP Client 端根據不同的任務判斷,選擇是否需要調用某個能力,然後通過每層的輸入和輸出,構建一個可以處理複雜、多步對話和統一上下文的 Agent。

3. AI Agent 和 MCP 間的關係 

  1. AI Agent 是一個智能系統,它可以自主運行以實現特定目標。傳統的 AI 聊天僅提供建議或者需要手動執行任務,AI Agent 則可以分析具體情況,做出決策,並自行採取行動。

  2. AI Agent 可以利用 MCP 提供的功能描述來理解更多的上下文,並在各種平臺 / 服務自動執行任務。

4. MCP 如何工作 

MCP 採用客戶端 - 服務器架構,MCP 架構主要包含以下核心組件:

MCP 的基本工作流程如下:

  1. 用戶向 AI 模型(MCP 客戶端)發送請求

  2. AI 模型分析請求,確定需要調用的外部工具或數據

  3. AI 模型通過 MCP 協議向相應的 MCP 服務器發送請求

  4. MCP 服務器處理請求並返回結果

  5. AI 模型整合結果,生成最終回覆給用戶

二、cline 配置本地模型 

我做測試使用的客戶端是 vscode+cline,cline 在 vscode 的插件市場中直接安裝即可。cline 對 MCP 的支持也是非常好的。因爲要使用到大模型,所以這裏還要給 cline 配置一個大模型。

外網開放的免費模型一般都不太好用,所以我一般都是配置本地模型來測試,這裏也介紹一下 cline 如何配置本地模型。

我這裏配置了 ollama 啓動的 qwen2.5:14b 模型。如下圖配置即可,這個配置過程比較簡單。

三、MCP 服務開發 

1. 典型應用場景 

  1. 實時數據分析:LLM 通過 MCP 直接查詢數據庫生成動態報告(如銷售數據可視化)。

  2. 跨平臺自動化:結合本地文件讀寫和 API 調用,實現 “讀取文檔 → 生成會議摘要 → 發送郵件” 全流程自動化。

  3. 隱私敏感任務:醫療數據存儲在本地 Server,模型處理時不外傳,符合 GDPR 合規要求。

  4. 工具增強生成:例如代碼編輯器中集成 MCP Server,根據用戶需求調用圖像生成工具自動插入圖片。

2. 案例開發 

這部分代碼參考了這篇文章:https://mp.weixin.qq.com/s/JmPxMBRZa8UhsIOQVgupLw ,這篇文章主要是一個按照時區獲取時間的工具,我在測試 ok 之後擴展了一個天氣獲取的工具。

天氣接口使用的是騰訊雲的接口,大家可以直接使用免費額度,代碼也是直接從騰訊雲 api 的介紹中拷貝過來的,這裏做了簡單的封裝調用。

下面的代碼直接拷貝過去就可以使用,不過天氣 api 的 key 要替換成你自己的。

package main
import (
	"context"
	"crypto/hmac"
	"crypto/sha1"
	"encoding/base64"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	gourl "net/url"
	"strings"
	"time"
	"github.com/mark3labs/mcp-go/mcp"
	"github.com/mark3labs/mcp-go/server"
)
func main() {
	// GetWeaher("北京")
	// Create MCP server
	s := server.NewMCPServer("MCPDemo""1.0.0")
	// Add Get time tool
	timetool := mcp.NewTool("current_time",
		mcp.WithDescription("Get current time with timezone, Asia/Shanghai is default"),
		mcp.WithString("timezone", mcp.Required(), mcp.Description("current time timezone")))
	// Add tool handler
	s.AddTool(timetool, currentTimeHandler)
	// add weather tool
	weathertool := mcp.NewTool("current_weather",
		mcp.WithDescription("Get current weather with city name, 北京 is default, 需要輸入中文"),
		mcp.WithString("city", mcp.Required(), mcp.Description("city name")))
	// Add tool handler
	s.AddTool(weathertool, weatherHandler)
	// Start the stdio server
	if err := server.ServeStdio(s); err != nil {
		fmt.Printf("Server error: %v\n", err)
	}
}
func currentTimeHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	timezone, ok := request.Params.Arguments["timezone"].(string)
	if !ok {
		return mcp.NewToolResultError("timezone must be a string"), nil
	}
	loc, err := time.LoadLocation(timezone)
	if err != nil {
		return mcp.NewToolResultError(fmt.Sprintf("parse timezone with error: %v", err)), nil
	}
	return mcp.NewToolResultText(fmt.Sprintf(`current time is %s`, time.Now().In(loc))), nil
}
func weatherHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	city, ok := request.Params.Arguments["city"].(string)
	if !ok {
		return mcp.NewToolResultError("city must be a string"), nil
	} 
	body, err := GetWeaher(city)
	if err != nil {
		return mcp.NewToolResultError(fmt.Sprintf("read response body with error: %v", err)), nil
	}
	return mcp.NewToolResultText(string(body)), nil
}
func calcAuthorization(source string, secretId string, secretKey string) (auth string, datetime string, err error) {
	timeLocation, _ := time.LoadLocation("Etc/GMT")
	datetime = time.Now().In(timeLocation).Format("Mon, 02 Jan 2006 15:04:05 GMT")
	signStr := fmt.Sprintf("x-date: %s\nx-source: %s", datetime, source)
	// hmac-sha1
	mac := hmac.New(sha1.New, []byte(secretKey))
	mac.Write([]byte(signStr))
	sign := base64.StdEncoding.EncodeToString(mac.Sum(nil))
	auth = fmt.Sprintf("hmac id=\"%s\", algorithm=\"hmac-sha1\", headers=\"x-date x-source\", signature=\"%s\"",
		secretId, sign)
	return auth, datetime, nil
}
func urlencode(params map[string]string) string {
	var p = gourl.Values{}
	for k, v := range params {
		p.Add(k, v)
	}
	return p.Encode()
}
func GetWeaher(city string) (ret string, err error) {
	// 雲市場分配的密鑰Id
	secretId := "xxx"
	// 雲市場分配的密鑰Key
	secretKey := "xxx"
	source := "usagePlan-xxx"
	// 簽名
	auth, datetime, _ := calcAuthorization(source, secretId, secretKey)
	// 請求方法
	method := "GET"
	// 請求頭
	headers := map[string]string{"X-Source": source, "X-Date": datetime, "Authorization": auth}
	// 查詢參數
	queryParams := make(map[string]string)
	queryParams["areaCn"] = city
	queryParams["areaCode"] = ""
	queryParams["ip"] = ""
	queryParams["lat"] = ""
	queryParams["lng"] = ""
	queryParams["need1hour"] = ""
	queryParams["need3hour"] = ""
	queryParams["needIndex"] = ""
	queryParams["needObserve"] = ""
	queryParams["needalarm"] = ""
	// body參數
	bodyParams := make(map[string]string)
	// url參數拼接
	url := "https://service-6drgk6su-1258850945.gz.apigw.tencentcs.com/release/lundear/weather1d"
	if len(queryParams) > 0 {
		url = fmt.Sprintf("%s?%s", url, urlencode(queryParams))
	}
	bodyMethods := map[string]bool{"POST": true, "PUT": true, "PATCH": true}
	var body io.Reader = nil
	if bodyMethods[method] {
		body = strings.NewReader(urlencode(bodyParams))
		headers["Content-Type"] = "application/x-www-form-urlencoded"
	}
	client := &http.Client{
		Timeout: 5 * time.Second,
	}
	request, err := http.NewRequest(method, url, body)
	if err != nil {
		panic(err)
	}
	for k, v := range headers {
		request.Header.Set(k, v)
	}
	response, err := client.Do(request)
	if err != nil {
		panic(err)
	}
	defer response.Body.Close()
	bodyBytes, err := ioutil.ReadAll(response.Body)
	if err != nil {
		panic(err)
	}
	fmt.Println(string(bodyBytes))
	return string(bodyBytes), nil
}

四、Cline 上配置自己開發的 MCP 服務 

在 Cline 上添加 MCP 服務點擊右上角 “+” 號旁邊的 4 個小方塊按鈕即可進入。

裏面有默認的服務市場和已經安裝,我們在已經安裝這個 tab 中選擇 “Configure MCP Servers”,打開配置文件編輯,填寫以下內容:

{
  "mcpServers"{
    "helight-mcpServers"{
      "command""/Users/helightxu/aillm/mcpgolangtest/mcp-server",
      "args"[],
      "env"{},
      "disabled": false,
      "autoApprove"[
        "current_weather",
        "current_time"
      ]
    }
  }
}

主要就是 MCP 服務的名稱,裏面服務二進制地址等配置。這裏還有一個 “autoApprove” 配置項,這個是在執行命令的時候是否需要人工二次確認還是自動確認。配置之後就可以看到下圖的內容了。

這裏還可以點擊下面的 “Restart Server”,對服務進行重啓,重啓之後也會獲取最新的服務工具信息。在你重新編譯 MCP 服務之後,這裏一般需要點擊重啓一下。

五、案例測試 

1. 時間獲取案例 

這裏輸入 “當前東京的時間是多少”,執行過程如下,就會在大模型解析之後去調用我們的工具進行執行獲取結果,並且會對工具返回的結果使用大模型進行再次組織。

2. 天氣獲取案例 

天氣這裏也是,提問:“深圳的天氣怎麼樣”,在大模型分析之後就會調用我們的天氣獲取工具進行天氣信息獲取,獲取之後再使用大模型進行信息組織和展示。

這裏是對信息的再次組織

五、MCP 的優勢和典型應用場景 

1. MCP 的核心優勢 

從系統集成和開放集成的角度來看,我認爲 MCP 會帶來以下的突破。

  1. 打破數據孤島:通過統一協議連接異構系統(如本地文檔、雲服務),減少大量的適配代碼開發量。

  2. 雙向動態交互:支持實時請求 - 響應和主動通知(如 WebSocket),相比傳統 API 的靜態交互更靈活。

  3. 隱私與安全:

  4. 數據隔離:敏感操作(如醫療數據處理)在本地 Server 完成,無需向 LLM 提供商暴露密鑰。

  5. 權限控制:Server 可自主定義訪問範圍,防止越權操作。

  6. 開發效率提升: 開發者只需關注業務邏輯,無需重複實現通信層,例如通過 Python SDK 快速構建天氣查詢服務。

  7. MCP 目前比較典型的應用場景 


我認爲目前 MCP 的一些典型應用場景會在以下幾個方面,不過這個應該發展會很快,未來也許會有更爲複雜的應用場景出現。

  1. 智能助手增強

MCP 可以顯著增強智能助手的能力,使其能夠:

  1. 企業知識管理

在企業環境中,MCP 可以幫助:

六、總結 

我認爲這種模式應該是 AI 應用的趨勢,不做大模型,而是做大模型、內容信息和工具之間的一種膠水層,是一種思考模式的實現。感覺未來大廠的這些工具都會向這個方向發展。在特定的領域之內,結合大模型、具體場景信息和相關工具,思考組織執行方式和流程,最終完成一個自動化大規模計算的複雜任務。比如複雜的 k8s 集羣運維,複雜大規模的數據分析。

多智能體協作系統應該是未來的大趨勢。而 MCP 在多智能體系統中的應用是其最強大的特性之一。通過 MCP,多個 AI 智能體可以協同工作,各自負責不同的任務,並通過標準化的接口進行通信。

多智能體可能的一個架構:

多智能體系統
多智能體系統
├── 協調者智能體(Coordinator Agent)
├── 專家智能體 1(Expert Agent 1)
├── 專家智能體 2(Expert Agent 2)
├── ...
└── 專家智能體 N(Expert Agent N)

六、參考 

  1. https://mp.weixin.qq.com/s/JmPxMBRZa8UhsIOQVgupLw
  2. https://guangzhengli.com/blog/zh/model-context-protocol/
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/mhihJ-cRuT81PdBzAFdX2Q