mcpbeat Sign in

Drupal Code Query MCP Server

answering

Drupal Code Query is answering right now. Last checked moments ago. It exposes 16 tools.

Drupal core API changes, contrib usage, upgrade readiness, patch verdicts, code search, SQL.

Uptime history 15 days of history · worst day 92%
15 days agonow
98.9%
Uptime 24h
91 of 92 checks
16
Tools
read from the server
202 ms
Response time
average over 24h
open, no key
Access
streamable-http

Drupal Code Query does not always answer

Over the last week it answered 98.7% of our checks. We check every 15 minutes, so you hear about the next outage within the hour — not from your users.

Three servers free · no card

Connect this server

Endpoint below is the one we actually reach during checks — not the one copied from a README. Last verified 0 min ago.

run in your terminal
claude mcp add drupal-code-query --transport http https://mcp.tresbien.tech/mcp
~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "drupal-code-query": {
      "url": "https://mcp.tresbien.tech/mcp"
    }
  }
}
~/.codex/config.toml
[mcp_servers.drupal-code-query]
url = "https://mcp.tresbien.tech/mcp"
.cursor/mcp.json
{
  "mcpServers": {
    "drupal-code-query": {
      "url": "https://mcp.tresbien.tech/mcp"
    }
  }
}
.vscode/mcp.json
{
  "mcpServers": {
    "drupal-code-query": {
      "url": "https://mcp.tresbien.tech/mcp"
    }
  }
}

Available tools 16

Read directly from the server with tools/list, grouped by what they act on. If a tool disappears, we record the date.

