前言

官方SDK仓库:https://github.com/modelcontextprotocol/go-sdk
官方demo:https://github.com/modelcontextprotocol/go-sdk/blob/main/examples/hello/main.go

demo代码:

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
// Copyright 2025 The Go MCP SDK Authors. All rights reserved.
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.

package main

import (
"context"
"flag"
"fmt"
"log"
"net/http"
"net/url"
"os"

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

var httpAddr = flag.String("http", "", "if set, use streamable HTTP at this address, instead of stdin/stdout")

type HiArgs struct {
Name string `json:"name" jsonschema:"the name to say hi to"`
}

func SayHi(ctx context.Context, ss *mcp.ServerSession, params *mcp.CallToolParamsFor[HiArgs]) (*mcp.CallToolResultFor[struct{}], error) {
return &mcp.CallToolResultFor[struct{}]{
Content: []mcp.Content{
&mcp.TextContent{Text: "Hi " + params.Arguments.Name},
},
}, nil
}

func PromptHi(ctx context.Context, ss *mcp.ServerSession, params *mcp.GetPromptParams) (*mcp.GetPromptResult, error) {
return &mcp.GetPromptResult{
Description: "Code review prompt",
Messages: []*mcp.PromptMessage{
{Role: "user", Content: &mcp.TextContent{Text: "Say hi to " + params.Arguments["name"]}},
},
}, nil
}

func main() {
flag.Parse()

server := mcp.NewServer(&mcp.Implementation{Name: "greeter"}, nil)
mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)
server.AddPrompt(&mcp.Prompt{Name: "greet"}, PromptHi)
server.AddResource(&mcp.Resource{
Name: "info",
MIMEType: "text/plain",
URI: "embedded:info",
}, handleEmbeddedResource)

if *httpAddr != "" {
handler := mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server {
return server
}, nil)
log.Printf("MCP handler listening at %s", *httpAddr)
http.ListenAndServe(*httpAddr, handler)
} else {
t := mcp.NewLoggingTransport(mcp.NewStdioTransport(), os.Stderr)
if err := server.Run(context.Background(), t); err != nil {
log.Printf("Server failed: %v", err)
}
}
}

var embeddedResources = map[string]string{
"info": "This is the hello example server.",
}

func handleEmbeddedResource(_ context.Context, _ *mcp.ServerSession, params *mcp.ReadResourceParams) (*mcp.ReadResourceResult, error) {
u, err := url.Parse(params.URI)
if err != nil {
return nil, err
}
if u.Scheme != "embedded" {
return nil, fmt.Errorf("wrong scheme: %q", u.Scheme)
}
key := u.Opaque
text, ok := embeddedResources[key]
if !ok {
return nil, fmt.Errorf("no embedded resource named %q", key)
}
return &mcp.ReadResourceResult{
Contents: []*mcp.ResourceContents{
{URI: params.URI, MIMEType: "text/plain", Text: text},
},
}, nil
}

代码解析

1. 程序结构

  • flag 模块:用于解析命令行参数,这里定义了一个 httpAddr,用于指定是否通过 HTTP 提供服务。
  • mcp 模块:这是 MCP SDK 的核心模块,用于定义和处理 tool、prompt 和 resource。
  • main 函数:程序入口,负责初始化 mcp.Server,注册工具、提示和资源,并启动服务。

2. mcp 的主要概念

  • Tool:工具函数,用于执行特定的任务。例如,SayHi 是一个工具函数,它接收一个名字,并返回一个问候消息。
  • Prompt:提示函数,用于生成对话框的内容。例如,PromptHi 生成一个提示用户输入名字的对话框。
  • Resource:资源,用于存储和提供数据。例如,handleEmbeddedResource 提供了一个嵌入式的资源,返回一段文本。

3. 程序的主要功能

  • 工具函数 SayHi:
    • 接收一个名字作为参数。
    • 返回一个包含问候消息的 mcp.CallToolResult。
  • 提示函数 PromptHi:
    • 生成一个提示消息,让用户输入名字。
  • 资源 info:
    • 提供一个嵌入式的资源,返回一段固定的文本。
  • 服务启动:
    • 如果设置了 httpAddr,则通过 HTTP 提供服务。
    • 否则,通过标准输入/输出与用户交互。

mcp 的 prompt、resource 和 tool 的使用

1. Tool 的使用

  • 定义:通过 mcp.AddTool 将工具函数注册到 mcp.Server。
  • 调用:客户端可以通过调用工具的名称,传递参数,获取结果。
  • 示例:
    1
    mcp.AddTool(server, &mcp.Tool{Name: "greet", Description: "say hi"}, SayHi)

2. Prompt 的使用

  • 定义:通过 server.AddPrompt 将提示函数注册到 mcp.Server。
  • 调用:客户端可以通过调用提示的名称,获取提示内容。
  • 示例:
    1
    server.AddPrompt(&mcp.Prompt{Name: "greet"}, PromptHi)

3. Resource 的使用

  • 定义:通过 server.AddResource 将资源注册到 mcp.Server。
  • 调用:客户端可以通过资源的 URI 获取资源内容。
  • 示例:
    1
    2
    3
    4
    5
    server.AddResource(&mcp.Resource{
    Name: "info",
    MIMEType: "text/plain",
    URI: "embedded:info",
    }, handleEmbeddedResource)

资源查询场景的举例

1. 功能概述

实现一个查询资源的 mcp 服务,包含以下功能:

  • 用户输入查询文本进行模糊查询。
  • 如果查询结果超过阈值(如10个),系统自动提取资源类型列表,并提示用户选择一个资源类型。
  • 用户选择资源类型后,系统进行更精确的查询并返回结果。

2. 伪代码实现

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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package main

