🪮

FastMCP2 を使って MCP Server をひとまとめにしよう!

に公開

最初にまとめ

  • 複数の MCP Server をひとまとめにして、MCP Server 設定の手間を減らします
  • MCP Server を Devcontainer(Docker Desktop) の仮想環境から利用できるようにします

詳細は以降で説明します。

この記事の概要

Python OSS である FastMCP 2.0 を用いて、それぞれバラバラな Transport である複数の MCP Server をひとまとめにした MCP Server を作成します。
ひとまとめにして何が嬉しいの??」と疑問をお持ちかもしれませんが、筆者は自身の開発環境で次のような課題がありメリットを感じています。

課題1 複数のLLMアプリケーションで MCP Server の設定をするのが手間

MCP は大変便利で複数の MCP Server を LLM アプリケーションに設定しています。しかし、LLMアプリケーションも Claude Code や Cursor など複数利用するようになり、それぞれに MCP Server の設定の追加・更新をするのが面倒に感じてきました

課題2 VSCode の Devcontainer(Docker Desktop) 環境で MCP Server が利用しづらい

筆者はできるだけ Host を汚したくないため、開発環境として VSCode や Cursor で Docker Desktop 上でのDevcontainerを利用しています。そうすると、コンテナ環境からは Host の実行ファイルなどはそのままでは利用できませんので、Stdio TransportMCP Server は Dockerfile に含めるなどしてコンテナ環境で実行できるようにする必要がありました
また、MCP Server の実行コマンドが Docker を利用している場合、コンテナ環境内で Docker コマンドを使えるようにする必要があります。これらをプロジェクト毎に設定する必要があり、MCP Server の設定以外の設定が必要でした

・・・そうだ、ひとまとめにしちゃおう!

上記の課題を解決するため、利用する複数の MCP Server を Streamable HTTP Transport の MCP Server にひとまとめにします。その MCP Server を Host の localhost で起動しておきます。
そうすると LLM アプリケーションは、この MCP Server のみを設定するだけで、複数の MCP Server を利用できます
また、devcontainer 環境の場合も、localhost のサーバに HTTP 通信できればいいので、MCP Server 以外に初期設定が必要ありません。

具体的な方法を以下に記載します。

書かないこと

以下の内容は本記事では説明しません。

  • MCP (Model Context Protocol) の具体的な内容や Transport Layer の説明
  • Devcontainer の利用方法
  • Cursor の MCP Server 設定方法

バージョン・環境

  • Mac
  • FastMCP2: 2.10.5

1. FastMCP 2.0 とは

サイトから抜粋

FastMCP 2.0 has evolved into a comprehensive platform that goes far beyond basic protocol implementation. While 1.0 provided server-building capabilities (and is now part of the official MCP SDK), 2.0 offers a complete ecosystem including client libraries, authentication systems, deployment tools, integrations with major AI platforms, testing frameworks, and production-ready infrastructure patterns.

日本語訳)

FastMCP 2.0は、基本的なプロトコル実装をはるかに超える包括的なプラットフォームへと進化しました。1.0はサーバー構築機能を提供していましたが(現在は公式MCP SDKの一部です)、2.0はクライアントライブラリ、認証システム、デプロイメントツール、主要AIプラットフォームとの統合、テストフレームワーク、本番環境対応のインフラストラクチャパターンなど、包括的なエコシステムを提供します。

https://gofastmcp.com/getting-started/welcome

上記の通り FastMCP2.0 は、公式 MCP Python SDK に組み込まれた FastMCP 1.0 から機能拡張されています。この拡張機能を利用していきます。

2. Proxy Server

FastMCP 2 には、Proxy Server という機能があります。Proxy Server はクライアントからのリクエストをバックエンドの MCP Server に転送し、レスポンスを受信して、そのレスポンスを元のクライアントに中継します。Proxy Server は、ある Transport 上で実行されているサーバーを別の Transport 経由で公開することができます(Transport Bridging)。

この Proxy Server の機能の中に Configuration-Based Proxiesというものがあり、MCP Config スキーマに準拠した設定から直接プロキシを作成することができます

2.1 Proxy Server のサンプル