change
get_change_record
Return one core change record in full: title, flavour, target version, machine-checkable tracks, linked issues, linked core symbols, and contrib adoption counts. To find records use list_change_records or what_changed's change_record_nids; for one symbol's detail use lookup_core_symbol. - Flavour: coming-break (API going away), coming-new-API (API added), landed-but-still-relevant (shipped), policy-only (no code target, no adoption). - Adoption counts contrib development branches only. legacy = still calls the going-away side (not adopted); migrated = moved. - Returns counts across every affected project plus a ranked head, outstanding first. For one project use project_upgrade_report rather than raising top_projects. - symbols: the core API the record is about, role from (going away) or to (replacement); use the fqn with lookup_core_symbol. Empty means the catalog links no symbol, which is common.
list_change_records
List the core change records (the human write-ups) targeting a range of core versions, tagged by flavour and ranked, with counts. For the symbol-level diff use what_changed; for one record in full use get_change_record. - Bounds inclusive; forms 11.2, 11.2.x, 11.2.0, 11, 11.x; a bare major covers every minor of it. - Flavours: coming-break (an API going away), coming-new-API (one being added), landed-but-still-relevant (already shipped). policy-only records target no version and never appear here. - project narrows to one maintainer's view: each record then carries how many of that project's development branches are still on the legacy side. - Returns the count of every record in range plus a ranked head; each entry carries its nid for get_change_record.
project
project_profile
Answer "what is this contrib project, how big is it, which core versions does it support, what moves with it" for one drupal.org project. Pending change records: project_upgrade_report; core symbols it calls: list_symbol_users. - project: title, type, status, security, installs. installs_by_version: install base per contrib version. - dev_branches: each scanned development branch with installs, core_constraint (composer constraint of the newest release on that line, from drupal.org — not the unreleased tip), core_minors it admits (a minor counts when any patch satisfies), latest commit date, era. latest_core_minor = newest admitted. - releases (newest first, ≤ releases, with core_compatibility and security), release_count, supported_branches. missing_sections names what the bundle could not fill. - related (summary): requires / required_by counts + top 3, co_installed top 3 or a note, delta_12m per release line ("*" = total). - include=["related"]: related becomes the full report: requires (every dependency, via info_yml or composer, core modules left out), required_by paged by installs (count, next_offset; dependents / dependents_offset), co_installed (rho, rank, method residual_spearman) or a note. - include=["trend"]: trend = months (oldest first), series per release_line aligned with months (null = no count, "*" = total), lines with installs_now, share, delta_3m / delta_12m / delta_36m, peak, first_seen / last_seen. drupal.org month-end installs. - Unknown project: error. An included section refuses when the bundle predates its view.
project_upgrade_report
Answer "is this contrib project ready for a target core version, and what work is left". For one record's detail use get_change_record; for a symbol's users use list_symbol_users. - A record applies when it targets the target version or earlier and a development branch of the project matched one of its tracks. - Verdict per record: outstanding (a branch still calls the going-away side), in_progress (some branches moved), adopted (all moved). - Returns counts by verdict, then a ranked head of outstanding records with file and line evidence from the project's own code. - Each entry carries how many other projects are outstanding on the same record (wait for upstream or not) and its nid for get_change_record. - removed_symbols: core symbols removed by the target, or scheduled for removal at or before it, that the project's development branches still reference, with occurrences, files and the replacement when the catalog names one. These need no change record, so they hold the breakage a record-only view misses: a class moved to another module, a service dropped. removed_symbol_count is the whole set; the head is newest removal first, then by occurrences; test and test-support symbols are left out.
class
list_class_relations
List the contrib classes that extend, implement or use a core class, interface or trait, one row per edge with file and line, paged: "who subclasses FormBase". Call-site counts: list_symbol_users; the symbol's status: lookup_core_symbol. - target with or without the leading backslash; unknown → refused, pointing at lookup_core_symbol (a fragment lists candidates). rel_kind narrows to one of extends, implements, uses_trait; project to one machine name. - Development branches only. edge_count = every edge, project_count = projects, count = edges after the per_project cap (default 3 per project per page, so "top projects" reads off one page; project_edges on each row is the full count; per_project=50 with project= lists one project in full). - Rows by project installs, project, source_fqn: project, branch, installs, source_fqn, rel_kind, file, line, project_edges. limit ≤ 100; next_offset absent on the last page.
code
search_code
Search the source of every indexed contrib project, plus core, for a code pattern: which files, or with by_repo which projects. Who-uses-a-core-symbol counts: list_symbol_users or lookup_core_symbol. - query is a regex; set literal for exact text with ( [ ] . $ : or a space, and put r: f: lang: case: sym: b: terms in filters. repos takes machine names. The index runs RE2: no lookaround, no backreferences. - .module, .install, .theme, .engine, .profile, .inc count as PHP (lang:php reaches them; sym: resolves inside them); no language filter is applied for you. - Returns total_matches and total_files plus a head of files (repo, path, matching lines), limit ≤ 50. by_repo: (repo, file_count) rows from a pull of ≤ 1000 files, total_files the ceiling, truncated when cut. A parse error returns the index's own message.
dataset
query_dataset
Run read-only DuckDB SELECTs over the dataset behind the other tools, for a question none of them asks. Call describe_dataset first (it lists the 29 views, their columns, joins and recipes); prefer a typed tool when one fits. - statements=[…] runs up to 5 in one call, one result or error each — explore in one round trip. - Result: columns, rows as arrays, ≤ max_rows (≤ 500, default 100) and ≤ 16 KB; truncated → aggregate, filter, LIMIT/OFFSET. Single SELECT (or SHOW, DESCRIBE, FROM-first), no semicolon, 15 s limit, nothing outside the bundle. - Dev-branch isolation: JOIN contrib_branch AND filter kind = 'dev_branch' AND project <> 'drupal' before counting projects (change_record_adoption, symbol_usage, core_symbol_evidence carry release tags too; core_symbol_evidence is the full rollup, symbol_usage its string-scan subset). - Adoption polarity: legacy = still on the old API (NOT adopted); migrated = adopted. Versions are text: compare *_seq integers (major*1000+minor). Never SUM(usage) across branch rows. - Errors name the views, the columns of the views you used, or the join map; an empty result over a backslash-less fqn carries a hint. - The same views are downloadable as parquet under https://api.tresbien.tech/data/docs; its cookbook targets api.duckdb plus prelude views this mirror does not carry, so take recipes from describe_dataset.
describe
describe_dataset
Learn the dataset before writing SQL for query_dataset; call with no arguments first. Returns text. Prefer a typed tool when one answers the question. - No arguments: every view with one line, the join map, the four invariants (dev-branch isolation, no SUM(usage), *_seq compares, adoption polarity), the gotchas behind empty results (fqn forms, placeholders, machine names), the recipe index. - view=<name>, or views=[…] for several in one call: columns with types and descriptions, an example filter that returns rows, the joins that reach the view. - recipe=<id>: a ready-to-run statement for a common question (who uses a symbol, deprecated symbols per project, a change record's adoption, symbols deprecated between minors, one project's deprecated uses, change records between minors); substitute the placeholders and run it with query_dataset.
lookup
lookup_core_symbol
Answer "is this core symbol safe to use, and who still uses it" for one symbol; a name that resolves to nothing returns the catalog names containing it instead. Every user in pages: list_symbol_users. Changed between versions: what_changed. Code text: search_code. - fqn forms: namespaced with or without the leading backslash, a bare function or constant name, a prefixed pseudo-symbol (fn:check_markup, core:hook:preprocess_page, core:library:claro/drupal.shortcut), or a fragment (fromRoute, EntityInterface). - Resolved: symbol with status flags: removed (gone), scheduled_removal (@deprecated names the version, still present), deprecated, internal, placeholder (catalog row no scanner located); audience contrib | test_support | test | theme. usage: contrib development branches only, evidence rollup as of evidence_built_at; projects and branches counts plus top_projects (≤ 30) by install base. change_records: records that touched the symbol, with nid for get_change_record. - Not resolved (found false): count = catalog names containing the fragment (case-insensitive, public symbols; kind narrows), candidates a head of ≤ candidates with fqn, kind, subsystem, stamps, projects_using, change_record_nids; names starting with the fragment first, then by projects_using. Call again with one fqn.
patches
check_patches
Does each composer patch still apply to the release the site installed, and is its fix already in that release? Send composer_json + composer_lock (plus patch_files for local patches) after composer update and every extra.patches entry is checked at its installed version in one call; do not download releases or run patch --dry-run yourself. - Send a package's patches in the order composer.json declares them, and keep them together: they are judged in that order because a patch manager applies them in it, so a later patch is applied to what the earlier ones left. Reordering them changes the verdicts. - items: up to 50 of {project, version, patch, title}, when you would rather name them yourself. project is the drupal.org machine name (webform) or composer name (drupal/webform), drupal or core for Drupal core; version is the composer version (1.13.0, 2.0.0-beta4, 11.4.5); patch is the diff text or its URL on www.drupal.org/files/issues or a git.drupalcode.org merge request .patch/.diff. - Per item: tag (the release tag matched, or unknown_version), applies_at (the -p level at which git apply passes, null when none), fuzzy (applied only with reduced context and whitespace ignored, as GNU patch fuzz does: review it), hunks_failed (file, line, reason), reverse_applies (the patched lines are already in the tag), mr (merge-request state when the patch is an MR URL), merged_in_version, suggested. - suggested is the verdict: merged (fix is in the release: drop the patch), applies (applies cleanly: keep it), conflicts (neither: call reroll_patch with that item, it returns the re-rolled diff), unknown (tag or patch unresolved; see error). - next_step names the follow-up calls and the row count the report owes; counts is the verdict tally to check that table against. An item that could not be judged comes back suggested unknown with the reason in error: report it as unclear, never leave the row out. - files_shipped on a result names the patch's files the release already carries when only some of them do: the patch is partly upstream and what is left is a re-roll, not a drop. A failed hunk carries moved_to when the release keeps that file under another path. - next_step names the follow-up calls and the row count the report owes; counts is the verdict tally to check that table against. An item that could not be judged comes back suggested unknown with the reason in error: report it as unclear, never leave the row out. - hunks_shipped and files_shipped name the parts of a patch the release already carries when only some of it does: a partly-merged patch, so what is left is a re-roll rather than a drop. A failed hunk carries moved_to when the release keeps that file under another path. - next_step names the follow-up call when any item needs a re-roll. Each verdict is the result of running git apply against the release the site installed, so it replaces a hand dry-run rather than pointing at one. - core_references: what the code the patch adds references in core, checked at target_core (defaults to the lock's core): flagged lists removed or moved classes and calls whose argument count no longer fits the target signature (parent::__construct is checked against the constructor the core parent class actually declares, walking core's extends chain), each with file, line, change_record and replacement; deprecated is a short side list; checked counts references the catalog knows. Scope: added lines, direct references only (extends, implements, trait use, new, static calls, parent::__construct). A patch that does not apply carries a note instead. - Verdicts come from git apply --check against the tag's tree; an applies patch can still be wrong at runtime.
projects
list_projects
List drupal.org projects after filters, largest install base first, paged: "top modules without an 11.4 branch", "themes over 10k installs without security coverage". One project: project_profile; its pending records: project_upgrade_report. - Filters combine: type, min_installs, security (covered, not-covered, revoked), core_minor (+ missing), era (d8plus, pre_d8); all apply to count and rows. - core_minor reads the composer constraint of the NEWEST RELEASE on each development branch (drupal.org metadata, composer/semver; a minor counts when any patch of it satisfies); an unreleased tip change is not seen. missing=true keeps projects with no branch declaring it — the upgrade gap list. - Row: machine_name, title, type, installs, security, status, dev_branches (branch, installs, core_minors, era), latest_release. count = projects; next_offset absent on the last page.
reroll
reroll_patch
Re-roll one composer patch that check_patches reported conflicts: a 3-way merge of the patch onto the installed release, returned as a new diff. Call check_patches first for the whole list; call this per patch that came back conflicts. - Input: project, version, patch (text or URL), title. One patch per call. A hand-made patch without index lines is merged from the newest tag it applies to (reroll.base). - reroll.verified true means the service already ran the check a caller would run by hand; reroll.verified_by names it (the command, the -p level, the tag). Write reroll.patch to the patch file and move on: no git apply --check, no patch --dry-run, no pristine copy of the release to diff against. composer install is the test. - A conflicts result is not the end: send the same project, version and patch again with resolutions, one per region of reroll.conflicts[].hunks ({file, region, choice: release|patch}, {file, region, text}, or {file, region, delete: true}), and the service re-merges with your decisions and apply-checks the diff. What comes back is the finished patch file: write it and run composer install. Deciding regions this way replaces reading the release files, editing the patch by hand and dry-running it. - reroll.status: clean (every file merged and the diff apply-checked against the release, reroll.verified true: write reroll.patch as the new patch file, no re-test needed; an empty reroll.patch with reroll.note means the release already carries the change and suggested is merged), conflicts (reroll.patch holds the hunks that merged cleanly, apply-checked when reroll.verified is true; each file in reroll.conflicts carries hunks with the three sides of every region: release = what the release has, base = what the patch was written against, patch = what the patch wants, plus release_line and release_context, the release file's numbered lines around the region. Write the missing hunks from those and append them to reroll.patch; no download, file read or dry-run needed), unavailable (no index lines and no recent tag takes the patch; re-roll by hand from hunks_failed). - reroll.patch paths are relative to the repository root: contrib patches apply at -p1, core patches carry core/ and apply at -p2 from web/core. patch_truncated means the diff was cut to the result budget; POST /v1/patch/check on api.tresbien.tech with reroll: true returns it whole. - A patch that still applies (applies_at set) or is already in the release (merged) returns its verdict with reroll null. - core_references: as on check_patches, read from the re-rolled diff when the merge was clean: removed or moved core classes and calls whose argument count no longer fits the target signature, at target_core.
scan
scan_composer
Which of a site's drupal/* packages need a new release for a target core, and which have none? Send composer.lock (and composer.json) once instead of one query per package; the answer is the release scan for every package plus extra.patches as check_patches items. Constraints are read by composer's own semver library. - composer_lock: the file, or the slim form {"packages":[{"name","version"}]} for drupal/* only. composer_json adds constraints and extra.patches. target_core: 11.4 or 11.4.5; empty scans against the installed core (target_is_installed), i.e. what can be updated without a core upgrade. - A row whose installed version the release data does not carry says installed_unknown and offers no older release; installed_from_tag says the project's git tag answered instead. Neither is a finding about the site. - A sub-module has no releases of its own: drupal.org packages it as a metapackage built from its project's release. Its row says submodule_of and carries that project's answer. Send composer_lock with each entry's type, and require plus extra.drupal.datestamp for a metapackage, and the pairing is made for you; the slim form without them reports the sub-module as an unknown project. - rows, ranked problems first: no_release (no published release supports the target; latest_any and dev_branch say what exists), update (a compatible release the site does not have: latest, latest_core), unknown (not a drupal.org project in the bundle; the note says where its releases are), current (installed_supports and nothing newer needed; such a row carries package, installed and installed_supports only). counts covers every package even when rows are cut. - candidates on every row that is not current: the newest three releases with version, core constraint, date and supports_target. That is what choosing a version needs; project_profile adds installs and branches, not more releases. - patches: one item per extra.patches entry with the installed version filled in; pass them to check_patches after composer update (local paths need the file text). A patch declared on a package outside drupal/ gets no item and no row; outside_drupal names it. - patches:true answers with plan instead: every patch judged against the release its package would install for target_core, so the join needs no second call. plan.counts is the verdict tally, plan.package_counts the scan tallies, plan.no_release the packages that block the target, plan.patches one row per patch needing a decision (conflicts, unknown, merged), and plan.applying the number that still apply. The scan rows themselves are not in a plan; call again without patches for them. A patch whose package has no release for the target is judged against the branch when the lock installs a dev version, and unknown otherwise with the reason in note; plan.warnings says when a package is in no_release because of something the site controls, naming the requirement to change: its own constraint forbids the release that would work, or its minimum stability does; a row's project for reroll_patch is its package without the drupal/ prefix. - A plan never carries a re-rolled diff: plan.next_step names reroll_patch, one call per patch, when any patch no longer applies. - bundle_date is when the release data was published; a release after it is invisible here, so confirm a no_release with composer show or drupal.org before acting.
subsystem
subsystem_coupling
Answer "what else tends to fail when a merge request touches this core subsystem" from CI history over ~9,000 core MRs; with issue_nid, "which subsystems does this issue touch and what co-fails with them". No arguments: the directory of subsystem names (shared by core_symbol.subsystem and list_symbol_users subsystem=). - subsystem resolves a name (Views, case-insensitive) or a bucket (module:views, core_lib:Entity); unknown → refused with the nearest names. - Edge: touched_bucket → failing_bucket (+ subsystem), lift, co_fail_count, touched_count, failed_count, total_mrs; lift = P(fail | touched) − P(fail | any MR). Self edges left out. - Floors min_lift (0.05) and min_co_fail (3) drop noise; count = edges above them, edges ≤ limit (≤ 50) by lift. When nothing clears the floors the strongest edges are listed with a note. - issue_nid: touched_subsystems first; a note explains an issue with no MR file data.
symbol
list_symbol_users
List every contrib project using one core symbol or any symbol in a class of symbols, one row per project, paged — and the matched symbols themselves. lookup_core_symbol answers one symbol with a head of 30. - Pass fqn, or filters: kind, status (deprecated, scheduled_removal, removed), subsystem, removal_in (13.0), deprecated_in (11.4). "Top modules still calling a method going away in 13.0" = kind=method, status=scheduled_removal, removal_in=13.0. Filters take public symbols only. - Development branches only, evidence rollup as of evidence_built_at. count = projects; symbol_count = matched symbols, listed in symbols_matched (≤ matched, most-used first, with stamps, projects_using, change_record_nids). - Rows by installs, occurrences, name: project, title, installs, branch, branches, symbol_count, occurrences, symbols (heaviest first, each fqn, kind, removal_in, occurrences, files, change_record_nids → get_change_record). next_offset absent on the last page.
what
what_changed
Answer "what changed in the core API between two core versions": symbols added, deprecated, removed, as ranked groups with counts. One symbol: lookup_core_symbol. Human write-ups: list_change_records. Every row of one bucket: flat=true with bucket, paged; every row of the whole diff: the result's bulk block (attach + sql for the published catalog, rows to disk not context). - Bounds inclusive; forms 11.2, 11.2.x, 11.2.0, 11, 11.x; same version twice = one minor; upgrading 11.2 → 11.4 means from=11.3. Buckets are independent. - Grouped (default): a head entry is a symbol group (grouped_by: change_record | name_prefix | singleton); head counts groups (≤ 5), count symbols, group_count groups; members ≤ 3 per group, highest usage first; group fields appear only when every member agrees. Rank: max_projects_using desc, then member_count magnitude, tier, count, key (ranked_by). head_covers_all_callers false → narrow with kind / min_projects_using or page with offset. - Flat (flat=true, bucket=added, deprecated, removed): rows by projects_using then fqn: fqn, kind, subsystem, stamp, projects_using, removal_in, removal_kind, replacement when the catalog names one, change_record_nids. count = public symbols in the bucket after filters; limit ≤ 100, next_offset absent on the last page. - Filters apply to every count. internal_excluded and test_theme_excluded are counted apart and left out; unplaceable_symbols have no minor in their stamp. - Usage = contrib development branches only, evidence rollup as of usage_evidence_built_at; 0 = no caller observed. - change_record_nids → get_change_record. removal_kind observed = gone, scheduled = promised and still present (say "scheduled for removal in 13.0").

Tools removed

These were exposed before and are gone now. No other catalogue tracks this.

search_contrib_code
removed 21 Aug 2026
find_core_symbol
removed 26 Aug 2026
list_changed_symbols
removed 26 Aug 2026
project_trend
removed 26 Aug 2026
related_projects
removed 26 Aug 2026

Endpoints

URLTransportStateLatencyChecked
https://mcp.tresbien.tech/mcp streamable-http answering 134 ms 0 min ago

Alternatives to Drupal Code Query

same job, measured the same way
repoctx
by nugehs

Local-first code context, impact analysis, and merge-readiness verdicts for AI agents.

118 installs/wk local only
Claude Code Conversation Search MCP
by tonysimonovsky

Search Claude Code conversation history with natural language queries across all projects

82 installs/wk local only
Ghostlink
by bgorzelic

Sandboxed repo access for coding agents: search, read, patch, run, git -- confined to a repo root.

local only
BumpGuard
by appcreationsca

Reports which of your code's API usages break when you upgrade a dependency (Python, .NET, Java).

96 installs/wk local only
US Code of Federal Regulations
by mccallar

Look up, search, and track changes to the US Code of Federal Regulations (eCFR).

1 544 installs/wk local only
livewiki
by eduardoabreu81

A repository wiki of Markdown pages anchored to real code: read, search, and verified writes.

328 installs/wk local only
Clawhub
by maxz712

Git hosting for AI agents: repos, changes, focused diffs, reviews, issues, code search.

19 installs/wk local only
GitWhy — The Context Layer for Git
by gitwhy-cli

The shared AI context engine for git — save, search, and share the reasoning behind code changes.

50 installs/wk local only

Drupal Code Query — questions

Answers built from our own checks of this server.

What can Drupal Code Query do?
It exposes 16 tools, read directly from the server on our last check. Among them: check_patches, describe_dataset, get_change_record, list_change_records, list_class_relations, list_projects and 10 more. The full list with descriptions is on this page — we take it from the server itself via tools/list, not from a README. How MCP servers expose tools in the first place →
What is Drupal Code Query mostly used for?
Its tools cluster around change and project. That is what this server is built to work with — the grouping comes from the actual tool names, not from a category we assigned.
Is Drupal Code Query working right now?
We send a real MCP handshake every 15 minutes. Over the last 24 hours 91 of 92 checks got a reply (98.9%), average response time 202 ms. The bar chart above shows every period we have measured.
Did Drupal Code Query ever remove tools?
Yes. search_contrib_code, find_core_symbol, list_changed_symbols, project_trend and 1 more are no longer exposed — we recorded the date each one disappeared. A tool vanishing usually means a breaking change for anything that depended on it.
How do I connect Drupal Code Query?
Copy the ready config from this page — we generate it for Claude Code, Claude Desktop, Codex, Cursor and VS Code, each with the file path that client actually reads. It is a remote server, so there is nothing to install — the client connects to the address.
Does Drupal Code Query need an API key?
No. Drupal Code Query completed a full MCP handshake with us as an anonymous client and listed its tools without asking for anything. All 16 of them are readable on this page. This is what we observed, not what the docs claim.
How fast is Drupal Code Query?
It answers our handshake in 202 ms on average, which is faster than 72% of all working MCP servers we measure. The comparison comes from our own checks across the whole registry, every 15 minutes.