Skip to main content
Glama
edelvalle

django-admin-fastmcp

by edelvalle

django-admin-fastmcp

Django の管理サイトを MCP サーバーとして公開する再利用可能な Django アプリで、FastMCP 上に構築されています。

すべてのツール呼び出しは、ベアラートークンを所有するスタッフユーザーとして実行されます。すべてのツール呼び出しは、最初に ModelAdmin に権限を問い合わせます。スーパーユーザーは管理サイトでスーパーユーザーができることをすべて実行できます。スタッフユーザーは、そのスタッフユーザーが管理サイトでできることだけを実行でき、それ以上はできません。

SPEC.md が完全な仕様です。

仕組み

このパッケージを定義する3つのルール:

  1. 並列の権限システムは持たない。 認可は ModelAdmin のメソッドに委譲します: has_view_permission、has_add_permission、has_change_permission、has_delete_permission、get_queryset、get_readonly_fields、get_actions。行を隠す get_queryset のオーバーライドは、MCP からも行を隠します。

  2. 並列のデータ面は持たない。 書き込みは管理サイト自身の ModelForm と save_model を経由し、その後 LogEntry を記録します。管理サイトの履歴ページは真実のままです。

  3. フェイルクローズ。 未解決のルックアップ、欠落した ModelAdmin、不明なアクション、不明なフィールド、不明なツールは、すべて呼び出しを拒否します。

Related MCP server: Globalping

インストール

uv add django-admin-fastmcp

アプリを設定に追加します:

INSTALLED_APPS = [
    ...,
    "django.contrib.admin",
    "django_admin_fastmcp",
]

ADMIN_FASTMCP = {
    "SERVER_NAME": "acme-admin",
    "EXCLUDE_MODELS": ("auth.Permission", "auth.Group"),
    "WRITABLE_MODELS": (),          # empty means no writes at all
}

OAuth エンドポイントを管理サイトと同じサイトにマウントします:

# urls.py
urlpatterns = [
    # RFC 8414 fixes this one at the site root.
    path("", include("django_admin_fastmcp.well_known_urls")),
    # This prefix is yours to choose. Match it to the path in MCP_URL.
    path("admin/mcp/", include("django_admin_fastmcp.urls")),
    path("admin/", admin.site.urls),
]

このパッケージはプレフィックスをハードコードしません。メタデータドキュメントが宣伝するすべての URL は reverse() から取得されるため、エンドポイントを /backoffice/oauth/ にマウントするプロジェクトは、そのパスがディスカバリに現れ、クライアントはそれに従います。2つのルールがあります: well-known ドキュメントはルートに置く必要があります。クライアントがその URL を issuer から導出するためです。また、エンドポイントは管理サイトと同じサイトに置く必要があります。同意ページが管理セッションの Cookie に依存するためです。

マイグレーションを適用します:

python manage.py migrate django_admin_fastmcp

これ以外は何もありません。モデルごとの登録も、mixin も、デコレータもありません。サーバーは管理サイトがすでに公開しているものをそのまま公開します。

クライアントを接続する

サーバーを実行し(デプロイを参照)、登録します:

# Claude Code
claude mcp add --transport http acme-admin https://<host>/admin/mcp

末尾にスラッシュのないパスを使用してください。/admin/mcp/ は /admin/mcp への 307 リダイレクトに応答しますが、すべてのクライアントが POST でリダイレクトに従うわけではありません。

トークンもヘッダーも不要です。最初の呼び出しで標準の MCP OAuth フローが開始されます:

  1. クライアントが Django サイトの認可ページでブラウザを開きます。

  2. 管理セッションの Cookie があなたを識別します。ログアウトしている場合は、通常の管理ログインが最初に表示されます。

  3. 同意ページにクライアント名と承認の意味が表示されます。あなたが承認します。

  4. クライアントはトークンを受け取り、接続します。トークンはクライアント自身が更新します。

