{"name":"tech.tresbien/drupal-code-query","slug":"tresbien-drupal-code-query","title":"Drupal Code Query","description":"Drupal core API changes, contrib usage, upgrade readiness, patch verdicts, code search, SQL.","url":"https://mcp.market/server/tresbien-drupal-code-query","rating":null,"grade":"D","score":53,"certified":false,"status":"active","category":"data","tags":["data","search"],"presence":{"score":8,"stars":null,"forks":null,"downloads_week":null,"last_push_at":null,"license":null},"uptime":{"percent":100,"checks":1,"ok":1,"last_checked_at":"2026-09-19T20:06:05.539Z","last_ok_at":"2026-09-19T20:06:05.539Z","latency_ms":1246},"claimed":false,"transport":"remote","callable_via_gateway":true,"default_price_micros":0,"repository":null,"website":"https://mcp.tresbien.tech","version":"0.42.0","remotes":[{"type":"streamable-http","url":"https://mcp.tresbien.tech/mcp"}],"packages":[],"tools":[{"name":"check_patches","description":"Does each composer patch still apply to the release the site installed, and is its fix already in that release?\n- Send composer_json and composer_lock after composer update. Add patch_files for every patch, a local one read from disk and a URL one fetched by you. Every extra.patches entry is checked at its installed version in one call.\n- Do not download releases. Do not run patch --dry-run.\n- Send a package's patches together, in the order composer.json lists them.\n- items: up to 50 of {project, version, patch, title}, in place of the composer files. project: the drupal.org machine name (webform) or composer name (drupal/webform); drupal or core for Drupal core. version: the installed composer version (1.13.0, 2.0.0-beta4, 11.4.5). patch: the diff text, or its URL on www.drupal.org/files/issues or a git.drupalcode.org merge request or commit, ending .patch or .diff.\n- Per item. tag: the release tag matched, or unknown_version. applies_at: the -p level at which git apply passes, null when none. fuzzy: true when it applied only with reduced context and whitespace ignored; review it. hunks_failed: [{file, line, reason}]. reverse_applies: true when the patched lines are already in the tag. mr: the merge request state when the patch is an MR URL. merged_in_version. suggested. failure_mode: set when a patch applies and a file it touches no longer parses; keep the patch and fix the file. syntax_errors: the files behind it, one per file, path then the parser's message.\n- suggested: the verdict. merged: the fix is in the release; drop the patch. applies: keep the patch. conflicts: call reroll_patch with that item. unknown: see error.\n- next_step: the follow-up calls and the row count the report must have. counts: the verdict tally; check the report's table against it. An item that could not be judged comes back unknown with the reason in error. Report it as unclear. Never leave the row out.\n- hunks_shipped, files_shipped: the parts of a patch already in the release verbatim. On a conflicts row, check whether the rest is still needed before re-rolling. moved_to: on a failed hunk, the release path of the renamed file.\n- core_references: what the added code references in core, checked at target_core. flagged: removed or moved classes, and calls whose argument count does not fit the target signature, each with file, line, change_record and replacement. parent::__construct is checked against the constructor of the core parent class. deprecated: a short side list. checked: how many references the catalog knows. Scope: added lines and direct references only (extends, implements, trait use, new, static calls, parent::__construct). A patch that does not apply has a note instead.\n- Verdicts come from git apply --check against the tag. An applies patch can still be wrong at runtime.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"items":{"type":["null","array"],"items":{"type":"object","properties":{"project":{"type":"string"},"version":{"type":"string"},"patch":{"type":"string"},"source":{"type":"string"},"upstream":{"type":"string"},"provenance":{"type":["null","object"],"properties":{"mr":{"type":"string"},"commit":{"type":"string"},"url":{"type":"string"},"base":{"type":"string"},"head":{"type":"string"},"fetched":{"type":"string"},"rerolled":{"type":"string"}},"additionalProperties":false},"merge_patch":{"type":"string"},"title":{"type":"string"},"base":{"type":"string"},"resolutions":{"type":["null","array"],"items":{"type":"object","properties":{"file":{"type":"string"},"region":{"type":"integer"},"choice":{"type":"string"},"text":{"type":"string"},"delete":{"type":"boolean"}},"required":["file","region"],"additionalProperties":false}}},"required":["project","version","patch"],"additionalProperties":false},"description":"Up to 50 patches to check. Each item has project, version, patch and title. project: webform or drupal/webform; drupal or core for Drupal core. version: the installed composer version, 1.13.0 or 11.4.5. patch: the diff text. This service downloads nothing, so fetch a URL patch yourself and send what came back. source: optional, the path or URL the patch was declared with. merge_patch: optional, the .diff form of a merge request, used for the merge of a re-roll while patch decides the verdict. provenance: optional, where a copied patch's bytes came from, {mr, commit, url, base, head, fetched, rerolled}. Send it when source is a local path. title: optional, echoed back. base: optional, the release the site had before the upgrade. A commit sha or a composer version, tried first as the merge base of a re-roll. Leave empty to build the items from composer_json and composer_lock."},"composer_json":{"type":"string","description":"The contents of composer.json. Send the text; this server cannot read your filesystem. Its extra.patches becomes the item list, one item per patch."},"composer_lock":{"type":"string","description":"The contents of composer.lock. It gives the installed version of each patched package."},"patches_file":{"type":"string","description":"The text of the patches file, for a site on cweagans/composer-patches 2.x that keeps its patches out of composer.json. The path is in its own extra.composer-patches.patches-file, patches.json by default."},"patches_lock":{"type":"string","description":"The text of patches.lock.json, which 2.x of the patch manager writes and applies from. Send it to judge what the site actually applies: it answers alone, over extra.patches and the patches file."},"patch_files":{"type":"object","description":"The text of every patch the site declares, keyed by the source as written: a path (patches/x.patch) or its base name, or the URL. Fetch a URL patch yourself and send its text under that URL. For a merge request .patch URL, send its .diff form under a second key too.","additionalProperties":{"type":"string"}},"target_core":{"type":"string","description":"Core the added code is checked against: a minor (11.4 or 11.4.x) or a release (11.4.5). A minor with no core release in the data is refused, and so is a major alone. latest checks no references. Defaults to the lock's drupal/core, else the version of a drupal item."}},"additionalProperties":false}},{"name":"describe_dataset","description":"Learn the dataset before writing SQL for query_dataset. Call it with no arguments first. Returns text. Prefer a typed tool when one answers the question.\n- No arguments: one line per view, the join map, the four invariants, the causes of empty results, and the recipe index. The invariants: dev-branch isolation, no SUM(usage), *_seq compares, adoption polarity. Empty results come from fqn forms, placeholders and machine name forms.\n- view=<name>, or views=[…] for several in one call. Returns the columns with types and descriptions, an example filter that returns rows, and the joins into the view.\n- recipe=<id>: a ready-to-run statement for a common question. Fill in the placeholders and run it with query_dataset. Recipes cover who uses a symbol, deprecated symbols per project, and a change record's adoption. Others cover symbols deprecated between minors, one project's deprecated uses, and change records between minors.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"view":{"type":"string","description":"One view name. Returns its columns with types and descriptions, an example filter, and the joins into it. Leave empty for the overview."},"views":{"type":["null","array"],"items":{"type":"string"},"description":"Up to 8 views at once, documented one after the other in one call."},"recipe":{"type":"string","description":"One recipe id from the overview's index. Returns a ready-to-run query_dataset statement with its placeholders explained."}},"additionalProperties":false}},{"name":"get_change_record","description":"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 the change_record_nids from what_changed. For one symbol's detail, use lookup_core_symbol.\n- flavour: coming-break (an API going away), coming-new-API (an API added), landed-but-still-relevant (shipped), policy-only (no code target, no adoption).\n- Adoption counts contrib development branches only. legacy: still calls the going-away side. migrated: moved to the new side.\n- Returns counts across every affected project plus a ranked head, outstanding first. For one project, use project_upgrade_report; do not raise top_projects.\n- 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; that is common.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"nid":{"type":"integer","description":"The change record's drupal.org node id, for example 3581981."},"top_projects":{"type":"integer","description":"How many impacted projects to return in the ranked head. Defaults to 10, capped at 40."}},"required":["nid"],"additionalProperties":false}},{"name":"list_change_records","description":"List the core change records (the human write-ups) that target 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.\n- Bounds are inclusive. Forms: 11.2, 11.2.x, 11.2.0, 11, 11.x. A bare major covers every minor of it.\n- Flavours: coming-break (an API going away), coming-new-API (an API added), landed-but-still-relevant (already shipped). policy-only records target no version and never appear here.\n- project: one machine name. Each record then has the count of that project's development branches still on the legacy side.\n- Returns the count of every record in range plus a ranked head. Each entry has its nid for get_change_record.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"from":{"type":"string","description":"Lower core version bound, inclusive: 11.2, 11.2.x, 11.2.0, 11, or 11.x. A bare major (11) covers every minor of it."},"to":{"type":"string","description":"Upper core version bound, inclusive. Same forms as from."},"project":{"type":"string","description":"Optional contrib project machine name. Narrows the list to records a development branch of that project still matches."},"limit":{"type":"integer","description":"Records in the head, 1 to 40 (default 15). Above the cap the call is refused."}},"required":["from","to"],"additionalProperties":false}},{"name":"list_class_relations","description":"List the contrib classes that extend, implement or use a core class, interface or trait, one row per edge with file and line, paged. Example: \"who subclasses FormBase\". Call-site counts: list_symbol_users. The symbol's status: lookup_core_symbol.\n- target: with or without the leading backslash. An unknown target is refused with a pointer at lookup_core_symbol; a fragment there lists candidates. rel_kind: one of extends, implements, uses_trait. project: one machine name.\n- Development branches only. edge_count: every edge. project_count: projects. count: edges after the per_project cap. project_edges on each row: the project's full count. per_project=50 with project= lists one project in full.\n- Rows are ordered by project installs, project, source_fqn. Each row: project, branch, installs, source_fqn, rel_kind, file, line, project_edges. limit ≤ 100. next_offset is absent on the last page.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"target":{"type":"string","description":"The core class, interface or trait, with or without the leading backslash: Drupal\\Core\\Form\\FormBase."},"rel_kind":{"type":"string","description":"extends, implements, or uses_trait; any when absent."},"project":{"type":"string","description":"Restrict to one project machine name."},"per_project":{"type":"integer","description":"Edges shown per project on a page, 1 to 50 (default 3). Each row has project_edges, the project's full count."},"limit":{"type":"integer","description":"Rows per page, 1 to 100 (default 50)."},"offset":{"type":"integer","description":"Skip this many rows in the same ranked order. Pass the previous page's next_offset."}},"required":["target"],"additionalProperties":false}},{"name":"list_projects","description":"List drupal.org projects after filters, largest install base first, paged. Examples: \"top modules without an 11.4 branch\", \"themes over 10k installs without security coverage\". One project: project_profile. Its pending records: project_upgrade_report.\n- Filters combine: type, min_installs, security (covered, not-covered, revoked), core_minor (+ missing), era (d8plus, pre_d8), member_of (+ max_depth). All apply to count and rows.\n- core_minor reads the composer constraint of the newest release on each development branch. A minor counts when any patch of it satisfies the constraint. An unreleased tip change is not seen. missing=true keeps projects with no branch declaring it: the upgrade gap list.\n- member_of keeps one root's members, direct and transitive; max_depth (needs member_of) drops members past that many requires away. A filtered list adds membership: root_release, complete, unread_count and unread (up to 20, package + cause).\n- Row: machine_name, title, type, installs, security, status, dev_branches (branch, installs, core_minors, era), latest_release, cms_depth, member_of_count. cms_depth is the row's membership depth under the cms root; member_of_count is how many roots include it. A project in no root has neither; a project that is not a cms member has member_of_count alone. count: projects. next_offset is absent on the last page.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"type":{"type":"string","description":"Filter: project_module, project_theme, project_distribution, project_general, project_theme_engine, project_translation."},"min_installs":{"type":"integer","description":"Filter: reported install base at or above this."},"security":{"type":"string","description":"Filter: security advisory coverage: covered, not-covered, revoked."},"core_minor":{"type":"string","description":"Filter: a core minor in 11.4.x form. Keeps projects where the newest release on some development branch declares support for it. With missing=true, keeps projects with no such branch."},"missing":{"type":"boolean","description":"Invert core_minor: keep projects whose development branches do not declare it."},"era":{"type":"string","description":"Filter: d8plus (Drupal 8+ code) or pre_d8, on any development branch."},"member_of":{"type":"string","description":"Filter: keep only members of this root project's composer requires walk, for example cms."},"max_depth":{"type":["null","integer"],"description":"With member_of: drop members further than this many requires away, 1 or more."},"limit":{"type":"integer","description":"Projects per page, 1 to 100 (default 50)."},"offset":{"type":"integer","description":"Skip this many projects in the same ranked order. Pass the previous page's next_offset."}},"additionalProperties":false}},{"name":"list_symbol_users","description":"List every contrib project using one core symbol, or any symbol in a class of symbols, one row per project, paged, with the matched symbols. lookup_core_symbol answers one symbol with a head of 30.\n- Pass fqn, or filters: kind, status (deprecated, scheduled_removal, removed), subsystem, removal_in (13.0), deprecated_in (11.4). Example: \"top modules still calling a method going away in 13.0\" is kind=method, status=scheduled_removal, removal_in=13.0. Filters take public symbols only.\n- Development branches only, from the evidence rollup as of evidence_built_at. count: projects. symbol_count: matched symbols. symbols_matched: those symbols, up to matched, most used first, each with stamps, projects_using and change_record_nids.\n- Rows are ordered by installs, occurrences, name. Each row: project, title, installs, branch, branches, symbol_count, occurrences, symbols. symbols: heaviest first, each with fqn, kind, removal_in, occurrences, files and change_record_nids for get_change_record. next_offset is absent on the last page.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"fqn":{"type":"string","description":"One core symbol, as lookup_core_symbol takes it. Pass this or a filter."},"kind":{"type":"string","description":"Filter: one symbol kind (function, method, class, library, hook, service …)."},"status":{"type":"string","description":"Filter: deprecated | scheduled_removal (still present) | removed (gone)."},"subsystem":{"type":"string","description":"Filter: a core subsystem (Entity API, Views), case-insensitive. subsystem_coupling with no arguments lists them."},"removal_in":{"type":"string","description":"Filter: removal stamp in this core minor (13.0). With status=scheduled_removal: still present, going away then."},"deprecated_in":{"type":"string","description":"Filter: symbols deprecated in this core minor (11.4)."},"limit":{"type":"integer","description":"Projects per page, 1 to 100 (default 50)."},"offset":{"type":"integer","description":"Skip this many projects. Pass the previous next_offset."},"symbols":{"type":"integer","description":"Symbols per project row, 1 to 30 (default 10), heaviest first."},"matched":{"type":"integer","description":"Matched symbols listed on the envelope, 1 to 50 (default 20)."}},"additionalProperties":false}},{"name":"lookup_core_symbol","description":"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 entries containing it instead. Every user in pages: list_symbol_users. Changed between versions: what_changed. Code text: search_code.\n- fqn forms: a namespaced name with or without the leading backslash, a bare function or constant name, a prefixed pseudo-symbol, or a fragment. Pseudo-symbols: fn:check_markup, core:hook:preprocess_page, core:library:claro/drupal.shortcut. Fragments: fromRoute, EntityInterface.\n- Resolved (found true): symbol, with status flags. removed: gone. scheduled_removal: still present, @deprecated gives the removal version. deprecated, internal. placeholder: a catalog row no scanner located. audience: contrib, test_support, test or theme. usage: contrib development branches only, from the evidence rollup as of evidence_built_at. It has projects and branches counts, plus top_projects (≤ 30) by install base. change_records: records that touched the symbol, each with nid for get_change_record.\n- Not resolved (found false): count: catalog entries containing the fragment, case-insensitive, public symbols only; kind narrows. candidates: a head of up to candidates entries, each with fqn, kind, subsystem, stamps, projects_using, change_record_nids. Entries starting with the fragment come first, then by projects_using. Call again with one fqn.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"fqn":{"type":"string","description":"Core symbol name, or part of one. A namespaced name with or without the leading backslash (Drupal\\Core\\Entity\\EntityInterface, Drupal\\Core\\Recipe\\RecipeRunner::installModule). A bare function or constant name (check_markup, DRUPAL_DISABLED). A prefixed pseudo-symbol as what_changed reports it (fn:check_markup, core:hook:preprocess_page, core:library:claro/drupal.shortcut). A fragment of 3+ characters (fromRoute), which returns candidates when no symbol has that exact name."},"top_projects":{"type":"integer","description":"How many using projects to return in the ranked head. Defaults to 10, capped at 30."},"candidates":{"type":"integer","description":"When the name resolves to no symbol: how many catalog entries containing it to return, 1 to 50 (default 10)."},"kind":{"type":"string","description":"Restrict candidates to one symbol kind (class, method, function, hook, library, service …)."}},"required":["fqn"],"additionalProperties":false}},{"name":"project_profile","description":"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.\n- project: title, type, status, security, installs. installs_by_version: install base per contrib version.\n- dev_branches: each scanned development branch, with installs, core_constraint, core_minors, latest commit date and era. core_constraint: the composer constraint of the newest release on that line, as drupal.org publishes it. The unreleased tip is excluded. core_minors: the minors it admits; a minor counts when any patch satisfies. latest_core_minor: the newest admitted.\n- releases: newest first, up to releases, each with core_compatibility and security. release_count, supported_branches. missing_sections: what the bundle could not fill.\n- related (summary): requires and required_by counts with the top 3, co_installed top 3 or a note, delta_12m per release line (\"*\" is the total).\n- member_of: how many roots pull this project in (count), and up to 10 {root, depth}, shallowest and alphabetical first.\n- include=[\"related\"]: related becomes the full report. requires: every dependency, via info_yml or composer, core modules left out. required_by: paged by installs, with count and next_offset; page with dependents and dependents_offset. co_installed: rho, rank, method residual_spearman, or a note.\n- include=[\"trend\"]: trend has months (oldest first), series per release_line aligned with months (null: no count, \"*\": total), and lines. Each line: installs_now, share, delta_3m, delta_12m, delta_36m, peak, first_seen, last_seen. Counts are drupal.org month-end installs.\n- Unknown project: error. An included section refuses when the bundle predates its view.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"project":{"type":"string","description":"drupal.org project machine name, e.g. webform. The composer name drupal/webform is accepted."},"releases":{"type":"integer","description":"Releases to list, newest first, 1 to 30 (default 10). release_count says how many exist."},"include":{"type":["null","array"],"items":{"type":"string"},"description":"Optional sections: trend (monthly install series per release line) and related (full requires / required_by / co_installed lists, replacing the related summary)."},"months":{"type":"integer","description":"With include trend: months of history, 1 to 120 (default 36), ending at the latest month in the data."},"dependents":{"type":"integer","description":"With include related: dependents per page in required_by, 1 to 100 (default 20)."},"dependents_offset":{"type":"integer","description":"With include related: dependents to skip. Pass required_by.next_offset from the previous call."}},"required":["project"],"additionalProperties":false}},{"name":"project_upgrade_report","description":"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.\n- A record applies when it targets the target version or earlier, and a development branch of the project matched one of its tracks.\n- Verdict per record: outstanding (a branch still calls the going-away side), in_progress (some branches moved), adopted (all moved).\n- Returns counts by verdict, then a ranked head of outstanding records with file and line evidence from the project's own code.\n- Each entry has the count of other projects outstanding on the same record, and its nid for get_change_record.\n- removed_symbols: core symbols removed by the target, or scheduled for removal at or before it, that the project's development branches still reference. Each has occurrences, files, and the replacement when the catalog lists one. They need no change record: a class moved to another module, a service dropped. removed_symbol_count: the whole set. The head is newest removal first, then by occurrences. Test and test-support symbols are left out.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"project":{"type":"string","description":"Contrib project machine name as it appears on drupal.org, for example webform. The composer name drupal/webform is accepted. Packages from other vendors are not in the dataset."},"target_version":{"type":"string","description":"Target core version: 11.2, 11.2.x, 11.2.0, 11, or 11.x. A bare major (11) covers every minor of it."},"limit":{"type":"integer","description":"Outstanding records to return with evidence, 1 to 15 (default 8). Above the cap the call is refused."}},"required":["project","target_version"],"additionalProperties":false}},{"name":"query_dataset","description":"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 31 views, their columns, joins and recipes. Prefer a typed tool when one fits.\n- statements=[…]: up to 5 statements in one call, one result or error each.\n- Result: columns, and rows as arrays, up to max_rows (≤ 500, default 100) and 16 KB. When truncated is true: aggregate, filter, or use LIMIT and OFFSET. One SELECT (or SHOW, DESCRIBE, FROM-first), no semicolon, 15 s limit, nothing outside the bundle.\n- Dev-branch isolation: JOIN contrib_branch and filter kind = 'dev_branch' AND project <> 'drupal' before counting projects. change_record_adoption, symbol_usage and core_symbol_evidence hold release tags too. core_symbol_evidence is the full rollup; symbol_usage is its string-scan subset.\n- Adoption polarity: legacy is still on the old API (not adopted); migrated is adopted. Versions are text: compare *_seq integers (major*1000+minor). Never SUM(usage) across branch rows.\n- Errors list the views, the columns of the views you used, or the join map. An empty result over an fqn without a leading backslash gets a hint.\n- 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 have, so take recipes from describe_dataset.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"sql":{"type":"string","description":"One DuckDB SELECT over the views describe_dataset lists. SHOW TABLES, DESCRIBE <view> and FROM-first syntax also work. No semicolon, one statement. Use statements for several at once."},"statements":{"type":["null","array"],"items":{"type":"string"},"description":"Up to 5 statements in one call. Results come back in order under results, each with its own rows or error. The byte budget is shared. Use this instead of sql for more than one statement."},"max_rows":{"type":"integer","description":"Rows to return per statement, 1 to 500 (default 100). The result byte budget may cut a wide result sooner. truncated says so either way."}},"additionalProperties":false}},{"name":"reroll_patch","description":"Re-roll one composer patch that check_patches reported conflicts or fuzzy: a new diff that applies to the installed release. Call check_patches first for the whole list. Call this once per patch that came back conflicts or fuzzy.\n- Input: project, version, patch, title, base. patch is the diff text; this service downloads nothing, so fetch a URL patch yourself and send what came back. base is the release the site had before the upgrade. One patch per call. A hand-made patch without index lines is merged from base when it applies there, else from the newest tag it applies to. reroll.base is the tag used.\n- drop_tests: true leaves the patch's test files out, false keeps them. Without it, core 12 and later leave them out. reroll.dropped_tests lists them.\n- reroll.verified true: the service already ran the check. reroll.verified_by: the command, the -p level and the tag. Write reroll.patch to the patch file and move on. Do not run git apply --check or patch --dry-run. Do not diff against a copy of the release. composer install is the test.\n- After a conflicts result, send the same project, version and patch again with resolutions, one per region of reroll.conflicts[].hunks. A resolution is {file, region, choice: release|patch}, {file, region, text}, or {file, region, delete: true}. 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. Do not read the release files, edit the patch by hand, or dry-run it.\n- reroll.status clean: every file merged, or GNU patch applied every hunk, 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 has the change; suggested is merged.\n- reroll.status conflicts: reroll.patch holds the hunks that merged cleanly, apply-checked when reroll.verified is true. Each file in reroll.conflicts has 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. 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 is needed.\n- reroll.syntax_errors: files that do not parse after the merge, with the parser's line. reroll.verified is false then, though the diff applies. Fix the text of the resolutions on those files and call again.\n- reroll.status unavailable: no release takes the patch, or it was made from a commit in no release; reroll.error says which. Re-roll by hand from hunks_failed.\n- reroll.patch paths are relative to the repository root. Contrib patches apply at -p1. Core patches start with core/ and apply at -p2 from web/core. patch_truncated: the diff was cut to the result budget; POST /v1/patch/check on api.tresbien.tech with reroll: true returns it whole.\n- A patch already in the release (merged), or one git apply accepts unchanged, returns its verdict with reroll null. A patch that applies only because a lenient sweep took it (fuzzy) is re-rolled, since a strict patch manager refuses it. Its re-roll keeps the lines GNU patch writes with fuzz, the code a lenient patch manager installs, and reroll.base stays empty.\n- 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.","write_action":true,"price_micros":0,"input_schema":{"type":"object","properties":{"project":{"type":"string","description":"drupal.org machine name (webform) or composer name (drupal/webform); drupal or core for Drupal core."},"version":{"type":"string","description":"The composer version installed, e.g. 1.13.0 or 11.4.5."},"patch":{"type":"string","description":"Unified diff text. This service downloads nothing, so fetch a URL patch yourself and send what came back. With the index lines git diff writes, it is merged onto the release. Without them (hand-made), it is merged from base when it applies there, else from the newest tag it applies to. reroll.base is the tag used."},"title":{"type":"string","description":"Optional, echoed back."},"base":{"type":"string","description":"Optional. The release the site had before the upgrade. Its commit sha from composer.lock (source.reference), or its composer version: 6.1.0 when version is 6.2.0. A patch without index lines is merged from it when it applies there, before the newest tags are tried."},"target_core":{"type":"string","description":"Core the added code is checked against: a minor (11.4 or 11.4.x) or a release (11.4.5). A minor with no core release in the data is refused, and so is a major alone. latest checks no references. Defaults to version for a core patch."},"drop_tests":{"type":["null","boolean"],"description":"Optional. true leaves the patch's test files out of the re-roll, false keeps them. Without it, core 12 and later leave them out and every other patch keeps them."},"resolutions":{"type":["null","array"],"items":{"type":"object","properties":{"file":{"type":"string"},"region":{"type":"integer"},"choice":{"type":"string"},"text":{"type":"string"},"delete":{"type":"boolean"}},"required":["file","region"],"additionalProperties":false},"description":"Decisions for the conflicted regions of a previous call on the same project, version and patch, one entry per region. region: the index in that file's conflicts[].hunks. {file, region, choice}: choice is release or patch. {file, region, text}: your own text for the region. {file, region, delete: true}: empty the region. The service re-runs the merge with them and apply-checks the result. Regions you leave out keep the release side and come back in reroll.resolutions_missing."}},"required":["project","version","patch"],"additionalProperties":false}},{"name":"scan_composer","description":"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; do not query one package at a time. 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.\n- composer_lock: the whole file, or its path to get the upload command. A real lock does not fit a call. With the path, the files go from disk to the api by curl and only the plan comes back. composer_json: adds constraints and extra.patches. patches_file and patches_lock: the two documents a site on cweagans/composer-patches 2.x keeps its patches in, since such a site declares none under extra.patches. Sent as text with a lock path, the command lists every patch to send. target_core: 11.4, 11.4.5, or latest for the newest core the site's constraint allows. Without patches, target_from holds the package whose constraint decided latest. When the site requires no core package, latest is the installed core and target_from says so. Empty scans against the installed core (target_is_installed): what can be updated without a core upgrade.\n- installed_unknown: the release data does not have the installed version; the row offers no older release. installed_from_tag: the project's git tag answered instead. Neither is a finding about the site.\n- 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 has that project's answer. The lock's type, require and extra.drupal.datestamp fields pair a metapackage with its project.\n- rows, 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; note says where its releases are. current: installed_supports and nothing newer needed; such a row has package, installed and installed_supports only. counts covers every package even when rows are cut.\n- candidates, on every row that is not current: the newest three releases, each with version, core constraint, date and supports_target. project_profile adds installs and branches to the same three.\n- patches: one item per declared patch with the installed version filled in. Pass them to check_patches after composer update; local paths need the file text. A patch on a package outside drupal/ gets no item and no row; outside_drupal lists it.\n- patches:true answers with plan instead: every patch judged against the release its package would install for target_core. plan.counts: 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 an applies that only a lenient apply took). plan.applying: the number that apply with nothing to decide. The scan rows are not in a plan; call again without patches for them.\n- A patch whose package has no release for the target is judged against the branch when the lock installs a dev version. Otherwise it is unknown, with the reason in note. plan.warnings: a package is in no_release because of something the site controls. Each warning gives the requirement to change: its own constraint, or its minimum stability. A row's project for reroll_patch is its package without the drupal/ prefix.\n- A plan never has a re-rolled diff. plan.next_step points at reroll_patch, one call per patch, when any patch needs a re-roll.\n- bundle_date: when the release data was published. A release after it is invisible here. Confirm a no_release with composer show or drupal.org before acting.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"composer_json":{"type":"string","description":"The contents of composer.json (require, require-dev, extra.patches). Send the text; this server cannot read your filesystem. Optional; without it there are no constraints and no patches."},"composer_lock":{"type":"string","description":"The text of composer.lock. A real lock is too big to paste: send its path (composer.lock) instead. The answer is then the curl command that uploads the files from the site root."},"patches_file":{"type":"string","description":"The text of the patches file, for a site on cweagans/composer-patches 2.x that keeps its patches out of composer.json. The path is in its own extra.composer-patches.patches-file, patches.json by default. A site on 2.x declares nothing under extra.patches, so without this its patches are invisible."},"patches_lock":{"type":"string","description":"The text of patches.lock.json, which 2.x of the patch manager writes and applies from. Send it to judge what the site actually applies: it answers alone, over extra.patches and the patches file."},"target_core":{"type":"string","description":"Core to move to: a minor (11.4), a release (11.4.5), or latest for the newest core the site's own constraint allows. Leave empty to scan against the core the site already runs: which packages have a newer release for it."},"patches":{"type":"boolean","description":"Judge the site's patches in the same call: each one against the release its package would install for target_core. Returns a verdict per patch instead of items to check yourself."},"patch_files":{"type":"object","description":"The text of every patch the site declares, keyed by the source as written: a path (patches/x.patch) or its base name, or the URL. Only needed with patches:true. Fetch a URL patch yourself and send its text under that URL. For a merge request .patch URL, send its .diff form under a second key too.","additionalProperties":{"type":"string"}},"installed_core":{"type":"object","description":"What each installed release requires of core, keyed by composer name. Read it from the site's own vendor directory: composer show -f json <pkg>, or the drupal/core entry of its composer.json. Send it and the installed release is judged by what it declares.","additionalProperties":{"type":"string"}}},"required":["composer_lock"],"additionalProperties":false}},{"name":"search_code","description":"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.\n- 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: project machine name list. The index runs RE2, which has no lookaround or backreferences.\n- .module, .install, .theme, .engine, .profile and .inc count as PHP: lang:php includes them and sym: resolves inside them. No language filter is applied for you.\n- 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 up to 1000 files; total_files is the ceiling; truncated when cut. A parse error returns the index's own message.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"query":{"type":"string","description":"Code to find, a regex unless literal is true. Filters may be inline when literal is false, else in filters. Example: hook_form_alter f:\\.module$"},"literal":{"type":"boolean","description":"true: query is matched as exact text. Use it for text with ( [ ] $ . such as #[Hook(. With literal, filters go in filters."},"filters":{"type":"string","description":"Zoekt filters appended as written: r:<repo regex> f:<path regex> lang:<language> b:<branch> sym:<symbol> case:yes."},"repos":{"type":["null","array"],"items":{"type":"string"},"description":"Restrict to these projects, by machine name, in one query."},"limit":{"type":"integer","description":"Files to return, default 20, cap 50. Ignored with by_repo."},"by_repo":{"type":"boolean","description":"Return repos as (repo, file_count) instead of files. Pulls up to 1000 files; total_files is the ceiling."}},"required":["query"],"additionalProperties":false}},{"name":"subsystem_coupling","description":"Answer \"what else tends to fail when a merge request touches this core subsystem\" from CI history over about 9,000 core merge requests. With issue_nid: \"which subsystems does this issue touch, and what co-fails with them\". No arguments: the list of subsystems, the same one core_symbol.subsystem and list_symbol_users subsystem= use.\n- subsystem: a name (Views, case-insensitive) or a bucket (module:views, core_lib:Entity). An unknown one is refused, with the nearest matches.\n- 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 are left out.\n- Floors min_lift (0.05) and min_co_fail (3) drop noise. count: edges above them. edges: up to limit (≤ 50), by lift. When nothing clears the floors, the strongest edges are listed with a note.\n- issue_nid: touched_subsystems first. A note explains an issue with no MR file data.","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"subsystem":{"type":"string","description":"A core subsystem (Views, Entity API) or a path bucket (module:views, core_lib:Entity). Leave both subsystem and issue_nid empty to get the list of subsystems."},"issue_nid":{"type":"integer","description":"A drupal.org core issue nid: reports the subsystems its merge request touches, then the coupling edges from each."},"min_lift":{"type":"number","description":"Keep edges with lift at or above this (default 0.05)."},"min_co_fail":{"type":"integer","description":"Keep edges seen in at least this many merge requests (default 3)."},"limit":{"type":"integer","description":"Edges to return, 1 to 50 (default 20). count says how many pass the floors."}},"additionalProperties":false}},{"name":"what_changed","description":"Answer \"what changed in the core API between two core versions\": symbols added, deprecated and 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, with attach and sql for the published catalog. Write those rows to a file, so they stay out of the context window.\n- Bounds are inclusive. Forms: 11.2, 11.2.x, 11.2.0, 11, 11.x. The same version twice is one minor. Upgrading 11.2 → 11.4 means from=11.3. Buckets are independent.\n- Grouped (default): a head entry is a symbol group; grouped_by is change_record, name_prefix or singleton. head: groups, up to 5. count: symbols. group_count: groups. members: up to 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 states it. When head_covers_all_callers is false, narrow with kind or min_projects_using, or page with offset.\n- Flat (flat=true, bucket=added, deprecated, removed): rows by projects_using then fqn. Each row: fqn, kind, subsystem, stamp, projects_using, removal_in, removal_kind, replacement when the catalog lists one, change_record_nids. count: public symbols in the bucket after filters. limit ≤ 100. next_offset is absent on the last page.\n- 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.\n- Usage: contrib development branches only, from the evidence rollup as of usage_evidence_built_at. 0 means no caller observed.\n- change_record_nids: for get_change_record. removal_kind observed: gone. removal_kind scheduled: promised and still present; say \"scheduled for removal in 13.0\".","write_action":false,"price_micros":0,"input_schema":{"type":"object","properties":{"from":{"type":"string","description":"Lower core version bound, inclusive: 11.2, 11.2.x, 11.2.0, 11, 11.x. Upgrading from 11.2 means from=11.3."},"to":{"type":"string","description":"Upper bound, inclusive, same forms."},"head":{"type":"integer","description":"Groups per bucket, 1 to 5 (default 5). Ignored when flat is true."},"kind":{"type":"string","description":"Restrict to one symbol kind (method, class, function, hook, service, library …). Applies to counts too."},"min_projects_using":{"type":"integer","description":"Drop symbols fewer than this many contrib projects call on a development branch. Applies to counts too."},"offset":{"type":"integer","description":"Skip this many groups per bucket (grouped) or rows (flat), in ranked order. Flat: pass the previous page's next_offset."},"flat":{"type":"boolean","description":"true: one bucket as flat symbol rows, paged, instead of three buckets of ranked groups. Needs bucket."},"bucket":{"type":"string","description":"With flat: which side of the diff to list, added, deprecated, or removed. One bucket per call."},"limit":{"type":"integer","description":"With flat: rows per page, 1 to 100 (default 50). A page of very long fqns may come back shorter. next_offset always says where the next page starts."}},"required":["from","to"],"additionalProperties":false}}],"scan":{"score":53,"grade":"D","scanned_at":"2026-09-19T19:48:35.488Z","report":{"scannerVersion":"0.1.5","scannedAt":"2026-09-19T19:48:35.470Z","components":{"code":{"score":-1,"max":25,"notes":["remote-only server, no package to scan"]},"reliability":{"score":17,"max":20,"notes":["remote reachable in 2002ms"]},"poisoning":{"score":13,"max":15,"notes":["16 tool descriptions checked"]},"auth":{"score":3,"max":15,"notes":["open endpoint exposes 2 write-action tools with no auth"]},"maintenance":{"score":3,"max":15,"notes":["no repository listed"]},"identity":{"score":4,"max":10,"notes":["verified namespace with website, no repo"]}},"findings":[{"id":"auth.open-write","severity":"high","component":"auth","title":"Write-action tools reachable without authentication"},{"id":"poison.long-description","severity":"low","component":"poisoning","title":"Unusually long tool description (over 2,000 characters)","evidence":"tool 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 and composer_lock after composer update. Add patch_files for every patch, a local one read from disk and a URL one fetched by you. Every extra.patches entry is checked at its installed version in one call. - Do not download releases. Do not run patch --dry-run. - Send a package's patches together, in the order composer.json lists them. - items: up to 50 of {project, version, patch, title}, in place of the composer files. project: the drupal.org machine name (webform) or composer name (drupal/webform); drupal or core for Drupal core. version: the installed composer version (1.13.0, 2.0.0-beta4, 11.4.5). patch: the diff text, or its URL on www.drupal.org/files/issues or a git.drupalcode.org merge request or commit, ending .patch or .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: true when it applied only with reduced context and whitespace ignored; review it. hunks_failed: [{file, line, reason}]. reverse_applies: true when the patched lines are already in the tag. mr: the merge request state when the patch is an MR URL. merged_in_version. suggested. failure_mode: set when a patch applies and a file it touches no longer parses; keep the patch and fix the file. syntax_errors: the files behind it, one per file, path then the parser's message. - suggested: the verdict. merged: the fix is in the release; drop the patch. applies: keep the patch. conflicts: call reroll_patch with that item. unknown: see error. - next_step: the follow-up calls and the row count the report must have. counts: the verdict tally; check the report's table against it. An item that could not be judged comes back unknown with the reason in error. Report it as unclear. Never leave the row out. - hunks_shipped, files_shipped: the parts of a patch already in the release verbatim. On a conflicts row, check whether the rest is still needed before re-rolling. moved_to: on a failed hunk, the release path of the renamed file. - core_references: what the added code references in core, checked at target_core. flagged: removed or moved classes, and calls whose argument count does not fit the target signature, each with file, line, change_record and replacement. parent::__construct is checked against the constructor of the core parent class. deprecated: a short side list. checked: how many references the catalog knows. Scope: added lines and direct references only (extends, implements, trait use, new, static calls, parent::__construct). A patch that does not apply has a note instead. - Verdicts come from git apply --check against the tag. An applies patch can still be wrong at runtime.…"},{"id":"maint.no-repo","severity":"low","component":"maintenance","title":"No source repository listed"}],"inputs":{"probes":[{"url":"https://mcp.tresbien.tech/mcp","reachable":true,"authRequired":false,"latencyMs":2002,"serverInfo":{"name":"drupal-code-query","version":"0.42.0"}}],"packages":[],"repo":{"found":false},"icon":{"url":"https://tresbien.tech/core/misc/favicon.ico","source":"registry","width":32,"height":32},"presence":{"stars":null,"forks":null,"downloadsWeek":null,"license":null,"lastPushAt":null,"score":8}}}},"grade_history":[{"kind":"downgrade","fromGrade":"C","toGrade":"D","reason":"score 53: Write-action tools reachable without authentication; Unusually long tool description (over 2,000 characters); No source repository listed","createdAt":"2026-09-19T19:48:39.425Z"}],"reviews":[]}