🍐

MCP サーバーの基礎をしっかり学びLLMアプリを自作してみる

に公開

Linc'well では 3 ヶ月に一度普段の開発チームとは別に 2〜5 人の少人数チームを組成して、テーマとして設定した課題に取り組む横断 WG(ワーキンググループ)という活動があります。

横断 WG の主目的は、正式には以下となります。

  • 職能やチームを横断して知見を集めることで、今までとは違った形での成果創出や技術探索の機会を生み出すこと
  • チーム外との業務を通した交流の機会を生み出すこと

なお、通常の勤務時間を少し使って良いことになっています。

最近は AI がキテるよね、ということで私たちはもはや最近耳にしない日はない「MCP サーバー」を自作することにより、AI 開発やその仕組みをちょっと深掘りするという研究活動をしてみました。こんなにおもしろそうな企画なのに、なんとチーム人数は 2 名!崖っぷちの組成でした。2 名は寂しいかと思いきや、超少数精鋭でもりもり研究活動を頑張りました。

MCP サーバーが出始めた頃に、自作 MCP サーバーが色々と流行りました。今は自作しなくても、公式の MCP サーバーが続々と誕生しています。やや旬を去った感じではありましたが、AI 開発におけるトレンドに対するちゃんとした理解に有意義な時間になりました。

今回はこの活動を通じて理解した内容を元に、復習の意味を込めてこの記事を書きました。

  • MCP サーバーの定義と LLM の周辺技術の深掘り
  • 自作 MCP サーバーと周辺成果物のご紹介

MCP サーバーについて知っているが、まだその概念がフワフワしている方で、もっとディープに理解したい!という方は、ぜひ読んでみてください。

私たちはこの活動を以下の流れでやってみました。

  • MCP サーバーの概念について学び、まずローカルで MCP サーバーを立ててみる
  • MCP サーバーとデータについて学ぶため、RAG の構成を自作してみる
  • リモート MCP サーバーを作ってみる
  • MCP サーバーに接続する簡単なアプリケーションを作ってみる
  • リモート MCP サーバー化する

活動を行う中で学んだことが順番になるように、この流れでご紹介をさせてください。

結局 MCP サーバーとはなんなのか

MCP サーバーとは。という説明に関して、シュッと答えられますか?
活動時には私はこの質問に対して、よく言われている「接続することで AI エージェントがアクセスできるようになる USB-C のような何か」というくらいの回答しかできない解像度で挑みました。USB-C という表現、有名ですね。

https://modelcontextprotocol.io/docs/getting-started/intro

MCP サーバーは AI 技術に関する何か特別なサーバーというイメージがあり、最初自作すると考えた時は「一体どうやってサーバー化するんだ。専用のデプロイ先とかあるのか?」と戦々恐々としていました……。

調べてみて整理した、より一段抽象度を下げた「MCP サーバーとは」の答えですが、以下のような定義となります。

  • LLM クライアント が接続するために
  • 主にリソース・ツール・プロンプトという 3 つの概念を持ち
  • JSON-RPC プロトコルという仕様の規格で
  • 主に Streamable HTTP という通信方式で行われる
  • initializeという初期化依頼と合意(ハンドシェイクのようなもの)ができる
  • サーバー

大体こんな感じです(正確にはもう少し色々と細かい決まりがあるかもしれません)。こう聞くと、USB-C と言われる意味に納得感が持てますね。MCP サーバーは LLM が接続するために統一されたインターフェースを満たすサーバーという以外に、特別な意味はありません。

こう考えると、少し解像度が上がりませんか? 私は魔法のようなサーバーと考えていたので、MCP サーバーという存在が急に親しみやすくなり、解像度が爆上がりしました。

もう少し深掘りしていきます。

リソース・ツール・プロンプト

私たちが MCP サーバーの開発を行うにあたりとても重宝したインスペクタを利用して紹介させてもらいます。

https://modelcontextprotocol.io/docs/tools/inspector

普段 MCP サーバーを利用する際は、Claude Code の CLI 上での MCP サーバー登録コマンドや、設定用の json ファイルへの書き込みにより、MCP サーバーへ接続するためのコマンドを指定します。MCP サーバーはこのコマンドを実行し、サーバーと情報のやり取りを行うことができます。

この MCP インスペクターは、実際の MCP サーバーへデモ的に接続できます。
npx -y @modelcontextprotocol/inspectorで画面が開くため、いつも LLM クライアント に渡しているコマンド情報を入れると、画像のように普段は LLM クライアント が接続している MCP サーバーの世界を垣間見ることができます。

