|
1 | 1 | # gomcp |
2 | | -Unofficial Golang SDK for Anthropic Model Context Protocol |
3 | 2 |
|
4 | | -This is still very much a work in progress. |
| 3 | +## Description |
| 4 | +An unofficial Golang implementation of the Model Context Protocol defined by Anthropic. |
5 | 5 |
|
6 | | -You'll find a presentation of the project in this [blog post](http://pcarion.com/blog/go_model_context_protocol/) |
| 6 | +The officialannouncement of the Model Context Protocol is [here](https://www.anthropic.com/news/model-context-protocol). |
7 | 7 |
|
| 8 | +The Model Context Protocol (MCP) provides a standardized, secure mechanism for AI models to interact with external tools and data sources. |
8 | 9 |
|
9 | | -# Reference documentation |
| 10 | +By defining a precise interface and communication framework, MCP allows AI assistants, such as the Claude desktop application, to safely |
| 11 | +extend their capabilities. |
10 | 12 |
|
11 | | -You'll find the documentation of the Model Context Protocol [here](https://modelcontextprotocol.io/introduction) |
| 13 | +## Reference documentation |
| 14 | + |
| 15 | +* the full documentation of the Model Context Protocol is [here](https://modelcontextprotocol.io/introduction) |
| 16 | +* the official TypeScript SDK is available[here](https://github.com/modelcontextprotocol/typescript-sdk) |
| 17 | +* the official Python SDK is available[here](https://github.com/modelcontextprotocol/python-sdk) |
| 18 | + |
| 19 | +## Installation |
| 20 | + |
| 21 | +``` |
| 22 | +go get github.com/llmcontext/gomcp |
| 23 | +``` |
| 24 | + |
| 25 | +Direct dependencies: |
| 26 | + |
| 27 | +* [github.com/invopop/jsonschema](https://github.com/invopop/jsonschema): to generate JSON Schemas from Go types through reflection |
| 28 | +* [github.com/xeipuuv/gojsonschema](https://github.com/xeipuuv/gojsonschema): for JSON schema validation |
| 29 | +* [gopkg.in/yaml.v3](https://github.com/go-yaml/yaml): for YAML parsing of the prompts definitionfile |
| 30 | +* [github.com/stretchr/testify](https://github.com/stretchr/testify): for testing |
| 31 | +* [go.uber.org/zap](https://github.com/uber-go/zap): for logging |
| 32 | + |
| 33 | +## Usage |
| 34 | + |
| 35 | +Let's consider a simple example where we want to use the `mcp` package to create a server that can retrieve the content of a Notion page so that you can use it in a Claude chat. |
| 36 | + |
| 37 | +The way to do this is to define a set of tools that can be exposed to the LLM through the Model Context Protocol and to implement them in Go. |
| 38 | + |
| 39 | +The first step is to define the tools that will be exposed to the LLM. |
| 40 | + |
| 41 | +In Mcp, you define a set of `Tool providers` and each provider is a set of `Tools`. |
| 42 | + |
| 43 | +In our case, we have a single provider called `notion` that has a single tool to retrieve the content of a Notion page. |
| 44 | + |
| 45 | +### configuration file |
| 46 | + |
| 47 | +Once compiled, the `mcp` command needs a configuration file to start the server. |
| 48 | + |
| 49 | +An example of configuration file would be: |
| 50 | + |
| 51 | +```json |
| 52 | +{ |
| 53 | + "serverInfo": { |
| 54 | + "name": "gomcp", |
| 55 | + "version": "0.1.0" |
| 56 | + }, |
| 57 | + "logging": { |
| 58 | + "file": "/var/log/gomcp/mcpnotion.log", |
| 59 | + "level": "debug", |
| 60 | + "withStderr": false |
| 61 | + }, |
| 62 | + "prompts": { |
| 63 | + "file": "/etc/gomcp/prompts.yaml" |
| 64 | + }, |
| 65 | + "tools": [ |
| 66 | + { |
| 67 | + "name": "notion", |
| 68 | + "description": "Get a notion document", |
| 69 | + "configuration": { |
| 70 | + "notionToken": "ntn_<redacted>" |
| 71 | + } |
| 72 | + } |
| 73 | + ] |
| 74 | +} |
| 75 | +``` |
| 76 | + |
| 77 | +The `serverInfo` section is used to identify the server and its version, it is mandatory as they are used in the MCP protocolto identify the server. |
| 78 | + |
| 79 | +The `logging` section is used to configure the logging system. The `file` field is the path to the log file, the `level` field is the logging level (debug, info, warn, error) and the `withStderr` field is used to redirect the logging to the standard error stream. |
| 80 | + |
| 81 | +The `prompts` section is used to define the path to the YAML file containing the prompts to expose to the LLM. See below for a description of the YAML syntax to define the prompts. |
| 82 | + |
| 83 | +The `tools` section is used to define the tools that will be exposed to the LLM. This is an array of tool providers, each provider is an object with a `name` and a `description` field. The `configuration` field is an object that contains the configuration for the tool provider. |
| 84 | + |
| 85 | +In our case, we have a single tool provider called `notion` that has a single tool to retrieve the content of a Notion page. |
| 86 | + |
| 87 | +The configuration for the `notion` tool provider is the Notion token. |
| 88 | + |
| 89 | +This configuration must be backed by a Golang struct that will be used to parse the configuration file: |
| 90 | + |
| 91 | +```go |
| 92 | +type NotionGetDocumentConfiguration struct { |
| 93 | + NotionToken string `json:"notionToken" jsonschema_description:"the notion token for the Notion client."` |
| 94 | +} |
| 95 | +``` |
| 96 | +The tags here (`json` and `jsonschema_description`) are used to generate the JSON Schema for the configuration data. |
| 97 | +If the configuration is invalid, the `mcp` command will fail to start. |
| 98 | + |
| 99 | +You then create a function that will use those configuration data to generate a `Tool Context`: |
| 100 | + |
| 101 | +```go |
| 102 | +func NotionToolInit(ctx context.Context, config *NotionGetDocumentConfiguration) (*NotionGetDocumentContext, error) { |
| 103 | + client := notionapi.NewClient(notionapi.Token(config.NotionToken)) |
| 104 | + |
| 105 | + // we need to initialize the Notion client |
| 106 | + return &NotionGetDocumentContext{NotionClient: client}, nil |
| 107 | +} |
| 108 | +``` |
| 109 | + |
| 110 | +And the definition of the `Tool Context`: |
| 111 | + |
| 112 | +```go |
| 113 | +type NotionGetDocumentContext struct { |
| 114 | + // The Notion client. |
| 115 | + NotionClient *notionapi.Client |
| 116 | +} |
| 117 | +``` |
| 118 | +This time, you don't need to add tags to your type as this struct is internal to the tool provider: the instance of this struct is created by the `ToolInit` function and is passed to the functions implementing the tool(s). |
| 119 | + |
| 120 | +A tool function is defined like this: |
| 121 | + |
| 122 | +```go |
| 123 | +func NotionGetPage( |
| 124 | + ctx context.Context, |
| 125 | + toolCtx *NotionGetDocumentContext, |
| 126 | + input *NotionGetDocumentInput, |
| 127 | + output types.ToolCallResult) error { |
| 128 | + logger := gomcp.GetLogger(ctx) |
| 129 | + logger.Info("NotionGetPage", types.LogArg{ |
| 130 | + "pageId": input.PageId, |
| 131 | + }) |
| 132 | + |
| 133 | + content, err := getPageContent(ctx, toolCtx.NotionClient, input.PageId) |
| 134 | + if err != nil { |
| 135 | + return err |
| 136 | + } |
| 137 | + output.AddTextContent(strings.Join(content, "\n")) |
| 138 | + |
| 139 | + return nil |
| 140 | +} |
| 141 | +``` |
| 142 | +The first parameter is the context, it is mandatory as it is used to retrieve the logger. |
| 143 | + |
| 144 | +The second parameter is the tool context, it is the instance of the struct created by the `ToolInit` function. |
| 145 | + |
| 146 | +The third parameter is the input, it is an object that contains the input data for the tool call. Those input parameter are provided by the LLM when the tool is called. |
| 147 | + |
| 148 | +The last parameter is the output, it is an interface that allows the function to construct the data it wants to return to the LLM. |
| 149 | + |
| 150 | +Here again, the input type must be tagged appropriately: |
| 151 | + |
| 152 | +```go |
| 153 | +type NotionGetDocumentInput struct { |
| 154 | + PageId string `json:"pageId" jsonschema_description:"the ID of the Notion page to retrieve."` |
| 155 | +} |
| 156 | +``` |
| 157 | + |
| 158 | +Those tags will be used to generate the JSON Schema for the input data: |
| 159 | +* they will be returned to the LLM during the discovery phase of the MCP protocol |
| 160 | +* they will be used to validate the input data when the tool is called |
| 161 | + |
| 162 | +Once those typea and functions are defined, you can bind them in the MCP server by calling the `RegisterTool` function: |
| 163 | + |
| 164 | + |
| 165 | +```go |
| 166 | +func RegisterTools(toolRegistry types.ToolRegistry) error { |
| 167 | + toolProvider, err := toolRegistry.DeclareToolProvider("notion", NotionToolInit) |
| 168 | + if err != nil { |
| 169 | + return err |
| 170 | + } |
| 171 | + err = toolProvider.AddTool("notion_get_page", "Get the markdown content of a notion page", NotionGetPage) |
| 172 | + if err != nil { |
| 173 | + return err |
| 174 | + } |
| 175 | + return nil |
| 176 | +} |
| 177 | +``` |
| 178 | + |
| 179 | +This `RegisterTools` function is called from the `main()` function of your MCP server: |
| 180 | + |
| 181 | +```go |
| 182 | +package main |
| 183 | + |
| 184 | +import ( |
| 185 | + "flag" |
| 186 | + "fmt" |
| 187 | + "os" |
| 188 | + |
| 189 | + "github.com/llmcontext/gomcp" |
| 190 | + "github.com/llmcontext/mcpnotion/tools" |
| 191 | +) |
| 192 | + |
| 193 | +func main() { |
| 194 | + configFile := flag.String("configFile", "", "config file path (required)") |
| 195 | + flag.Parse() |
| 196 | + |
| 197 | + if *configFile == "" { |
| 198 | + fmt.Println("Config file is required") |
| 199 | + flag.PrintDefaults() |
| 200 | + os.Exit(1) |
| 201 | + } |
| 202 | + |
| 203 | + mcp, err := gomcp.NewModelContextProtocolServer(*configFile) |
| 204 | + if err != nil { |
| 205 | + fmt.Println("Error creating MCP server:", err) |
| 206 | + os.Exit(1) |
| 207 | + } |
| 208 | + toolRegistry := mcp.GetToolRegistry() |
| 209 | + |
| 210 | + err = tools.RegisterTools(toolRegistry) |
| 211 | + if err != nil { |
| 212 | + fmt.Println("Error registering tools:", err) |
| 213 | + os.Exit(1) |
| 214 | + } |
| 215 | + |
| 216 | + transport := mcp.StdioTransport() |
| 217 | + |
| 218 | + mcp.Start(transport) |
| 219 | +} |
| 220 | +``` |
| 221 | + |
| 222 | +* `gomcp.NewModelContextProtocolServer(*configFile)` creates a new MCP server with the configuration file |
| 223 | +* `mcp.GetToolRegistry()` returns the tool registry, it is used to register the tools. The `tools.RegisterTools` is the function we defined earlier and that binds the tools to the MCP server. |
| 224 | +* `mcp.StdioTransport()` creates a new transport based on standard input/output streams. That's the transport used to integrate with the Claude desktop application. |
| 225 | +* `mcp.Start(transport)` starts the MCP server with the given transport |
| 226 | + |
| 227 | + |
| 228 | +## integration with Claude desktop application |
| 229 | + |
| 230 | +## Changelog |
| 231 | + |
| 232 | +### [0.1.0](https://github.com/llmcontext/gomcp/tree/v0.1.0) |
| 233 | + |
| 234 | +- Initial release |
| 235 | +- Support Tools for the Model Context Protocol |
| 236 | + |
| 237 | +### [0.2.0](https://github.com/llmcontext/gomcp/tree/v0.2.0) |
| 238 | + |
| 239 | +- Change signature of `mcp.Start(serverName, serverVersion, transport)` to `mcp.Start(transport)`, the server name and version are now read from the configuration file |
| 240 | +- Add support for prompts stored in a YAML file. File path is read from the configuration file. |
0 commit comments