内容
# gomcp
## 描述
Anthropic 模型上下文协议(Model Context Protocol,简称 MCP) 的非官方 Golang 实现。
MCP 官方公告参见 [这里](https://www.anthropic.com/news/model-context-protocol)。
模型上下文协议(MCP)为 AI 模型与外部工具和数据源交互提供了一种标准化的、安全机制。
通过定义精确的接口和通信框架,MCP 允许 AI 助手(如 Claude 桌面应用程序)安全地扩展其功能。
## 参考文档
* MCP 完整文档参见 [这里](https://modelcontextprotocol.io/introduction)
* 官方 TypeScript SDK 参见 [这里](https://github.com/modelcontextprotocol/typescript-sdk)
* 官方 Python SDK 参见 [这里](https://github.com/modelcontextprotocol/python-sdk)
## 安装
```bash
go get github.com/llmcontext/gomcp
```
直接依赖:
* [github.com/invopop/jsonschema](https://github.com/invopop/jsonschema):通过反射从 Go 类型生成 JSON 模式
* [github.com/xeipuuv/gojsonschema](https://github.com/xeipuuv/gojsonschema):用于 JSON 模式验证
* [gopkg.in/yaml.v3](https://github.com/go-yaml/yaml):用于 YAML 解析
* [github.com/stretchr/testify](https://github.com/stretchr/testify):用于测试
* [go.uber.org/zap](https://github.com/uber-go/zap):用于日志记录
## 用法
考虑一个简单的示例,我们使用 `mcp` 包创建一个可以检索 Notion 页面内容的服务器,以便在 Claude 聊天中使用。
实现此目的的方法是定义一组可以通过模型上下文协议暴露给 LLM 的工具,并在 Go 中实现它们。
第一步是定义将暴露给 LLM 的工具。
在 MCP 中,定义一组 `工具提供者`,每个提供者是一组 `工具`。
在我们的例子中,我们有一个名为 `notion` 的提供者,它有一个工具来检索 Notion 页面的内容。
### 配置文件
MCP 服务器需要一个配置文件才能启动。
示例配置文件如下:
```json
{
"serverInfo": {
"name": "gomcp",
"version": "0.1.0"
},
"logging": {
"file": "/var/log/gomcp/mcpnotion.log",
"level": "debug",
"withStderr": false
},
"prompts": {
"file": "/etc/gomcp/prompts.yaml"
},
"tools": [
{
"name": "notion",
"description": "获取 Notion 文档",
"configuration": {
"notionToken": "ntn_<redacted>"
}
}
]
}
```
`serverInfo` 部分用于标识服务器及其版本,这是必需的,因为它们在 MCP 协议中用于标识服务器。
`logging` 部分用于配置日志记录系统。`file` 字段是日志文件的路径,`level` 字段是日志记录级别(debug、info、warn、error),`withStderr` 字段用于将日志记录重定向到标准错误流。
`prompts` 部分用于定义 YAML 文件的路径,该文件包含要暴露给 LLM 的提示。有关 YAML 语法的描述,请参见下面的内容。
`tools` 部分用于定义将暴露给 LLM 的工具。这是一个工具提供者的数组,每个提供者是一个具有 `name` 和 `description` 字段的对象。`configuration` 字段是一个包含工具提供者配置的对象。
在我们的例子中,我们有一个名为 `notion` 的工具提供者,它有一个工具来检索 Notion 页面的内容。
`notion` 工具提供者的配置是 Notion token。
此配置必须由一个 Go 结构体支持,该结构体将用于解析配置文件:
```go
type NotionGetDocumentConfiguration struct {
NotionToken string `json:"notionToken" jsonschema_description:"the notion token for the Notion client."`
}
```
这里的标签(`json` 和 `jsonschema_description`)用于为配置数据生成 JSON 模式。
如果配置无效,`mcp` 命令将无法启动。
然后,您可以创建一个函数,使用这些配置数据生成一个 `Tool Context`:
```go
func NotionToolInit(ctx context.Context, config *NotionGetDocumentConfiguration) (*NotionGetDocumentContext, error) {
client := notionapi.NewClient(notionapi.Token(config.NotionToken))
// 我们需要初始化 Notion 客户端
return &NotionGetDocumentContext{NotionClient: client}, nil
}
```
以及 `Tool Context` 的定义:
```go
type NotionGetDocumentContext struct {
// Notion 客户端。
NotionClient *notionapi.Client
}
```
这次,您不需要在类型中添加标签,因为此结构体是工具提供者内部的:此结构体的实例由 `ToolInit` 函数创建,并传递给实现工具的函数。
工具函数定义如下:
```go
func NotionGetPage(
ctx context.Context,
toolCtx *NotionGetDocumentContext,
input *NotionGetDocumentInput,
output types.ToolCallResult) error {
logger := gomcp.GetLogger(ctx)
logger.Info("NotionGetPage", types.LogArg{
"pageId": input.PageId,
})
content, err := getPageContent(ctx, toolCtx.NotionClient, input.PageId)
if err != nil {
return err
}
output.AddTextContent(strings.Join(content, "\n"))
return nil
}
```
第一个参数是上下文,这是必填的,因为它用于检索记录器。
第二个参数是工具上下文,它是由 `ToolInit` 函数创建的结构体实例。
第三个参数是输入,它是一个包含工具调用输入数据的对象。这些输入参数由 LLM 在调用工具时提供。
最后一个参数是输出,它是一个接口,允许函数构造要返回给 LLM 的数据。
同样,输入类型必须正确标记:
```go
type NotionGetDocumentInput struct {
PageId string `json:"pageId" jsonschema_description:"the ID of the Notion page to retrieve."`
}
```
这些标签将用于为输入数据生成 JSON 模式:
* 它们将在 MCP 协议的发现阶段返回给 LLM
* 它们将用于在调用工具时验证输入数据
一旦定义了这些类型和函数,您就可以通过调用 `RegisterTool` 函数将它们绑定到 MCP 服务器:
```go
func RegisterTools(toolRegistry types.ToolRegistry) error {
toolProvider, err := toolRegistry.DeclareToolProvider("notion", NotionToolInit)
if err != nil {
return err
}
err = toolProvider.AddTool("notion_get_page", "获取 Notion 页面的 markdown 内容", NotionGetPage)
if err != nil {
return err
}
return nil
}
```
此 `RegisterTools` 函数从 MCP 服务器的 `main()` 函数调用:
```go
package main
import (
"flag"
"fmt"
"os"
"github.com/llmcontext/gomcp"
"github.com/llmcontext/mcpnotion/tools"
)
func main() {
configFile := flag.String("configFile", "", "配置文件路径(必填)")
flag.Parse()
if *configFile == "" {
fmt.Println("配置文件是必填项")
flag.PrintDefaults()
os.Exit(1)
}
mcp, err := gomcp.NewModelContextProtocolServer(*configFile)
if err != nil {
fmt.Println("创建 MCP 服务器出错:", err)
os.Exit(1)
}
toolRegistry := mcp.GetToolRegistry()
err = tools.RegisterTools(toolRegistry)
if err != nil {
fmt.Println("注册工具出错:", err)
os.Exit(1)
}
transport := mcp.StdioTransport()
mcp.Start(transport)
}
```
* `gomcp.NewModelContextProtocolServer(*configFile)` 使用配置文件创建一个新的 MCP 服务器
* `mcp.GetToolRegistry()` 返回工具注册表,用于注册工具。`tools.RegisterTools` 是我们之前定义的函数,它将工具绑定到 MCP 服务器。
* `mcp.StdioTransport()` 创建一个基于标准输入/输出流的新传输。这是用于与 Claude 桌面应用程序集成的传输。
* `mcp.Start(transport)` 使用给定的传输启动 MCP 服务器
## 提示定义文件
提示定义文件是一个 YAML 文件,它定义了要暴露给 LLM 的提示。
以下是示例提示定义文件的 _dummy_ 版本:
```yaml
prompts:
- name: "hello"
description: "说你好"
arguments:
- name: "name"
description: "要问候的人的名字"
required: true
prompt: >
你好 {{.name}},你怎么样?
请以友好和引人入胜的方式回应。
```
此文件不言自明:
* `prompts` 部分是一个提示数组
* 每个提示是一个具有 `name` 和 `description` 字段的对象
* `arguments` 部分是一个参数数组,每个参数是一个具有 `name`、`description` 和 `required` 字段的对象
* `prompt` 部分是要暴露给 LLM 的提示。它使用 [Go 模板语法](https://pkg.go.dev/text/template) 嵌入参数
有关如何从 Claude 访问提示的更多信息,请参见 [这里](https://github.com/llmcontext/mcpnotion?tab=readme-ov-file#prompts-access)。
## 与 Claude 桌面应用程序集成
有关如何将您的 MCP 服务器与 Claude 桌面应用程序集成的更多信息,请参见 [mcpnotion](https://github.com/llmcontext/mcpnotion) 项目的 [README](https://github.com/llmcontext/mcpnotion/blob/main/README.md)。
## 待办事项
- 更好的错误管理:例如,初始化函数失败时
## 更新日志
### 0.5.0
- 添加工具来检测死代码:go install golang.org/x/tools/cmd/deadcode@latest
### 0.4.0
- 添加对代理/多路复用器的支持。在配置文件中,将 `proxy.enabled` 字段设置为 true 以启用代理。代理是一个工具,允许您在任意 MCP 服务器和 LLM 之间代理消息。
- 为代理的输出添加 pterm
- 重构 JSON 协议消息以处理客户端和服务器消息
- 删除日志记录的 fifo 选项
### [0.3.0](https://github.com/llmcontext/gomcp/tree/v0.3.0) - 2024-12-08
- 添加协议调试文件的支持。在配置文件中,将 `logging.protocolDebugFile` 字段设置为协议调试信息的写入路径。
- 添加检查器的支持。在配置文件中,设置 `inspector.enabled` 和 `inspector.listenAddress` 字段以设置检查器服务器地址。检查器是一个工具,允许您检查 MCP 服务器和 LLM 之间交换的消息。仍在开发中。
- 当父进程初始化时,杀死 MCP 服务器。虽然我们使用上下文停止服务器和 goroutine,但似乎当 MCP 服务器停止时,父进程并不总是被杀死。这就是为什么我们现在在父进程初始化时杀死 MCP 服务器的原因。
### [0.2.0](https://github.com/llmcontext/gomcp/tree/v0.2.0) - 2024-12-07
- 将 `mcp.Start(serverName, serverVersion, transport)` 的签名更改为 `mcp.Start(transport)`,服务器名称和版本现在从配置文件中读取
- 支持存储在 YAML 文件中的提示。文件路径从配置文件中读取。
### [0.1.0] - 已删除
- 初始版本
- 支持模型上下文协议的工具