4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Livebook で MCP サーバーを起動する【VSCode の Copilot チャットにずんだもんの声で喋ってもらう】

Last updated at Posted at 2025-04-27

はじめに

VSCode で MCP サーバーを使っていますか?

予め設定を加えておくことで、 GitHub Copilot が必要に応じて MCP サーバーで提供されている機能にアクセスし、各種 API などと連携して回答してくれるようになります

本記事では Elixir の Livebook 上で MCP サーバーを起動することで、 VSCode の Copilot チャットから VOICEVOX の音声合成を実行します

実装したノートブックはこちら

事前準備

Livebook のインストール

公式サイトから Livebook のインストーラーを取得し、インストールします

VSCode のインストール

公式サイトから VSCode のインストーラーを取得し、インストールします

GitHub Copilot の開始

GitHub にアカウントを作成しましょう

アカウント作成後、右上のユーザーアイコンをクリックしてください

スクリーンショット 2025-04-27 11.01.50.png

開いたメニューから "Your Copilot" をクリックします

スクリーンショット 2025-04-27 11.00.37.png

開いた Copilot の設定画面で "Start using Copilot Free" をクリックします

スクリーンショット 2025-04-27 10.59.28.png

Copilot が Free プランで利用できるようになり、チャット画面が開きます

スクリーンショット 2025-04-27 11.31.28.png

VSCode に Copilot 用の拡張機能をインストールしましょう

インストール後、 VSCode の上部、検索ボックスの右側にある Copilot アイコン(バツマーク付き)をクリックします

スクリーンショット 2025-04-27 11.37.24.png

VSCode から GitHub にサインインします

スクリーンショット 2025-04-27 11.37.30.png

サインインできると、 Copilot アイコンのバツマークが外れています

Copilot アイコンをクリックして開くドロップダウンから "Open Chat" をクリックしてください

スクリーンショット 2025-04-27 11.40.51.png

Copilot とチャットするための画面が開きました

スクリーンショット 2025-04-27 11.41.05.png

公式のドキュメントはこちら

VOICEVOX のインストール

公式サイトから VOICEVOX のインストーラーを取得し、インストールします

Node.js のインストール

本記事では MCP サーバーの挙動を確認するための Inspector を利用します

その実行環境として Node.js が必要です

いずれかの方法で Node.js をインストールしてください

mise

mise use node@latest

asdf

asdf plugin add nodejs
asdf install nodejs latest

Homebrew

brew install node

公式インストーラー

公式サイトからインストーラーを取得し、インストールします

MCP サーバーの実装

セットアップ

Livebook を起動し、ホーム画面右上の "+ New Notebook" をクリックします

スクリーンショット 2025-04-27 11.48.14.png

新しいノートブックが開きます

スクリーンショット 2025-04-27 11.49.06.png

セットアップセル("Notebook dependencies and setup" と書いてある枠の中)をクリックし、以下のコードを貼り付けます

Mix.install([
  {:mcp_sse, "~> 0.1.6"},
  {:kino, "~> 0.15.3"},
  {:jason, "~> 1.4"},
  {:plug, "~> 1.17"},
  {:bandit, "~> 1.6"},
  {:req, "~> 0.5.10"}
])

左上の "Reconnect and setup" をクリックしてください

スクリーンショット 2025-04-27 11.52.49.png

これにより、実装に必要な外部モジュールがインストールされます

キーとなるモジュールは MCP over SSE です

Elixir で MCP サーバーの基本的な動作を実装してくれています

ルーターの定義

Copilot からのリクエストを各処理に振り分けるためのルーターを定義します

以下のコードをセル(黒い枠)に貼り付けてください

defmodule MyRouter do
  use Plug.Router

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

  plug :match
  plug :ensure_session_id
  plug :dispatch

  # Middleware to ensure session ID exists
  def ensure_session_id(conn, _opts) do
    case get_session_id(conn) do
      nil ->
        # Generate a new session ID if none exists
        session_id = generate_session_id()
        %{conn | query_params: Map.put(conn.query_params, "sessionId", session_id)}
      _session_id ->
        conn
    end
  end

  # Helper to get session ID from query params
  defp get_session_id(conn) do
    conn.query_params["sessionId"]
  end

  # Generate a unique session ID
  defp generate_session_id do
    Base.encode16(:crypto.strong_rand_bytes(8), case: :lower)
  end

  forward "/sse", to: SSE.ConnectionPlug
  forward "/message", to: SSE.ConnectionPlug

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

セルの左上、 "Evaluate" ボタンをクリックし、コードを実行します

スクリーンショット 2025-04-27 12.08.17.png

MCP サーバーの実装

先ほど実行したコードの下中央にカーソルを持っていくと、以下のようなボタン群が表示されます

スクリーンショット 2025-04-27 12.10.24.png

