🎃

MCPサーバーを書くうえで工夫したところ

に公開

MCPサーバーを書いた

詳細はこの記事で書いた。要はTickTickというタスク管理アプリのMCPサーバーを書いたという話。

https://zenn.dev/jolantern/articles/f241f75a159b9a

これ自体は、昨今珍しいことでもない。MCPサーバーを書くためのフレームワークが充実してきて、性質上複雑なビジネスロジックが必要とされないからか雨後のたけのこの如く沢山作られている。たけのこ派なのでこの潮流に乗ってみたというわけ。

しかし、作ってみると学びがあるもので多少の工夫が必要だった。先述の記事はMCPサーバー自体の説明に紙幅を割きたかったので、別でまとめてみることにした。

APIエラーの取り扱い

TickTickのAPIドキュメントには記述されていないが、TickTickはいわゆるToo Many Requestエラーを返すときに429ではなく500を返す特徴がある。1分間に100リクエストを超えるとこの状態になり、1分後にリトライする必要がある。500を返すのは特徴的であまり一般的ではないと思う。なので、LLMがこれを受け取ったときに適切にToo Many Request相当のことが起きている事がわかるようにレスポンスを調整した。

ただし、リトライ処理はMCP側で実装していない。これは1分間待ってリトライする処理を入れてしまうと、LLMの利用者から見るとMCPサーバーが機能していないように見えてしまうからだ。SSEを使っている場合小分けに応答を返すこともできるらしいのだが、今回はSTDIOを使ってMCPサーバーとやり取りする実装にしたのと、クライアント側の実装に依存するらしいので実装しなかった。claude-sonnet-4.6ぐらい賢ければ勝手に1分待ってくれる気もする。

APIメソッドと 1:1 対応でツールを実装しなかった

まず前提として、MCPのリファレンスによると、なるべくツールの単位は原始的な操作に保つようにとされている。

Keep tool operations focused and atomic

典型的なCRUDや検索など、モデル側が組み合わせやすい小さい操作に保つことで再利用性の高いツールになるということだろう。なので、概ねAPIエンドポイントと 1:1 対応になるようにツールを実装している。また、一般に出ているMCPサーバーはそういうのが多いと思う。分かる話だ。

しかし、このMCPサーバーには list_all_tasks というAPIメソッドに対応しないツールが実装されている。これはユーザー(あるいはLLM)向けには「TickTickに登録されているすべてのタスクを取得する」ツールとして動作する。しかし実態としては、「TickTick上のプロジェクトをすべて取得し、更にプロジェクトIDごとにプロジェクトの詳細を取得し、そのレスポンスに含まれるタスク一覧をマージして返す」というような動作をしている。

これはTickTickのAPIにタスク一覧に相当するものがないためこのような動作になっている。LLMがいちいちTickTickのAPI仕様を把握しないとこの動作は出来ないのでは、MCPサーバーの存在意味が薄れるように思う。また、MCPサーバーの提供するツールのdescriptionに書くという手もあるが、MCPサーバーにLLMが接続するたびに読み込まれてコンテキストを消費するので、使い方をつらつら書くのは良い手段とは思えない。

私のユースケースでは今日のタスク一覧が欲しいとLLMに投げることが想像されたし、TickTickをわざわざMCPでLLMにつなぐ人はタスクの管理をなるべく楽にやりたいはずだ。 AGENTS.md や事前プロンプトに、「まずはプロジェクト一覧を取得してから、それの詳細を取得して、タスクの一覧を抜き取って下さい」と書くのは面倒だ。API側の都合がユーザーまで漏洩している。MCPサーバーの対応するサービスのUIと同じ動作をLLMに教え込むのであればいいと思うが、そうでないならMCPサーバー側で抽象化してあげたほうが良さそうに感じた。MCPサーバーのベストプラクティスについて書かれた記事でも、「自然言語のユースケース単位に寄せたツールを用意しておくとLLMの計画・連鎖負荷を下げて信頼性が上がる」というようなことが書かれているのを見て納得感が高かったのでこの方針に落ち着いた。(参考:https://www.linkedin.com/pulse/mcp-server-best-practices-designing-tools-actually-work-r-6vlmc)

まとめ

MCPはびっくりする勢いで標準化された規格だが、まだベストプラクティス/アンチパターンが積み重ねられるほど枯れていない規格だと思う。なのでここに書かれていることもそのうち陳腐化するかもしれないし、私なりの解釈が入っているので(なるべく調べはしたものの)誤りがあるかもしれない。今のところ、APIなどのMCPサーバーにラップさせたい対象を素朴にラップさせるだけのものなら簡単にかけるが、自然言語で扱うときのことを考えて適切に抽象化したほうが良さそうでもあり、UI設計っぽい雰囲気を感じている。User Interfaceだけではなくて、LLM Interfaceみたいなことを考える日も来るのだろうか。

Discussion