@modelcontextprotocol/inspectorの様子

Resource / Tools / Prompts というのは、MCP サーバーが持つ役割を主に 3 分割した概念です。
リソースは GET に似たイメージで素材を取得するもので副作用を持ちません。ツールは何かアクションをするものであり副作用を持ちます。プロンプトは再利用可能な LLM 用テンプレートを配布します。というイメージです。MCP サーバーの開発者として何か特定のメソッドを生やす時、この主に 3 つの中のどれかに分類することがまず求められます。

この MCP サーバーの利用を通じて、与えられた 2 つの数字を足して返すaddというツールが使えることがわかります。

JSON-RPC プロトコル

RPC は REST と一緒に考えるのがわかりやすいのですが、REST がリソースでおしゃべりするのに対し、RPC は命令でおしゃべりするイメージです。
命令ベースのおしゃべりを JSON でやり取りするので、JSON-RPCプロトコルです。これは MCP サーバー限定ではなく、規格の 1 つです。

RPCの概念についてここでは深掘りしませんが、詳しく知りたい方は、以前こんな記事を書いたので、ぜひ見てみてください。

https://zenn.dev/moepyxxx/articles/b3f72d49beab69

LLM クライアント と MCP サーバーは実際には以下のようなリクエストを投げ合って通信しています。
REST や gRPC の通信と比べると馴染みがないですが、以下のようなリクエストやレスポンスを送り合っています。

POST /mcp HTTP/1.1
Host: localhost:8888
Content-Type: application/json
Accept: application/json, text/event-stream

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "roots": {
        "listChanged": true
      },
      "sampling": {},
      "tools": {}
    },
    "clientInfo": {
      "name": "test-client",
      "version": "1.0.0"
    }
  }
}
HTTP/1.1 200 OK
date: Mon, 22 Sep 2025 10:23:26 GMT
server: uvicorn
cache-control: no-cache, no-transform
content-type: text/event-stream
mcp-session-id: 77134e19963246829a64fc06b355d9b4
x-accel-buffering: no
Transfer-Encoding: chunked
Connection: close

event: message
data: {
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "experimental": {},
      "prompts": { "listChanged": false },
      "resources": { "subscribe": false, "listChanged": false },
      "tools": { "listChanged": false }
    },
    "serverInfo": { "name": "Demo", "version": "1.13.0" }
  }
}

Streamable HTTP

MCP サーバーにはさまざまな通信方式がありますが、代表的な手段としては Streamable HTTP が今は旬かなと思います。HTTP , sse , stdio 等があります。 Streamable HTTP は MCP サーバーが出て少ししてから、歴史的な経緯を諸々経て誕生しました。双方向で通信でき、同じリクエストに対して複数のレスポンスをイベントストリーム型で返せるのが特徴です。MCP サーバーはその性質上、規模の大きいデータを一つのリクエストで処理することが求められたり、中間結果を LLM クライアント に返して処理させたりできる柔軟性が求められていた背景から、このような通信方法が生まれたみたいです。

initialize メソッド

MCP サーバーにはinitializeというメソッドが必ず搭載されています。
これはハンドシェイクのようなもので、LLM クライアント(詳しく後述しますが、ここではエージェントとしての意味が強いです)がセッションを開始する時には、必ず一度 MCP サーバーに接続しに行くことがルールになっています。

詳しくは以下のライフサイクルがとてもわかりやすいですが、

https://modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle

クライアント: やあ!ぼくはこういうものです。
サーバー: 私はこういうものです。
(いそいそやりとり)
クライアント: 接続できそうなので、使いたい時には使いますね。

といって接続の準備が整うわけです。

私はよく Claude Code を利用しますが、claude コマンドで接続した後に少しのタイムラグが起こり、/mcpすると必ず MCP サーバーの利用が確認できますよね。

Claude Codeの/MCPの様子

これは、起動時に内部のエージェントが MCP サーバーの設定情報を元に、必ずinitializeを行っているためです。以下は notion のツール例ですが、すでに MCP サーバーが初期化依頼と合意を MCP サーバーと行い、どんなツールをどんな意図で利用できるかわかることを示しています。

Claude CodeでMCPサーバーのことがわかる様子

Claude CodeでMCPサーバーのことがわかる様子

ここまで、MCP サーバーの概念について 1 つずつ紐解いてきました。理解が少し深まっていれば嬉しいです。
色々と考えると、実は MCP サーバーをゼロから自作するのは少し大変と思いませんか? 規格や通信方法が普段のそれとはまるで違うので、少し不安になりました。

