mcp 入门(实践篇)

​ 本文将会从 0 实现一个 mcp server,一个基于高德天气api的 天气查询 mcp服务。完整代码见:(https://github.com/Lucareful/mcp-wather)

开始

​ 安装创建一个带有 MCP Go 库。

1
2
go mod init mcp-weather
go get github.com/mark3labs/mcp-go

定义API数据结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package weather

// 见文档:https://lbs.amap.com/api/webservice/guide/api-advanced/weatherinfo
type AllInfo struct {
	Status    string `json:"status"`
	Count     string `json:"count"`
	Info      string `json:"info"`
	Infocode  string `json:"infocode"`
	Forecasts []struct {
		City       string `json:"city"`
		Adcode     string `json:"adcode"`
		Province   string `json:"province"`
		Reporttime string `json:"reporttime"`
		Casts      []struct {
			Date           string `json:"date"`
			Week           string `json:"week"`
			Dayweather     string `json:"dayweather"`
			Nightweather   string `json:"nightweather"`
			Daytemp        string `json:"daytemp"`
			Nighttemp      string `json:"nighttemp"`
			Daywind        string `json:"daywind"`
			Nightwind      string `json:"nightwind"`
			Daypower       string `json:"daypower"`
			Nightpower     string `json:"nightpower"`
			DaytempFloat   string `json:"daytemp_float"`
			NighttempFloat string `json:"nighttemp_float"`
		} `json:"casts"`
	} `json:"forecasts"`
}


type BaseInfo struct {
	Status   string `json:"status"`
	Count    string `json:"count"`
	Info     string `json:"info"`
	Infocode string `json:"infocode"`
	Lives    []struct {
		Province         string `json:"province"`
		City             string `json:"city"`
		Adcode           string `json:"adcode"`
		Weather          string `json:"weather"`
		Temperature      string `json:"temperature"`
		Winddirection    string `json:"winddirection"`
		Windpower        string `json:"windpower"`
		Humidity         string `json:"humidity"`
		Reporttime       string `json:"reporttime"`
		TemperatureFloat string `json:"temperature_float"`
		HumidityFloat    string `json:"humidity_float"`
	} `json:"lives"`
}

构建天气工具

​ 输入: 城市(city),天气信息(type)(枚举类型:完整,摘要)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// GetForecast Add forecast tool
func GetForecast() mcp.Tool {
	weatherTool := mcp.NewTool("get_weather_forecast",
		mcp.WithDescription("Get weather forecast for a city"),
		mcp.WithString("city",
			mcp.Required(),
			mcp.Description("city name"),
		),
		mcp.WithString("type",
			mcp.Required(),
			mcp.Description("get weather forecast info base/all"),
			mcp.Enum("base", "all"),
		),
	)
	return weatherTool
}

​ 当 MCP 客户端连接到 MCP 服务器时,MCP 服务器会公开 GetForecast 作为有效 mcp 工具,请求city和type。当LLM 传入参数之后,程序会执行获取天气信息,返回给LLM。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
func ForecastCall(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
	// get city name
	city := request.Params.Arguments["city"].(string)
	if len(city) == 0 {
		return nil, fmt.Errorf("city name is empty")
	}

	// get info type
	infoType := request.Params.Arguments["type"].(string)
	if len(infoType) == 0 {
		return nil, fmt.Errorf("info type is empty")
	}

	adcode, exists := cityMap.CityClient.GetAdcode(city)
	if !exists {
		return nil, fmt.Errorf("city %s not found, code %s", city, adcode)
	}

	// get weather forecast
	weatherInfo, err := weather.FetchWeatherData(adcode, infoType)
	if err != nil {
		return nil, err
	}
	// parse weather info
	forecast := make([]ForecastResult, 0)

	if infoType == "base" {
		// parse base info
		var baseInfo weather.BaseInfo
		if err := json.Unmarshal([]byte(weatherInfo), &baseInfo); err != nil {
			return nil, fmt.Errorf("parse base info failed: %v", err)
		}
		for _, forecastInfo := range baseInfo.Lives {
			// append forecast info
			forecast = append(forecast, ForecastResult{
				Name:        forecastInfo.City,
				Temperature: forecastInfo.Temperature,
				Wind:        forecastInfo.Windpower,
				Forecast:    forecastInfo.Weather,
				Date:        forecastInfo.Reporttime,
			})
		}
	} else if infoType == "all" {
		// parse all info
		var allInfo weather.AllInfo
		if err := json.Unmarshal([]byte(weatherInfo), &allInfo); err != nil {
			return nil, fmt.Errorf("parse all info failed: %v", err)
		}
		for _, forecastInfo := range allInfo.Forecasts {
			for _, cast := range forecastInfo.Casts {
				forecast = append(forecast, ForecastResult{
					Name:        forecastInfo.City,
					Temperature: cast.Daytemp,
					Wind:        cast.Daywind,
					Forecast:    cast.Dayweather,
					Date:        cast.Date,
				})
			}
		}
	}

	forecastResponse, err := json.Marshal(&forecast)
	if err != nil {
		return nil, fmt.Errorf("error marshalling forecast: %w", err)
	}

	return mcp.NewToolResultText(string(forecastResponse)), nil
}

程序入口

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package main

import (
	"fmt"

	"github.com/mark3labs/mcp-go/server"
	_ "go.uber.org/automaxprocs"

	"mcp-weather/internal/city"
	"mcp-weather/pkg/mcp"
)

func main() {
	// Create a new MCP server
	s := server.NewMCPServer(
		"Weather Demo",
		"1.0.0",
		server.WithResourceCapabilities(true, true),
		server.WithLogging(),
		server.WithRecovery(),
	)

	// Load the city code map
	err := city.CityClient.LoadCodeMap()
	if err != nil {
		fmt.Printf("Error loading city code map: %v\n", err)
		return
	}

	// Add the forecast tool and its handler function
	s.AddTool(mcp.GetForecast(), mcp.ForecastCall)

	fmt.Println("Weather Demo started, waiting for requests...")
	// Start the server
	if err := server.ServeStdio(s); err != nil {
		fmt.Printf("Server error: %v\n", err)
	}
}

集成到 LLM

1
go build -o forecast .
1
2
3
4
5
6
7
8
9
// 配置 mcp 服务器
{
    "mcpServers": {
        "weather": {
            // 程序二进制路径 
            "command": "/your-path/forecast" 
        }
    }
}
image