Skip to main content
Glama
joshuadavidthomas

Django Shell MCP Server

server.py9.55 kB
from __future__ import annotations import logging from typing import Annotated from typing import Literal from django.apps import apps from django.conf import settings from fastmcp import Context from fastmcp import FastMCP from mcp.types import ToolAnnotations from .resources import AppResource from .resources import ModelResource from .resources import ProjectResource from .resources import SettingResource from .resources import filter_models from .routing import RouteSchema from .routing import ViewMethod from .routing import filter_routes from .routing import get_all_routes logger = logging.getLogger(__name__) mcp = FastMCP( name="Project", instructions="Inspect Django project structure, configuration, and URL routing. Access project metadata, installed apps, models, settings, and route definitions for understanding your Django application's architecture.", ) PROJECT_TOOLSET = "project" @mcp.tool( name="get_project_info", annotations=ToolAnnotations( title="Django Project Information", readOnlyHint=True, idempotentHint=True, ), tags={PROJECT_TOOLSET}, ) def get_project_info() -> ProjectResource: """Get comprehensive project information including Python environment and Django configuration. Use this to understand the project's runtime environment, installed apps, and database configuration. """ return ProjectResource.from_env() @mcp.resource( "django://app/{app_label}", name="Django App Details", annotations={"readOnlyHint": True, "idempotentHint": True}, tags={PROJECT_TOOLSET}, ) def get_app( app_label: Annotated[ str, "Django app label (e.g., 'auth', 'contenttypes', 'myapp')" ], ) -> AppResource: """Get details for a specific Django app.""" return AppResource.from_app(apps.get_app_config(app_label)) @mcp.resource( "django://app/{app_label}/models", name="Django App Models", annotations={"readOnlyHint": True, "idempotentHint": True}, tags={PROJECT_TOOLSET}, ) def get_app_models( app_label: Annotated[ str, "Django app label (e.g., 'auth', 'contenttypes', 'myapp')" ], ) -> list[ModelResource]: """Get all models for a specific Django app.""" app_config = apps.get_app_config(app_label) return [ ModelResource.from_model(model) for model in app_config.get_models() if not model._meta.auto_created ] def list_apps() -> list[AppResource]: """Get a list of all installed Django applications with their models. Use this to explore the project structure and available models without executing code. """ return [AppResource.from_app(app) for app in apps.get_app_configs()] mcp.resource( "django://apps", name="Installed Django Apps", annotations={"readOnlyHint": True, "idempotentHint": True}, tags={PROJECT_TOOLSET}, )(list_apps) mcp.tool( name="list_apps", annotations=ToolAnnotations( title="List Django Apps", readOnlyHint=True, idempotentHint=True, ), tags={PROJECT_TOOLSET}, )(list_apps) @mcp.resource( "django://model/{app_label}/{model_name}", name="Model Details", annotations={"readOnlyHint": True, "idempotentHint": True}, tags={PROJECT_TOOLSET}, ) def get_model( app_label: Annotated[ str, "Django app label (e.g., 'auth', 'contenttypes', 'myapp')" ], model_name: Annotated[str, "Model name (e.g., 'User', 'Group', 'Permission')"], ) -> ModelResource: """Get details for a specific Django model.""" model = apps.get_model(app_label, model_name) return ModelResource.from_model(model) def list_models( ctx: Context, include: Annotated[ list[str] | None, "Specific app labels to include (e.g., ['auth', 'myapp']). When provided, only models from these exact apps are returned (overrides scope).", ] = None, scope: Annotated[ Literal["project", "all"], "Filter scope when include is not specified: 'project' (default) for your project's models only, 'all' for everything including Django and third-party packages.", ] = "project", ) -> list[ModelResource]: """Get detailed information about Django models with optional filtering. By default returns only models from your project directory. Use include for specific apps or scope='all' for everything including Django and third-party packages. When include is provided, it overrides the scope parameter. Use this for quick model introspection without shell access. """ logger.info( "list_models called - request_id: %s, client_id: %s, include: %s, scope: %s", ctx.request_id, ctx.client_id or "unknown", include, scope, ) all_models = list(apps.get_models()) total_count = len(all_models) filtered_models = filter_models(all_models, include=include, scope=scope) result = [ModelResource.from_model(model) for model in filtered_models] logger.debug( "list_models completed - request_id: %s, total_models: %d, returned_models: %d", ctx.request_id, total_count, len(result), ) return result # TODO: Uncomment once upstream bug is fixed # Resource templates with only optional query parameters don't work when called without # query params. # Ref: https://github.com/jlowin/fastmcp/pull/2323 # mcp.resource( # "django://models{?include,scope}", # name="Django Models", # annotations={"readOnlyHint": True, "idempotentHint": True}, # tags={PROJECT_TOOLSET}, # )(list_models) mcp.tool( name="list_models", annotations=ToolAnnotations( title="List Django Models", readOnlyHint=True, idempotentHint=True, ), tags={PROJECT_TOOLSET}, )(list_models) @mcp.resource( "django://models", name="Django Models", annotations={"readOnlyHint": True, "idempotentHint": True}, tags={PROJECT_TOOLSET}, ) def list_models_resource() -> list[ModelResource]: """Resource endpoint without query parameters - uses default filtering (project scope).""" # TODO: Replace with query-param template once upstream bug is fixed. # This static resource provides access with default settings until the bug is fixed. # Use the list_models() tool for filtering options. # Ref: https://github.com/jlowin/fastmcp/pull/2323 all_models = list(apps.get_models()) filtered_models = filter_models(all_models, include=None, scope="project") return [ModelResource.from_model(model) for model in filtered_models] @mcp.tool( name="list_routes", annotations=ToolAnnotations( title="List Django Routes", readOnlyHint=True, idempotentHint=True ), tags={PROJECT_TOOLSET}, ) async def list_routes( ctx: Context, method: Annotated[ ViewMethod | None, "Filter routes by HTTP method (e.g., 'GET', 'POST'). Uses contains matching - returns routes that support this method.", ] = None, name: Annotated[ str | None, "Filter routes by name. Uses contains matching - returns routes whose name contains this string.", ] = None, pattern: Annotated[ str | None, "Filter routes by URL pattern. Uses contains matching - returns routes whose pattern contains this string.", ] = None, ) -> list[RouteSchema]: """List all Django URL routes with optional filtering. Returns comprehensive route information including URL patterns, view details, HTTP methods, namespaces, and URL parameters. All filters use contains matching and are AND'd together. """ logger.info( "list_routes called - request_id: %s, client_id: %s, method: %s, name: %s, pattern: %s", ctx.request_id, ctx.client_id or "unknown", method, name, pattern, ) routes = get_all_routes() total_count = len(routes) if any([method, name, pattern]): routes = filter_routes(routes, method=method, name=name, pattern=pattern) logger.debug( "list_routes completed - request_id: %s, total_routes: %d, returned_routes: %d", ctx.request_id, total_count, len(routes), ) return routes @mcp.resource( "django://route/{pattern*}", name="Route by Pattern", annotations={"readOnlyHint": True, "idempotentHint": True}, tags={PROJECT_TOOLSET}, ) async def get_route_by_pattern( pattern: Annotated[ str, "URL pattern to search for (e.g., 'admin', 'api', 'users')" ], ) -> list[RouteSchema]: """Get routes matching a specific URL pattern.""" all_routes = get_all_routes() return filter_routes(all_routes, pattern=pattern) def get_setting( key: Annotated[ str, "Django setting key (e.g., 'DEBUG', 'DATABASES', 'INSTALLED_APPS')" ], ) -> SettingResource: """Get a Django setting by key. Returns the setting value along with type information. Raises AttributeError if the setting does not exist. """ value = getattr(settings, key) # Will raise AttributeError if missing return SettingResource(key=key, value=value, value_type=type(value).__name__) mcp.resource( "django://setting/{key}", name="Django Setting", annotations={"readOnlyHint": True, "idempotentHint": True}, tags={PROJECT_TOOLSET}, )(get_setting) mcp.tool( name="get_setting", annotations=ToolAnnotations( title="Get Django Setting", readOnlyHint=True, idempotentHint=True, ), tags={PROJECT_TOOLSET}, )(get_setting)

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/joshuadavidthomas/mcp-django-shell'

If you have feedback or need assistance with the MCP directory API, please join our Discord server