ということで、もちろん横断 WG 活動時間がたくさんあるわけではなかったため、私たちは以下の MCP サーバーツールキットを使って、とても簡単に MCP サーバーを立てました。

https://github.com/modelcontextprotocol/python-sdk

メソッドに@mcp.tool()とつければツールになりますし、この状態で立ち上げればツールキットが自動でinitializeコマンドやそれ以外の LLM クライアント に渡す情報なども勝手に作ってくれます。便利ですね。

ゼロから作ったらそれはそれでかなりの学びがあったかもしれません。しかし上記のツールキットを利用するのにも、これまで説明してきたような概念が頭に入っているとのと入っていないのでは、内部の動きの想像力に大きな違いが出ますので、これはこれでよしとします。

MCP サーバーを取り巻く周辺用語のおさらい

改めて MCP サーバーについてほぼ完全に理解した後、今度はその周辺でよく聞く用語についても整理して噛み砕いて深掘りしてみました。LLM とか、LLM クライアント とか、エージェントとか、何だか似たようなキーワードが多くないですか? RAG ってよく聞きますが、つまりデータベースなんですかそれは? などなど、その整理をしていきました。

これは ChatGPT の台頭の時代に遡ったところから歴史的に説明すると、頭に入りやすいと思います。

LLM クライアント

AI コーディングの走りといえば、開発者の間で大大大ブームとなった ChatGPT ですね。
当時の ChatGPT は、私たちとおしゃべりをしてくれるツールでした。

LLM

LLM は私たちが送った自然言語を元に思考や判断をしてくれるツールです。人間でいえば頭脳のような存在ですね。
この時 LLM へリクエストする存在のことを、LLM クライアント と呼びます。

AI エージェント

時は流れ、タスクを実行する AI 搭載ロボットが現れるようになりました。
Cursor は命令をすれば勝手にコーディングを行ってくれるし、Devin は PR まで出してくれます。

LLM は頭脳のため、手足を動かすような力はありません。

LLM を搭載しつつ、手足を動かせるような装備(さまざまな技術)をセットして、ロボットのような状態にしたものが AI エージェントです。脳が行動を決めている私たち人間と同じく、LLM とは司令部であり、他にも操作技術が組み合わさり AI エージェントという技術が広まりました。

この時私たちは AI エージェントにタスクを命令します。AI エージェントがタスクを完了するまで LLM に問い合わせと指示受け取り・操作を複数回続けることとなります。LLM クライアント は AI エージェントとなります。

LLM エージェント

個人的には LLM クライアントという言葉がわかりにくいと感じていたのですが、LLM クライアントが何かの物体を指すのではなく、システムの動き方によって対象が変わることに気づき、理解が深まりました。

RAG

RAG とは何かを理解するのには少し時間がかかりました。
RAG = データ保管庫というイメージが強かったのですが、実態としては不正解で、RAG は外部データを LLM と接続するという構図そのものを指す概念や手法という意味となります。

RAG

どんなデータベースの種類でも RAG になりうるのですが(もちろん普通のデータベースから引っこ抜いたデータを LLM に渡せば、それは立派な RAG です)、RAG アプリケーションとしてはベクトルデータベースがよく採用される印象です。類語や文脈での検索が得意なので、LLM との相性が良さそうという理由からです。

https://python.langchain.com/docs/tutorials/rag/

ローカルのアプリケーションで、ベクトル化した PDF を読ませて解説をさせるものを実験的に作りました。なんでもいいのかなと思い、最初はベクトル化の設定をかなり適当に行いましたが、設定によって回答の精度が変わりました。

サンプルとして同じ PDF を異なる設定でチャンクし、同じプロンプトを渡した際に、片方はきちんと答えられたが、片方にはシラを切られました。このあたりは RAG のチューニングのしどころなのだなあと感じました。

ここではかかりつけの医師の説明について、うまく回答を引き出せています。

良い回答と悪い回答①

ここでは失敗してしまっています。

良い回答と悪い回答②

MCP サーバー

ここまで理解したところで、大元の MCP サーバーがあることで、LLM にどのように役に立つか改めて考えてみました。MCP サーバーが発表された当時、多くの方がこの登場を喜んだ理由が、ようやくわかるようになってきました。

先ほどの AI エージェントの例に、MCP サーバーを利活用した例を追加してみましょう。

MCP サーバー