ストリーミング HTTP と OAuth を話す MCP クライアントはどれも同じように動作します。たとえば Cursor や FastMCP の Client などです。

アクセスに関するルール:

  • スタッフユーザーは、自分自身に限り、クライアントを認可できます。

  • グラントはあなた自身の管理権限で動作し、それ以上にはなりません。別の権限システムはありません。管理サイトでモデルを変更できる人は、サーバーがそのモデルを WRITABLE_MODELS にリストしている場合、MCP 経由でも変更できます。

  • リフレッシュトークンは REFRESH_TOKEN_TTL_DAYS(デフォルト 90)後に期限切れになるため、再同意がその頻度で発生します。失効はグラントの変更リストでの管理アクションです。

ツール

admin 名前空間の下にマウントされた11の汎用ツール。ワイヤー名は admin_list_models などです。各ツールは model を "app_label.ModelName" として受け取ります。ツールリストは静的です。ユーザーごとに異なるのは、各ツールがそのユーザーに何を見せ、何をさせるかです。

読み取り

ツール

引数

戻り値

list_models

なし

この呼び出し元が表示できるすべての公開モデルと権限フラグ。

describe_model

model

フィールド、リスト表示、フィルター、検索フィールド、読み取り専用フィールド、利用可能なアクション。

search_objects

model、q、filters、order_by、page、page_size

行と total。q は管理サイト自身の検索を使用します。不明なフィルターはエラーです。

get_object

model、pk

1つのシリアライズされたインスタンス。

object_history

model、pk

そのオブジェクトの管理ログエントリ(新しい順)。

recent_actions

limit

管理ログエントリ。呼び出し元がスーパーユーザーでない限り、呼び出し元にスコープされます。

書き込み

書き込みツールは、モデルが WRITABLE_MODELS にリストされている必要があります。残りはモデルごと、オブジェクトごとに、あなた自身の管理権限が決定します。リストにないモデルは、誰が呼んでもすべての書き込みとすべてのアクションを拒否します。イベントログなどの機密モデルはリストから外しておけば、MCP クライアントがそれらに書き込むことは決してできません。

ツール

引数

動作

create_object

model、data

管理フォームで検証し、保存してログを記録します。

update_object

model、pk、data

部分更新。読み取り専用フィールドは無視されます。

delete_object

model、pk、confirm

confirm がない場合、正確な削除カスケードを返し、何も変更しません。

run_action

model、action、pks、confirm

管理アクションを実行します。confirm がない場合、プレビューを返します。

autocomplete

model、field、q

関連モデルを検索して、外部キー値を主キーに解決します。

返されるすべての行には pk が文字列として、admin_url が含まれるため、エージェントは人に実際の管理サイトへのリンクを渡すことができます。

設定

すべてのキーは ADMIN_FASTMCP ディクショナリにあります。不明なキーは起動時にエラーになります。

キー

デフォルト

意味

SERVER_NAME

"django-admin"

MCP サーバーが宣伝する名前。

ADMIN_SITE

"django.contrib.admin.site"

AdminSite へのドットパス。

MODELS

()

"app_label.ModelName" の許可リスト。空でない場合、それ以外は公開されません。

EXCLUDE_MODELS

()

拒否リスト。"app_label.*" をサポートします。

WRITABLE_MODELS

()

書き込みを受け付けるモデル。空は書き込みなしを意味します。誰が呼んでもです。

DISABLED_TOOLS

()

カタログから完全に削除されるツール名。

REDACT_FIELDS

("password", "token", "secret", "api_key", "private_key")

フィールド名の部分一致。値は "[redacted]" と読み取られます。

MAX_PAGE_SIZE

200

search_objects のページサイズの上限。

MAX_PKS

1000

run_action ごとの pks の上限。

ACCESS_TOKEN_TTL_MINUTES

60

アクセストークンの有効期間。クライアントはリフレッシュトークンで更新します。

