Everything Contour
does endpoint↔consumer mapping, full chain tracing (UI → API → service → database),
drift detection, SOLID structural lint, test coverage overlay, scheduled-task /
service-to-service / event-driven flow tracking, use-case tagging with Jira linkage, and
the shareable visual report plus a bundled local, read-only MCP server that exposes
the same trace index to Claude Code, Claude Desktop, and other MCP clients.
This extension installs side by side with plain Contour you don't need to choose one. If you don't
need the MCP server, install
Contour
instead; this is a superset of it.
How this differs from vanilla Contour
The same trace engine, packaged for this extension. The indexing, matching, drift
detection, coverage overlay, and use-case tagging live in
@contour/trace-engine-mcp, a workspace package inside this repo.
Plus an MCP server. On first activation this extension writes a local, git-ignored
MCP server registration (.mcp.json for Claude Code, .vscode/mcp.json for VS Code's
own MCP support) pointing at a bundled Node script no separate install step, and each
client's own trust prompt still applies (this extension never auto-approves itself).
The trace index becomes a cache file, not just in-memory extension state. Every
time Contour finishes a reindex (full or incremental never a separate timer), it
atomically rewrites .contour-mcp/cache.json in your workspace. The MCP server reads
that file fresh on every tool call, so an agent's answer always reflects your latest
edit, or clearly tells you to wait for Contour to finish reindexing rather than
guessing.
Install
Install from the Marketplace (search "Contour MCP"), or build and install a .vsix
locally:
Open a workspace with at least one backend (Java/Spring) and one frontend (React/JS)
folder. Contour MCP indexes automatically on activation; watch the "Contour MCP" output
channel for progress. Once the first index completes, the bundled MCP server is
registered automatically Claude Code or VS Code will prompt you to trust it the first
time it's discovered, same as adding any other MCP server by hand.
The eight tools
All read-only. None writes a file, runs a command, or calls any external API including
Jira: if a flow is tagged with a Jira key, only what's already stored locally is
surfaced, never a live lookup. get_coupling and list_hotspots are only registered
while Coupling Map is enabled, and get_quality_findings only while
Quality Lens is enabled — genuinely absent from the tool list while
they're off, not just hidden.
Tool
Input
Returns
list_flows
entity
Flow summaries (endpoints, scheduled tasks, service calls, events) touching a file, service, or table
get_trace
file, symbol?
The scoped call chain for a file UI/consumer → API → service → database
get_flow
jira_key
The flow tagged to that ticket description, tags, test coverage, as stored locally
get_drift
scope?
Where the traced architecture and the real code have diverged, optionally scoped
export_context
scope, task
A minimal, packaged context bundle sized for a specific task
get_coupling
target
Whether a DTO, service method, or entry point is shared across unrelated parts of the system, and how risky (good/warning/critical/excluded/unknown)
list_hotspots
minSeverity?, limit?
The ranked list of shared-contract/high-fan-in coupling findings, worst first
get_quality_findings
none, file + symbol?, or jira_key
With no arguments, a workspace summary (totals by source and severity, how many files Sonar has results for, the 20 flows with the most issues). Otherwise SonarQube for IDE and Contour findings for a file, a member, or a whole flow, grouped like the Quality view, with the files nobody has analysed yet
Example prompts, once the server is connected:
list_flows"What flows touch OrderController?"
get_trace"Trace OrderController.java end to end, from the API down to the
database."
get_flow"What's the flow tagged to ORD-123, and is it tested?"
get_drift"Has anything drifted between the frontend and backend in the
orders service?"
export_context"I need to add a cancel-order endpoint give me the minimal
context for that."
get_coupling"Is OrderSummaryDto safely shared, or is it hidden coupling?"
list_hotspots"What are the worst coupling hotspots in this codebase right
now?"
get_quality_findings"What quality issues are there along the POST /orders
flow, and which files haven't been analysed?"
The chain tracer understands real Spring DI now
Interfaces with more than one real implementation used to be invisible to the tracer.
Now every hop in a chain resolves through a priority chain — an explicit
chains.beanOverrides config hint, then @Primary, then @Qualifier/bean-name
matching, then (the common case) the single remaining candidate — and when none of
those disambiguate it, the chain says so explicitly instead of guessing:
Every hop also carries a confidence (certain / resolved / ambiguous) and, where
relevant, a note disclosing an @Transactional/@Async/@Cacheable AOP annotation, a
@Profile/@ConditionalOnProperty conditional-wiring guard, or a
@PreAuthorize/@Secured/custom authorization annotation on that hop — verbatim,
including arguments, never evaluated or assumed. DDD/hexagonal (ports-and-adapters)
layouts are supported too: a domain-layer repository interface with no Spring Data
ancestry, implemented by an @Repository-annotated infrastructure adapter, resolves
correctly — a record or enum in that adapter role (not just a class) resolves the
same way. All of this is configurable per project under chains in
contour.config.json; VS Code offers completion and descriptions for every key while
you edit that file.
Controllers built contract-first from an OpenAPI spec are indexed too
A Controller that implements an OpenAPI-codegen-generated interface (the
interfaceOnly: true pattern common with openapi-generator-maven-plugin) puts its real
@RequestMapping/@GetMapping/etc. annotations on the generated interface's methods, not
on the @Overrides in your own source the generated interface itself lives under
target/, which the indexer correctly never treats as project source. The endpoint
indexer now resolves those @Override methods back to their generated interface's own
mapping annotation, scoped per backend service so it can't cross-resolve across unrelated
services in a monorepo. Confirmed against real spring-petclinic-reactjs: previously 7 of
its 8 controllers indexed to zero endpoints; now all 8 resolve correctly. Works out of the
box for the common Maven default output path
(target/generated-sources/openapi/**); add more locations via backend.generatedApiGlobs
in contour.config.json for a Gradle build or a customized <output>.
SOLID structural lint
Advisory-only Problems-panel warnings, off by default (contourMCP.solidLint.enabled).
Five checks, each independently toggleable via the solidLint namespace in
contour.config.json:
Controller → Repository layering a Controller calling a Repository directly,
skipping the Service layer.
Field injection@Autowired on a field instead of a constructor. Applies
everywhere, not just Controllers; never fires inside src/test/**, where field-level
@Autowired/@MockBean is normal and still recommended by Spring itself.
Entity leak a @Entity-annotated class returned from, or accepted via
@RequestBody into, a Controller endpoint coupling the API shape directly to the
database schema.
Concrete injection (new in 0.4.0, concrete-injection) a Spring bean injected as
its concrete class (OrderServiceImpl) when it implements an interface in the same
codebase (OrderService). A @Qualifier excuses it only when two or more beans
implement that interface.
Direct API call in a React component (new in 0.4.0, frontend-direct-api-call)
a fetch, axios or configured wrapper call made inside a component (function or
class, including useEffect callbacks, handlers and componentDidMount) instead of
in an API/service module or a use* hook. Files under api/, services/, hooks/,
queries/, store/ and similar folders are allowed; add your own with
solidLint.frontendApiAllowedIn.
All five default on once the setting is enabled; set solidLint.fieldInjection,
solidLint.entityLeak, solidLint.concreteInjection and/or
solidLint.frontendDirectApiCall to false in contour.config.json to turn a specific
one off without disabling the rest. Every check was validated against real Spring Boot source
before shipping, not just synthetic fixtures the field-injection detector, for example,
was proven against a real 2-true/9-false-if-naive split in production code, where a
naive "flag any @Autowired" regex would have wrongly caught 9 real constructor/method
injections alongside the 2 real field-injection violations.
Coupling Map
Off by default (contourMCP.couplingMap.enabled). A different question than the SOLID
lint above: instead of "is this one class well-formed," it asks "is this DTO or service
method being reused by two or more unrelated parts of the system in a way that
creates hidden coupling" — built on the same traced call graph, plus two lightweight
signals (cognitive complexity, integration fan-out). A bare-minimum, defensible signal,
not a full architecture-governance suite.
Tuning lives under a couplingMap namespace in contour.config.json: exclude
(built-in defaults plus your own package/name-pattern exclusions), markedShared
(targets a human has reviewed and accepted as intentionally shared), relatedness (how
flows get clustered), and theme (webview colors). Marking, unmarking, or including an
excluded target back into analysis is always a human decision made in the editor —
never something an MCP client can do; get_coupling and list_hotspots (see the tools
table above) are read-only, same guarantee as the rest of the server.
Two commands:
Contour MCP: Open Coupling Map — a webview with three views: Overview (every
cluster, sized by scope, ringed by risk), Detail (one flow's full internal chain plus
any bridge into another flow), and Matrix (every pair of flows at once). Click
through to mark/unmark a target as intentionally shared, or include an excluded
target back into analysis — both write straight to contour.config.json.
Contour MCP: Export Coupling Map — the same map as a single, self-contained HTML
file for sharing with someone who doesn't have the extension installed. Pan/zoom,
search, and hover all still work standalone; marking and click-to-navigate need the
live webview.
Quality Lens
Off by default (contourMCP.qualityLens.enabled). Issue counts where you read code:
SonarQube for IDE's findings plus Contour's own SOLID lint, on
every Java / JS / JSX / TS / TSX file Contour indexes, and — what nothing else shows —
summed along each traced flow.
File lens on line 1: Quality: 3 issues · part of 2 flows (backend) or
Quality: 1 issue · calls 1 endpoint (frontend).
Member lens above each class, method, function or React component with findings:
save() · 1 issue · used by 1 endpoint.
Flow count next to Contour's existing endpoint, job, event and call-site lenses:
quality: 4 issues, counted across the whole chain (frontend → controller → service →
repository), or quality: clean.
The Contour Quality view (Explorer, below the use-case view) lists the issues
behind whichever lens you clicked: by member for a file, by chain layer for a flow.
Filter… picks All / Sonar / Contour / the highest severity, Pin holds the
current target, Copy for AI copies the issues (with the flow's AI context for a
flow), and a file Sonar hasn't analysed yet gets Open to analyse. Clicking any row
opens the file there.
All issues (the view's Show all issues button, or Contour MCP: Show All
Quality Issues) opens an editor tab with every issue in the workspace, as three
sortable tables: by flow (worst first), by file and by rule. The header says how much
of the workspace Sonar has results for ("Sonar has results for 12 of 99 indexed files ·
open files to analyse more"). Click a flow to see it in the Quality view, or a file or
finding to open the code. The tab updates itself as results change (at most every few
seconds), and Show suppressed and baselined lists the findings that aren't counted.
A file SonarQube for IDE hasn't analysed (it usually analyses files as you open them) is
always reported as not analysed, never as clean, and a flow touching one says how
many files are missing. Contour's own findings count only while
contourMCP.solidLint.enabled is on; the view and the lens tooltip say so when it isn't.
Settings
Setting
Default
contourMCP.qualityLens.enabled
false
Master switch: lenses, the Quality view and the MCP quality data.
contourMCP.qualityLens.scope
withFindings
flows: only the flow counts. withFindings: also file and member lenses where there are findings. all: also clean members.
contourMCP.qualityLens.includeSonar
true
Read SonarQube for IDE diagnostics; off counts Contour's findings only.
contourMCP.qualityLens.showCriticalMarker
false
Append ⚠ when a count includes a High or Blocker finding (see the severity note below).
contourMCP.qualityLens.maxMemberLensesPerFile
40
Beyond this, one +N more members with findings lens opens the view.
contourMCP.qualityLens.sonarDiagnosticSources
["sonarqube", "sonarlint"]
Diagnostic source values treated as Sonar.
In contour.config.json, qualityLens.excludeSonarRules (e.g. ["typescript:S1172"])
lists Sonar rules never to count, and qualityLens.baselineFile moves the baseline file
(below).
Sonar, via connected mode. Contour reads only the diagnostics SonarQube for IDE has
already published in VS Code. Bind SonarQube for IDE to your SonarQube Server or Cloud
project (connected mode) and those diagnostics follow the team's quality profile and
server-side issue status, so Contour's counts do too. Contour never runs an analysis and
never talks to SonarQube. SonarQube for IDE publishes every issue at VS Code level Warning,
so Contour reads Sonar's own impact severity (Blocker / High / Medium / Low / Info) from the
diagnostic's data.impactSeverity, and Filter… → High+ lists High and Blocker issues.
That field isn't a documented SonarQube for IDE API and could change in a release; an issue
without it is shown at its VS Code level (Error / Warning / Info) and the output says the
severity is approximate.
Closed files. SonarQube for IDE analyses files as you open them and clears their
results when you close them. Contour keeps a closed file's last Sonar results, marked "as
of
No network calls. Quality Lens makes no external calls of any kind: everything comes
from VS Code's local diagnostics and Contour's own index.
Baseline. To adopt the lint on an existing codebase, run Contour MCP: Create
Quality Baseline. It records every current Contour finding in contour.baseline.json
(next to contour.config.json, or in the first workspace folder), and those findings
stop counting, leave the Problems panel and the MCP tools, and move to a collapsed
Baselined (N) group in the Quality view. New findings still show. A fingerprint
survives lines added or removed elsewhere in the file; editing the flagged line itself,
or moving it into another method, makes it a new finding. Commit the file to share it;
Contour picks up changes to it (an edit, a git pull) without a re-index. Contour
MCP: Clear Quality Baseline deletes it. Sonar findings are never baselined: SonarQube
has its own new-code period.
Suppression. For a single accepted finding, add a comment on the line above it, or at
the end of its line:
The same // syntax works in JS/TS. List several codes with commas
(// contour-ignore concrete-injection, field-injection: …). A comment without a reason
still suppresses, but shows an Information-level "add a reason" hint. Suppressed findings
aren't counted or shown in the Problems panel; Filter… → Show suppressed lists them,
with their reasons, in the Quality view. A contour-ignore never applies to Sonar
findings; suppress those in SonarQube.
For AI agents. While Quality Lens is on, the extension writes
.contour-mcp/quality.json next to the trace cache (at most every few seconds, removed
when the feature is turned off). The MCP server then offers get_quality_findings and
adds a one-line quality summary to get_trace and get_flow; every answer says when
the snapshot was taken and which files weren't analysed. get_drift lists Contour
findings the same way the Problems panel does: baselined and suppressed ones are counted
in a single "not shown" line.
Restart your MCP client after turning Quality Lens on or off. The MCP server reads
the setting once, when it starts, so get_quality_findings appears only after Claude
Code (or your client) reconnects. Contour reminds you once when you turn it on. The same
applies to Coupling Map.
Staleness
The MCP server never trusts an in-memory copy it re-reads .contour-mcp/cache.json
from disk on every call. If the cache is missing, or a file your query touches was
edited after the cache was last built, or the cache itself has passed its backstop max
age (contourMCP.cacheStalenessThresholdMinutes, default 360 minutes), you get a clear
message to let Contour finish reindexing never stale or fabricated data.