それでは、Proxy Server 機能を利用して、いくつかの MCP Server をまとめたサンプル MCP Server を作成してみます。
まとめる対象となるバックエンドの MCP Server は、 MCP のレファレンスの中から選択しています。
https://github.com/modelcontextprotocol/servers/tree/main
Docker コマンドを利用する MCP Server については、サイトの README を参考に事前に docker image をビルドしておきます。

main.py
from fastmcp import FastMCP

config_refs = {
    "mcpServers": {
        "sequentialthinking": {
            "command": "docker",
            "args": [
            "run",
            "--rm",
            "-i",
            "mcp/sequentialthinking"
            ],
            "transport": "stdio"
        },
        "fetch": {
            "command": "docker",
            "args": [
            "run",
            "-i",
            "--rm",
            "mcp/fetch"
            ],
            "transport": "stdio"
        },
        "memory": {
            "command": "npx",
            "args": [
                "-y",
                "@modelcontextprotocol/server-memory"
            ]
        }  
    }
}


class MCPProxy():
    def __init__(self, name, config):
        
        self._name = name
        self._mcp = FastMCP.as_proxy(config, name=self._name)

    def run(self, transport: str, host: str, port: int, path: str):
        self._mcp.run(transport=transport, host=host, port=port, path=path)

if __name__ == "__main__":
    proxy = MCPProxy("mcp_refs", config_refs)
    proxy.run(transport="streamable-http", host="127.0.0.1", port=12345, path="/mcp")

こちらをターミナルで実行すると MCP Server がローカルで開始されます。起動表示を抜粋して記載します。
URL が http://127.0.0.1:12345/mcp で起動していることが分かります。

