MCP(Model Context Protocol)仕様概要とAzure OpenAI ServiceによるMCPクライアントの実装

# はじめに

本記事では、急速に普及しつつある「MCP (Model Context Protocol)」に焦点を当て、その仕様の概要からAzure OpenAIを用いたクライアントの実装方法までを解説します。
従来、LangChainなどのフレームワークごとにツールの実装方法が異なるため、効率性や一貫性に課題がありましたが、MCPは、こうした課題を解決するために開発されたプロトコルで、呼び出し先のツール(MCPサーバー)に依存せず、クライアントを実装できるため開発効率の向上が期待できます。
そこで今回、可能な限り素のAzure OpenAIにてMCPクライアントを実装してみましたので、皆様のプロジェクトでMCPを活用する際にご参考頂ければと思います。

# MCP(Model Context Protocol)とは

MCPは Anthropicが策定した、アプリケーションがLLMにコンテキストを提供する方法を標準化するオープンなプロトコルで、[公式ページ](https://docs.anthropic.com/ja/docs/agents-and-tools/mcp)では、USB‑Cが多様なデバイス接続を可能にするように、AIアプリケーションに対して統一的な接続インタフェースを提供する点が協調されています。
MCPのアーキテクチャについて[公式ドキュメント](https://modelcontextprotocol.io/introduction)から引用すると下記の通りで、クライアントとサーバー間の通信がMCPにより標準化され疎結合になっているので、LLMアプリケーションがMCPに対応(MCP Client)していれば、自由にMCPサーバーを追加・削除でき、容易にLLMアプリケーションの拡張が出来る仕組みになっています。

```mermaid
flowchart LR
    subgraph "Your Computer"
        Host["Host with MCP Client\n(Claude, IDEs, Tools)"]
        S1["MCP Server A"]
        S2["MCP Server B"]
        S3["MCP Server C"]
        Host <-->|"MCP Protocol"| S1
        Host <-->|"MCP Protocol"| S2
        Host <-->|"MCP Protocol"| S3
        S1 <--> D1[("Local\nData Source A")]
        S2 <--> D2[("Local\nData Source B")]
    end
    subgraph "Internet"
        S3 <-->|"Web APIs"| D3[("Remote\nService C")]
    end
```

このMCPクライアント、サーバーを実装する際に仕様だけでなくメジャーな各言語のSDKがMITライセンスにて公開されており、様々な言語で実装できる環境が整いつつあります。  
特に既存システムをLLMへ対応する際に効果を発揮し、今はまだセキュリティの関連でローカルマシン上での実行が主となっていますが、今後バージョンアップが進みセキュリティ実装も容易になれば、インターネット上でMCPサーバーを公開することが標準になってくると考えられます。

| 言語                                                                 | バージョン       | GitHubスター数 |
| -------------------------------------------------------------------- | ---------------- | -------------- |
| [Python](https://github.com/modelcontextprotocol/python-sdk)         | v1.6.0           | 7,150          |
| [TypeScript](https://github.com/modelcontextprotocol/typescript-sdk) | v1.8.0           | 4,043          |
| [Java](https://github.com/modelcontextprotocol/java-sdk)             | v0.8.1           | 743            |
| [Kotlin](https://github.com/modelcontextprotocol/kotlin-sdk)         | v0.4.0           | 476            |
| [C#](https://github.com/modelcontextprotocol/csharp-sdk)             | v0.1.0-preview.4 | 1,102          |

※バージョンとスター数は2025年4月2日時点

# MCPの仕様概要

MCPの仕様は、[Web上](https://spec.modelcontextprotocol.io/latest)で公開されており、[GitHub](https://github.com/modelcontextprotocol/specification)を通じて自由に仕様の提案等が可能になっています。
2025年4月2日時点での最新バージョンは`2025-03-26`となっており、前回のバージョン(`2024-11-05`)から以下の点が変更されています。

* `2024-11-05`から`2025-03-26`の変更点 ([リンク](https://spec.modelcontextprotocol.io/specification/2025-03-26/changelog/))：
  * OAuth 2.1に基づく認証フレームワークの追加
  * HTTPを利用した通信方式を、HTTP+SSE(Server-sent events)からStreamable HTTPに変更
  * JSON-RPCのバッチ処理をサポート
  * ツールの動作をより適切に記述するための包括的な注釈の追加

以下に2025年4月2日時点で実装が行われている`2024-11-05`をベースに、通信方式、サーバー・クライアント機能の概要について記載します。

## 通信方式 ([仕様](https://spec.modelcontextprotocol.io/specification/2024-11-05/basic/transports/))

MCPクライアントとサーバー間のすべてのメッセージは[JSON-RPC 2.0](https://www.jsonrpc.org/specification)に従って実装し、MCPクライアントとサーバー間は以下の2つの通信方式が策定されています。

| 通信方式 | 概要                                             | 備考                                              |
| -------- | ------------------------------------------------ | ------------------------------------------------- |
| STDIO    | 標準入出力を使用した通信方法                   |                                                   |
| HTTP+SSE | HTTPを使用した通信方法、複数クライアント接続可 | `2025-03-26`では`Streamable HTTP`に変更されます。 |

またオプションの機能として、`Ping(接続確認)`、`Cancellation(通信中断)`、`Progress(進捗確認)`が定義されています。

## サーバー機能 ([仕様](https://spec.modelcontextprotocol.io/specification/2024-11-05/server/))

MCPサーバーはLLMにコンテキストを追加するために基本的な下記のコンポーネント(プリミティブ)を提供する仕様となっています。

| プリミティブ | コントール       | 概要                                                   | 例                                         |
| ------------ | ---------------- | ------------------------------------------------------ | ------------------------------------------ |
| Prompts      | ユーザー         | ユーザーの選択によって呼び出される対話型テンプレート   | スラッシュコマンド、<br>メニューオプション |
| Resources    | アプリケーション | クライアントによって付与・管理されるコンテキストデータ | ファイルコンテンツ、<br> Git履歴           |
| Tools        | モデル           | アクションを実行するために LLM に公開される関数        | API POST リクエスト、<br>ファイル書き込み  |

もう少し分かりやすく記載すると以下の通りです。

* `Prompts(プロンプト)`：ユーザーによって選択されるプロンプトテンプレートで、提供されるツール等を適切に利用するために利用されます。  
* `Resources(リソース)`：RAG等を実施する際のファイル等のコンテンツについて、一覧表示したりコンテンツを読み込んだりします。
* `Tools(ツール)`：OpenAIでいう所のFuncation callingに相当するもので、ツール(関数)の一覧取得やその呼び出しが可能となっています。

また上記の各プリミティブは、変更通知機能も定義されており、プロンプトやリソース等に何かしらの変更があった場合に、サーバーからクライアントへ変更情報を送信することが可能な仕様となっています。
この他にも、補助的な機能として補完(Completion)機能や、ロギング機能、各一覧要求に対するページング機能が定義されています。

## クライアント機能 ([仕様](https://spec.modelcontextprotocol.io/specification/2024-11-05/client/))

クライアントはMCPサーバーを強化するための機能として、以下の2つが定義されています。

* `Roots`: サーバーからクライアント上のファイル等を取得するための機能
* `Sampling`: サーバーからクライアントを経由してLLMにアクセスするための機能

## MCPをサポートするアプリケーション (2025年4月2日時点)

メジャーなクライアントと上記機能の対応について[公式ページ](https://modelcontextprotocol.io/clients)より抜粋した表を下記に示します。
サーバー機能のうちツール(Tools)については、ほぼ全てのクライアントが対応していますが、逆にクライアント機能については、ほぼ全てが非対応となっています。

| Client                       | [Resources] | [Prompts] | [Tools] | [Sampling] | [Roots] | Notes                                              |
| ---------------------------- | ----------- | --------- | ------- | ---------- | ----- | -------------------------------------------------- |
| [Claude Desktop App][Claude] | ✅           | ✅         | ✅       | ❌          | ❌     | Full support for all MCP features                  |
| [Claude Code][Claude Code]   | ❌           | ✅         | ✅       | ❌          | ❌     | Supports prompts and tools                         |
| [Cline][Cline]               | ✅           | ❌         | ✅       | ❌          | ❌     | Supports tools and resources.                      |
| [Continue][Continue]         | ✅           | ✅         | ✅       | ❌          | ❌     | Full support for all MCP features                  |
| [Cursor][Cursor]             | ❌           | ❌         | ✅       | ❌          | ❌     | Supports tools.                                    |
| [Emacs Mcp][Mcp.el]          | ❌           | ❌         | ✅       | ❌          | ❌     | Supports tools in Emacs.                           |
| [Microsoft Copilot Studio]   | ❌           | ❌         | ✅       | ❌          | ❌     | Supports tools                                     |
| [fast-agent][fast-agent]     | ✅           | ✅         | ✅       | ✅          | ✅     | Full multimodal MCP support, with end-to-end tests |

[Claude]: https://claude.ai/download
[Claude Code]: https://claude.ai/code
[Cursor]: https://cursor.com
[Continue]: https://github.com/continuedev/continue
[Cline]: https://github.com/cline/cline
[fast-agent]: https://github.com/evalstate/fast-agent
[Mcp.el]: https://github.com/lizqwerscott/mcp.el
[Resources]: https://modelcontextprotocol.io/docs/concepts/resources
[Prompts]: https://modelcontextprotocol.io/docs/concepts/prompts
[Tools]: https://modelcontextprotocol.io/docs/concepts/tools
[Sampling]: https://modelcontextprotocol.io/docs/concepts/sampling
[Roots]: https://modelcontextprotocol.io/docs/concepts/roots
[Microsoft Copilot Studio]: https://learn.microsoft.com/en-us/microsoft-copilot-studio/agent-extend-action-mcp

# MCP実装サンプル

MCPについて詳しく理解するために、[MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)を用いたMCPサーバー、クライアントの実装と、Azure OpenAI Serviceを利用した際のMCP Clientの実装サンプルについて掲載いたします。

## MCP Python SDKによる実装

[Python SDK](https://github.com/modelcontextprotocol/python-sdk) v1.6.0におけるMCPサーバーとMCPクライアントの実装を下記に示します。

### MCPサーバーの実装

MCPサーバーの実装は非常に簡単で、Pythonのデコレータにより指定する形になっています。  
このデコレータで指定した関数について、関数名やヒアドキュメントが自動的にツールやリソースに変換され、MCPクライアントから関数の定義情報の取得やリモート呼び出しが可能となります。

``` python:example_server.py
from mcp.server.fastmcp import FastMCP

# MCPサーバーの作成
mcp = FastMCP("Demo")

# ツールの追加
@mcp.tool()
def add(a: int, b: int) -> int:
    """Add two numbers"""
    return a + b

# 挨拶リソースの追加(動的)
@mcp.resource("greeting://{name}")
def get_greeting(name: str) -> str:
    """Get a personalized greeting"""
    return f"Hello, {name}!"

# サーバーの起動（標準入出力を使用）
if __name__ == "__main__":
    mcp.run()
```

### MCPクライアントの実装

MCPクライアントの実装も非常にシンプルになっています。  
通信方式とセットでサーバーアプリの起動方法を設定し初期化を行うと、ツールやリソース等の一覧や実行・取得が可能になります。  
ここでの注意点として、サーバーを事前に起動するのではなく、クライアントからサーバーアプリを起動する形になっています。  

``` python:example_client.py
from mcp import ClientSession, StdioServerParameters, types
from mcp.client.stdio import stdio_client

# 標準入出力でサーバーを作成
server_params = StdioServerParameters(
    command="python",
    args=["example_server.py"], 
    env=None,
)

async def run():
    async with stdio_client(server_params) as (read, write):
        async with ClientSession( read, write, sampling_callback=None) as session:
            # 接続を初期化
            await session.initialize()

            # 利用可能なツールを取得
            tools = await session.list_tools()
            print(f"利用可能なツール: {[tool.name for tool in tools.tools]}")

            # addツールを呼び出す
            tool_result = await session.call_tool("add", {"a": 1, "b": 2})
            print(f"サーバーからの応答: {tool_result.content[0].text}")

            # リソースの一覧を取得
            resources = await session.list_resource_templates()
            print(f"利用可能なリソース: {[resource.name for resource in resources.resourceTemplates]}")

            # greetingリソースを読み込む
            resource_result = await session.read_resource("greeting://sample_user")
            print(f"リソース コンテンツ: {resource_result.contents[0].text}")


if __name__ == "__main__":
    import asyncio

    asyncio.run(run())
```

### 実行結果

実行結果は以下の通りです。MCPクライアントを実行すると、同時にMCPサーバーが起動し、MCPサーバー上のツールやリソースの一覧取得や実行ができたことが分かります。  

``` shell
> python example_client.py
利用可能なツール: ['add']
サーバーからの応答: 3
利用可能なリソース: ['get_greeting']
リソース コンテンツ: Hello, sample_user!
```

## Azure OpenAI Serviceを利用したMCPクライアントの実装

次により実践的なサンプルとして、実際にLLM(Azure OpenAI Service)を利用しMCP Clientを実装してみます。  
[別のサイトでも記載](https://www.ai-shift.co.jp/techblog/5226)されていますが、MCPツールの定義情報はClaudeのFunction calling形式となっており、これをOpenAI形式に変換し設定する必要があります。  
こちらについては[OpenAI社が2025年3月27日に発表](https://x.com/OpenAIDevs/status/1904957755829481737)がありましたが、OpenAI Agents SDK(v0.0.7)にMCPサポートが追加され、定義情報の変換についても実装されています。
今回本サンプルは、このSDKを一部利用し、動作の仕組みを分かりやすくするため可能な限り素のOpenAI APIを呼び出す形でMCP Clientを実装しています。

``` python: example_aoai_playwright.py
import os
import json
from mcp.types import TextContent, ImageContent
from agents.models.openai_chatcompletions import ToolConverter
from agents.mcp import MCPUtil, MCPServerStdio
from azure.identity import DefaultAzureCredential, get_bearer_token_provider
from openai import AzureOpenAI
from typing import Any

# .envファイルの読み込み
from dotenv import load_dotenv
load_dotenv()
AZURE_OPENAI_DEPLOYMENT_NAME = os.getenv("AZURE_OPENAI_DEPLOYMENT")
AZURE_OPENAI_API_VERSION = os.getenv("AZURE_OPENAI_API_VERSION", "2024-10-21")
AZURE_OPENAI_ENDPOINT = os.getenv("AZURE_OPENAI_ENDPOINT")
SYSTEM_PROMPT = "あなたは親切なアシスタントです。"

# Azure OpenAIクライアントの作成
credential = DefaultAzureCredential()
client = AzureOpenAI(
    api_version=AZURE_OPENAI_API_VERSION,
    azure_endpoint=AZURE_OPENAI_ENDPOINT,
    azure_ad_token_provider=get_bearer_token_provider(credential, "https://cognitiveservices.azure.com/.default"),
)

# MCPサーバー経由でツールを実行し、その結果を返却する
async def call_tool(mcp_server: MCPServerStdio, function_name: str, function_args: dict[str, Any] | None):
    try:
        resp_items = []
        print(f"Function Name: {function_name} Function Args: {function_args}")
        func_response = await mcp_server.call_tool(function_name, function_args)
        # ツールの実行結果をメッセージとして処理可能な形式に変換
        for item in func_response.content:
            if isinstance(item, TextContent):
                resp_items.append({"type": "text", "text": item.text})
            elif isinstance(item, ImageContent):
                resp_items.append({
                    "type": "image_url",
                    "image_url": {
                        "url": f"data:{item.mimeType};base64,{item.data}",
                    },
                })
            else:
                raise ValueError(f"Unsupported content type: {type(item)}")
    except Exception as e:
        resp_items.append({"type": "text", "text": str(e)})
    return json.dumps(resp_items)


async def run():
    messages = [{"role": "system", "content": SYSTEM_PROMPT}]
    # 標準入出力でサーバーを作成
    async with MCPServerStdio(
        name="Playwright MCP Server",
        params={
            "command": "npx",
            "args": ["-y", "@playwright/mcp@latest", "--browser", "msedge"],
        },
    ) as server:
        # サーバのツールをOpenAIのFunciton Tool形式に変換
        available_tools = await MCPUtil.get_function_tools(server)
        openai_tools = [ToolConverter.to_openai(tool) for tool in available_tools] if available_tools else []
        print(f"利用可能なツール: {[tool.name for tool in available_tools]}")

        print("メッセージを入力してください。'quit'で終了します。")
        while True:
            try:
                user_message = input("User> ").strip()
                if user_message.lower() == 'quit':
                    break

                messages.append({"role": "user", "content": user_message})
                response = client.chat.completions.create(
                    model=AZURE_OPENAI_DEPLOYMENT_NAME,
                    messages=messages,
                    tools=openai_tools,
                    tool_choice="auto",
                )
                response_message = response.choices[0].message
                finish_reason = response.choices[0].finish_reason
                messages.append(response_message)

                # ツールを呼び出していれば実行して結果をmessagesに追加する
                if response_message.tool_calls:
                    function_arguments = ""
                    function_name = ""
                    tool_call_id = ""
                    is_collecting_function_args = False
                    tool_called = False

                    for tool_call in response_message.tool_calls:
                        # ファンクション名の取得
                        if tool_call.function.name:
                            function_name = tool_call.function.name
                            tool_call_id = tool_call.id
                        
                        # 引数の取得
                        if tool_call.function.arguments:
                            function_arguments = tool_call.function.arguments
                            is_collecting_function_args = True
                        
                        if finish_reason == "tool_calls" and is_collecting_function_args:
                            # ツールを呼び出しメッセージとして追加
                            func_response = await call_tool(server, function_name, json.loads(function_arguments))
                            print(f"Function Response: {json.loads(func_response)}")
                            messages.append({
                                "tool_call_id": tool_call_id,
                                "role": "tool",
                                "name": function_name,
                                "content": json.loads(func_response),
                            })
                            tool_called = True
                    
                    if tool_called:
                        # ツールが呼び出された際は、再度OpenAIに問い合わせ回答を生成
                        response = client.chat.completions.create(
                            model=AZURE_OPENAI_DEPLOYMENT_NAME,
                            messages=messages,
                        )
                        response_message = response.choices[0].message
                        messages.append(response_message)
                    
                    # 回答の表示
                    print(f"Assistant> {response_message.content}\n")

            except Exception as e:
                print(f"\nError: {str(e)}")

if __name__ == "__main__":
    import asyncio

    asyncio.run(run())
```

前述のMCPクライアントと大きく異なる点として、MCPのPython SDKを利用するのではなく、OpenAI Agents SDKを利用し、MCP Serverの初期化を行っています。  
これによりツール情報の読み出しと、OpenAIのFunction callingの形式に変換しています。
あとの処理は、Microsoftのドキュメントに記載されている関数呼び出しを使用する方法の通りに実装しております。

また今回の例では、MCPサーバーとして最近Microsoft社が発表した[Playwright MCP](https://github.com/microsoft/playwright-mcp)を利用していますが、他のMCPサーバーでも動作することを確認していますので、是非切り替えてMCPのメリットを体験してみてください。  

# 終わりに

MCPの仕様と実装方法について簡単にご紹介させて頂きました。本記事に記載した通り簡単に実装でき、またメリットも大きいためデスクトップアプリやエージェント系を中心に実装が進められています。
本記事では簡単なAzure OpenAIを利用したMCPクライアントの実装サンプルを記載させて頂きましたが、記載の通り既存のLLMアプリケーションにも容易に組み込むことが出来、これまで手が出し辛かったデータベースとの連携等も公開されているMCPサーバーを利用することで実現可能になります。
現在の実装ではMCPサーバーが複数のクライアントからの接続を想定されていない等の課題はありますが、新たに公開された`2025-03-26`版の仕様ではOAuthによる認証やセッション管理の仕様が追加されたので、MCPサーバーのインターフェースに対応したWebサービスが続々とリリースされてくると思われます。
また今後として、多数のMCPサーバーと連携しようとした場合に誤ってツールが呼び出される危険性も高くなりますので、引き続き業界動向をウォッチしていきたいと思います。

# 参考記事

* [MCPサーバーの機能を全部（Prompt, Resource, Tool）試してみる](https://qiita.com/ipeblb/items/535709fa06cbb40c400c)
* [【AI Shift Advent Calendar 2024】MCP ClientをOpenAIモデルで実装する](https://www.ai-shift.co.jp/techblog/5226)
* [Azure OpenAI Service で関数呼び出しを使用する方法 - Azure OpenAI Service](https://learn.microsoft.com/ja-jp/azure/ai-services/openai/how-to/function-calling)
* [Model Context Protocol (MCP): Integrating Azure OpenAI for Enhanced Tool Integration and Prompting | Microsoft Community Hub](https://techcommunity.microsoft.com/blog/azure-ai-services-blog/model-context-protocol-mcp-integrating-azure-openai-for-enhanced-tool-integratio/4393788)
* [Model context protocol (MCP) - OpenAI Agents SDK](https://openai.github.io/openai-agents-python/mcp/)