---
title: "FastAPI MCPの実装手順｜既存APIをfastapi-mcpでMCPツール化する方法と認証・公開範囲"
url: "https://www.issoh.co.jp/tech/details/19722/"
published: 2026-11-13
updated: 2026-11-13
categories: ["開発"]
publisher: "株式会社一創"
---

# FastAPI MCPの実装手順｜既存APIをfastapi-mcpでMCPツール化する方法と認証・公開範囲

手元で動いているFastAPIのエンドポイントを、Claude CodeやCursorから呼べるMCPツールに変える方法をまとめた記事です。中心はfastapi-mcp 0.4.0で、既存のアプリに数行足して`/mcp`を生やすところから、公開するツールの絞り込み、`Authorization`ヘッダーを使った利用者ごとの認可までをコードで確認します。2026年11月時点ではmcp（公式Python SDK）が2系に上がっており、fastapi-mcpをそのまま入れると動かない組み合わせになる点と、その回避策も扱います。FastMCPの`from_fastapi`へ移る場合の違いと、既存APIをそのままMCPにしない方がよい場面も書きました。

## まとめ：既存FastAPIをMCPサーバー化する手段の選び方と結論

利用者ごとのトークンで認可している既存APIを、そのままエージェントに呼ばせたいならfastapi-mcpが近道です。クライアントが送った`Authorization`ヘッダーを既定で各エンドポイントへ渡すため、今ある`Depends`の認証がそのまま効きます。ただし0.4.0は2025年7月28日から新しい版が出ておらず、依存のmcpを1系に固定して使うことが前提です。

これから長く保守するMCPサーバーを作るなら、保守が続いているFastMCP（4.1.8）を選びます。FastMCPは受信した`Authorization`を転送しないので、利用者ごとの認可を引き継ぐ設計は自分で組む必要があります。

どちらを使う場合も、公開するエンドポイントは許可リストで選び、更新系は最初は出しません。ツール名は`operation_id`で決まるため、ルートに明示的な名前を付けるところから始めます。

## fastapi-mcp 0.4.0で既存FastAPIにMCPの入口を足す最小構成

