Skip to Content
FeaturesModulesMCP (AI Agents)MCP Tools

MCP Tools

Tool registry

tools/base.py defines MCPTool (abstract base class) and MCPToolRegistry. A single global tool_registry instance is shared across the whole Django process.

Tools are registered in handlers/tools.py at import time. That module is imported by auto_loader.py, which is called from AppConfig.ready(). By the time Django finishes startup, all tools are registered and available to both the MCP protocol server and the admin chat agent.

Built-in tools

Introspection (tools/introspection.py)

Controlled by DjangoMCPModuleConfig.introspection. When introspection.enabled = False, these tools exist in the registry but return permission-denied responses.

ToolDescription
list_appsLists all installed Django apps and their models.
get_model_schemaReturns field definitions for a specific model.
list_urlsReturns URL patterns (requires introspection.expose_urls = True).

Model queries (tools/model_tools.py)

Both tools check config.is_model_exposed(app_label, model_name) before executing. Fields listed in ModelMCPConfig.hidden_fields are stripped from results. max_results caps the queryset size.

ToolDescription
query_modelFilter and list records from any exposed model.
get_objectRetrieve a single record by primary key.

Management commands (tools/command_tool.py)

ToolDescription
execute_commandRun a Django management command.

Only commands in CommandMCPConfig.allowed_commands are permitted. Execution is capped at timeout_seconds. Output is captured and returned as a string.

User info (tools/user_info.py)

ToolDescription
get_user_infoReturns information about the current agent session.

Advanced analytics (agent/orm_tools.py)

These tools bypass the per-model exposure check — they are always available when MCP is enabled. They are intended for analytical queries.

ToolDescription
aggregate_modelCOUNT, SUM, AVG, MIN, MAX on any model field.
time_seriesGroup records by time period (day, week, month).
top_valuesTop N values for a field with occurrence counts.
distributionValue distribution / histogram for a numeric field.

How tools work

Every tool is a class inheriting MCPTool:

class MCPTool(ABC): name: str = "" # unique identifier used in tool calls description: str = "" # shown to the LLM — affects when the tool is chosen input_schema: Dict # JSON Schema for the arguments object def execute(self, context: MCPContext, arguments: Dict) -> str: ... # must return a string
  • name is what the LLM uses in a tool call. Must be unique across the registry.
  • description is the most important field — it determines when the LLM decides to use the tool. Write it precisely.
  • input_schema is sent verbatim to the LLM as the function parameter schema in OpenAI tool format.
  • execute() must always return a str. For structured data, return a JSON-encoded string. For errors, return a plain error message string — do not raise exceptions (the runner catches them and returns the message).

Adding a custom tool

Step 1: Create the tool class

# my_extension/mcp_tools.py from django_cfg.modules.django_mcp.tools.base import MCPTool from django_cfg.modules.django_mcp.services.context import MCPContext class MyCustomTool(MCPTool): name = "my_custom_tool" description = "Does something specific. Use when the user asks about X." input_schema = { "type": "object", "properties": { "param": { "type": "string", "description": "The input parameter", }, }, "required": ["param"], } def execute(self, context: MCPContext, arguments: dict) -> str: param = arguments.get("param", "") # Access config through context if not context.config.enabled: return "MCP is not enabled" # Do your work return f"Result: {param}" my_custom_tool = MyCustomTool()

Step 2: Register the tool

From a Django extension’s AppConfig.ready():

# my_extension/apps.py from django.apps import AppConfig class MyExtensionConfig(AppConfig): name = "my_extension" def ready(self): from django_cfg.modules.django_mcp.tools.base import tool_registry from .mcp_tools import my_custom_tool tool_registry.register(my_custom_tool)

From the core module (when modifying django_mcp itself), add to handlers/tools.py:

from django_cfg.modules.django_mcp.tools.my_module import my_custom_tool tool_registry.register(my_custom_tool)

Context differences between subsystems

Tools are called with slightly different context depending on which subsystem calls them:

# MCP protocol server — full MCPContext with request info result = tool.execute(context, arguments) # ^^^^^^^ MCPContext: has access_key, tenant, user info # Admin chat agent — config only result = tool.execute(context.config, arguments) # ^^^^^^^^^^^^^^ DjangoMCPModuleConfig only

This is a known inconsistency. Tools that need request-level information (tenant, IP address, user identity) cannot be used safely from the admin chat agent until it passes a full MCPContext. Built-in tools work correctly because they only use context.config or ignore context entirely.

PII redaction

MCPTool exposes execute_with_redaction() which applies the configured redactor after execution:

mode = RedactionMode(context.config.redaction.mode.lower()) return redactor.redact_string(result, mode)

Redaction is opt-in per tool — ToolsHandler calls execute() directly, not execute_with_redaction(). This is intentional: not all tools produce output that needs redaction, and the check adds overhead.

Built-in patterns cover: email addresses, phone numbers, credit card numbers, SSNs. Add custom patterns via redaction.custom_patterns in the configuration.

Last updated on