╭─ FastMCP 2.0 ──────────────────────────────────────────────────────────────╮
│                                                                            │
│        _ __ ___ ______           __  __  _____________    ____    ____     │
│       _ __ ___ / ____/___ ______/ /_/  |/  / ____/ __ \  |___ \  / __ \    │
│      _ __ ___ / /_  / __ `/ ___/ __/ /|_/ / /   / /_/ /  ___/ / / / / /    │
│     _ __ ___ / __/ / /_/ (__  ) /_/ /  / / /___/ ____/  /  __/_/ /_/ /     │
│    _ __ ___ /_/    \__,_/____/\__/_/  /_/\____/_/      /_____(_)____/      │
│                                                                            │
│                                                                            │
│                                                                            │
│    🖥️  Server name:     mcp_refs                                            │
│    📦 Transport:       Streamable-HTTP                                     │
│    🔗 Server URL:      http://127.0.0.1:12345/mcp                          │
│                                                                            │
│    📚 Docs:            https://gofastmcp.com                               │
│    🚀 Deploy:          https://fastmcp.cloud                               │
│                                                                            │
│    🏎️  FastMCP version: 2.10.5                                              │
│    🤝 MCP version:     1.11.0                                              │
│                                                                            │
╰────────────────────────────────────────────────────────────────────────────╯

動作確認をしてみましょう。FastMCP2.0 ではクライアント側も作成できるので、以下のようにテストクライアントを用意しました。

test_client.py
from fastmcp import Client
import asyncio

async def main():
 
    async with Client("http://127.0.0.1:12345/mcp") as client:

        tools = await client.list_tools()
        for tool in tools:
            print(f"Tool: {tool.name}")
        

if __name__ == "__main__":
    asyncio.run(main())

こちらを別ターミナルで起動してみます。
すると、Stdio Transport であった複数の MCP Server が、 作成した Streamable HTTP Transport のサーバとしてまとめられました。

Tool: sequentialthinking_sequentialthinking
Tool: fetch_fetch
Tool: memory_create_entities
Tool: memory_create_relations
Tool: memory_add_observations
Tool: memory_delete_entities
Tool: memory_delete_observations
Tool: memory_delete_relations
Tool: memory_read_graph
Tool: memory_search_nodes
Tool: memory_open_nodes

3. Server Composition

FastMCP 2 には、 さらに Server Comosition という機能もあります。 MCPアプリケーションが成長してきたら、ツールやリソース、プロンプトを論理的にモジュール化したり、既存のサーバーコンポーネントを再利用したくなります。
このような場面で使えるのが Server Composition になります。 Server Composition は2つの方法があります。

  • import_server: 他のサーバーコンポーネントを一回限りでコピーし、プレフィックス付きで取り込む方法です。
  • mount: メインサーバーがリクエストをサブサーバーに委譲するライブリンクを作成する方法です。

Server Composition は、先程作成した Proxy MCP Server も統合することができます。

3.1 Server Composition のサンプル

それでは、Server Composition 機能を利用して、2つの Proxy MCP Server をまとめたサンプルを作成してみます。
先程の例に追加でもう1つ Proxy MCP Server を作成します。まとめる対象となるバックエンドの MCP Server は、今度は AWS MCP Servers の中から選択しています。

https://github.com/awslabs/mcp

Docker コマンドを利用する MCP Server については、サイトの README を参考に事前に docker image をビルドしておきます。

main2.py

from fastmcp import FastMCP

config_refs = {
    "mcpServers": {
        "sequentialthinking": {
            "command": "docker",
            "args": [
            "run",
            "--rm",
            "-i",
            "mcp/sequentialthinking"
            ],
            "transport": "stdio"
        },
        "fetch": {
            "command": "docker",
            "args": [
            "run",
            "-i",
            "--rm",
            "mcp/fetch"
            ],
            "transport": "stdio"
        },
        "memory": {
            "command": "npx",
            "args": [
                "-y",
                "@modelcontextprotocol/server-memory"
            ]
        }  
    }
}


config_aws = {
    "mcpServers": {
        "aws_documentation_mcp_server": {
            "command": "docker",
            "args": [
                "run",
                "--rm",
                "--interactive",
                "--env",
                "FASTMCP_LOG_LEVEL=ERROR",
                "mcp/aws-documentation:latest"
            ],
            "env": {}
        },
        "core_mcp_server": {
            "command": "docker",
            "args": [
            "run",
            "--rm",
            "--interactive",
            "--env",
            "FASTMCP_LOG_LEVEL=ERROR",
            "awslabs/core-mcp-server:latest"
            ],
            "env": {},
        }
    }
}


class MCPProxy():
    def __init__(self, name, config):
        
        self._name = name
        self._mcp = FastMCP.as_proxy(config, name=self._name)

    def run(self, transport: str, host: str, port: int, path: str):
        self._mcp.run(transport=transport, host=host, port=port, path=path)

    def server(self):
        return self._mcp


if __name__ == "__main__":
    proxy_refs = MCPProxy("mcp_refs", config_refs)
    proxy_aws = MCPProxy("mcp_aws", config_aws)

    composit = FastMCP(name="mcp_composit")
    composit.mount(proxy_refs.server())
    composit.mount(proxy_aws.server())
    
    composit.run(transport="streamable-http", host="127.0.0.1", port=12345, path="/mcp")

先程と同じテストクライアントを実行すると、先程の結果に加え、AWS MCP Server の Tool が追加されていることが分かります。

Tool: sequentialthinking_sequentialthinking
Tool: fetch_fetch
Tool: memory_create_entities
Tool: memory_create_relations
Tool: memory_add_observations
Tool: memory_delete_entities
Tool: memory_delete_observations
Tool: memory_delete_relations
Tool: memory_read_graph
Tool: memory_search_nodes
Tool: memory_open_nodes
Tool: aws_documentation_mcp_server_read_documentation # 追加
Tool: aws_documentation_mcp_server_search_documentation # 追加
Tool: aws_documentation_mcp_server_recommend # 追加
Tool: core_mcp_server_prompt_understanding # 追加

このように論理的に MCP Server をモジュール化しておくと、MCP Server の変更や再利用がしやすくなります。

4. Devcontainer (Docker Desktop) からの利用

作成した MCP Server は、ローカル環境で HTTP サーバとして起動しています。そのため、Devcontainer(Docker Desktop) 環境であっても、こちらの MCP Server へ HTTP アクセスできれば利用できます。例として、Cusor での設定 mcp.jsonを記載します。

mcp.json
{
    "mcpServers": {
      "mcp_composit": {
        "url": "http://host.docker.internal:12345/mcp"
      }
    }
}

Docker Desktop コンテナから Host の localhost へアクセスする場合、host.docker.internal を指定するとアクセスできます。

おわりに

以上、FastMCP2 を利用した プロキシ MCP Server を紹介しました!
少しでも、ご参考になれば幸いです。

参考になる記事

https://zenn.dev/gladevise/articles/access-localhost-from-docker-container

https://zenn.dev/collabostyle/articles/1152b59484107b#まとめ

Discussion