django-mcp
v2.0.1
Published
Model Context Protocol server that serves Django documentation to AI coding agents
Maintainers
Readme
django-mcp
Ask an AI assistant what a Django setting does, or which arguments a queryset method takes, and you will often get an answer that is plausible, confident, and subtly wrong — a keyword argument removed two releases ago, or a setting that never existed. The model is recalling training data, and it has no way to check.
django-mcp is a Model Context Protocol
server that gives the assistant a way to check. It reads Django's own published
object inventory — around 8,100 documented settings, template tags, field
lookups, classes and methods — so it can resolve a name like INSTALLED_APPS
straight to its documentation and return only that section. It runs locally,
needs no API key, and writes nothing to disk.
Contents
- How it works
- Requirements
- Installation
- Connect it to your editor
- Tools
- Configuration
- Usage examples
- Troubleshooting
- Upgrading from 1.x
- Contributing
- License
How it works
Sphinx — the toolchain behind the Django docs — publishes two things this server exploits: a compressed inventory of every documented object, and the original reStructuredText source of every page.
flowchart LR
A[AI assistant] -- "tool call<br/>(stdio JSON-RPC)" --> B[django-mcp]
B --> C{In cache?}
C -- yes --> J[Return]
C -- no --> D["objects.inv<br/>~8,100 symbols"]
D --> E["Resolve name →<br/>page + anchor"]
E --> F["Fetch reST source<br/>from _sources/"]
F --> G[Convert to markdown:<br/>headings, code, roles]
G --> H[Extract just<br/>that section]
H --> I[(In-memory<br/>LRU cache)]
I --> J
J --> AThe inventory is one ~110 KB download, cached for a day. It is what makes the
difference between "here is the settings reference" and "here is
INSTALLED_APPS":
| Question | Naive approach | With django-mcp |
| --- | --- | --- |
| What does INSTALLED_APPS do? | Read ref/settings — ~27,000 tokens | symbol: "INSTALLED_APPS" — ~390 tokens |
| How does select_related() work? | Read ref/models/querysets — ~37,000 tokens | symbol: "select_related" — ~1,000 tokens |
| Where is icontains documented? | Guess, or grep | search_django_docs — ~200 tokens |
Requirements
- Node.js 20 or later — check with
node --version - An MCP-capable client (Claude Code, Claude Desktop, Cursor, Cline, Windsurf, Antigravity, or anything else that speaks MCP)
- Outbound HTTPS access to
docs.djangoproject.com
No API key, account, or database is required. Python and Django do not need to be installed — the server reads published documentation, not your project.
Installation
Option A — npx (recommended)
Nothing to install. Point your MCP client at npx and it fetches the package on
first run. Jump straight to Connect it to your editor.
Option B — global install
npm install -g django-mcp
django-mcp --versionOption C — from source
git clone https://github.com/ajaymahato431/django-mcp.git
cd django-mcp
npm install
node index.js --versiongit clone https://github.com/ajaymahato431/django-mcp.git
cd django-mcp
npm install
node index.js --versionThe server speaks JSON-RPC over stdio. Running node index.js by hand will look
like it has hung — it is waiting for a client. Use --help to inspect it.
Connect it to your editor
Claude Code
claude mcp add django-docs -- npx -y django-mcpClaude Desktop
Edit claude_desktop_config.json:
- macOS —
~/Library/Application Support/Claude/claude_desktop_config.json - Windows —
%APPDATA%\Claude\claude_desktop_config.json - Linux —
~/.config/Claude/claude_desktop_config.json
{
"mcpServers": {
"django-docs": {
"command": "npx",
"args": ["-y", "django-mcp"]
}
}
}Cursor
Edit .cursor/mcp.json in your project, or ~/.cursor/mcp.json globally, using
the same mcpServers block.
Cline / Roo (VS Code)
Edit cline_mcp_settings.json via MCP Servers → Configure, using the same block.
Antigravity
Edit ~/.gemini/config/mcp_config.json (on Windows,
%USERPROFILE%\.gemini\config\mcp_config.json), using the same block.
Running from a local clone
{
"mcpServers": {
"django-docs": {
"command": "node",
"args": ["/path/to/django-mcp/index.js"]
}
}
}Use the full path to your clone. On Windows either escape the backslashes
("C:\\path\\to\\django-mcp\\index.js") or use forward slashes.
Matching your project's Django version
This matters more here than in most servers — settings and queryset methods change between releases:
{
"mcpServers": {
"django-docs": {
"command": "npx",
"args": ["-y", "django-mcp"],
"env": { "DJANGO_DOCS_VERSION": "5.2" }
}
}
}Restart your client after editing its configuration.
Tools
Four tools are exposed. All are read-only.
| Tool | Purpose | Typical cost |
| --- | --- | --- |
| search_django_docs | Find any documented symbol or page | ~200 tokens |
| read_django_docs | Read a page, a section, or a single symbol | 300–1,500 tokens with symbol |
| list_django_docs | Browse the documentation index | ~80 tokens |
| django_best_practices | Curated guidance and anti-patterns | ~150–900 tokens |
search_django_docs
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| query | string | required | What to look for, e.g. ForeignKey, select_related. |
| role | enum | — | Narrow to one kind of object (below). |
| maxResults | integer | 10 | Number of results (SEARCH_MAX_RESULTS). |
role accepts page, setting, templatetag, templatefilter, fieldlookup,
class, method, attribute, module, command, and label.
Dotted names are matched on their last segment, so searching ForeignKey finds
django.db.models.ForeignKey without you having to know the full path.
read_django_docs
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| symbol | string | — | A documented name. Resolves to its page and returns just that section. |
| path | string | — | Page path, e.g. topics/db/models. Required unless symbol is given. |
| section | string | — | Return only this heading's content. |
| outline | boolean | false | Return only the page's headings. |
Prefer symbol. It is the cheapest and most precise way to answer a specific
question, and it reports the resolved role and a deep link to the anchor.
section matching prefers an exact heading, then a prefix, then a substring, and
compares against both the formatted heading and its bare form — so DEBUG
returns the `DEBUG` setting rather than the Debugging section above it.
list_django_docs
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| section | string | — | Section to list. Omit for the summary. "all" lists every page. |
| limit | integer | all | Maximum pages to return. |
| offset | integer | 0 | Pages to skip, for paging. |
# Django 6.0 documentation
665 pages across 9 sections.
releases — 389 pages
ref — 119 pages
topics — 67 pages
howto — 34 pages
internals — 22 pages
intro — 13 pages
(top-level) — 10 pages
faq — 8 pages
misc — 3 pagesMost of the page count is release notes. Reach for
search_django_docsrather than listing thereleasessection.
django_best_practices
| Argument | Type | Default | Description |
| --- | --- | --- | --- |
| topic | enum | — | One of architecture, models, views, templates, forms, security, performance, testing. Omit for all. |
Answers instantly, with no network access.
Configuration
Precedence is CLI flag → environment variable → built-in default.
| Flag | Environment variable | Default | Description |
| --- | --- | --- | --- |
| --docs-version | DJANGO_DOCS_VERSION | 6.0 | Docs version: 6.0, 5.2, 4.2, stable, dev |
| --timeout | REQUEST_TIMEOUT_MS | 15000 | Per-request timeout (ms) |
| --retries | REQUEST_RETRIES | 2 | Retries for transient failures |
| --cache-max | CACHE_MAX_ENTRIES | 100 | Maximum cached documents |
| --doc-ttl | DOC_TTL_MS | 10800000 | Page cache lifetime (3 hours) |
| --index-ttl | INDEX_TTL_MS | 86400000 | Inventory cache lifetime (24 hours) |
| --negative-ttl | NEGATIVE_TTL_MS | 60000 | How long a failed fetch is remembered |
| --max-results | SEARCH_MAX_RESULTS | 10 | Default search result count |
| --env-file | — | — | Load a specific .env file |
| --help | — | — | Show help and exit |
| --version | — | — | Show the version and exit |
Using a .env file
cp .env.example .envEdit it and restart. A missing .env is not an error — every value has a
default. Variables already set in the environment (including your MCP client's
env block) always win over the file.
.env is git-ignored. Only .env.example, which contains no secrets, is
committed. This server needs no credentials at all.
Why there is no Dockerfile
This server is launched as a subprocess by your editor and talks over stdio; it
is not a long-running service. A container would add a process boundary and
startup cost without providing isolation your editor does not already have.
npx is the intended distribution.
Usage examples
Checking a setting before using it
"Look up
CSRF_TRUSTED_ORIGINSin the Django docs and tell me the correct format for the values."
Getting a method's real signature
"What arguments does
select_related()take? Check the docs."
Fixing an N+1 problem
"Read the Django best practices for models, then review this view for N+1 queries."
Finding where something is documented
"Where is the
icontainslookup documented?"
Matching an older project
Set DJANGO_DOCS_VERSION=4.2 and the same questions return 4.2 answers.
Calling a tool directly (from a client that supports it)
{ "name": "read_django_docs", "arguments": { "symbol": "INSTALLED_APPS" } }Troubleshooting
Restart the client after editing its configuration — most read it only at
startup. Then check node --version is 20 or later, and validate your JSON.
Verify the server runs on its own:
npx -y django-mcp --versionSome clients cannot resolve npx from a bare name. Use the full path:
(Get-Command npx).SourcePut that in command, or install globally with npm install -g django-mcp and
use django-mcp as the command.
DJANGO_DOCS_VERSION probably names a version that is not published. Valid
values are those that appear at docs.djangoproject.com/en/<version>/ — for
example 6.0, 5.2, 4.2, stable or dev.
"env": { "REQUEST_TIMEOUT_MS": "45000", "REQUEST_RETRIES": "4" }Behind a corporate proxy, set HTTPS_PROXY in the same env block. Node
respects it natively from version 20.
Only documented objects are in the inventory — private helpers and undocumented
internals are not. Use search_django_docs first; it suggests close matches.
Note also that the symbol must exist in the configured version.
Call list_django_docs and check the heading names the version you expect. If
not, set DJANGO_DOCS_VERSION to match your project.
Use symbol rather than path wherever you can. Failing that, use section, or
outline: true first to see what the headings are. Avoid section: "all" on
list_django_docs.
Upgrading from 1.x
list_django_docsno longer takes apath. It served onetoctreelevel at a time, so finding a page meant walking the tree. It now returns the whole page list from the inventory, and with no arguments gives a section summary. Passsection: "all"for every page.search_django_docsis new — there was no search before.read_django_docsacceptssymbol, which is usually what you want.- Node.js 20 or later is required.
If your MCP configuration uses an absolute path to index.js, consider switching
to npx -y django-mcp.
Full details are in the changelog.
Related servers
Built on the same core, for the rest of the stack:
- filament-mcp — Filament documentation
- livewire-mcp — Livewire documentation
Contributing
Contributions are welcome — see CONTRIBUTING.md. Please also read the Code of Conduct, and report security issues via SECURITY.md rather than a public issue.
npm test # offline unit tests
npm run test:integration # against the live documentationLicense
Released under the MIT License. © 2026 Ajay Mahato.
Django is a registered trademark of the Django Software Foundation. This project is not affiliated with or endorsed by the DSF; it only reads the public documentation.
