@descryy/mcp
v0.23.1
Published
Descry's MCP server: the agent-facing front door over the query layer. The stdio transport is JSON-RPC 2.0 by hand rather than seventeen packages of HTTP and OAuth. Eighteen tools; most read the graph and nothing else. The @descryy dependencies are here f
Readme
@descryy/mcp
Descry's MCP server. It reads a repository's source into a graph of code, routes, tables and tests, and lets a coding agent ask that graph what a change reaches, what is tested, and what breaks when the app runs.
It reports a few findings it can back with evidence, and an explicit list of what it could not check. Something it could not read is reported as "not analysable", never as clean.
Everything runs on your machine. The graph lives in the repository's .descry/ folder.
Requirements
- Node.js 22.5 or newer (the graph uses Node's built-in SQLite).
- A git repository with at least one commit. Every node in the graph is stamped with the commit it was read at, so a folder that is not a repository, or a repository with no commits, cannot be built.
- Linux for sandboxed runs. The
observeandgenerate_testtools run your code inside a sandbox: bubblewrap by default, with the network denied, or Docker withsandboxBackend: "container". If neither works they refuse to start instead of running unconstrained. Descry's own tests run on Linux; macOS and Windows have not been verified. - Chromium, only for the browser tools.
npx -y @descryy/cli setupinstalls it. Without it those tools refuse and say why.
Quickstart
In the repository, write its configuration:
npx -y @descryy/cli initThis detects the language adapters that recognise the repository, writes them to
.descry/config.json, and prints the next two steps with this server's exact version filled in.Register the server with your agent. For Claude Code:
claude mcp add descry --scope local -- npx -y @descryy/mcp@<version> "$(pwd)"Use the version
initprinted. For any other MCP client, the server is a stdio command:npxwith the arguments-y,@descryy/mcp@<version>, and the absolute path of the repository.In the agent, call the
workspacetool with verbanalyze(or runnpx -y @descryy/cli analyze). That builds the graph. Every other tool then answers from it, and most will build it themselves on first use.
A repository with no .descry/config.json, or one with no sources, connects but cannot build. The first reply from any tool says so on its first line and names the fix: run the init command above, or call workspace with verb setup.
Commit .descry/config.json and .descry/.gitignore. Everything else in .descry/ is this machine's and is ignored by git.
Tools
Eighteen tools, and a nineteenth, descry_confirm, that only a client known to ask the person before every call to it is shown. The class says what a call can do: read only reads, stateful-read may write Descry's own derived files (the graph, inventories) and is safe to repeat, and action does something that is not a plain read (runs repository code, drives a browser, or writes a configuration or store). A tool's class is that of its strictest verb. tools/list also carries verbClasses for a tool whose verbs differ, and every reply's class is the class of the verb that was called, so questions with nothing to record is read, an answer is stateful-read and workspace with verb analyze is stateful-read. A verb that has nothing to confirm runs on one call, whatever its tool's class; each tool's description says which verbs ask first.
| tool | class | what it does |
| --- | --- | --- |
| workspace | action | Build the graph (analyze, stateful-read), build a graph across linked repositories (index), declare a shared workspace (link), stop this checkout reading a shared graph or remove a dead link (unlink), write this repository's adapters (setup), confirm the adapters the config names (trust_sources). |
| change_scope | action | The blast radius of a diff (diff), of a branch against its merge base split into tested and untested (pr), or every pre-push check in one reply (pre_push, which takes head to review a branch that is not checked out), all read-only. record stores which symbols a branch changes so check cross_pr can compare it with other branches, and forget removes one. |
| code_context | read | Find code by name (search), outline a file or folder (outline), blast radius with covering tests and incidents (impact), files that change together (history), where a value flows (propagation), click to handler (chain), ranked causes of a failure (root_cause). |
| git_context | read | Live reads of git, not the stored graph: a diff (diff) and one file's history (log). Works with no graph. |
| disclosure | read | What the answers so far did not cover: coverage by test runs (coverage), references the graph could not resolve (unresolved), and a check that a path or target you cite was returned by an earlier call (verify_claim). |
| check | read | Calls that reach no route and routes nothing calls (contracts), missing env vars and out-of-order migrations (validate), files usually changed together and in-flight changes on the same code (cross_pr), and error-handling and similar hazards as leads, never as defects (hazards, which accepts a limit and says how many it cut). |
| reach | read | Which pages a data change reaches (predict), and a navigate-only journey from the pages a diff touches (propose). Never drives a browser. |
| prove_reach | action | Drives the predicted journey in a real browser and gates the result. A clean page load is reported as unconfirmed, not as proof of no bug. |
| draw_conclusion | read | Runs a graph path and runtime evidence through the governance gate (gate), and reads stored findings back (get, list). |
| questions | stateful-read | Lists the open questions whose answers unblock the most (read-only; verb chooses list, answer or forget), and records or removes your answer, validating the value first. An answer is used by the next build and capped at reliability B. |
| slop | read | Low-quality code in a change (scope change, default) or the whole repository (repo): invented references, placeholder bodies, swallowed errors, unused additions, needless wrappers, copy-paste, tests that cannot fail. Reports properties of the code, never who or what wrote it, edits nothing, and lists every check it could not run. |
| similar_incidents | read | Ranks recorded incidents by resemblance to files touched or error text. |
| mark_incident | action | Records a defect and the files it lived in, so later failures can be matched against it, and removes a record again with unmark. |
| observe | action | Boots or attaches to the application and records what it does (runtime), or runs the project's own test suite and records which tests ran (tests). |
| browser_read | stateful-read | Read-only browser operations: snapshot, navigate, wait_for, close, layout, record_start, record_stop. |
| generate_test | action | Finds changed functions with no covering test (targets, read-only), runs generated tests and keeps only those that pass, fail when the code is changed and pass three runs (verify), and writes a proposal (apply). |
| qa | action | What the repository's tests prove and where proof is missing: gaps, inventories, weak and stale tests, authorization gaps, scenarios (scenarios takes includeSources to add the cited source text, off by default), and a full report (analyze), all of which run no code. gaps and analyze are compact by default; pass compact: false for the full reply. workflows no longer creates stored workflows on a read: refresh defaults to false, and says how many it created when true. delete_scenario, delete_manual and delete_workflow remove a stored scenario, manual case or workflow. validate_tests runs your tests against deliberate changes to the code they cover. |
| browser | action | Drives a real browser, one verb per call: start_session, click, type, fill, select, submit, save_scenario, run_scenario, explore, autopilot. |
| descry_confirm | action | Approves one pending action by its token. Listed only to a Claude Code version that asks the person before every call to it; see below. |
Required arguments are stated per verb. The schema keeps only verb as required, because some hosts reject conditional schemas, so each argument's description says which verbs need it and the schema carries the same rules in an x-requiredByVerb keyword. A call that leaves one out is refused before anything runs, naming it. A limit whose default differs by verb lists the defaults one by one, and x-defaultsByVerb carries them as numbers.
Long calls report progress when the client sent a progress token: a message every few seconds naming the call and how long it has run. Cancelling a call stops its progress and sends no answer for it, though a tool that does not read its cancellation signal may keep working until it finishes.
Confirming actions
A verb that runs code, writes into a repository or drives a browser with write safety does nothing on its first call. It returns a description of exactly what it will do, a confirmToken, and (where a queue is available) an approval id. Calling the same tool again with that token runs it, but only after a person has approved that exact description. A token is single-use, bound to the arguments of the first call (a changed argument on the second call is discarded and named in the reply), and expires after 15 minutes. A token the calling agent presents on its own authority is refused, and the refusal lists the ways to get it approved. It stays valid after a refusal, so the approval can arrive later.
A token is approved by any one of these:
descry_confirm, in Claude Code. The agent callsdescry_confirmwithtokenand withsummaryset to the sentence the first call returned, character for character. The tool is markedanthropic/requiresUserInteraction, so Claude Code shows its own permission prompt on every call, in every permission mode including auto, and an allow rule cannot skip it. The person reads the sentence in that prompt. Then the agent calls the verb again with the same arguments and the sameconfirmToken. Asummarythat is not the stored sentence is refused and changes nothing; a denied prompt leaves the token pending. IndontAskmode, and in a headless run with no one to answer, Claude Code denies the call, and the other routes apply. The tool is listed, and honoured, only when the client names itself as a Claude Code version that supports the flag; for any other client it does not exist and the reply names the other routes. Each outcome is recorded in~/.descry/approvals-audit.jsonl(a short token reference, never the token).- MCP elicitation. If the client declares it, the second call asks the person directly, showing the exact text. It runs only if they approve, and a decline by a person is final for that token. A decline that came back faster than anyone could read it is reported as unattended, and the token stays pending for another route.
descry approvals, at a terminal. The first call of a gated verb queues an approval under~/.descry/approvals(15 minutes, at most 50, never the arguments themselves).descry approvals listshows what is waiting,descry approvals approve <id>shows the sentence and has the person type the id back, anddescry approvals deny <id>refuses. The agent's next call with its token then runs once. This needs no client support, so it works in the VS Code extension, in auto mode and in headless runs.- A standing approval, made at a terminal.
descry approvemakes a file under~/.descry/grants, private to the user, bound to this repository, with a time limit and no--yes:browser --origin <address>for read-only sessions on exact origins (up to 7 days),browser --write --origin <loopback address>for write-level sessions on localhost, 127.0.0.1 or [::1] only,tests --command <command>for one test command typed exactly,sandboxforqavalidate_testsandgenerate_testverifyin this repository, andstorefor writes to Descry's own store. The last three and the write kind last at most 24 hours.descry approve --listshows them and--revoke <id>removes one. A call a grant covers says which grant approved it. - The command-line twin of the verb, for a person or a pipeline:
descry browse,descry observe tests,descry qa validate-tests,descry scenario record,descry trust,descry init,descry setup,descry analyze. Each prints the same consent text the server would, asks at the terminal, and with no terminal refuses unless--yesis given. The prompt reads from the controlling terminal, discards anything pasted before it appeared, and takes only what is typed after it. Run each command alone and wait for its prompt.
DESCRY_REQUIRE_HUMAN_CONFIRMATION is on unless set to 0. With it on, as it is by default, no client can confirm for itself. Set it to 0 in the server's environment only for a setup where the calling agent is meant to confirm its own actions (a script you wrote and trust).
Do not redeem tokens through a side client, a script or a shell command, and do not add a hook that approves whatever Descry asks. The value of the step is that a person saw the sentence.
Three confirmations are worth knowing before you meet them.
workspace with verb trust_sources. .descry/config.json lists adapter modules, and loading one runs its code with options the repository chose. Because a repository can change that file, a source is not loaded until this machine has confirmed it. The first call lists exactly which module, export and options would load; the second records the confirmation outside the repository, in ~/.descry/confirmed-sources.json. A change to the module, export or options asks again. init and setup confirm the adapters they write themselves. To confirm one repository without a prompt, run descry trust (npx -y @descryy/cli trust) in it. To trust every repository this server opens, set DESCRY_TRUST_CONFIGURED_SOURCES=1 in the server's environment; that covers every repository, not just one. While adapters are untrusted, every graph read leads its headline with "Source not read" and reports its freshness as unread.
Replaying a saved scenario. browser with verb run_scenario replays a committed file from descry/scenarios/. A scenario this machine has not approved at its current content gets the two-call confirmation first. The first call names the file, its content hash, the origins it visits and the secret references it types, and replays nothing. Approval is stored in ~/.descry/approved-scenarios.json; editing the file asks again.
Running your code. observe and generate_test start the application or its tests, in the sandbox described above. qa with verb validate_tests also runs your tests, against deliberate changes to the code they cover, and takes the same two calls. The first call says what will be started.
Running an evaluation
An unattended run (a benchmark, a CI job, an agent harness) has no person to answer the consent prompts, so set it up before the first call:
- Run
npx -y @descryy/cli init --yesin the repository first. It writes.descry/config.jsonand confirms the adapters it detected, so the first tool call reads source instead of asking for setup. - Build the graph once with
npx -y @descryy/cli analyze, or callworkspacewith verbanalyze. - For the verbs that run code, write files or drive a browser, choose one route from "Confirming actions" above. For a harness you wrote and trust, start the server with
DESCRY_REQUIRE_HUMAN_CONFIRMATION=0so the agent's second call with itsconfirmTokenruns. AddDESCRY_TRUST_CONFIGURED_SOURCES=1if the repository's.descry/config.jsonnames adapters this machine has not confirmed. Both settings apply to every repository the server opens; keep them out of an everyday setup. - Read each reply's category and its "What this answer does not cover" list. A refusal or a partial result is reported as one, and scoring it as a wrong answer measures the wrong thing.
Reading a result
Every answer states how much it rests on. A finding carries one of these categories:
confirmed-observed: confirmed, and backed by evidence from a real run of the application.confirmed-code-only: confirmed from the code alone, on direct evidence of the mechanism from two or more independent kinds. Nothing was observed running.strongly supported: the evidence is strong but short of confirmed.unconfirmed: a prediction. It is never reported as fact.not analysable: Descry could not read this part, here. That is different from "found nothing".unsupported: Descry does not do this at all.
Each reply also lists what it does not cover, including a stale graph, a language with no adapter, and references the graph could not resolve. Test coverage is a separate axis, reported as verification status.
Troubleshooting
npx -y @descryy/cli doctorchecks the Node version and built-in SQLite, that one copy of the engine is installed, that the CLI and the server beside it are the versions released together, npx's cache for a half-extracted package, the standing approvals and the approvals waiting for you, and, in a repository, its git state, config, adapters, whether this machine has confirmed them, and the graph. Each finding names its fix. --fix removes only the broken Descry entries from npx's cache.
Common first-run messages:
- "has no .descry/config.json yet" or "has no sources": run
npx -y @descryy/cli init, or callworkspacewith verbsetup. - "not confirmed on this machine" or "no source was read": see the entry below.
- "is not a git repository" or "has no commits yet": run
git initand make a first commit. - "descry-mcp: not found" or
ENOTEMPTYfrom npx: rundoctor --fix.
Analyze says no source was read / adapter not confirmed
The graph then holds only git history, no code. Fix it in this order:
- Run
npx -y @descryy/cli init --yesin the repository, which records this machine's confirmation of the adapters it writes. Or rundescry trust [--yes] [path], which confirms a repository's configured adapters on this machine, per repository.descry analyzethen builds the graph from the command line. - If the config already listed adapters, call
workspacewith verbtrust_sourcesfrom the agent and approve the prompt it raises. A client that cannot show an approval prompt cannot complete this step; use step 1. - Call
workspacewith verbanalyzeagain (or rundescry analyze).
Do not set DESCRY_TRUST_CONFIGURED_SOURCES=1 in a global server entry. It trusts every repository the server opens, and a repository can change its adapter list by editing a committed file. If you need it, set it for one trusted repository, in that repository's own server entry.
Claude Code permissions and auto mode
Claude Code decides each tool call before Descry sees it, by an allow rule, an ask rule or, in auto mode, a classifier; Descry's own approval comes after. An allow rule answers only the first. Rules are named mcp__<server-name>__<tool>, where <server-name> is the name given to claude mcp add. In .claude/settings.local.json:
{
"permissions": {
"allow": [
"mcp__descry__code_context",
"mcp__descry__git_context",
"mcp__descry__disclosure",
"mcp__descry__check",
"mcp__descry__reach",
"mcp__descry__similar_incidents",
"mcp__descry__draw_conclusion"
]
}
}Those seven only read. The tools with write or run verbs (workspace, change_scope, qa, generate_test, observe, browser, browser_read, prove_reach, questions, mark_incident) can be allowed by rule too, because their first call changes nothing and the person is asked at descry_confirm, which an allow rule cannot skip. Allowing them also lets Claude Code's classifier stop denying a harmless first call. Allow mcp__descry__descry_confirm and Claude Code still asks the person on every call.
If a gated call is declined within a fraction of a second, something other than a person declined it. Look for an Elicitation hook (grep -n Elicitation ~/.claude/settings.json .claude/settings.local.json), for --permission-prompts none, and for a non-interactive launch. An Elicitation hook that blanket-accepts defeats the step; if you write one, accept only a message that names a loopback origin and a read-only or sandboxed verb, and exit without a decision otherwise. autoMode settings in ~/.claude/settings.json can describe the setup in prose (a .descry/ store and a loopback dev app are expected); they cannot name a tool, and are read only from user or managed settings.
When Claude Code starts a new session onto a newer Descry, its own tool list can still show the old verbs. Descry announces notifications/tools/list_changed once on the first connection after its tool surface changes, which makes clients that honour it re-list; for one that keeps its own cache, restart the session or reconnect the server.
Known limits
- Playwright and other end-to-end tests are linked to code only through executed coverage (
observeverbtests). A static link from a test'spage.gotois limited to exact literal paths, so until coverage has run, such a suite shows as registered but not covering anything. - The reliability class of a change-scope answer is the minimum over the edges that answer touched. A broad diff can be class C because of a handful of R0 edges.
- A frontend-only repository has no API routes in its graph, so
checkverbcontractsis not analysable until the backend repository is linked withworkspaceverblink.
Privacy
Descry has no hosted component, and this server calls no Descry service. The graph, evidence, findings and answers you record are files in .descry/ in your repository, and only config.json is meant to be committed. Confirmations are kept in ~/.descry/ on this machine, and stored browser credentials are encrypted there. The browser and runtime tools talk only to the application address you give them.
License
MIT
