Contour
The shape of your system, traced end to end for developers and AI agents alike.
When AI tools help write code, they're usually looking at one file at a time. They don't automatically know that a React component calls a specific API endpoint, that endpoint calls three layers of services and repositories, or that a scheduled job quietly writes to the same table. That gap leads to two common problems: agents editing one side of a contract without touching the other, and developers spending real time reconstructing a flow by hand across five files just to make a safe change.
Contour builds a live map of your codebase's real call chains from a UI click, through your REST API, through your service and repository layers, down to the database and makes that map available three ways: inline in your editor, as a shareable visual report, and as compact, accurate context you can hand directly to an AI agent.
Two versions of this extension exist. This is the standalone edition everything above (inline annotations, drift detection, visual reports, AI context copy) works entirely within VS Code, no MCP required. If you want Contour's trace data exposed as an MCP server so tools like Claude Code, Claude Desktop, or any other MCP client can query flows, chains, and drift directly install Contour MCP instead.
Requirements
This extension is built for a Java / Spring Boot backend paired with a React / JavaScript or TypeScript frontend. It works with a single service or multiple microservices in the same workspace. It is not a general-purpose tool for other stacks if your backend uses something other than Spring MVC-style controllers, or your frontend doesn't use axios/fetch-style HTTP calls, results will be limited or empty.
Open your backend and frontend folders together in one VS Code workspace (multi-root) to get the full picture. A backend-only or frontend-only workspace still works, with reduced cross-linking.
What it does
Endpoint ↔ consumer mapping
See directly above each @GetMapping/@PostMapping/etc. how many frontend call sites use it and above each axios/fetch call, exactly which controller method handles it. Click to jump straight there. No known caller, or a call that resolves to nothing, is always shown explicitly never silently hidden.
Full call chain tracing
Follow a request all the way from the controller through your service layer, into your repository layer, and down to the actual database operation including the derived query or @Query behind a Spring Data method. Multi-branch chains and deep call graphs are both supported.
Drift detection (opt-in)
Catch it when a path, method, or DTO field changes on one side of a contract and the other side wasn't updated to match flagged inline as you edit, with an escape hatch for intentionally versioned endpoints. Off by default; turn it on once you're ready to rely on it.
Test coverage overlay
See, right next to each endpoint, whether it's covered by a real test pulled from your existing JaCoCo and Jest/Vitest coverage reports, not recomputed. No report found is shown honestly, not as a misleading 0%.
AI context export
Copy a compact, accurate summary of one endpoint its full call chain, its real consumers, its test coverage, its use case straight to your clipboard, ready to paste into a prompt. This is the difference between an AI agent guessing at your architecture and knowing it.
Use-case tagging
Group related flows under a human-readable label "Order Checkout," "User Onboarding" with an optional Jira key and free-form tags. Browse everything under one label in the sidebar, or export it as one combined context block.
Visual HTML report
Export a single, self-contained HTML file showing your whole traced system grouped by use case, filterable by type and coverage status that anyone can open in a browser, no extension required. Share it in a wiki, a PR description, or with someone who doesn't use VS Code at all.
Beyond REST: schedulers, service calls, and events
Contour doesn't stop at HTTP. It also traces:
- Scheduled tasks (
@Scheduled methods) see what they do and how they're covered.
- Service-to-service calls (
RestTemplate, WebClient, Feign clients) trace outbound calls to other services in your workspace.
- Event-driven flows Spring application events, Kafka, RabbitMQ, JMS, and (with clearly marked lower confidence) Solace JCSMP matched from publisher to consumer by event key or topic.
SOLID structural lint (opt-in)
Get a gentle nudge, not a hard error, when a controller reaches directly into a repository and skips the service layer. Off by default.
Project-specific conventions
If your project deviates from the defaults above a custom controller or scheduling annotation, a hand-rolled HTTP client wrapper, a Gradle multi-module layout, a non-Spring-Data repository base interface declare it in a committed contour.config.json, instead of those cases going undetected.
Where to put it: one contour.config.json per workspace folder, at that folder's root next to pom.xml/build.gradle for a backend folder, next to package.json for a frontend folder. For a Maven multi-module parent folder, it's still just one file at that folder's root (not one per submodule); scope a key to a specific submodule with a "WorkspaceFolderName/submodule-path" map key (see backend.serviceIds below).
What Contour: Generate Starter Config actually does: it's narrow, by design it only writes a file when it finds one of two specific things it can verify with certainty: a Gradle multi-module workspace (settings.gradle/.kts's include(...) list, when Maven's own <modules> isn't already covering it), or real process.env.REACT_APP_* usage in your frontend source. If neither applies to your project which is the common case it writes nothing at all, since guessing at values it can't verify isn't something this extension does anywhere. For everything else, write the file by hand; it's plain JSON, and you get editor autocomplete (hover for a description of every key) the moment the file is named contour.config.json.
Reference every available key, with example values (valid JSON comments aren't supported, so this is meant to copy from and trim down, not paste as-is):
{
"backend": {
"controllerAnnotations": ["ApiController"],
"mappingAnnotations": { "ApiRoute": "ANY" },
"serviceIds": { "Backend/gateway-svc": "api-gateway" },
"modules": ["gateway-svc", "orders-svc"],
"configFiles": [
{ "path": "config/app.yml", "portKey": "app.port", "contextPathKey": "app.base-path" }
],
"pathConstants": { "ApiPaths.USERS": "/users" },
"excludeGlobs": ["**/generated/**"]
},
"frontend": {
"httpWrappers": [
{ "functionName": "apiRequest", "methodArgIndex": 0, "urlArgIndex": 1 }
],
"clientNames": ["internalHttpClient"],
"baseUrlPatterns": [
{ "kind": "env", "prefix": "process.env.REACT_APP_" }
],
"excludeGlobs": ["**/__mocks__/**"],
"fetchLikeFunctions": ["authorizedFetch"]
},
"serviceCalls": {
"clientTypes": [
{ "typeName": "InternalHttpClient", "methods": { "call": { "methodArgIndex": 0, "urlArgIndex": 1 } } }
]
},
"events": {
"listenerAnnotations": { "CustomEventListener": "custom-bus" },
"producerTypes": [
{ "typeName": "CustomEventBus", "methodName": "publish", "mechanism": "custom-bus" }
],
"cloudStreamConfigFiles": ["config/bindings.yml"]
},
"scheduledTasks": {
"annotations": ["QuartzScheduled"]
},
"chains": {
"serviceAnnotations": ["BusinessService"],
"repositoryBaseInterfaces": ["MyBatisMapper"]
}
}
| Key |
What it's for |
backend.controllerAnnotations |
Custom annotation(s) that mark a class as a controller, beyond @RestController/@Controller |
backend.mappingAnnotations |
Custom HTTP-mapping annotation → method ("ANY" for a catch-all), beyond @GetMapping/etc. |
backend.serviceIds |
Explicit serviceId override, keyed by workspace-folder name (or "folderName/submodule-path") |
backend.modules |
Gradle submodule relative paths only needed if auto-detection didn't already find them |
backend.configFiles |
Extra server-config file(s)/key(s) to check for port and base path |
backend.pathConstants |
Resolves a Java constant reference used as a path (e.g. @RequestMapping(ApiPaths.USERS)) to its literal value |
backend.excludeGlobs |
Extra glob(s) to exclude from the backend file walk |
frontend.httpWrappers |
A hand-rolled HTTP wrapper's call shape, e.g. apiRequest(method, url) |
frontend.clientNames |
Extra known axios-instance-equivalent variable names |
frontend.baseUrlPatterns |
Extra base-URL env-var conventions, beyond Vite's import.meta.env.* |
frontend.excludeGlobs |
Extra glob(s) to exclude from the frontend file walk |
frontend.fetchLikeFunctions |
Custom function(s) sharing native fetch's own calling convention exactly — name(url, init?), method read from init.method, defaulting to GET. Use this instead of httpWrappers when the method is a property inside an object argument, not a bare positional one |
serviceCalls.clientTypes |
A custom internal HTTP client type for backend-to-backend calls, beyond RestTemplate/WebClient/@FeignClient |
events.listenerAnnotations |
Custom event-listener annotation → mechanism label, beyond @EventListener/@KafkaListener/@RabbitListener/@JmsListener |
events.producerTypes |
Custom event-producer type/method, beyond ApplicationEventPublisher/KafkaTemplate/RabbitTemplate/JmsTemplate |
events.cloudStreamConfigFiles |
Extra config file(s) to check for Spring Cloud Stream binding destinations |
scheduledTasks.annotations |
Custom scheduling annotation(s), beyond @Scheduled |
chains.serviceAnnotations |
Custom Service stereotype annotation(s), beyond @Service |
chains.repositoryBaseInterfaces |
Custom repository base interface(s), beyond Spring Data's own |
Personal overrides on top of a shared, committed file go in the apiContractTracer.config VS Code setting (same shape, layered on top array values append to the committed file's by default).
Getting started
- Open your backend and frontend folders in one VS Code workspace.
- The extension indexes automatically no setup required for basic use.
- Look for inline annotations above your controller methods and API calls.
- Use the Contour view in the Explorer sidebar to browse everything by use case, endpoint, scheduled task, service call, or event or use its search to find anything by name.
- Right-click any endpoint or call site, or use its inline action, to copy AI-ready context or export the visual report.
Commands
| Command |
What it does |
| Contour: Rebuild API Index |
Full re-scan of backend and frontend |
| Contour: Copy AI Context for This Endpoint |
Copies a traced summary to the clipboard |
| Contour: Show All Flows for a Use Case |
Copies a combined summary for every flow under one label |
| Contour: Tag This Flow |
Assigns or edits a use-case label |
| Contour: Export Visual Report |
Saves a shareable, self-contained HTML report |
| Contour: Preview Visual Report |
Opens the same report in a VS Code panel |
| Contour: Find |
Search across every endpoint, scheduled task, service call, and event |
| Contour: Generate Starter Config |
Detects Gradle multi-module structure and CRA-style env vars, writes/updates contour.config.json |
Settings
| Setting |
Default |
Purpose |
apiContractTracer.basePaths |
auto-detected |
Per-service API base path, confirmed or corrected on first index |
apiContractTracer.driftDetection.enabled |
false |
Turn on contract-drift diagnostics |
apiContractTracer.solidLint.enabled |
false |
Turn on the controller/repository layering nudge |
apiContractTracer.config |
{} |
Personal overlay on top of a committed contour.config.json same shape, layered on top |
apiContractTracer.configReplaceKeys |
[] |
Array-valued config paths (e.g. "backend.excludeGlobs") that should replace the committed file's value instead of appending to it |
Good to know
- Everything runs locally through static analysis of your source files. Your code never leaves your machine.
- This is static analysis, not a compiler it reads source text and annotations, not runtime behavior. Highly dynamic or reflection-based code may not be traced.
- Some capabilities (like Solace JCSMP event detection) are built from documented reference patterns rather than validated against a wide range of real production codebases these are clearly labeled with a lower-confidence badge wherever they appear, so you always know how much to trust what you're looking at.
- Non-HTTP entry points that aren't yet covered like raw JDBC access outside a repository, or non-JPA data layers are treated as a known limitation, not silently ignored.
Feedback
Found something that looks wrong, or a pattern this doesn't recognize? That's genuinely useful to know please open an issue with what you found.