REFRESH_TOKEN_TTL_DAYS

90

リフレッシュトークンの有効期間。再同意はこの頻度で発生します。

SITE_URL

"http://127.0.0.1:8000"

Django サイトの公開 URL。OAuth の発行者であり、MCP サーバーはそれを認可サーバーとして指定します。

MCP_URL

"http://127.0.0.1:8765/admin/mcp"

MCP エンドポイントの公開 URL。

実際のデプロイでは SITE_URL と MCP_URL を設定してください。MCP_URL は、エンドポイントが提供されるパス、ディスカバリが宣伝する resource、すべてのトークンがバインドされるオーディエンスという、一致しなければならない3つのものの単一のソースです。そのパスはデフォルトで /admin/mcp です。起動時チェックは、パスのない MCP_URL を拒否します。その場合、オリジン全体が保護されたリソースとして宣伝されるためです。

ModelAdmin ごとのノブ

ModelAdmin クラスに設定します。mixin は不要です:

class InvoiceAdmin(admin.ModelAdmin):
    mcp_expose = False                     # hide this model from MCP entirely
    mcp_fields = ("number", "total")       # allowlist of serialized fields
    mcp_exclude_fields = ("internal_note",)  # denylist of serialized fields

REDACT_FIELDS は mcp_fields より優先されます。パスワードフィールドを明示的にリストしても、それは明かされません。

安全性

スーパーユーザー向けの管理 MCP サーバーは、言語モデルによって駆動される、本番データベース上のリモートシェルです。レール:

  • WRITABLE_MODELS はデフォルトで空なので、デプロイが名前を指定するまで、どのモデルも書き込みを受け付けません。それ以外はすべて、通常の Django 権限であり、呼び出しのたびに ModelAdmin を通じて問い合わせられます。

  • アクセストークンは短命です。ソルト付きハッシュのみが保存されるため、漏洩したデータベース行を再生することはできません。

  • delete_object と run_action はデフォルトでプレビューし、confirm=True まで何も変更しません。

  • すべての変更は、グラントのユーザーに帰属する LogEntry を記録し、変更メッセージにクライアント名が含まれます。例: "Changed status. Via MCP (client: Claude Code)."。LogEntry を記録できない書き込みはロールバックされます。

  • パッケージ自身のモデル、sessions.Session、authtoken.Token は、設定が何と言おうと公開されません。

  • auth.Permission と auth.Group を WRITABLE_MODELS から外してください。権限を付与できるエージェントは、権限モデルから脱出できます。

デプロイ

別プロセス。 Django プロジェクトの隣で MCP サーバーを実行します:

python manage.py admin_mcp_serve

MCP_URL のパス(デフォルトでは /admin/mcp)を、MCP_URL のポート(その URL がポートを指定しない場合は 8765)で提供します。どちらも --host と --port で上書きできます。既存の提供設定は何も変わりません。/admin/mcp をイングレス経由でそのポートにルーティングし、Authorization ヘッダーが通過することを確認してください。

マウント (M3)。 プロジェクトの asgi.py 内の /admin/mcp にサーバーをマウントします。1つの制約: 正確なパスでディスパッチします。OAuth エンドポイントは同じプレフィックスの直下(/admin/mcp/authorize など)にあり、Django はそれらを提供し続ける必要があるため、/admin/mcp の下のすべてを FastMCP に送るディスパッチャーはそれらを飲み込んでしまいます。レシピはマイルストーン M3 に同梱されています。

サーバーはステートレスなので、ロードバランサーの背後にあるどのインスタンスも任意のリクエストを提供できます。

開発

make install     # bootstrap uv, pin Python, install dependencies
make test        # run the permission matrix
make check       # format, lint, typecheck, and test
make migrate     # migrate the test project
make serve       # run the MCP server against the test project on :8765/admin/mcp
make help        # everything else

ライセンス

MIT

Related MCP Connectors

Related MCP Servers