0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MCPサーバーで構造化されたデータを通知する。

Last updated at Posted at 2025-09-02

はじめに

前回まで、認証の処理や通信の暗号化などセキュリティ面の確認を行ってきました。

今回は、そういうのは一旦さておき、MCPサーバーとしての実際の機能を作りこんでみたいと思います。

構造化データ

前回までの実装では、チャンネル一覧取得の結果として、1つの文字列(チャンネル名)のみを通知していました。
この方法でも、賢いLLMはいろいろと考え対応してくれるのかもしれませんが、出力内容の構造を詳しく伝えることで、有効に活用してもらえるのではと思い、Toolの定義としてInput Schemaだけではなく、Output Schemaも通知するようにしたいと思います。

Output Schema

以下のようにチャンネル番号とサービス名(チャンネル名)を配列の形式を定義し、tools/listで通知します。

{
  "type":"object"
  "properties": {
    "content": {
      "type":"array"
      "items": {
        "type": "object" 
        "properties": {
          "channel_no": {
            "type": "string"
            "description": "channel no"
          }
          "service_name": {
            "type": "string"
            "description": "service name"
          }        
        }
     }
   }
   "required": [ "content" ]
}

structuredContent

Output Schemaを定義した場合、tools/callでは、structuredContentで通知します。
しかし、structuredContentをcontent同様に配列とすることはできません。
その為、上記のように、contentという配列を定義して、その中にチャンネルの一覧を定義するようにしています。
結果、tools/callで通知する内容は以下のような形式になりました。

{
  "jsonrpc": "2.0",
  "id": 5,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"channel_no\": \"011\", \"service_name\": \"NHK G\"}"
      },
      {
        "type": "text",
        "text": "{\"channel_no\": \"021\", \"service_name\": \"ETV\"}"
      }
    ],
    "structuredContent": {
      "content": {
        "items": [
          {
            "channel_no": "011",
            "service_name": "NHK G"
          },
          {
            "channel_no": "021",
            "service_name": "ETV"
          }
        ]
      }
    }
  }
}

後方互換性のため、従来のcontentでもtext形式でデータを通知するとのことです。
LLMは、structuredContentが存在しても、まだこちらのcontentの方を参照していることが多いようです。

この方法であってるのか?

正直、この方法が正しいのか、ハッキリしません。
MCPのSpecには、typeにarrayなるものは定義されておらず、 そもそもtypeに何が使えるかさえ定義されていません。

が、そもそもそこはLLMです。
このような曖昧な通知でも、いくらでも勝手に解釈してくれるのだろうと今の時点では判断しました。
(それなら、OutputShema自体要らないのではというのは、さておき...)

MCPサーバーへの実装の反映

OutputSchemaの通知やstructuredContentの形式でデータを通知する機能をMCPServerライブラリに追加しました。
以下のような実装で、その機能を利用します。

	server.AddTool(
		"get_channels",
		"Returns a list of available TV channels.",
        // InputSchemaの定義
		std::vector<McpServer::McpProperty> {
			{ "location", McpServer::PROPERTY_STRING, "location of TV", true }
		},
        // OutputSchemaの定義
		std::vector<McpServer::McpProperty> {
			{ "channel_no", McpServer::PROPERTY_STRING, "channel no", true },
			{ "service_name", McpServer::PROPERTY_STRING, "service name", true }
		},
		[](const std::map<std::string, std::string>& args) -> std::vector<McpServer::McpContent> {
			std::vector<McpServer::McpContent> contents;

			McpServer::McpContent content{
				.property_type = McpServer::PROPERTY_OBJECT,
				.value = ""
			};

            // 1つめのデータを通知
			content.properties.push_back({
				.property_name = "channel_no",
				.value = "011"
				});
			content.properties.push_back({
				.property_name = "service_name",
				.value = "NHK G"
				});
			contents.push_back(content);

            // 2つめのデータを通知
			content.properties.push_back({
				.property_name = "channel_no",
				.value = "021"
				});
			content.properties.push_back({
				.property_name = "service_name",
				.value = "ETV"
				});
			contents.push_back(content);

			return contents;
		}
	);

