agent-module-documentation & the Agent Module Knowledge MCP

Introduction When requesting a one-time login link (request new password or password forgotten function), it often comes to the fact that the link arrives invalidated/invalid via e-mail. This can…
Categories: Access control
It fixes this one, which every Drupal site behind Outlook has seen:
“You have tried to use a one-time login link that has either been used or is no longer valid.”
Mail security scanners click the reset link first, so the user's own click is already too late.
With the worst title.
…and a description that could be better.
No question asked. No module searched for.
A brand-new, custom OAuth flow written from scratch, enabled on your site and maintained by nobody.
There is a covered contrib module for this, and the agent never looked.
drupal.org's module search is an HTML app behind a bot challenge. The structured data is on a JSON:API endpoint that Project Browser knows about. The agent does not:
GET /jsonapi/node/project_module ?sort=-field_active_installs_total &filter[...]=field_core_semver_minimum …
…for the thing that logs users in. Good or bad?
Means the security team issues advisories for stable releases. It is not a code review.
Finished and stable, or slowly dying?
It reads the module, its parent framework, its dependencies and the vendor library, then guesses the config schema.
Every task, every project, every developer. The same reading, over and over.
Extra cost per task vs. having the docs (long-tail evals, Opus 4.8)
+47.6k tokens · +24s · +$0.10
If 10,000 Drupal developers did that once a day:
0 tokens/day
0 agent-hours/day
0 /year
…to re-learn what one documentation run already wrote down.
state check → login CSRFA non-developer can't spot any of these. And nobody gets a security advisory for your custom module.
Reusing a covered module means thousands of sites and a security process behind your login button.
# CLAUDE.md (every project, every developer…) Before writing custom code, search drupal.org for an existing contrib module. Prefer modules with security advisory coverage. Check Drupal 11 compatibility and maintenance status. Read the module's docs before using it. Never implement OAuth yourself. …
It should already know.
Every rule we have to write is one that someone else forgot to write.
When agents pick the stack, “it just worked” wins, and Drupal gets left behind.
Semantic search over the knowledge base, plus a skill so agents can call it.
An MCP server shipped as a DDEV add-on.
One command, and your agent knows the Drupal ecosystem.
Tools: search_modules · get_module · list_docs · read_doc · list_categories
In popularity order: install on a real Drupal 11 site in DDEV → read code & config → write compact, agent-first docs.
Kept current: scanners watch drupal.org release feeds and re-document new stable minors.
~1,000 commits · 1,843 modules with eval suites · open on GitHub
Browse it: drupal-ai.github.io/agent-module-documentation ↗
usage.md: short summary, long summary, then 15–30 use cases written in the user's words. Plus categories, subcategories and keywords in data.json.
shy_one_time / data.json "categories": ["Security", "User management"], "subcategories": ["Login", "Email deliverability"], "keywords": ["password reset", "one-time login", "crawler", "bot", "email scanner", …]
shy_one_time / usage.md: use cases - Stop a mail scanner consuming a reset link. - Fix "link already used" complaints. - Protect one-time login links from bots. - Reduce password-reset support tickets. - Survive a corporate email gateway. - Prevent link-preview services burning tokens. - Keep reset links usable in enterprise email. - Handle a security appliance that follows links. - Support users behind a filtering proxy. …
These are the sentences people actually type, so problem #2's search now has something to match.
social_auth_facebook / 4.0.x / data.json { "core_version_requirement": "^9.5 || ^10 || ^11", "security_advisory_coverage": "covered", "maintenance_status": "Minimally maintained", "development_status": "Maintenance fixes only", "contributor_count": 17, "dependent_modules": [ … ], "composer_requirements": { … }, "provides_drush_commands": false, "tooling": { "provides_tool_plugins", "agent_skills" } }
The long summary is an honest assessment, not marketing:
“What deserves saying plainly is that this is a trade, not a fix: a link that survives being fetched by a third party is a link that third party could still use…”
shy_one_time / usage.md
The agent gets trade-offs and caveats, not just a feature list, before it recommends anything.
The agent reading the source to document it also looks for:
Security notes are git-ignored and never pushed to the public repo. They go through coordinated disclosure, not GitHub.
We automate as much as we can, in a way that makes their work easier. They do the hard part.
social_auth_facebook/4.0.x/ ├── data.json ├── usage.md └── agent/ ├── start.md ← cheap index ├── configure/ │ └── settings.md └── api/ └── network-and-manager.md
The rule: every agent doc must be shorter than reading the source. If it isn't, cut it.
# Social Auth Facebook: agent index Facebook (Meta) OAuth2 login for Drupal, built on Social Auth / Social API. Adds `/user/login/facebook`, a settings form, and a Facebook button. - Settings config (client_id, client_secret, graph_version…) → configure/settings.md - Network plugin + FacebookAuthManager auth flow, Rules events → api/network-and-manager.md Key facts: - Config object `social_auth_facebook.settings`. No config/install: the object does not exist until the settings form is saved (or you create it). - Form route → /admin/config/social-api/social-auth/facebook - OAuth callback /user/login/facebook/callback (register it in the Meta app) - `graph_version` is stored without the leading `v`; must match e.g. `17.0`.
The highlighted gotchas are what an agent would otherwise have to dig out of the source, one file at a time.
shy_one_time/2.0.x/ ├── agent/start.md 28 lines ├── usage.md 27 lines └── data.json 54 lines
109 lines. Dense, structured, honest, grounded in the real code.
A narrated walkthrough
Everything in one place
Learn it by playing it
Three hosts, one terrible module name
Your docs, your format, your language, generated on demand.
Write the knowledge once. Render it for every audience.
Expected at presentation/shy.mp4.
Open shy-one-time-guide.pdf ↗ · 4 pages · problem → how it works → setup → trade-offs
Keyboard + mouse inside the game.
Use Next → to leave.
Three hosts. One module. A lot of opinions about the name.
Voices generated with ElevenLabs from a script written from the agent docs.
Jess: Welcome back to Module of the Week! Today's module is my absolute favourite. It's called... Shy One-Time. Charlie: Shy... One-Time? Is that a dating app? Gunnar: It's a Drupal module, Charlie. Although the name does a great job of hiding that. Jess: Okay, picture this. A user forgets their password. They request a reset link, open the email, click it, and Drupal says: "You have tried to use a one-time login link that has either been used or is no longer valid." Charlie: But they didn't use it! They literally just clicked it! Gunnar: Correct. Somebody else clicked it first. The corporate mail gateway. The link-preview bot. A search crawler. They all follow the URL before the human even sees the email. Charlie: So a robot... is resetting my password? Gunnar: No. The robot is spending your password reset. Drupal marks the link as used, and the actual human gets nothing. Charlie: That's the most enterprise thing I've ever heard. … 4:49 · full script in derived/podcast/script.md
Same task, three arms. easy = answer · medium = inspect the live site · hard = build it, verified against live site state.
Precision goes up on long-tail modules, most for smaller models. Time drops ~36% with the skill and ~72% with docs in context. Cost: $0.19 → $0.15 → $0.09.
Honest bit: the skill arm spends tokens learning to find the docs. Handing over the right doc directly is what cuts tokens about 3×, and that's the MCP's job.
39 modules with run results (19 outside the top 100) · mostly 1 run per cell · Claude only · arms: vanilla / SKILL.md prepended / distilled docs pre-loaded
Pathauto, Token and Webform are all over the internet, so the model learned them in training. The docs add little except a bit of speed (36s → 31s).
Smaller models still benefit, even here: Haiku on hard top-100 builds went from 33% → 78% (only 3 cases, so treat as a hint).
Top 100 modules: years of blog posts, Stack Exchange answers, docs and issues, all in the training data.
The long tail: a README, and maybe a project page.
So we publish 24,642 agent docs for every Drupal 11 module, openly, on GitHub.
Agents read the docs through the MCP.
Public, structured, grounded in real code. Exactly what the next training run can pick up.
Synthetic, yes, but generated from installing and reading the actual module. It is still better Drupal training data than nothing.
agent-module-documentation on GitHub
indexer.py builds docs.db + server image
docs.db.gz (~200 MB)
container image
MCP over HTTP:9130/mcp
Claude Code, Claude Desktop, …
No new infrastructure to approve or scale. Distribution runs on GitHub Releases and GHCR. Search runs on your laptop.
DDEV is already the default local stack. Install the add-on, restart, and register the URL with your agent.
Non-root container, read-only DB, doc bodies served from the DB with no filesystem access. Exposed only through the DDEV router.
SQLite FTS5. Matches the words you typed.
Great for graph_version, pathauto, exact names and config keys.
Misses “mail scanner eats my reset link”.
sqlite-vec KNN over embeddings. Matches meaning.
“reset link already used in Outlook” lands near “Stop a mail scanner consuming a reset link”.
Fuzzy on exact identifiers.
Reciprocal Rank Fusion merges both lists:
score = Σ 1 / (60 + rank)
Everything is in one SQLite file, docs.db: metadata, doc bodies, FTS5 index and vectors. It's built once in CI from the corpus.
minishlab/potion-retrieval-32M, 512 dimensions, tuned for retrieval# requirements.txt (the whole runtime)
mcp==1.29.0
sqlite-vec==0.1.9
model2vec==0.9.0
numpy>=1.26
Build == query: the indexer and the server share embed.py, and the server refuses to start if docs.db was built with a different model.
It runs on any machine that runs DDEV: laptops, CI, old hardware.
A shared global service or a hosted endpoint may be the better long-term shape.
Questions?
Dataset
github.com/Drupal-AI/agent-module-documentation
DDEV MCP add-on
github.com/Drupal-AI/ddev-drupal-agent-module-knowledge-mcp
Browse the catalog
drupal-ai.github.io/agent-module-documentation
Module Finder
module-finder.marcusmailbox.com