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.jsonfor Claude Code,.vscode/mcp.jsonfor 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.jsonin 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:
npm install
npm run compile
npx vsce package
code --install-extension api-contract-tracer-mcp-*.vsix
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 five 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.
| 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 |
Example prompts, once the server is connected:
list_flows"What flows touchOrderController?"get_trace"TraceOrderController.javaend to end, from the API down to the database."get_flow"What's the flow tagged toORD-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."
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:
-> JdbcOwnerRepositoryImpl.findById() [repository, ambiguous]
[note: 2 candidate implementations, none disambiguated — see chains.beanOverrides | @Profile("jdbc")]
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 — see
docs/indexing-engine-upgrades.md for the full
config reference and the evidence behind each of these claims (real bugs found on real
open-source codebases, not just synthetic fixtures).
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).
Three checks today, 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
@Autowiredon a field instead of a constructor. Applies everywhere, not just Controllers; never fires insidesrc/test/**, where field-level@Autowired/@MockBeanis normal and still recommended by Spring itself. - Entity leak a
@Entity-annotated class returned from, or accepted via@RequestBodyinto, a Controller endpoint coupling the API shape directly to the database schema.
All three default on once the setting is enabled; set solidLint.fieldInjection and/or
solidLint.entityLeak 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.
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.
Further reading
docs/indexing-engine-upgrades.mdDI resolution, DDD/hexagonal support, the confidence/disclosure model, targeted invalidation, and the optional tree-sitter AST parsing path, each claim backed by a test or a real-repo validation run.docs/mcp-technical-writeup.mdwhy handing an already-computed trace to an AI agent beats letting it re-derive the same call graph by reading files, with token-count evidence from a real trace.