"+ Elixir" のボタンをクリックしてください

新しいセルが追加されるので、以下のコードを貼り付けて実行してください

defmodule VoicevoxMCPServer do
  @moduledoc """
  Hex Server implementation and definitions of tools
  - list_hex_packages
  """
  use MCPServer
  require Logger

  @protocol_version "2024-11-05"
  @voicevox_api_url "http://localhost:50021"

  @impl true
  @spec handle_ping(any()) :: {:ok, %{id: any(), jsonrpc: <<_::24>>, method: <<_::32>>}}
  def handle_ping(request_id) do
    {:ok,
     %{
       jsonrpc: "2.0",
       id: request_id,
       result: %{}
     }}
  end

  @impl true
  def handle_initialize(request_id, params) do
    # Log client connection
    client_name = get_in(params, ["client_info", "name"]) || "Unknown Client"
    client_version = get_in(params, ["client_info", "version"]) || "Unknown Version"

    Logger.info("Client connected: #{client_name} v#{client_version}")

    case validate_protocol_version(params["protocolVersion"]) do
      :ok ->
        # Return server capabilities
        {:ok,
         %{
           jsonrpc: "2.0",
           id: request_id,
           result: %{
             protocolVersion: @protocol_version,
             serverInfo: %{
               name: "VoiceVoxMcpServer",
               version: "0.1.0"
             },
             capabilities: %{
               tools: %{
                 listChanged: true
               }
             }
           }
         }}

      {:error, reason} ->
        {:error, reason}
    end
  end

  @impl true
  def handle_list_tools(request_id, _params) do
    # MCP サーバーとして提供するツールの一覧を定義する
    # description でどういうツールなのか生成AIが理解できるようにしておく
    {:ok,
     %{
       jsonrpc: "2.0",
       id: request_id,
       result: %{
         tools: [
           %{
             name: "speak",
             description: "Create an audio file and obtain the audio file path",
             inputSchema: %{
               type: "object",
               properties: %{
                 words: %{
                   type: "string",
                   description: "The words to speak"
                 }
               }
             },
             outputSchema: %{
               type: "object",
               properties: %{
                 audio_file_path: %{
                   type: "string",
                   description: "The audio file path"
                 }
               }
             }
           }
         ]
       }
     }}
  end

  @impl true
  def handle_call_tool(request_id, %{
        "name" => "speak",
        "arguments" => %{"words" => words}
      }) do
    # ツールが呼ばれたときの具体的な挙動を定義する

    case speak(words) do
      {:ok, audio_file_path} ->
        {:ok,
         %{
           jsonrpc: "2.0",
           id: request_id,
           result: %{
             content: [
               %{
                 type: "text",
                 text: audio_file_path
               }
             ]
           }
         }}

      {:error, reason} ->
        {:error,
         %{
           jsonrpc: "2.0",
           id: request_id,
           error: %{
             code: -32_000,
             message: "Failed to say: #{reason}"
           }
         }}
    end
  end

  @impl true
  def handle_call_tool(request_id, %{"name" => unknown_tool} = params) do
    Logger.warning(
      "Unknown tool called: #{unknown_tool} with params: #{inspect(params, pretty: true)}"
    )
    # 定義していないツールが呼び出されたときの挙動

    {:error,
     %{
       jsonrpc: "2.0",
       id: request_id,
       error: %{
         code: -32_601,
         message: "Method not found",
         data: %{
           name: unknown_tool
         }
       }
     }}
  end

  defp speak(words) do
    case Req.post("#{@voicevox_api_url}/audio_query", params: %{text: words, speaker: 3}) do
      {:ok, %{status: 200, body: audio_query}} ->

        case Req.post("#{@voicevox_api_url}/synthesis", params: %{speaker: 3}, json: audio_query, receive_timeout: 120_000) do
          {:ok, %{status: 200, body: data}} ->
            audio_file_path = "/tmp/#{String.slice(words, 0..10)}.wav"
            File.write!(audio_file_path, data)

            {:ok, audio_file_path}

          {:ok, %{status: status, body: body}} ->
            error_message = get_in(body, ["error", "message"]) || "HTTP error: #{status}"
            Logger.error("Voicevox API error: #{error_message}")
            {:error, error_message}
    
          {:error, exception} ->
            Logger.error("Request error: #{inspect(exception)}")
            {:error, "Failed to connect to Voicevox service"} 
        end

      {:ok, %{status: status, body: body}} ->
        error_message = get_in(body, ["error", "message"]) || "HTTP error: #{status}"
        Logger.error("Voicevox API error: #{error_message}")
        {:error, error_message}

      {:error, exception} ->
        Logger.error("Request error: #{inspect(exception)}")
        {:error, "Failed to connect to Voicevox service"}
    end
  end
end

handle_list_tools 関数で MCP サーバーとして提供可能なツールの一覧を返しています