複雑なOutputSchemaを定義したいと思うようなことがある場合、このような仕組みではなく、シンプルにJson文字列を受け取り、それを転送するだけの方がよいかもしれません。
ですが、そこまでキッチリと構造化したデータをLLMに伝えても、それが利用されるのかは、何とも言えず。
現時点では、そこまで考える必要はないのかもしれません。

動作確認

そろそろ MCP Inspector ではなく、MCPサーバー本来の使い方として、LLM から呼び出してみたいと思います。

LM Studio

今回は、LM Studioを使ってみようと思います。
LLMには、openai の gpt-oss-20b を使用しました。
(このあたりの設定は、少し横道にそれてしまうので省きます)

MCP Serverの設定

LM Studio に今回作成した MCP Server を設定します。
ウィンドウの下にあるコンセントのようなアイコンをクリック、Install から Edit mcp.json を実行します。

3.png

以下のような警告が表示されますが、Got It と進みます。

4.png

時間の経過とともに、もっと厳しいチェックが入るようになるのだと思いますが、まだ過渡期なので、自己判断で気を付けてねというような警告なのでしょう。

mcp.json の内容を編集します。

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": [
        "@playwright/mcp@latest"
      ]
    },
+    "tvgate": {
+      "url": "http://localhost:8000/mcp"
+    }
  }
}

playwrightは以前から別に使っているだけで、今回は関係ありません。^^;

設定を追加すると以下のようにメッセージが表示されます。

5.png

その後、再度コンセントのアイコンをクリック。mcp/tvgate を有効にします。

6.png

使ってみる

MCPサーバーが有効になったので、「リビングのTVのチャンネル一覧をリストアップして」と聞いてみます。
LLMが MCPサーバーを使用したいけど良いですか?と確認が表示されます。

1.png

実行をクリックします。
結果は以下の通りです。

2.png

流石、OpenAIのLLMさんです。チャンネル追加や削除なんて機能はありませんが、対応してくれるらしいです。そんな事はないですが笑

ちなみに、上記の表示は、以下のように私の作成している別アプリのソースを利用しています。
もちろん勉強がてら、わざわざC言語で作っているというのもありますが、過去の資産を利用しやすい状況を作るというのも、C言語で作ってきた一つの理由だったりします。

	server.AddTool(
		"get_channels",
          :
		[](const std::map<std::string, std::string>& args) -> std::vector<McpServer::McpContent> {
			std::vector<McpServer::McpContent> contents;

			CChannelManager* channel_manager = CChannelManager::GetInstance();
			ChannelList& channels = channel_manager->GetChannels(CHANNEL_TYPE_ISDB_T_JPN);

			for (auto it = channels.begin(); it != channels.end(); it++)
			{
                McpServer::McpContent content{
                    .property_type = McpServer::PROPERTY_OBJECT,
                    .value = ""
                };

                content.properties.push_back({
                    .property_name = "channel_no",
                    .value = CChannelManager::ConvertChannelNo(it->channel_no)
                    });

                char service_name[255];
                utf16_to_utf8(it->service_name, service_name, sizeof(service_name));
                content.properties.push_back({
                    .property_name = "service_name",
                    .value = service_name
                    });

                contents.emplace_back(content);
			}

			return contents;
		}
	);

おわりに

MCP Inspectorではなく、LLMから実行すると一気にらしくなってきました。
チャンネル情報だけではなく、番組情報と組み合わせることで、いろいろできるかもと期待させる結果になりました。

GitHub Copilotと組み合わせると、よりサクサク動作したりますが、ローカルLLMでもこのように動作させることは可能です。

追記...

番組表の検索も追加してみた。
しかし情報量が多いようで、ローカルLLMだと厳しいようでした。
そこで、VS Codeから Github copilot を利用して番組表検索ツールを実行。
時間での検索しかMCPサーバー側では処理していないにも関わらず、LLMが追加でニュースと言うキーワードでフィルタリングを行い、さらに番組内容も要約して表示してくれている。
自分のアプリにAIをこんなにも簡単に組み込むことができるんだと改めて思う今日この頃です。
(いやまだ組み込んだ訳ではないのですが...)

+アルファ.png

0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?