Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ktr0731/go-mcpでMCPサーバー作ってみた

 ktr0731/go-mcpでMCPサーバー作ってみた

Avatar for kashiwa

kashiwa

June 11, 2025
Tweet

More Decks by kashiwa

Other Decks in Programming

Transcript

  1. GoでMCPサーバーを作るときの選択肢 Goには公式SDKがまだない ※(2025年6月時点) • 自前で実装する💪 ◦ MCPの仕様の詳細を理解する必要があるので少し大変 • ライブラリを使う📚 ◦

    mark3labs/mcp-go ◦ metoro-io/mcp-golang ◦ ktr0731/go-mcp ◦ etc. ※ プロトタイプが作られてディスカッション中の段階 https://github.com/orgs/modelcontextprotocol/discussions/364
  2. GoでMCPサーバーを作るときの選択肢 Goには公式SDKがまだない ※(2025年6月時点) • 自前で実装する💪 ◦ MCPの仕様の詳細を理解する必要があるので少し大変 • ライブラリを使う 📚

    ◦ mark3labs/mcp-go ◦ metoro-io/mcp-golang ◦ ktr0731/go-mcp ◦ etc. ※ プロトタイプが作られてディスカッション中の段階 https://github.com/orgs/modelcontextprotocol/discussions/364
  3. MCPサーバーの定義・コード生成 func main() { outDir := "internal/go-mcp" if err :=

    os.MkdirAll(outDir, 0o755); err != nil { log.Fatalf("Failed to create output directory: %v", err) } f, err := os.Create(filepath.Join(outDir, "mcp.gen.go")) if err != nil { log.Fatalf("Failed to create file: %v", err) } defer f.Close() def := &codegen.ServerDefinition{ Capabilities: codegen.ServerCapabilities{ Tools: &codegen.ToolCapability{}, Logging: &codegen.LoggingCapability{}, }, Implementation: codegen.Implementation{ Name: "Scrapbox MCP Server", Version: "1.0.0", }, Tools: []codegen.Tool{ { Name: "get_page", Description: "Get a Scrapbox page by title", InputSchema: struct { PageTitle string `json:"page_title" jsonschema:"description=page title,required"` }{}, }, // 他の機能は省略 }, } if err := codegen.Generate(f, def, "scrapbox"); err != nil { log.Fatalf("Failed to generate code: %v", err) } } ktr0731/go-mcp type ServerToolHandler interface { // 定義したtoolに対応するメソッドのシグネチャの集合を表現するインターフェース HandleToolGetPage(ctx context.Context, req *ToolGetPageRequest) (*mcp.CallToolResult, error) } type ToolGetPageRequest struct { PageTitle string `json:"page_title"` } var ( ToolGetPageInputSchema = json.RawMessage(`{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{"page_title":{"type":"string","de scription":"Page title to retrieve"}},"additionalProperties":false,"type":"object","required":["page_title"]}`) ) var ToolList = []protocol.Tool{ { Name: "get_page", Description: "Get a Scrapbox page by title", InputSchema: ToolGetPageInputSchema, }, } func NewHandler(toolHandler ServerToolHandler) *mcp.Handler { // handlerのコンストラクタ h := &mcp.Handler{} h.Capabilities = protocol.ServerCapabilities{ Tools: &protocol.ToolCapability{}, Logging: &protocol.LoggingCapability{}, } h.Implementation = protocol.Implementation{ Name: "Scrapbox MCP Server", Version: "1.0.0", } h.Tools = ToolList h.ToolHandler = protocol.ServerHandlerFunc[protocol.CallToolRequestParams](func(ctx context.Context, method string, req protocol.CallToolRequestParams) (any, error) { idx := slices.IndexFunc(ToolList, func(t protocol.Tool) bool { return t.Name == req.Name }) if idx == -1 { return nil, fmt.Errorf("tool not found: %s", req.Name) } switch method { case "tools/call": switch req.Name { case "get_page": var in ToolGetPageRequest if err := json.Unmarshal(req.Arguments, &in); err != nil { return nil, err } inputSchema, _ := ToolList[idx].InputSchema.(json.RawMessage) if err := protocol.ValidateByJSONSchema(string(inputSchema), in); err != nil { return nil, err } return toolHandler.HandleToolGetPage(ctx, &in) default: return nil, fmt.Errorf("tool not found: %s", req.Name) } default: return nil, fmt.Errorf("method %s not found", method) } }) return h } 型定義からコード生成
  4. Toolの実装 // ServerToolHandler interfaceの実装 type ToolHandler struct { client *scrapbox.Client

    } func NewToolHandler(client *scrapbox.Client) *ToolHandler { return &ToolHandler{ client: client, } } // Toolの実装 func (h *ToolHandler) HandleToolGetPage(ctx context.Context, req *ToolGetPageRequest) (*mcp.CallToolResult, error) { page, err := h.client.GetPage(ctx, req.PageTitle) if err != nil { log.Printf("Failed to get page: %v", err) return nil, fmt.Errorf("failed to get page: %w", err) } return &mcp.CallToolResult{ Content: []mcp.CallToolContent{ mcp.TextContent{Text: fmt.Sprintf("Title: %s\nLines: %v", page.Title, page.Lines)}, }, }, nil } func NewServer(client *scrapbox.Client) *server.MCPServer { // MCPサーバーの作成 mcpSrv := server.NewMCPServer( "Scrapbox MCP Server", "1.0.0", ) // Toolの作成 getPageTool := mcp.NewTool("get_page", mcp.WithDescription("Get a Scrapbox page by title"), mcp.WithString("title", mcp.Required(), mcp.Description("Page title to retrieve")), ) // Toolの実装を追加 mcpServer.AddTool(getPageTool, func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { title, err := req.RequireString("title") if err != nil { return mcp.NewToolResultError(err.Error()), nil } page, err := client.GetPage(ctx, title) if err != nil { return mcp.NewToolResultError(fmt.Sprintf("Failed to get page: %v", err)), nil } b, _ := json.Marshal(page) return mcp.NewToolResultText(string(b)), nil }) // 他の機能は省略 return mcpSrv } ktr0731/go-mcp mark3labs/mcp-go
  5. MCPサーバーの起動 func main() { cfg, err := config.LoadConfig() if err

    != nil { log.Fatalf("Failed to load config: %v", err) } client := scrapbox.NewClient(cfg.ProjectName, cfg.ScrapboxSID) // ハンドラーの作成 toolHandler := mcpServer.NewToolHandler(client) handler := mcpServer.NewHandler(toolHandler) // stdio transportでMCPサーバーを起動 ctx, listener, binder := mcp.NewStdioTransport(context.Background(), handler, nil) srv, err := jsonrpc2.Serve(ctx, listener, binder) if err != nil { log.Fatalf("Failed to serve: %v", err) } srv.Wait() } func main() { cfg, err := config.LoadConfig() if err != nil { log.Fatalf("Failed to load config: %v", err) } client := scrapbox.NewClient(cfg.ProjectName, cfg.ScrapboxSID) // MCPサーバーの作成 mcpServer := mcpServer.NewServer(client) // stdio transportでMCPサーバーを起動 if err := server.ServeStdio(mcpServer); err != nil { log.Fatalf("Failed to start server: %v", err) } } ktr0731/go-mcp mark3labs/mcp-go
  6. 余談 • stdio transportの場合はMCPサーバーを起動したコンソール上でデバッグが可能 ⋊> go run cmd/go-mcp/main.go {"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"claude-ai","version":"0.1.0"}},"jsonrpc":"2.0","id":0} #

    入力 {"jsonrpc":"2.0","id":0,"result":{"protocolVersion":"2024-11-05","capabilities":{"tools":{"listChanged":false},"logging":{}},"serverInfo":{"name":"Scrapbox MCP Server","version":"1.0.0"}}} # レスポンス {"method":"notifications/initialized","jsonrpc":"2.0"} # 入力 {"method":"tools/list","params":{},"jsonrpc":"2.0","id":1} #入力 {"jsonrpc":"2.0","id":1,"result":{"nextCursor":"","tools":[{"name":"get_page","description":"Get a Scrapbox page by title","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{"page_title":{"type":"string","description":"Page title to retrieve"}},"additionalProperties":false,"type":"object","required":["page_title"]},"annotations":null},{"name":"list_pages","description":"Get a list of pages in the project (max 1000 pages)","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{},"additionalProperties":false,"type":"object"},"annotations":null},{"name":"search _pages","description":"Full-text search across all pages in the project (max 100 pages)","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{"query":{"type":"string","description":"Search query"}},"additionalProperties":false,"type":"object","required":["query"]},"annotations":null},{"name":"create_page_url","description":"Generate a URL for creating a new page","inputSchema":{"$schema":"https://json-schema.org/draft/2020-12/schema","properties":{"page_title":{"type":"string","description":"Page title"},"body_text":{"type":"string","description":"Body text for the new page"}},"additionalProperties":false,"type":"object","required":["page_title","body_text"]},"annotations":null}]}} # レスポンス