近年、LLM(大規模言語モデル)に外部のデータベースやAPIツールを統合するための統一規格として MCP(Model Context Protocol) が急速に広まっています。AIエージェントに自社サービスの機能を提供したい場合、MCPサーバーの構築は非常に強力な選択肢です。
しかし、「MCP用にわざわざ別サーバーを立てたり、APIロジックを二重で管理したりしたくない……」と感じる方も多いのではないでしょうか?
本記事では、Pythonのモダンで高速なAPIフレームワークである Django-Ninja をベースに、単一のDjangoサーバー上で「Webサービス」「REST API」「MCPサービス」をまとめてホストする構成 を紹介します。既存のDjango APIエンドポイントをそのままMCPツールとして活用できるため、エージェント連携のオーバーヘッドを劇的に削減できます。
利用ソフトウェアのバージョン
- uv: 0.12.3
- mcp: 2.0.0
- django: 6.0.8
- django-ninja: 1.6.2
- Bionic(クライアントとして確認用): 1.0.6+5
サンプルサーバー構築
以下の手順は、macOSで確認しています。
環境構築
uvでsampleというフォルダーを作りDjangoの環境構築をします。
uv init --no-package -p 3.14 sample
cd sample
mkdir src
rm main.py
uv add mcp==2.0.0 django==6.0.8 django-ninja==1.6.2
プロジェクトとアプリの作成
以下のようにしてプロジェクトとアプリを作成します。
uv run django-admin startproject project src
uv run --directory src manage.py startapp core
実装
ここでは、weatherというサービスを実装します。
src/project/settings.py抜粋
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"ninja",
"core.apps.CoreConfig",
]
ASGI_APPLICATION = "project.asgi.application"
src/project/asgi.py
import os
# djangoをインポートする前に設定しないといけない
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "project.settings")
from contextlib import asynccontextmanager
from django.core.asgi import get_asgi_application
from starlette.applications import Starlette
from starlette.routing import Mount
from core.api import api
from .mcp import mcp_server, register_ninja_to_mcp
@asynccontextmanager
async def lifespan(app):
# mounted sub-appのlifespanは自動実行されないため、host app側で起動する
async with mcp_server.session_manager.run():
yield
register_ninja_to_mcp(api, mcp_server)
django_app = get_asgi_application()
mcp_app = mcp_server.streamable_http_app(streamable_http_path="/")
application = Starlette(
routes=[
Mount("/api/mcp", app=mcp_app),
Mount("/", app=django_app),
],
lifespan=lifespan,
)
src/project/mcp.py
import inspect
from mcp.server import MCPServer
from ninja import NinjaAPI
api = NinjaAPI()
mcp_server = MCPServer(name="Sample API", description="API with MCP integration")
def register_ninja_to_mcp(api: NinjaAPI, mcp_server: MCPServer) -> None:
"""NinjaAPI に登録されているエンドポイントをMCPServer の Tool として登録"""
for _, router in api._routers:
for path_view in router.path_operations.values():
for op in path_view.operations:
func = op.view_func
# 1. 元の関数のシグネチャから 'request', 'req' 引数を取り除く
sig = inspect.signature(func)
new_params = [p for p in sig.parameters.values() if p.name not in ("request", "req")]
new_sig = sig.replace(parameters=new_params)
# 2. 型ヒント (__annotations__) からも 'request', 'req' を取り除く
orig_annotations = getattr(func, "__annotations__", {})
new_annotations = {k: v for k, v in orig_annotations.items() if k not in ("request", "req")}
# 3. async / sync 両対応のラッパー関数を生成
def make_wrapper(target_func, target_sig, target_annotations):
if inspect.iscoroutinefunction(target_func):
async def wrapper(*args, **kwargs):
return await target_func(None, *args, **kwargs)
else:
def wrapper(*args, **kwargs):
return target_func(None, *args, **kwargs)
# メタデータ・シグネチャ・型ヒントを引き継ぐ
wrapper.__name__ = target_func.__name__
wrapper.__doc__ = target_func.__doc__
wrapper.__module__ = target_func.__module__
wrapper.__signature__ = target_sig
wrapper.__annotations__ = target_annotations
return wrapper
mcp_server.add_tool(make_wrapper(func, new_sig, new_annotations))
src/project/urls.py
from django.contrib import admin
from django.urls import path
from core.api import api
urlpatterns = [
path("admin/", admin.site.urls),
path("api/", api.urls),
]
src/core/api.py
from project.mcp import api
@api.get("/weather/{city}")
def weather(request, city: str) -> dict[str, str]:
"""都市の天気"""
return {"message": f"{city}の天気は曇りのち晴れ"}
実行
マイグレーションを実行し、サーバーを起動します。
uv run --directory src manage.py migrate
uv run --directory src uvicorn project.asgi:application --reload --lifespan on
注意
manage.py runserverではエラーになります。
Web確認
http://localhost:8000/adminでWebを確認できます。以下でユーザーを作成するとログインできます。
uv run --directory src manage.py createsuperuser
API確認
http://localhost:8000/api/docsでAPIを確認できます。
注意
http://localhost:8000/api/docs/は404になります。最後のスラッシュは取ってください。
MCP確認
ここではBionicで確認します。以下からダウンロードしてインストールできます。
MCP設定
Bionicを起動したら設定画面(⌘+カンマ)を開き、左のタブの「Connected Apps」を選んでください。
右下の「Add custom MCP」で開く画面で以下を設定し、「Add MCP」を押してください。
- Name: Weather
- Connection: Web address
- Server address: http://localhost:8000/api/mcp/
注意
http://localhost:8000/api/mcpは404になります。最後のスラッシュは付けてください。
確認
左上の「Back to app」で戻り、チャット画面を開いてください。
「東京の天気」を入力してください。
「東京の天気は曇りのち晴れです。」と帰ってきたら成功です。
参考
下記は django-ninja-mcp を使った紹介記事です。本記事よりシンプルですが、django-ninja-mcp に依存するため最新版が使えないことがあります。