fastapi-mcpは、FastAPIが自動生成するOpenAPIの定義を読んで、各エンドポイントをMCPのツールに変換するライブラリです。[GitHubのtadata-org/fastapi\_mcp](https://github.com/tadata-org/fastapi%5Fmcp)で公開され、ライセンスはMITです。

### mcp 1系に固定してfastapi-mcpを入れる検証環境の作り方

[PyPIのfastapi-mcp](https://pypi.org/project/fastapi-mcp/)の最新は0.4.0（2025年7月28日公開）で、動作要件はPython 3.10以上です。依存の指定は`mcp>=1.12.0`と下限だけなので、何も考えずに入れると2系のmcpが入ります。理由は後の章で説明するとして、ここでは1系に固定して入れます。FastAPI自体の導入がまだなら、[FastAPIの最短の起動手順](https://www.issoh.co.jp/tech/details/3859/)を先に済ませてください。

```
# 仮想環境を作り、mcpを1系に固定してfastapi-mcpを入れる
python3 -m venv .venv
source .venv/bin/activate
pip install "fastapi[standard]==0.162.5" "fastapi-mcp==0.4.0" "mcp>=1.12,<2"

# 入った版を確かめる
pip show mcp fastapi-mcp | grep -E "^(Name|Version)"
```

`pip show`でmcpのVersionが1から始まっていれば準備完了です。

### FastApiMCPとmount\_httpで/mcpを生やす最小コード

注文の状況を返すAPIを例にします。末尾の3行がfastapi-mcpの追加分で、既存のエンドポイントには手を入れていません。

```
# main.py
from fastapi import FastAPI
from fastapi_mcp import FastApiMCP

app = FastAPI(title="Order API")


@app.get("/orders/{order_id}", operation_id="get_order_status", tags=["mcp"])
def get_order_status(order_id: int):
    """注文IDを受け取り、配送状況を返す。"""
    return {"order_id": order_id, "status": "shipped"}


mcp = FastApiMCP(app, name="Order API MCP")
mcp.mount_http()  # 既定のパスは /mcp
```

`fastapi dev main.py`で起動すると、通常のAPIに加えて`http://127.0.0.1:8000/mcp`がMCPのエンドポイントになります。[CHANGELOG](https://github.com/tadata-org/fastapi%5Fmcp/blob/main/CHANGELOG.md)によると、0.4.0でStreamable HTTP用の`mount_http()`が加わり、それまでの`mount()`は非推奨になりました。古い解説記事の`mcp.mount()`は、この版では`mount_http()`に書き換えます。SSEにしか対応していないクライアント向けには`mount_sse()`が残っています。

### Claude Codeから接続してツール一覧と呼び出しを確かめる手順

接続先には、Streamable HTTPに対応したクライアントならURLをそのまま登録できます。Claude Codeでは[公式ドキュメントのMCPの章](https://code.claude.com/docs/en/mcp)にある`--transport http`で追加します。

```
# FastAPIを起動したまま、別の端末で登録する
claude mcp add --transport http orders http://127.0.0.1:8000/mcp

# Claude Code の中で /mcp を開き、orders が connected になっているか見る
```

接続できたら「注文1234の状況を教えて」と頼むと、`get_order_status`ツールが呼ばれます。ツールの説明文にはエンドポイントのdocstringが入るので、何を返すのかを1文で書いておくと、エージェントが呼ぶべき場面を取り違えにくくなります。

## operation\_idとinclude\_tagsでMCPに出すツールを絞る設定手順

fastapi-mcpは、指定しなければ全エンドポイントをツールにします。管理用の削除APIまでエージェントから呼べる状態は避けたいので、名前と範囲を最初に決めます。

### operation\_id未指定で長いツール名が付く問題と命名の決め方

[公式のTool Naming](https://fastapi-mcp.tadata.com/configurations/tool-naming)のとおり、ツール名はFastAPIのルートの`operation_id`です。指定しないとFastAPIが自動で名前を作り、`/users/{user_id}`のGETなら`read_user_users__user_id__get`のような名前になります。エージェントはツール名と説明文を手がかりに呼ぶツールを選ぶため、この形は避けます。

命名は「動詞＋対象」のスネークケースに揃えます。`get_order_status`、`search_products`のように、名前だけで読み取りか更新かが分かる形です。`operation_id`はOpenAPIの定義にも載り、[Swagger UIの表示](https://www.issoh.co.jp/tech/details/2246/)にもそのまま出るので、人間向けのドキュメントの見やすさも同時に上がります。

### include\_tagsで許可リスト方式にするときの組み合わせ規則

絞り込みの引数は4つあります。[公式のCustomization](https://fastapi-mcp.tadata.com/configurations/customization)に書かれた規則を表にします。

| 引数                  | 指定する値             | 同時に使えない相手           |
| ------------------- | ----------------- | ------------------- |
| include\_operations | 公開するoperation\_id | exclude\_operations |
| exclude\_operations | 除外するoperation\_id | include\_operations |
| include\_tags       | 公開するタグ            | exclude\_tags       |
| exclude\_tags       | 除外するタグ            | include\_tags       |

同時指定は、[server.py](https://github.com/tadata-org/fastapi%5Fmcp/blob/main/fastapi%5Fmcp/server.py)のコンストラクタで`ValueError`になります。操作による絞り込みとタグによる絞り込みは併用でき、その場合はどちらかに当てはまるものが公開される和集合です。

おすすめは`include_tags=["mcp"]`の許可リスト方式です。除外リスト方式だと、後から足した管理用エンドポイントがタグを付け忘れた瞬間に公開されます。許可リストなら、付け忘れたエンドポイントは出ないだけで済みます。

### 起動後に追加したエンドポイントをsetup\_serverで反映する方法

`FastApiMCP(app)`を作った時点のルートでツール一覧が組まれます。プラグインの読み込みなどで、その後にエンドポイントを追加する構成では、[公式のRefreshing the Server](https://fastapi-mcp.tadata.com/advanced/refresh)にあるとおり`mcp.setup_server()`を呼んで作り直します。ルーターの`include_router`を全部済ませてから`FastApiMCP`を作る順番にしておけば、この呼び出しは要りません。

## Authorizationヘッダーの転送とDependsで利用者ごとに認可する実装

fastapi-mcpの一番の利点はここです。MCPの層で認証を作り直さず、既存のAPIの認証をそのまま使えます。

### 既定でauthorizationだけを転送する仕組みとheaders引数

ツールが呼ばれると、fastapi-mcpはHTTPを経由せず、ASGIで同じFastAPIアプリを直接呼びます。[server.pyの既定値](https://github.com/tadata-org/fastapi%5Fmcp/blob/main/fastapi%5Fmcp/server.py)では、このときMCPクライアントから受け取ったヘッダーのうち転送するのは`headers=["authorization"]`の許可リストだけです。大文字小文字は区別しません。

テナントIDを`X-Tenant-Id`で受けているAPIなら、`FastApiMCP(app, headers=["authorization", "x-tenant-id"])`のように足します。`Cookie`で認証しているAPIは、エージェントがブラウザのCookieを持たないため、この仕組みでは引き継げません。トークン方式の入口を別に用意します。

### AuthConfigのdependenciesでトークンの無い接続を入口で断る

認証付きにした完成形が次のコードです。`get_current_user`は既存のAPIでも使う依存関数で、MCP側の入口と各エンドポイントの両方で同じものを使います。

```
# main.py（認証付き）
from typing import Annotated

from fastapi import Depends, FastAPI, Header, HTTPException
from fastapi_mcp import AuthConfig, FastApiMCP

app = FastAPI(title="Order API")

# 検証用のトークン表。本番ではJWTの署名検証やDB照会に置き換える
TOKENS = {"token-alice": "alice"}


def get_current_user(authorization: Annotated[str | None, Header()] = None):
    if not authorization or not authorization.startswith("Bearer "):
        raise HTTPException(401, "token required")
    user = TOKENS.get(authorization.removeprefix("Bearer "))
    if user is None:
        raise HTTPException(401, "invalid token")
    return user


UserDep = Annotated[str, Depends(get_current_user)]


@app.get("/orders/{order_id}", operation_id="get_order_status", tags=["mcp"])
def get_order_status(order_id: int, user: UserDep):
    """自分の注文の配送状況を返す。"""
    return {"order_id": order_id, "owner": user, "status": "shipped"}


@app.delete("/orders/{order_id}", operation_id="cancel_order", tags=["admin"])
def cancel_order(order_id: int, user: UserDep):
    """注文を取り消す。MCPには公開しない。"""
    return {"order_id": order_id, "canceled": True}


mcp = FastApiMCP(
    app,
    name="Order API MCP",
    include_tags=["mcp"],
    auth_config=AuthConfig(dependencies=[Depends(get_current_user)]),
)
mcp.mount_http()
```

[公式のAuthentication & Authorization](https://fastapi-mcp.tadata.com/advanced/auth)では、`AuthConfig`の`dependencies`に依存関数を渡すと、トークンの無い接続をMCPの入口で拒否できると説明しています。クライアント側は、登録時にヘッダーを付けます。

```
claude mcp add --transport http orders http://127.0.0.1:8000/mcp \
  --header "Authorization: Bearer token-alice"
```

これで`get_order_status`の中の`user`には`alice`が入り、自分の注文だけを返す既存のロジックがそのまま働きます。`Depends`の連鎖やテストでの差し替えは[FastAPIのDependsの使い方](https://www.issoh.co.jp/tech/details/19720/)で扱っています。トークンをJWTにする場合の検証は、[JWTの構造と署名検証の仕組み](https://www.issoh.co.jp/tech/details/13453/)を土台にした検証方法です。

### OAuthのAuthConfigを使うのは社外の利用者に配る場合だけ

`AuthConfig`には`issuer`や`client_id`を渡してOAuthの認可フローを組む設定もあります。公式も「多くのOAuthプロバイダーはMCPクライアントと組み合わせるのに手直しが要る」として、`setup_proxies=True`で中継用のエンドポイントを作る方法を案内しています。社内の開発者が自分のAPIトークンを登録して使う用途なら、ここまでは要りません。社外の利用者にブラウザでログインさせて配る場合だけ、この設定を検討します。

## mcp 2系の環境でfastapi-mcpが動かない原因とmcp 1系へ固定する判断

導入手順でmcpを1系に固定したのは、0.4.0のソースと公式SDKの変更が噛み合わないためです。

### デコレーター登録のlowlevel Serverがmcp 2系で外れた経緯

fastapi-mcpの`setup_server`は、公式SDKの低水準API`mcp.server.lowlevel`の`Server`を作り、`@mcp_server.list_tools()`と`@mcp_server.call_tool()`のデコレーターでツール一覧と呼び出しの処理を登録しています。一方、[公式のv1からv2への移行ガイド](https://py.sdk.modelcontextprotocol.io/migration/)は、低水準の`Server`がデコレーター方式からコンストラクタの`on_*`引数方式に変わり、v1のまま`server.list_tools`を参照すると`AttributeError`になると明記しています。

2026年11月13日時点で[PyPIのmcp](https://pypi.org/project/mcp/)の最新は2.3.0です。fastapi-mcpの依存指定には上限が無いので、新しく作った環境ではこの2系が選ばれます。本記事では手元での実行による再現は確認できていません。ソースと移行ガイドの突き合わせから、1系に固定して使うのが安全と判断しました。

### mcp<2の固定で使い続けてよい条件と移行を始める合図

固定で使い続けてよいのは、社内向けの小さなMCPサーバーで、MCPの新しい仕様の機能を当面使わない場合です。`requirements.txt`や`pyproject.toml`に`mcp<2`を書き、Dependabotなどの自動更新で外れないようにします。

移行を始める合図は2つです。1つは、使っているMCPクライアントが新しいプロトコル版でしか接続しなくなったとき。仕様の変更点は[MCP仕様2026-07-28版の解説](https://www.issoh.co.jp/tech/details/18245/)にまとめています。もう1つは、mcp 1系やその依存にセキュリティ修正が必要になり、1系の範囲で直せないときです。どちらかが来たら、次の章のFastMCPへ移すか、公式SDK 2系で書き直します。SDK 2系への書き換え方は[MCP Python SDK v2移行の手順](https://www.issoh.co.jp/tech/details/18273/)で扱っています。

## FastMCP.from\_fastapiへ移す場合の書き方と認証ヘッダーの扱いの差

[PyPIのFastMCP](https://pypi.org/project/fastmcp/)は2026年11月13日時点で4.1.8です。[公式のFastAPI連携ガイド](https://gofastmcp.com/docs/integrations/fastapi)にある`FastMCP.from_fastapi()`で、同じ既存アプリを変換できます。

### RouteMapのEXCLUDEで更新系を外すfrom\_fastapiのコード

FastMCPも既定では全エンドポイントをツールにします。[OpenAPI連携のドキュメント](https://gofastmcp.com/docs/integrations/openapi)にある`RouteMap`で、更新系のメソッドを除外する設定です。`route_maps`は上から順に評価され、最初に当てはまった規則が使われます。

```
# mcp_server.py
from fastmcp import FastMCP
from fastmcp.server.providers.openapi import MCPType, RouteMap

from main import app

mcp = FastMCP.from_fastapi(
    app=app,
    name="Order API MCP",
    route_maps=[
        # 更新系はMCPに出さない
        RouteMap(methods=["POST", "PUT", "PATCH", "DELETE"], pattern=r".*", mcp_type=MCPType.EXCLUDE),
    ],
    # FastAPIへ送るヘッダーは固定値になる
    httpx_client_kwargs={"headers": {"Authorization": "Bearer service-token"}},
)

if __name__ == "__main__":
    mcp.run()
```

既存のFastAPIと同じプロセスに`http_app()`でマウントする方法と、`lifespan`の渡し忘れで起きる不具合は、[FastMCPでMCPサーバーを作る手順の記事](https://www.issoh.co.jp/tech/details/6507/)のFastAPI連携の章で扱っています。

### fastapi-mcpとFastMCPで利用者ごとの認可が引き継がれるかの違い

2つの一番大きな違いは、MCPクライアントが送った`Authorization`の扱いです。FastMCPのOpenAPI連携のドキュメントは、受信したMCPリクエストのヘッダーを転送するとしつつ、`Authorization`、`Cookie`、`Host`は対象外だと書いています。上のコードのようにサービス用の固定トークンを送る形になり、FastAPI側からはすべての呼び出しが同じ利用者に見えます。

| 比較項目             | fastapi-mcp 0.4.0 | FastMCP 4.1.8        |
| ---------------- | ----------------- | -------------------- |
| Authorizationの転送 | 既定で転送             | 転送しない                |
| APIの呼び方          | ASGIで直接           | httpxのクライアント経由       |
| 除外の指定            | operation\_id・タグ  | RouteMap（メソッド・パス・タグ） |
| 最新版の公開           | 2025年7月           | 2026年11月時点で更新中       |
| mcp 2系への対応       | 1系固定が前提           | 独立した実装               |

判断はこうです。利用者ごとに見える範囲が違うAPIを、利用者本人のトークンで呼ばせたいならfastapi-mcp。読み取り専用の社内APIをサービスアカウント1つで公開する、あるいはMCPサーバーを長く育てる前提ならFastMCPを選びます。FastMCPで利用者ごとの認可が要る場合は、MCP側で認証してから、検証済みの利用者情報をFastAPIへ渡す経路を自分で設計することになります。

## 既存APIをそのままMCPツールにしない方がよい場面と設計し直す基準

FastMCPの公式ドキュメントも、FastAPIからの自動変換は立ち上げや試作向けで、複雑なAPIほど目的に合わせて選んだツールの方がLLMの成績が良いと書いています。変換できることと、変換したものが使いやすいことは別です。

### 既存の画面向けAPIを変換するとエージェントが迷う3つの兆候

次のどれかに当てはまるエンドポイントは、変換せずにMCP用の入口を別に書きます。

- 1つの目的に複数回の呼び出しが要る（一覧で内部IDを取り、詳細を引き、さらに別APIで在庫を見る）
- 引数に画面の都合の値が並ぶ（ソート列、表示件数、UI用のフラグ）
- 応答が大きい（画面の表示に使う全項目を返し、1回で数万字になる）

この場合は、「注文番号から配送状況と到着予定日だけを返す」のように、エージェントの1つの質問に1回で答えるエンドポイントを`/mcp-api/`の下に新しく作り、そこだけを`include_tags`で公開します。既存のサービス層の関数を呼び直すだけなので、手間はエンドポイント1本につき数十行です。

### 更新系と時間のかかる処理をMCPツールとして公開する前に決めておくこと

削除や送金のような取り消せない操作は、最初の公開では出しません。出すなら、確認用の下書きを作るツールと確定するツールに分け、確定には人の承認を挟みます。エージェントは外部の文書に書かれた指示に引きずられることがあり、Claude Codeの公式ドキュメントも外部の内容を読み込むサーバーにはプロンプトインジェクションの危険があると注意しています。

時間のかかる処理にも注意します。fastapi-mcpの既定のHTTPクライアントはタイムアウトが10秒です。集計やファイル生成で10秒を超えるエンドポイントは、`http_client`引数に`httpx.AsyncClient(transport=httpx.ASGITransport(app=app), base_url="http://apiserver", timeout=60.0)`のように長めの設定を渡すか、ジョブの受付と結果の取得に分けます。

### 社内の業務APIをエージェントへ開放する設計を外部に任せる判断

公開するエンドポイントが数本で、利用者が社内の開発者に限られるなら、この記事の構成で十分に運用できる範囲です。基幹システムのAPIを営業や経理の担当者がエージェント経由で使う段階になると、ツールの粒度の設計、利用者ごとの権限、操作ログの保存、承認の挟み方を一緒に決める必要が出てきます。既存のFastAPI資産を生かしたまま、業務に合わせたエージェントとMCPサーバーを組む作業は、[AIエージェント開発の受託](https://www.issoh.co.jp/service/ai/agent/)で相談を受け付けています。

## よくある質問

既存のFastAPIをMCPサーバー化するときに出てくることの多い質問に、2026年11月時点の版で答えます。

### fastapi-mcpとFastMCPはどちらを使えばよいですか？

利用者本人のトークンで既存の認可をそのまま効かせたいならfastapi-mcpです。`Authorization`を既定で転送するため、既存の`Depends`が利用者を判定できます。ただし最新版の0.4.0は2025年7月の公開で、mcpを1系に固定して使う前提です。長く保守する前提、またはサービスアカウント1つで足りるならFastMCPを選びます。FastMCPの4系の変更点は[FastMCP 4の移行判断の記事](https://www.issoh.co.jp/tech/details/15907/)にまとめています。

### 既存のAPIと同じポートでMCPサーバーを動かせますか？

同じポートで動かすことが可能です。`mount_http()`は既定で同じアプリの`/mcp`にマウントするので、APIもMCPも同じuvicornのプロセスで動きます。パスを変えたい場合は`mount_http(router, mount_path="/my-http")`のように指定する方法です。MCP側の負荷をAPIから切り離したい場合は、MCP用に別のプロセスを立てて、そちらから既存APIを呼ぶ構成も取れます。

### 後から追加したエンドポイントがツールに出てこないのはなぜですか？

ツールの一覧は`FastApiMCP`を作った時点のルートで組まれるためです。`include_router`を`FastApiMCP`の生成より後に書いていないかを確かめます。どうしても後から足す構成なら、追加のあとに`mcp.setup_server()`を呼ぶことで反映が可能です。`include_tags`で絞っている場合は、追加したルートにタグが付いているかも見ます。

### SSEにしか対応していないMCPクライアントからも接続できますか？

接続できます。[公式のMCP Transport](https://fastapi-mcp.tadata.com/advanced/transport)のとおり、`mount_http()`と並べて`mount_sse()`を呼ぶと、`/sse`でSSEの接続も受けられます。0.4.0ではStreamable HTTPが推奨で、SSEは古いクライアントとの互換のために残された扱いです。新しく登録するクライアントは`/mcp`に向けます。

### ツールの呼び出しが10秒で失敗するのはなぜですか？

fastapi-mcpの既定のHTTPクライアントのタイムアウトが10秒だからです。エンドポイントの処理が10秒を超えると、ツールの呼び出しの結果は失敗です。`http_client`引数にASGIで同じアプリを呼ぶ`httpx.AsyncClient`を、長めのタイムアウトを付けて渡すと延ばせます。数分かかる処理なら、受付だけを即座に返し、結果を取るツールを別に用意する方が、エージェントの待ち時間も短く済みます。

## 関連記事

- [FastMCPとは？PythonでMCPサーバーを作る手順とFastMCP 4.1の使い方【2026年10月】](https://www.issoh.co.jp/tech/details/6507/)：FastMCPでMCPサーバーをゼロから作る手順と、http\_appでの同居です。
- [MCP Python SDK v2 移行：FastMCPからMCPServerへの書き換え手順と据え置き判断](https://www.issoh.co.jp/tech/details/18273/)：mcp 1系に固定したあとの移行先です。
- [FastAPIのDependsの使い方｜依存性注入・yield・テストの差し替えをコードで解説](https://www.issoh.co.jp/tech/details/19720/)：MCPの入口でも使う認証依存の書き方です。
- [MCP仕様2026-07-28版とは：ステートレス化の変更点と2025-11-25版からの移行手順](https://www.issoh.co.jp/tech/details/18245/)：プロトコル版の変更と移行の判断材料です。
- [FastAPIとは？最短の起動手順とasync/defの使い分け、0.162の変更点](https://www.issoh.co.jp/tech/details/3859/)：MCP化する前のFastAPI単体の入門です。

---

出典: [FastAPI MCPの実装手順｜既存APIをfastapi-mcpでMCPツール化する方法と認証・公開範囲](<https://www.issoh.co.jp/tech/details/19722/>)（株式会社一創）
