Go言語でMCPサーバーを実装する方法の備忘録です。
はじめに
MCPサーバーをGoで実装したくなりました。
やり方のメモを残します。
# 作業環境 $ go version go version go1.23.4 darwin/arm64 # mcp-goバージョン mcp-go v0.18.0
The English translation of this post is here.
前提: Go言語の公式MCP SDKはあるのか?
本記事執筆時点(2025/04)ではまだありません。
公式のGo言語MCP SDKを作ろうという議論は起きていますが、まだ議論中の段階です。
今回は上記discussionでも言及されている、サードパーティのGo SDKの中では最もメジャーなmark3labsのmcp-goを使って、ミニマムなMCPサーバーを実装してみます。
MCPサーバーをGoで実装する
実装するMCPサーバー
最小限のTool/Resource/Promptを実装するミニマムなMCPサーバーを実装します。
Python SDKであれば下記のように実装できるものを、Goで実装していきます。
from mcp.server.fastmcp import FastMCP mcp = FastMCP("HelloMCP") @mcp.tool() def add(a: int, b: int) -> int: """Add two numbers""" return a + b @mcp.resource("greeting://{name}") def get_greeting(name: str) -> str: """Get a personalized greeting""" return f"Hello, {name}!" @mcp.prompt() def translation_ja(txt: str) -> str: """Translating to Japanese""" return f"Please translate this sentence into Japanese:\n\n{txt}"
GoでのMCPサーバー実装
上記のようなMCPサーバーをmcp-goで実装したものがこちら:
package main import ( "context" "fmt" "strings" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) func main() { s := server.NewMCPServer( "Minimum Golang MCP Server", "1.0.0", ) // Tool: Add operation addTool := mcp.NewTool( "add", mcp.WithDescription("Add two numbers"), mcp.WithNumber("x", mcp.Required(), ), mcp.WithNumber("y", mcp.Required(), ), ) s.AddTool(addTool, addToolHandler) // Resource: Greeting template greetingResource := mcp.NewResourceTemplate( "greeting://{name}", "getGreeting", mcp.WithTemplateDescription("Get a personalized greeting"), mcp.WithTemplateMIMEType("text/plain"), ) s.AddResourceTemplate(greetingResource, greetingResourceHandler) // Prompt: Japanese translation template translationPrompt := mcp.NewPrompt( "translationJa", mcp.WithPromptDescription("Translating to Japanese"), mcp.WithArgument("txt", mcp.RequiredArgument()), ) s.AddPrompt(translationPrompt, translationPromptHandler) // Start server with stdio if err := server.ServeStdio(s); err != nil { fmt.Printf("Server error: %v\n", err) } } func addToolHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { x := request.Params.Arguments["x"].(float64) y := request.Params.Arguments["y"].(float64) return mcp.NewToolResultText(fmt.Sprintf("%.2f", x+y)), nil } func greetingResourceHandler(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) { name, err := extractNameFromURI(request.Params.URI) if err != nil { return nil, err } return []mcp.ResourceContents{ mcp.TextResourceContents{ URI: request.Params.URI, MIMEType: "text/plain", Text: fmt.Sprintf("Hello, %s!", name), }, }, nil } // Extracts the name from a URI formatted as "greeting://{name}" func extractNameFromURI(uri string) (string, error) { const prefix = "greeting://" if !strings.HasPrefix(uri, prefix) { return "", fmt.Errorf("invalid URI format: %s", uri) } name := strings.TrimPrefix(uri, prefix) if name == "" { return "", fmt.Errorf("name is empty in URI: %s", uri) } return name, nil } func translationPromptHandler(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) { txt := request.Params.Arguments["txt"] prompt := fmt.Sprintf("Please translate this sentence into Japanese:\n\n%s", txt) return mcp.NewGetPromptResult( "Translating to Japanese", []mcp.PromptMessage{ mcp.NewPromptMessage( mcp.RoleAssistant, mcp.NewTextContent(prompt), ), }, ), nil }
ソースコード全体はこちら:
Pythonで実装するよりもかなりコード量が多くなってしまいましたね。
それではTool/Resource/Promptの実装をそれぞれ見ていきます。
Toolの実装
// Tool: Add operation addTool := mcp.NewTool( "add", mcp.WithDescription("Add two numbers"), mcp.WithNumber("x", mcp.Required(), ), mcp.WithNumber("y", mcp.Required(), ), ) s.AddTool(addTool, addToolHandler) // ~中略~ func addToolHandler(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { x := request.Params.Arguments["x"].(float64) y := request.Params.Arguments["y"].(float64) return mcp.NewToolResultText(fmt.Sprintf("%.2f", x+y)), nil }
というのが大まかな構成です。
NewTool
はいわゆるOptions Patternになっており、ToolOption
型の関数を必要に応じて動的に渡すことでToolの定義を設定していきます。
NewToolのソースコード:
// NewTool creates a new Tool with the given name and options. // The tool will have an object-type input schema with configurable properties. // Options are applied in order, allowing for flexible tool configuration. func NewTool(name string, opts ...ToolOption) Tool { tool := Tool{ Name: name, InputSchema: ToolInputSchema{ Type: "object", Properties: make(map[string]interface{}), Required: nil, // Will be omitted from JSON if empty }, } for _, opt := range opts { opt(&tool) } return tool }
NewTool
にオプション引数として渡すToolOption
型の関数(を返す関数)には、下記が用意されています:
- WithDescription: Toolにdescriptionを追加
- WithBoolean: Toolにbooleanの引数を定義
- WithNumber: Toolにnumberの引数を定義
- WithString: Toolにstringの引数を定義
- WithObject: Toolにobjectの引数を定義
- WithArray: Toolにarrayの引数を定義
実際にToolが実行する処理は、上記コード例のaddToolHandler
のようにAddTool
で登録します。
この処理はToolHandlerFunc型の関数として実装します。
ToolHandlerFunc
はこのような形をした型です:
// ToolHandlerFunc handles tool calls with given arguments. type ToolHandlerFunc func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error)
Tool呼び出し時に渡されたパラメータは、request.Params.Arguments["<param name>"]
のようにして取得できます。
Resourceの実装
// Resource: Greeting template greetingResource := mcp.NewResourceTemplate( "greeting://{name}", "getGreeting", mcp.WithTemplateDescription("Get a personalized greeting"), mcp.WithTemplateMIMEType("text/plain"), ) s.AddResourceTemplate(greetingResource, greetingResourceHandler) // ~中略~ func greetingResourceHandler(ctx context.Context, request mcp.ReadResourceRequest) ([]mcp.ResourceContents, error) { name, err := extractNameFromURI(request.Params.URI) if err != nil { return nil, err } return []mcp.ResourceContents{ mcp.TextResourceContents{ URI: request.Params.URI, MIMEType: "text/plain", Text: fmt.Sprintf("Hello, %s!", name), }, }, nil } // Extracts the name from a URI formatted as "greeting://{name}" func extractNameFromURI(uri string) (string, error) { const prefix = "greeting://" if !strings.HasPrefix(uri, prefix) { return "", fmt.Errorf("invalid URI format: %s", uri) } name := strings.TrimPrefix(uri, prefix) if name == "" { return "", fmt.Errorf("name is empty in URI: %s", uri) } return name, nil }
- NewResourceTemplateでResourceのdescriptionや引数などの属性を定義
- AddResourceTemplateでResource返却ロジックと併せてResourceを登録
構成はToolの実装とよく似ていますので詳細は省きますが、ResourceでもTool同様にOptions Patternに沿って動的にResourceの定義を設定していきます。
※ 上記コード例はResource Template(dynamic URI)を実装しています。 (Resource Templateではない)Resourceには、それ用のAPIが用意されているのでそちらを使用してください。
Promptの実装
// Prompt: Japanese translation template translationPrompt := mcp.NewPrompt( "translationJa", mcp.WithPromptDescription("Translating to Japanese"), mcp.WithArgument("txt", mcp.RequiredArgument()), ) s.AddPrompt(translationPrompt, translationPromptHandler) // ~中略~ func translationPromptHandler(ctx context.Context, request mcp.GetPromptRequest) (*mcp.GetPromptResult, error) { txt := request.Params.Arguments["txt"] prompt := fmt.Sprintf("Please translate this sentence into Japanese:\n\n%s", txt) return mcp.NewGetPromptResult( "Translating to Japanese", []mcp.PromptMessage{ mcp.NewPromptMessage( mcp.RoleAssistant, mcp.NewTextContent(prompt), ), }, ), nil }
これもToolやResourceの実装と構成は同じです。 それぞれ用意された型を利用してPromptを実装していきます。
MCP InspectorでMCPサーバーをテスト実行する
最後に、Goで実装した上記MCPサーバーをMCP Inspectorでテスト実行していきます。
MCP Inspectorの使い方については別途記事にまとめているので、このツールが初見の方はこちらもご参照ください:
Goで実装したMCPサーバーをMCP Inspectorで起動:
npx @modelcontextprotocol/inspector go run main.go
無事MCP Inspectorが起動したら、ブラウザからhttp://127.0.0.1:6274を開いてMCPサーバーで実装したTool/Resource/Promptの各機能を叩いてみます。
無事、Goで実装したMCPサーバーが想定通りのレスポンスを返していることが確認できました。
おわりに
今回はMCPサーバーをGoで実装してみました。
Python SDKに比べるとコード量も多く煩雑ですが、Go製のツールと組み合わせてMCPサーバーを実装するのに役に立ちそうです。
以上、どなたかの参考になれば幸いです。
[関連記事]
参考
- GitHub - mark3labs/mcp-go: A Go implementation of the Model Context Protocol (MCP), enabling seamless integration between LLM applications and external data sources and tools.
- Proposal: official support for `modelcontextprotocol/go-sdk` · modelcontextprotocol · Discussion #224 · GitHub
- Option mode · Issue #40 · mark3labs/mcp-go · GitHub
- GitHub - bioerrorlog/hello-gomcp: The minimal Golang MCP server implementation with mcp-go.
- GitHub - bioerrorlog/hellomcp: The minimal Python MCP server implementation with MCP Python SDK.