# Benzi (pypi · benzi-mcp)

Compiler-backed code intelligence: calls, data flow, references and class hierarchy as tools.

- Trust score: 69/100 (medium)
- Change this week: +7
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-10-18

## Components

- pypi · `benzi-mcp`: 69/100 (this document), [markdown](https://verifymcp.io/servers/net-varianttech-benzi/benzi-mcp.md), [page](https://verifymcp.io/servers/net-varianttech-benzi/benzi-mcp)

## Channel facts

- Registry: `pypi`
- Package: `benzi-mcp`
- Version: `0.1.6`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-10-18.

- **Supply Chain Security**: 99/100
  - No malware found by supply-chain analysis.
  - No known CVEs affecting this package version or its production dependencies.
  - Runs setuptools.build_meta at install time, a recognised build step with no custom scripting around it.
  - 4 of 30 dependencies flagged as unhealthy.
- **Provenance & Transparency**: 32/100
  - Source repository is publicly reachable at the declared URL.
  - Provenance check failed: no build-provenance attestation is published.
  - License check failed: the license (Proprietary) isn't a recognized OSI-approved license.
  - Actively maintained (last published 3 days ago).
  - Disclosure check failed: no security disclosure policy was found in the source repository.
- **Schema Quality & AI Usability**: 67/100
  - AI-judged instruction clarity (excellent).
  - Context-footprint check failed: tool/resource definitions use about 5629 tokens (~268/item across 21 items; 21 tools + 0 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 50/100
  - Stability observed for 15 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 87/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 61% of tool parameters carry a description.
- **Tool Safety**: 100/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - We read all 21 captured tool definition(s), and no name or description among them implies an irreversible operation.
  - An AI judge read all 22 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 20/100
  - Spec-recency check failed: implements MCP spec 2024-11-05; the latest is 2026-07-28.

## Install

### How do I install the Benzi MCP server?

Benzi runs locally as a PyPI package, launched with uvx benzi-mcp. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add net-varianttech-benzi -- uvx benzi-mcp
```

### Cursor

```json
{
  "mcpServers": {
    "net-varianttech-benzi": {
      "command": "uvx",
      "args": [
        "benzi-mcp"
      ]
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "net-varianttech-benzi": {
      "command": "uvx",
      "args": [
        "benzi-mcp"
      ]
    }
  }
}
```

### Codex

```bash
codex mcp add net-varianttech-benzi -- uvx benzi-mcp
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "net-varianttech-benzi": {
      "type": "local",
      "command": [
        "uvx",
        "benzi-mcp"
      ],
      "enabled": true
    }
  }
}
```

### OpenClaw

```bash
openclaw mcp add net-varianttech-benzi --command uvx --arg benzi-mcp
```

### Hermes

```yaml
mcp_servers:
  net-varianttech-benzi:
    command: "uvx"
    args: ["benzi-mcp"]
```

### Netclaw

```json
{
  "McpServers": {
    "net-varianttech-benzi": {
      "Transport": "stdio",
      "Command": "uvx",
      "Arguments": [
        "benzi-mcp"
      ]
    }
  }
}
```

### Vellum

```bash
assistant mcp add net-varianttech-benzi -t stdio -c uvx -a benzi-mcp
```

### Other

```json
{
  "mcpServers": {
    "net-varianttech-benzi": {
      "command": "uvx",
      "args": [
        "benzi-mcp"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-10-17 (score 69, +1)

No change was recorded against any check on this day. Stability & Change Management went from 43 to 47. That category is still filling its 30-day observation window: 13 days of observed history at the previous scan, 14 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-10-15 (score 68, +32)

- [security improvement] Injection markers: unverified → pass
- [functional regression] MCP protocol: unverified → fail
- [functional improvement] Schema quality: 309 → 268
- [functional improvement] Tool coverage: unverified → 100
- [functional improvement] Stability: unverified → 0.40
- [functional] Package version: 0.1.3 → 0.1.6

### 2026-10-12 (score 36, −26)

- [security regression] Tool safety: pass → unverified
- [security] Stability: Stability not yet verified: we do not have a sandbox capture of the MCP schema this version of the package serves yet.
- [functional regression] Capabilities: fail → unverified
- [functional regression] Tool coverage: 100 → unverified
- [functional] Schema quality: Schema quality not yet verified: we do not have a sandbox capture of the MCP schema this version of the package serves yet.
- [functional] Package version: 0.1.3 → 0.1.5

### 2026-10-04 (score 62, +15)

- [security improvement] Malware scan: unverified → pass

### 2026-10-03 (score 47, +26)

- [security improvement] Injection markers: unverified → pass
- [security improvement] Malware scan: unverified → pass
- [security] First check of Judged manipulation: pass
- [security] Stability: Stability not yet verified: not enough scan history yet (needs a 30-day window).
- [functional regression] MCP protocol: unverified → fail
- [functional improvement] Tool coverage: unverified → 100
- [functional] First check of Tool coverage: 60
- [functional] First check of Schema quality: fail
- [functional] First check of Schema quality: excellent
- [functional] First check of Destructive annotations: pass
- [functional] First check of Schema quality: fail
- [functional] Package version: 0.1.1 → 0.1.3
- [functional] Package version: 0.1.1 → 0.1.2

### 2026-10-02 (score 21)

First indexed and scored.

## MCP tools (21)

### `read_reference` (~151 tokens)

Read one of Benzi's reference topics in full -- detail kept out of your system prompt so you only load what a job needs. Topics: "artifact" (how the map fits together -- call flow vs data flow and where they join, the symbol kinds, the UID-keyed tables the tools query), "uids" (the UID scheme, code and markup alphabets, the C/C++ declaration-vs-definition wrinkle), "languages" (how deep the map goes per language, and where an empty result means 'not indexed' rather than 'nothing uses it'), "markup" (anchors, the three layers, the cascade, its tools).

Input parameters:

- `topic` (string, required): which reference topic to read

### `list_files` (~171 tokens)

ONE directory level of the map (like `ls`, not `ls -R`). Returns {dir, files, subdirs}: `files` are the analyzed files DIRECTLY in `path`, each with language, line count, docstring, top-level symbols (id + kind + line -- every id is accepted by every other tool) and constant names; `subdirs` are the immediate folders, each with a recursive file count so you see where the code mass is. Omit `path` for the root, then drill with list_files("src/foo"). This is the project's OWN code; libraries are list_dependencies, non-code files list_unlisted_files.

Input parameters:

- `path` (string): a directory to open, project-root-relative (e.g. "src/parser"); omit or "" for the root

### `list_dependencies` (~264 tokens)

The LIBRARY surface, deduped to library roots with usage counts (`numpy.array`/`numpy.mean` both roll into one `numpy` entry) -- "third_party": external code leaned on but not owned, plus the raw import statements behind that evidence. OMIT name_or_id for THE WHOLE REPO; pass a file path or a scope id (function/class) to scope the same dedup to just that target. The always-deduped-roots view -- for raw call SITES (name/args/line) at any of the same granularities, use list_external_calls instead. A separate `unresolved_imports` field (sibling to third_party, NOT inside it) lists imports whose own syntax already proves in-repo intent (a relative import, require_relative, a quoted #include, rust's use crate/self/super) but still failed to resolve to a file -- a compiler gap, never counted as a dependency. Aggregated from references; no vendored code is parsed. Non-code repo files are list_unlisted_files, calls the compiler couldn't classify are list_unresolved_calls.

Input parameters:

- `name_or_id` (string): a repo-relative file path or a scope id (function/class); omit entirely for the whole repo

### `list_unlisted_files` (~180 tokens)

Repo files the map does NOT index, each with its reason: "no_grammar" (non-code assets -- images, data, configs, docs, lockfiles -- and any language without a grammar), "data_blob" (supported extension skipped as minified/base64), "parse_error" (a supported file that crashed analysis -- a defect worth flagging), "not_downloaded" (hosted demo only: listed by GitHub's tree, never fetched). Also excluded_dirs, the directories the walk deliberately skipped (vendored/build/VCS), so absence is never silent. Repo-relative paths: read_source opens any of these, and your own grep searches them -- benzi_grep does NOT, it only sees indexed files. An unlisted schema.sql, fixture json, or a whole language without a grammar can still hold the answer.

### `list_unresolved_calls` (~256 tokens)

Call sites the compiler SAW but could not settle -- neither in-repo nor provably a library call. Each carries `supposed` (raw call text as a scope::name id, `ax::plot` -- a lead, NOT a verified symbol) and `reason`: "opaque_receiver" (receiver expression dropped -- a nested call's result, a dereferenced pointer) or "untyped_local" (known name, type never inferred -- a parameter's .method(), a called function pointer), neither naming a target; or "name_collision" / "linkage_refused", which DID name 2+ real in-repo targets in `candidates` with no evidence to choose -- worth reading, not a dead end. OMIT name_or_id for THE WHOLE REPO (per-file counts, where the dark mass sits); pass a file path or a scope id (a function/class) for that target's entries with lines. Distinct from candidate_edges (dynamic dispatch, unresolvable statically) and list_dependencies (library evidence exists).

Input parameters:

- `name_or_id` (string): a repo-relative file path or a scope id (function/class); omit entirely for the whole-repo per-file summary

### `benzi_grep` (~377 tokens)

Find TEXT -- a literal, a comment, an error string, a CSS class, an HTML id, a partial token -- inside the files the map INDEXES: analyzed code files plus the markup files (HTML/CSS/DOM-js) the code graph doesn't parse. It does NOT reach UNINDEXED files: a language with no grammar, a .sql, a fixture (list_unlisted_files inventories them). There, this returns "no matches" whether or not the text is present -- so for anything unindexed use your own grep, which has no such limit. The PATTERN IS A PYTHON REGEX, not a literal -- escape ( ) . * for literal characters (`\.pipe\(`). Each match carries its ENCLOSING SYMBOL (`in` -- a usable id), so it locates the handler, not just a line. Returns {path, line, in, text}; uncapped, true total always reported. `context` gives N lines around each hit (like grep -C, skips a follow-up read_source); `multiline` for patterns spanning lines.

Input parameters:

- `context` (integer): optional -- lines of context to return around each hit (like grep -C); default 0
- `multiline` (boolean): optional -- search whole-file with . matching newlines, for a pattern that spans lines; default false
- `path` (string): optional -- only files whose path contains this substring
- `pattern` (string, required): a PYTHON REGEX -- ( ) [ ] . * + ? | are metacharacters, so escape them for literal text: `\.pipe\(` not `.pipe(`. A pattern with no metacharacters is just a substring. If it doesn't compile the searc…

### `search_symbols` (~154 tokens)

Turn a half-remembered NAME into the UIDs every other tool takes. Fuzzy case-insensitive SUBSTRING match over every symbol including nested methods ('pack' finds _skyline_pack, build_pack) AND every BINDING: function locals and class-body data (symbolKind "Binding", with its defining line and value). Each hit carries `id`, `symbolKind`, `path`, `line_span` and caller/reference counts, so you can usually tell which one you meant without a follow-up get_definition. Takes a LIST of names to resolve in one call.

Input parameters:

- `query` (string|array, required): A name (or partial name) to search for, or a list of names to resolve in one call

### `get_definition` (~192 tokens)

The declaration card -- what a symbol IS: file, line span, parent, children, signature verbatim (defaults, annotations, splats), return type, decorators (@app.route, [HttpGet] -- evidence of how it gets invoked), base classes, docstring, and "fields" (for a struct/enum the fields ARE the meaning). A module adds its imports and which resolve in-repo. Constants and VARIABLES get a binding card: the ordered assignment sequence, every line the value could have changed, each rhs classified. "called_by" is who PROVENLY calls this, with a count; "referenced_by" is who HOLDS or reads it WITHOUT calling -- attribute reads, registry values, Thread(target=...) wiring, aliases -- the half of blast radius get_callers can't see. Body: skim_source. Raw text: read_source.

Input parameters:

- `name_or_id` (string, required)

### `profile` (~96 tokens)

The full 360 on one symbol in ONE call -- its declaration (what it IS), the functions it calls + data it defines (what it DOES), who CALLS it + who HOLDS it (the full blast radius), and what each parameter is passed one hop back (what FEEDS it). Deeper on any dimension: skim_source, backflow, get_callers, read_source.

Input parameters:

- `name_or_id` (string, required)

### `get_hierarchy` (~120 tokens)

The class TREE for a type: resolved BASES (up) and direct SUBCLASSES (down) -- inheritance the compiler resolved at build time, which get_definition shows only as raw `extends` text. Returns "bases" (direct), "ancestry" (the whole chain, nearest first), "subclasses" (direct), each subclass tagged with the methods it OVERRIDES here and whether it has subclasses of its own. In-repo relations only (an external base like Thread won't appear).

Input parameters:

- `name_or_id` (string, required)

### `list_external_calls` (~280 tokens)

A FILE's, a SCOPE's, or (name_or_id omitted) THE WHOLE REPO's EXTERNAL (stdlib/third-party) call SITES, each with raw arguments and line -- the calls a skim shows WITHOUT a → . profile flattens these to bare names and drops args/lines. A class returns the union across its methods, a file the union across every scope in it, no argument the union across the whole repo (capped -- narrow to a file/scope for the rest, or use list_dependencies for the already-deduped library-roots-with-counts view instead of raw sites). Each entry tagged with which file/scope makes the call. Entries of kind "unresolved" are the honest can't-settle case: `reason` names the wall -- opaque_receiver / untyped_local (no target named) or name_collision / linkage_refused (2+ real in-repo targets in `candidates`, no evidence to choose; NOT library calls) -- and `supposed` holds the raw text as scope::name, a lead, not a verified id. list_unresolved_calls collects these too; get_callers is the in-repo direction.

Input parameters:

- `name_or_id` (string): a repo-relative file path or a scope id (function/class); omit entirely for the whole repo

### `get_callers` (~133 tokens)

Every function that calls X -- the reverse direction a body's own outline can't show. Dynamic dispatch is covered honestly: an override also gets "possible_callers_via_dispatch" (callers of the base method whose call can land here at runtime -- possible, not proven), and a method with overrides gets "overridden_by" (its callers may dispatch there instead). Treat both as blast radius. This is half of it; the other half -- who HOLDS X without calling it (registries, callbacks, aliases) -- is "referenced_by" on get_definition.

Input parameters:

- `name_or_id` (string, required)

### `call_tree` (~212 tokens)

The TRANSITIVE call closure from one function, which get_callers (one hop) and trace_path (two endpoints) can't give. "direction": "forward" = everything this transitively CALLS (what does this route actually touch?); "reverse" = everything that transitively calls it (blast radius before a change). Each node carries "depth" (hops out) and a "leaf" flag where the chain ends. Proven edges only unless include_candidates also follows the speculative tier, whose nodes come back tagged "via":"candidate". Cycle-safe and capped -- a truncated closure says so.

Input parameters:

- `direction` (string, required): forward = what it transitively calls (reachability); reverse = what transitively calls it (blast radius)
- `include_candidates` (boolean): optional: also follow speculative edges -- same-named ambiguity AND dynamic-dispatch candidates (an override reachable because its base method is called); such nodes come back tagged via:candidate (d…
- `name_or_id` (string, required)

### `skim_source` (~234 tokens)

Reach for this FIRST when you need to know what a body does; it tells you which lines are worth read_source. A one-level outline: calls (with the raw arguments at each site), assignments, return/yield/raise, LOOP exits (break/continue, with their label where they carry one -- so you can see whether a loop runs to completion), and condition headers verbatim (`if not data:`, `for row in cursor:`) -- in source order, line-numbered, INDENTED to mirror real nesting. A leading → marks a call landing in THIS codebase; skim that id to descend one level, no → means external. A class returns "members" instead (its calls live in its methods); a file returns both. It omits the computation BETWEEN these lines (arithmetic, expression bodies, comments). Long outlines page at 40 steps; a truncated tail names the offset for the next page.

Input parameters:

- `name_or_id` (string, required)
- `offset` (integer): optional: skip this many steps (for paging long outlines; a truncated page tells you the offset to use)

### `read_source` (~142 tokens)

The symbol's ACTUAL source, sliced to the exact span the map knows -- full fidelity: control flow, values, comments as written. Use when the answer hinges on real code ('what threshold', 'which branch'). Read the narrowest symbol that answers; start_line/end_line (absolute, the same lines skims/edges/callers report) fetch a window of a long body. Oversized spans truncate with a note.

Input parameters:

- `end_line` (integer): optional: last line to read (absolute file line number)
- `name_or_id` (string, required)
- `start_line` (integer): optional: first line to read (absolute file line number)

### `trace_path` (~158 tokens)

The call chain between two functions AND the data along it: each hop's call site (line + raw args) plus "binds", those args zipped to the callee's parameter names, so renames read step to step (`req.body` enters `validate` as `payload`). No direction argument -- it searches both ways and reports which in "direction": forward, reverse (hops still in real call order), or undirected (shared intermediates only -- noted, not a flow). RESOLVED sites only: a value stashed in a field or passed through an untraced method call won't appear; read_source that hop.

Input parameters:

- `from_name_or_id` (string, required)
- `to_name_or_id` (string, required)

### `backflow` (~205 tokens)

Where does this value come from? Reverse DATAFLOW (mirror: forwardflow) -- what FEEDS a function's parameters, in TWO tiers. TIER 1: per proven call site, the raw argument EXPRESSION bound to the parameter it lands on -- `data` is fed `req.body` from `handle` at app.py:40. TIER 2: each expression walked BACKWARD through the caller's assignments and up the call graph to where the value ORIGINATES (literal, constant, a call's return, an import) or an honest wall; depth-capped, exact or stopped, never guessed. A parameter a caller OMITTED comes back "defaulted" with the declared default and how many sites took it -- distinct from "no_static_feed" (nothing feeds it, no default). Best-effort on *args/**kwargs: a spread stays unresolved rather than guessed (as in trace_path).

Input parameters:

- `name_or_id` (string, required)

### `forwardflow` (~149 tokens)

I changed what this returns -- who consumes it, and how far does it reach? The MIRROR of backflow, in TWO tiers. TIER 1: for every proven caller, the variable that CATCHES the return (`data = load()`) or an inline use. TIER 2: each caught value walked FORWARD -- passed on as an argument (landing on a named callee parameter), stored in a self.x field, or returned onward up the call graph -- to where it LANDS or an honest wall; depth-capped, exact or stopped, never a guess. Same best-effort binding as backflow/trace_path.

Input parameters:

- `name_or_id` (string, required)

### `markup_list_files` (~39 tokens)

Markup-engine orientation: which files carry UI (HTML/CSS/JS, embedded or standalone) and how much, plus the total anchor count.

### `markup_find_anchor` (~83 tokens)

Fuzzy-find an ANCHOR by name -- a handle the UI hangs on: id (#tree-panel), class (.panel), or tag. Each hit shows its declare/style/grab tallies. Turns 'the left panel' into a handle for markup_get_definition.

Input parameters:

- `query` (string, required): handle name or partial (e.g. 'tree', '.row')

### `markup_get_definition` (~163 tokens)

The anchor card: where a handle is DECLARED (html element), STYLED (css rules + values + any @media), and GRABBED (js lookups), each with a layer-tagged uid (html::/css::/js::file:line). Those three lists ARE the blast radius. An id handle also gets `effective`: the WINNING rule per property (specificity + source order) as {prop:{uid,value}} -- to restyle, edit effective[prop].uid, since editing a non-winning styled rule does nothing on screen. Universal/@media/state/context rules appear in styled but aren't resolved into the winner.

Input parameters:

- `name_or_id` (string, required): a handle: name, #id, .class, or [attr]

## Diagnostics

Captured diagnostic sections: Provenance, Install scripts, Dependencies. The full working is on the page: https://verifymcp.io/servers/net-varianttech-benzi/benzi-mcp#diagnostics

## Score history

- 2026-10-18: 69
- 2026-10-17: 69
- 2026-10-16: 68
- 2026-10-15: 68
- 2026-10-14: 36
- 2026-10-13: 36
- 2026-10-12: 36
- 2026-10-07: 62
- 2026-10-06: 62
- 2026-10-04: 62
- 2026-10-03: 47
- 2026-10-02: 21

## Common questions

### What is the Benzi MCP server?

Benzi is an MCP server listed in the public MCP registry as net.varianttech/benzi. Compiler-backed code intelligence: calls, data flow, references and class hierarchy as tools. This page covers its PyPI package (benzi-mcp).

### Is the Benzi MCP server safe to use?

Benzi scores 69 out of 100 on VerifyMCP. We found no known CVEs affecting it as of 18 October 2026. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the Benzi MCP server expose?

Benzi exposes 21 tools: read_reference, list_files, list_dependencies, list_unlisted_files, list_unresolved_calls, and 16 more. Their descriptions and schemas cost roughly 3,759 tokens of context every time the server is loaded.

### Is the Benzi MCP server still maintained?

Benzi is still listed as active in the MCP registry. We last reached this channel on 18 October 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

### What licence is the Benzi MCP server under?

Benzi declares the Proprietary licence, which is not on the OSI-approved list. Read the terms before using it at work, and note this covers the source only, not the cost of any service it calls.

## Links

- PyPI project: https://pypi.org/project/benzi-mcp/
- Socket report: https://socket.dev/pypi/package/benzi-mcp
- Repository: https://github.com/oooscoos/Benzi
- Website: https://varianttech.net/
- Changelog RSS feed: https://verifymcp.io/servers/net-varianttech-benzi/benzi-mcp.xml
- Changelog JSON feed: https://verifymcp.io/servers/net-varianttech-benzi/benzi-mcp.json
- HTML version of this page: https://verifymcp.io/servers/net-varianttech-benzi/benzi-mcp