ツール毎に以下の項目を定義します

項目 内容
name ツールの名前
description ツールの機能説明。生成AIがここを見てツールの使用を判断する
inputSchema 入力形式。生成AIからはこの形式に沿って入力が与えられる
outputSchema 出力形式。生成AIに返す際の形式

handle_call_tool で、実際にツールが呼び出されたときの挙動を定義します

Elixir らしく、パターンマッチで定義できるようになっています

今回の記事では "name" => "speak" でツールが呼び出された場合、 speak 関数を呼び出して応答するようにしています

speak 関数では VOICEVOX の API を呼び出し、音声合成を実行しています

%{speaker: 3} の部分で声の種類を「ずんだもん」に指定しています

audio_file_path = "/tmp/#{String.slice(words, 0..10)}.wav" の部分で音声ファイルの保存先を指定しているので、必要に応じて(特に Windows の場合は)変更してください

Livebook からの VOICEVOX 呼び出しについては以下の記事に詳しく記載しています

アプリケーションの定義

MCP を Web サーバーとして起動するため、アプリケーションを定義します

defmodule MyApplication do
  use Application

  @impl true
  def start(_type, _args) do
    children = [
      {Bandit, plug: MyRouter, port: 4000}
    ]

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

plug: MyRouter で先に定義しておいたルーターを指定しています

ポート番号は 4000 を指定していますが、任意のポート番号を指定してください

MCP サーバーの起動

アプリケーションにアクセスされた際、実装した MCP サーバーが呼び出されるように設定します

Application.put_env(:mcp_sse, :mcp_server, VoicevoxMCPServer)

設定しないと MCP over SSE に定義されているデフォルトの MCP (大文字に変える "upcase" ツールを提供)が呼び出されます

以下のコードを実行し、サーバーを起動します

MyApplication.start(nil, nil)

Inspector による動作確認

以下のコマンドで Inspector を起動します

npx @modelcontextprotocol/inspector

インストールするか質問された場合、そのままエンターキーでインストールしてください

Inspector が起動したら ブラウザで http://127.0.0.1:6274 を開きましょう

Inspector の画面が開いたら、画面に以下のように入力します

スクリーンショット 2025-04-27 12.33.37.png

"Connect" ボタンをクリックしてください

ボタンの下に "Connected" と表示されたら接続できています

画面中央の "List Tools" ボタンをクリックすると、定義しておいた "speak" が表示されます

スクリーンショット 2025-04-27 13.08.25.png

"speak" をクリックすると、右側に "words" の入力フォームが表示されます

「こんにちは」と入れて "Run Tool" をクリックすると、実行結果として "/tmp/こんにちは.wav" と返ってきます

スクリーンショット 2025-04-27 13.13.11.png

作成されたファイルを再生すると、ちゃんと「ずんだもん」の声で「こんにちは」と喋りました

VSCode に MCP サーバーの設定を追加する

VSCode で上部中央の検索エリアをクリックしてドロップダウンを開きます

ドロップダウンから「コマンドの表示と実行」をクリックしてください

スクリーンショット 2025-04-27 22.01.27.png

コマンドの入力エリアに "settings json" と入力し、選択肢の中から「Open User Settings (JSOn)」をクリックします

スクリーンショット 2025-04-27 22.01.56.png

すると、 VSCode の設定ファイル(JSON形式)が開きます

設定に以下のコードを追加して保存します

  "mcp": {
    "servers": {
      "voicevox": {
        "type": "sse",
        "url": "http://localhost:4000/sse"
      }
    }
  },

Copilot チャットをエージェントモードに変更します(チャットの下中央を「エージェント」に設定)

スクリーンショット 2025-04-27 22.16.04.png

チャットの左上、工具アイコンの左に再読込のアイコンが出ていればクリックします

工具アイコンをクリックし、 MCP サーバーが追加されていることを確認します
(追加されていない場合、VSCode の再起動などを試しましょう)

スクリーンショット 2025-04-27 22.17.56.png

チャットからの MCP 呼び出し

Copilot チャットに以下のようなメッセージを書きましょう

  • 「こんにちは」と喋ってください

すると、 "speak" ツールを使っていいか聞いてくるので、「続行」をクリックします

スクリーンショット 2025-04-27 22.19.00.png

MCP サーバーが呼び出され、「こんにちは.wav」がリンクとして表示されます

「こんにちは.wav」をクリックすると VSCode 上で「こんにちは.wav」が開き、再生できます

スクリーンショット 2025-04-27 22.19.43.png

あるいは「音声を再生して」と頼んでも再生してくれます

まとめ

Livebook 上で MCP サーバーを立て、 VSCode の Copilot チャットから利用することができました

各種サービスのc API やローカル LLM など、連携先は多様にあるので、活用の幅は広そうですね

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?