import (
"context"
"flag"
"fmt"
"log"
"net/http"
"os"

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

var httpAddr = flag.String("http", "", "if set, use streamable HTTP at this address, instead of stdin/stdout")

// 查询资源的工具参数
type QueryResourcesArgs struct {
QueryText string `json:"queryText" jsonschema:"the query text"`
ResourceType string `json:"resourceType" jsonschema:"the resource type (optional)"`
}

// 定义资源结构体
type Resource struct {
ID string
Type string
}

// 资源管理器
type ResourceManager struct {
Resources []Resource
}

// 新建资源管理器
func NewResourceManager(resources []Resource) *ResourceManager {
return &ResourceManager{
Resources: resources,
}
}

// 查询资源的工具
func (rm *ResourceManager) QueryResources(ctx context.Context, ss *mcp.ServerSession, params *mcp.CallToolParamsFor[QueryResourcesArgs]) (*mcp.CallToolResultFor[[]string], error) {
queryText := params.Arguments.QueryText
resourceType := params.Arguments.ResourceType

// 模糊查询逻辑
var results []string
for _, res := range rm.Resources {
if resourceType == "" || res.Type == resourceType {
if queryText == "" || res.ID == queryText {
results = append(results, res.ID)
}
}
}

// 如果结果超过阈值,提示用户选择资源类型
if len(results) > 10 {
// 提取资源类型列表
typeSet := make(map[string]struct{})
for _, res := range rm.Resources {
typeSet[res.Type] = struct{}{}
}
typeList := make([]string, 0, len(typeSet))
for t := range typeSet {
typeList = append(typeList, t)
}

// 提示用户选择资源类型
return &mcp.CallToolResultFor[[]string]{
Content: []mcp.Content{
&mcp.TextContent{Text: fmt.Sprintf("Too many results (%d). Please specify a resource type from: %v", len(results), typeList)},
},
}, nil
}

return &mcp.CallToolResultFor[[]string]{
Content: []mcp.Content{
&mcp.TextContent{Text: fmt.Sprintf("Found %d resources: %v", len(results), results)},
},
Result: results,
}, nil
}

// 提示用户选择资源类型
func (rm *ResourceManager) PromptSelectResourceType(ctx context.Context, ss *mcp.ServerSession, params *mcp.GetPromptParams) (*mcp.GetPromptResult, error) {
// 提取资源类型列表
typeSet := make(map[string]struct{})
for _, res := range rm.Resources {
typeSet[res.Type] = struct{}{}
}
typeList := make([]string, 0, len(typeSet))
for t := range typeSet {
typeList = append(typeList, t)
}

return &mcp.GetPromptResult{
Description: "Select a resource type",
Messages: []*mcp.PromptMessage{
{Role: "user", Content: &mcp.TextContent{Text: fmt.Sprintf("Please select a resource type from: %v", typeList)}},
},
}, nil
}

func main() {
flag.Parse()

// 初始化资源管理器
mockResources := []Resource{
{"doc1", "document"},
{"doc2", "document"},
{"img1", "image"},
{"img2", "image"},
{"vid1", "video"},
{"vid2", "video"},
{"doc3", "document"},
{"doc4", "document"},
{"img3", "image"},
{"img4", "image"},
{"vid3", "video"},
{"vid4", "video"},
}
resourceManager := NewResourceManager(mockResources)

// 初始化 MCP 服务器
server := mcp.NewServer(&mcp.Implementation{Name: "resource-query"}, nil)

// 注册工具和提示
mcp.AddTool(server, &mcp.Tool{Name: "queryResources", Description: "Query resources by text and type"}, resourceManager.QueryResources)
server.AddPrompt(&mcp.Prompt{Name: "selectResourceType"}, resourceManager.PromptSelectResourceType)

// 启动服务
if *httpAddr != "" {
handler := mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server {
return server
}, nil)
log.Printf("MCP handler listening at %s", *httpAddr)
http.ListenAndServe(*httpAddr, handler)
} else {
t := mcp.NewLoggingTransport(mcp.NewStdioTransport(), os.Stderr)
if err := server.Run(context.Background(), t); err != nil {
log.Printf("Server failed: %v", err)
}
}
}

3. 功能说明

  1. 工具 queryResources:
  • 输入参数:
    • queryText:用户输入的查询文本。
    • resourceType:用户指定的资源类型(可选)。
  • 逻辑:
    • 如果用户未指定资源类型,进行模糊查询。
    • 如果查询结果超过10个,提取资源类型列表,并提示用户选择一个资源类型。
    • 如果用户指定了资源类型,进行精确查询。
  • 输出:
    • 如果结果超过阈值,返回提示信息,列出资源类型供用户选择。
    • 如果结果未超过阈值,返回查询到的资源列表。
  1. 动态提示:
  • 当查询结果超过10个时,系统会自动提取资源类型列表,并提示用户选择一个资源类型。
  • 提示信息格式:Too many results (12). Please specify a resource type from: [document, image, video]。

4. 交互流程

  1. 用户首次查询:
  • 用户调用 queryResources,仅输入查询文本(如 queryText=”doc”)。
  • 系统进行模糊查询,返回结果。
  • 如果结果超过10个,系统提取资源类型列表,并提示用户选择资源类型。
  1. 用户选择资源类型:
  • 用户根据提示选择一个资源类型(如 resourceType=”document”)。
  • 用户再次调用 queryResources,输入查询文本和资源类型。
  • 系统进行精确查询,返回更精确的结果。

5. 示例交互

首次查询:

1
2
User: queryResources(queryText="doc")
System: Too many results (12). Please specify a resource type from: [document, image, video]

用户选择资源类型并再次查询:

1
2
User: queryResources(queryText="doc", resourceType="document")
System: Found 4 resources: [doc1, doc2, doc3, doc4]