📑

Livebook MCPサーバを動かしてみる(hermes-mcp)

に公開

LivebookでMCPサーバを動かすことを試したのでメモ程度に記録します。

ライブラリとして下記のhermes-mcpを使いました。
https://github.com/cloudwalk/hermes-mcp

本記事では、特に何か独自サーバにするわけではなく、ほぼサンプルままです。

実装

setup
Mix.install([
  {:hermes_mcp, "~> 0.14.1"},
  {:plug, "~> 1.17"},
  {:bandit, "~> 1.6"}
])

サーバのツール実装

※ 公式GitHubのREADMEでhandle_toolを使っていますが、うまくいかなかったのでhandle_tool_callにしています。

defmodule MyApp.MCPServer do
  use Hermes.Server,
    name: "My Server",
    version: "1.0.0",
    capabilities: [:tools]

  alias Hermes.Server.Response

  @impl true
  def init(_client_info, frame) do
    {:ok,
     frame
     |> assign(counter: 0)
     |> register_tool("echo",
       input_schema: %{
         text: {:required, :string, max: 150, description: "the text to be echoed"}
       },
       annotations: %{read_only: true},
       description: "echoes everything the user says to the LLM"
     )}
  end

  @impl true
  def handle_tool_call("echo", %{text: text}, frame) do
    response =
      Response.tool()
      |> Response.text(text)

    {:reply, response, assign(frame, counter: frame.assigns.counter + 1)}
  end
end

ルータ

defmodule MyRouter do
  use Plug.Router

  plug Plug.Parsers,
    parsers: [:urlencoded, :json],
    pass: ["text/*"],
    json_decoder: JSON

  plug :match
  plug :dispatch

  forward "/mcp", to: Hermes.Server.Transport.StreamableHTTP.Plug, init_opts: [server: MyApp.MCPServer]

  match _ do
    send_resp(conn, 404, "Not found")
  end
end

起動

ポートはLivebook(とiframe)で使っているものを避けて設定が必要です。

children = [
  Hermes.Server.Registry,
  {MyApp.MCPServer, transport: :streamable_http},
  {Bandit, plug: MyRouter, port: 4002}
]

opts = [strategy: :one_for_one, name: MyApplication.Supervisor]
Supervisor.start_link(children, opts)

接続設定の例

Claude Codeであれば.mcp.jsonあたりに設定します。

{
  "mcpServers": {
    "livebook": {
      "type": "http",
      "url": "http://localhost:4002/mcp"
    }
  }
}

MyApp.MCPServerをいじれば、独自のMCPサーバになります。サーバ側をいじって再起動した際は、クライアント側も再接続しないとレスポンスが返ってこずにタイムアウトするので注意がいります。

Discussion