Zenn
🫢

何もわからんけど作ってみるMCPサーバー(mcp-go)

2025/04/11に公開
8

みなさんこんにちは!株式会社アルダグラムでエンジニアをしている大木です。
昨今AIの隆盛がとんでもなく、ついていくのに精一杯なのが正直なところです。けどついていけないと時代に取り残されそうなので頑張ります!ってことで何もわからんけど、とりあえずMCPサーバーを作っていこうと思います。

準備

今回は mcp-go を使って実装していきたいと思います。TypeScriptやKotlinはMCPの標準のSDK が用意されていますが、Goに関してはまだ存在しないようですね。
mcp-goの詳細は以下から確認いただけます!(サンプルも記載されていて、とてもありがたい…)
https://github.com/mark3labs/mcp-go

標準のmainのファイルを作成します

package main

import (
    "fmt"
)

func main() {
    fmt.Println("Hello World!")
}

今回作るもの

タスク管理ツールを想定したなんちゃって実装をしようと思います。

  1. タスクを一覧できる
  2. タスクの詳細を変更することができる
  3. タスクを削除することができる

そのため、以下のようなものを準備しました。(こういったサンプルコードもほぼ全てAIに書いてもらうような時代になりましたね)

package main

// listTasks returns a list of predefined tasks as a formatted string
// Each task is separated by a newline character
func listTasks() string {
    tasks := []string{
        "Send an email",
        "Schedule a meeting",
        "Prepare a report",
    }
    var res string
    for _, todo := range tasks {
        res += todo + "\n"
    }
    return res
}

// editTask updates a task with the specified ID and description
// Returns a confirmation message with the task ID and new description
func editTask(taskID string, description string) string {
    return "Task with ID " + taskID + " edited to: " + description
}

// deleteTask removes a task with the specified ID
// Returns a confirmation message with the deleted task ID
func deleteTask(ID string) string {
    return "Task with ID " + ID + " deleted"
}

サーバーの実装

mcp-go のサンプルのまま実装しています。名前とバージョンが指定できます。
Capabilityについては具体的な内容が読み取れず、どういったものなのかわからん… という感じでした。 おそらく、 Capability Negotiation というものに絡んでくる話なのではと考えていますが、果たして…

s := server.NewMCPServer(
    "Task Management System",
    "1.0.0",
    server.WithResourceCapabilities(true, true),
    server.WithLogging(),
)

ツールの追加

このサーバーで何ができるのかを定義することができる、と理解しています。先ほど記載した3つの要件を明示すると良さそうです。
公式のプラクティス なんかもあるので参考にしてみてください。

  • パラメータが必須であるかどうか (Required)
  • 列挙型であるかどうか(Enum)

なんかも定義として存在するようです。今回は以下のような tool で定義しました。

tool := mcp.NewTool("taskManager",
    mcp.WithDescription("Perform task management operations"),
    mcp.WithString("operation",
        mcp.Required(),
        mcp.Description("Task management operations"),
        mcp.Enum("list", "edit", "delete"),
    ),
    mcp.WithString("taskID",
        mcp.Required(),
        mcp.Description("Task ID"),
    ),
    mcp.WithString("description",
        mcp.Required(),
        mcp.Description("Task description"),
    ),
)

AddTool を使って tools/call がリクエストされた際の挙動を実装します。
https://modelcontextprotocol.io/specification/2025-03-26/server/tools#calling-tools

s.AddTool(tool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
    op := request.Params.Arguments["operation"].(string)
    taskID := request.Params.Arguments["taskID"].(string)
    description := request.Params.Arguments["description"].(string)

    var res string
    switch op {
    case "list":
        res = listTasks()
    case "edit":
        res = editTask(taskID, description)
    case "delete":
        res = deleteTask(taskID)
    }
    return mcp.NewToolResultText(res), nil
})

ビルドして、Claude Desktop から呼び出してみる

結果として、以下のような実装が出来上がりました。 listTasks() などの実装は別ファイルにしていますが、このファイル内にあっても全く問題ないです。
go build して、成果物のパスをコピーしておきます。

package main

import (
    "context"
    "fmt"
    "github.com/mark3labs/mcp-go/mcp"
    "github.com/mark3labs/mcp-go/server"
)

func main() {

    s := server.NewMCPServer(
        "Task Management System",
        "1.0.0",
        server.WithResourceCapabilities(true, true),
        server.WithLogging(),
    )

    tool := mcp.NewTool("taskManager",
        mcp.WithDescription("Perform task management operations"),
        mcp.WithString("operation",
            mcp.Required(),
            mcp.Description("Task management operations"),
            mcp.Enum("list", "edit", "delete"),
        ),
        mcp.WithString("taskID",
            mcp.Required(),
            mcp.Description("Task ID"),
        ),
        mcp.WithString("description",
            mcp.Required(),
            mcp.Description("Task description"),
        ),
    )

    s.AddTool(tool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
        op := request.Params.Arguments["operation"].(string)
        taskID := request.Params.Arguments["taskID"].(string)
        description := request.Params.Arguments["description"].(string)

        var res string
        switch op {
        case "list":
            res = listTasks()
        case "edit":
            res = editTask(taskID, description)
        case "delete":
            res = deleteTask(taskID)
        }
        return mcp.NewToolResultText(res), nil
    })

    if err := server.ServeStdio(s); err != nil {
        fmt.Printf("Server error: %v\n", err)
    }
}

LLM側から呼び出しをできるように設定を変更します。
Claude Desktop であれば、 claude_desktop_config.json を編集します。Cline であれば、 cline_mcp_settings.json が該当し、Cursor であれば、 mcp.json が該当するかと思われます。(間違っていたらごめんなさい…)

{
  "mcpServers": {
    "taskManagement": {
      "command": "go build した成果物へのPATH",
      "args": [],
      "env": {}
    }
  }
}

Claude Desktopであれば、以下のようなものが表示されていれば読み込みが成功していると思います。私の場合は、アプリケーションを再起動する必要がありました。

動かしてみる

すげえ〜

おお〜

すげえ〜(toolで定義したものをちゃんと読み取って、「これが必要」というのを理解しているみたいですね)

感動した

あまり詳細な記事ではありませんでしたが、ここまでご覧いただきありがとうございました。
自分で実装したものがLLMに読み込まれて、実際に画面に表示されると「使いこなしたったわ」感が出るのですがやっぱりまだまだわからんなという部分が多いです。

特に tool を定義したときに taskIDdescription は必須のパラメータにしているのに、一覧表示ではそれが必須ではないと判断できている理由が明確にわかっていなかったりします。
もう少しMCPの概念から学び直さないといけないなと思いました。やがてこういうAPIの開発が標準になっていくのかなと思うと、もっと勉強しないといけないなという気持ちになりますね。

弊社では生成AIなど、AIの活用は積極的に取り組んでいきたいと考えています。興味があれば、ぜひざっくばらんにでもお話させていただければと思います。
ここまでご覧いただきありがとうございました!

8
アルダグラム Tech Blog

Discussion

ログインするとコメントできます