MCP サーバーは、接続情報を渡しておくことによりエージェントが必要に応じてやりとりして、タスクの制度を上げてくれます。これまでの説明はどちらかというと GET のイメージが強いですが、たとえば私たちの代わりに notion MCP を利用して notion に何か書き込んでくれたり、figma MCP を利用してデザインを作ってくれたりするのも、この MCP サーバーという概念の登場が大きく寄与していることがわかります。

ところで先ほどの RAG の図と MCP サーバーを照らし合わせてみると、こんなこともできそうではないですか?

RAGをMCPサーバーで置換

MCP サーバーの登場時に RAG がやや下火になった気がしたのですが、こちらも納得ですね。
場合により、RAG は MCP サーバーで代用できることもあるのです。

MCP サーバーは RAG のように必ず接続しにいくようなイメージではなく、AI エージェントが必要に応じて判断し接続しに行く意味が強いです。この柔軟性も、MCP サーバーの魅力のひとつのように思えます。

MCP サーバーを自作し、自作 MCP サーバーを利用するツールを作る

最後に、これらのインプットを経てどのような MCP サーバーとそれを利用するアプリケーションを作ったかをご紹介します。

  • MCP サーバー
  • アプリケーション
    • @XXX という医療 IT クイズオペレーター Bot を呼び出すと、医療 IT に関するクイズを出題したり、回答の答えを教えてやりとりできる Slack Bot

アーキテクチャ概要

詳細な中身の実装は、以下のようになっています。

  • Slack Bolt は @XXX メンションを監視し、メンションがきた場合にはユーザー情報とそのスレッド内のメッセージを取得し、アプリケーションサーバーへ API を POST する。
  • アプリケーションサーバーは/quizに POST された情報を元にプロンプトを作成し、langchain 経由で AI エージェントを起動する。この AI エージェントはクイズの回答・出題・解説等をすべて行う。この時 MCP サーバーの接続情報を渡せる AI エージェントを起動している。AI エージェントは与えられた情報をたどって、スレッドのユーザーへのクイズ生成や解説、正解発表等の回答を作成する。
  • MCP サーバーはgenerate_quizsubmit_quiz_answerといったクイズに関するツールを持っている。
  • generate_quizでは中で langchain 経由で LLM が起動する。その際、LLM にはベクトルデータベースから取得した医療 IT 情報を渡してクイズを生成する(RAG)。
  • ベクトル化データベースには社内で活用経験のなかった Amazon S3 Vectors を採用。挿入と検索ができるような機構を作る。

https://aws.amazon.com/jp/s3/features/vectors/

最も時間を要したのは MCP サーバーの作り込みです。
うまくベクトルデータベースと連携して、難易度やテーマに応じたクイズを作れるように工夫しました。

アプリケーションサーバーはあえて/quizという API 1 つに絞り、Bot と自然な会話で、コンテキストに応じて質問を出したり答えを出したりするようにしました。LLM を利用することにより、API のエンドポイントが 1 つに抽象化でき、負荷が軽減できたのは面白いポイントでした。

また余談ですが、この超少人数チームにはインフラ専任のエンジニアがいませんでした。そのため、この構成をリモート MCP サーバー化するにあたり、かなり四苦八苦して(時折インフラチームメンバーへヘルプを求め)インフラ環境を整えることになりました。おかげで少しインフラに賢くなり、普段馴染みのない技術に触れるのは、横断 WG の楽しみの 1 つでした。

アプリケーションで遊ぶ様子

多少返答がちぐはぐの時はあるのですが、なんとか形になりました。

振り返れば今回のアプリケーションを作るのに、この構成はあまりに冗長ではありましたが、MCP サーバーについて理解を深めるのにはとても有意義でした。好奇心の赴くままに、使ってみたい技術で遊ばせていただきました。

終わりに

ここまで学んだことを色々と書いてきましたが、LLM の世界はとても奥深く、読まれた最近の LLM 界隈について詳しい方にとっては、「厳密にいえば違う!」「こう考えた方が良い!」という感想があるかもしれません。その際は、ぜひ教えていただけたらありがたいです。

弊社での今シーズンの横断 WG 活動の終わりはもうそろそろです。

なんとかチームで形にでき、ここまで MCP サーバーや LLM の理解を深める時間になり、とても満足しています。今では色々な LLM を利用したアイディアも湧いてくるようになりました。

個人的には AI を取り巻く技術の世界は、実運用に載せるというハードルがとても高いと思っています。次はここにチャレンジできるような仕組みを考えていきたいです!

長くなりましたが、ここまでお読みいただきありがとうございました。

Linc'well, inc.

Discussion