aicb-roslyn-mcp
Use it to analyze C#/.NET solutions and give coding agents Roslyn-based code intelligence, change-safety evidence, and token-budgeted context.
Analyze
.sln,.slnx, and.slnfsolutions with Roslyn, cache sessions, refresh after edits, and save/compare snapshots.Navigate code semantically: find symbols, usages, callers, implementations, overrides, type hierarchies, call graphs, and instantiation sites.
Assess change impact and risk, find covering tests, detect coverage gaps, and identify dead-code suspects.
Resolve dependency-injection registrations and lifetimes; find external API calls and side-effect profiles.
Find code by attributes and semantics such as layer, role, domain, or responsibility; find event subscriptions.
Measure method complexity and solution-wide quality metrics; list code insights and public API surface.
Orient architecture with overviews, circular namespace dependencies, layer checks, and config drift checks.
Inspect XAML/AXAML bindings and resource usages; find unresolved bindings.
Build focused context with
get_context,explain_symbol,pack_for_task, andprepare_task; export AI-Builder Markdown.Run multiple read-only queries in one call and measure answer sizes before pulling them.
Verify negative claims, evaluate proposed edits, and assemble review context for changed symbols.
Manage solution configuration for layers, exclusions, and tests; install agent hooks; inspect server docs, tool profiles, and usage reports.
Analyzes C#/.NET solutions (.sln/.slnx/.slnf) using MSBuild and Roslyn semantic models to build a symbol graph of declarations, calls, type references, DI registrations, tests and side effects, then answers caller/implementation/impact questions and renders budgeted, task-focused Markdown context packages for coding agents.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@aicb-roslyn-mcp What could be affected if I change ColorMixerService?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
AIContextBuilder (aicb)
Give coding agents a Roslyn-accurate map of your C#/.NET solution. aicb is
a code intelligence server for C# and .NET: it answers questions about callers,
implementations, dependency injection, tests, side effects and change impact,
then packs the relevant code into compact Markdown for an LLM. It runs locally as
an MCP server and CLI; a Windows desktop app adds visual context selection,
analysis and editing.
The software is closed source. This public repository contains its documentation, license and releases. It is free for individuals, education and organizations below the license thresholds.
This page is a short tour, not the reference. It names 38 of the 54 tools an agent sees by default; the three manuals run to 35 chapters, about 37 times the length of this page.
See it answer a code question
Ask your coding agent:
What could be affected if I change
ColorMixerService? Use AICB.
Or call the same tool from a terminal:
aicb call impact_of_change --sln C:/repo/App.sln --arg symbol=ColorMixerServiceAbridged output from the bundled ColorMixer.SelectionLab sample:
{
"symbol": "ColorMixerService",
"resolvedKind": "type",
"directCount": 1,
"transitiveCount": 2,
"risk": "low",
"productionImpactCount": 2,
"directImpact": {
"items": [
{
"name": "DemoCompositionRoot",
"file": "ColorMixer.SelectionLab.ConsoleApp/DemoCompositionRoot.cs",
"line": 9
}
]
}
}The desktop app's MCP Usage page records calls locally and separates guided refusals from suspected defects:

That answer comes from the Roslyn symbol graph, not a substring search. AICB distinguishes overloads, follows interface and override relationships, understands partial types and records DI construction paths.
Related MCP server: engram-serve
Build context that fits the task
AICB does more than answer individual symbol questions. It can assemble a focused, task-specific context package for an agent instead of sending an unfiltered source dump:
Need | Tool | What it returns |
Read one symbol in context |
| The symbol plus its direct dependencies and callees |
Explore a named symbol with selected surroundings |
| Callers, callees, implementations, tests or other requested dimensions |
Pack context for a natural-language goal |
| Goal-named symbols and their semantic neighborhood |
Prepare to edit |
| The goal-focused context plus covering tests and likely siblings such as a factory or validator |
Check the response cost first |
| The exact token count of one or more planned tool answers, without returning their large payloads |
The focused context tools accept a token budget, so a package fits the room your agent has. The symbols you name stay in; when the budget is tight, the package becomes less detailed around them instead of being cut off in the middle of a block, and a leading note says which types, tests or siblings were left out. AICB can therefore tell you that a bundle was reduced or capped; it cannot certify that what remains is enough to solve the task correctly. Whole-document rendering can use the same budget through a pipeline profile, with an allowance for overshoot and an optional report of what was trimmed.
The result is AI-Builder-MD: structured Markdown for an LLM, containing the selected code together with symbol relationships, architecture graphs, semantic metadata and provenance. It can use the established tag notation or YAML. See the context-document guide and the task-packing tools.
Add explicit meaning with AI Tags and semantic annotations
AICB works without annotations. Where source structure and conventions are not
enough, optional <ai> tags in XML documentation let a developer state the intended
role of a type or method explicitly:
/// <ai
/// role="service"
/// layer="Application"
/// responsibility="Coordinates order validation and submission."
/// stability="Stable"
/// />
public sealed class OrderServiceAnnotations can describe semantics such as role, domain, architectural layer,
priority, stability, responsibility and side effects. Explicit values take
precedence over heuristic inference; sentinel values such as none can deliberately
suppress inference for one field. AICB preserves provenance so an agent can
distinguish source-derived facts, author-provided meaning and inferred hints. The
AI annotation reference
documents the supported forms and fields.

What happens after your first question
The first question about a solution takes the longest. You name a
.sln,.slnxor.slnf; AICB loads it the way your build would and reads its C# code and its XAML/AXAML markup.The questions after it are quick. Callers, implementations, tests, injection and impact come from what the first analysis learned, as a single answer or as a context document sized to your budget.
Your saved edits are picked up. With the shipped settings the next question brings the analysis up to date with the saved files; an explicit refresh does the same in every setting.
AICB analyzes the solution once and then answers each question from what it learned: who calls what, which types use or implement which, what the markup refers to and which code reaches outside the program. Follow-up questions therefore come back quickly, and the context tools pick and render the part of the code a task needs. This does not mean that every possible answer or runtime relationship is known in advance.
Every agent session that starts aicb mcp gets its own analysis, and the desktop
app, the CLI and the server answer alike and can share configuration and saved
snapshots. After you edit source files, the next refresh picks up what changed,
usually without loading the solution again (force: true loads it completely). A
client that gives up waiting for a long first analysis can simply ask again: the
analysis finishes anyway, and a retry with the same arguments gets it from the
cache.
Live sessions, snapshots and persistent codebase memory
These states serve different purposes and should not be treated as interchangeable:
State | Lifetime and purpose | Important boundary |
Live MCP session | In-memory graph and Roslyn workspace reused by one server process | Sees saved files, not unsaved editor buffers; another server process has a separate session |
Remembered codebase |
| A recalled session has no live workspace, no reliable line numbers and a reduced insight contract; use |
Saved snapshot | Named baseline used by | A comparison baseline, not a live workspace |
| Git-trackable solution configuration | Contains rules and choices, never analysis results, sessions or credentials |
remember_codebase, recall_codebase and refresh_remembered are opt-in tools: no
MCP profile exposes them, so start the server with AICB_MCP_TOOLS naming them (or
AICB_MCP_TOOLS=all). recall_codebase reports whether the persisted model still matches the source,
payload schema and analyzer identity. It deliberately returns the recalled model
even when it is stale, with metadata that tells the agent when a live re-analysis
is necessary. See sessions, recall and staleness.
What the model can and cannot prove
AICB analyzes statically visible C# (including the C# the Razor generator compiles from
.cshtmlviews) and selected Razor, XAML and AXAML relationships. Code reached only through reflection, runtime assembly scanning, dynamic configuration or an external consumer can remain invisible.DI analysis recognizes statically readable Microsoft-DI-shaped registrations and MEF export attributes; runtime-produced registrations are disclosed as dynamic or unknown rather than invented.
A XAML binding is checked only where its data type is known for certain; where it is not, the binding is left unjudged rather than reported as broken.
A reported side effect means the method reaches such an operation in its code, itself or through the methods it calls. It tells you that a method can write a file, not which file or when.
Responses disclose stale sessions, unresolved projects and capped result sets. Read
staleness,incompleteProjects,totalFoundandtruncatedbefore treating an empty or short answer as proof.
How an agent should judge an answer
An AICB response is evidence together with its limits. Before acting on an empty, short or apparently definitive result, inspect the accompanying signals:
Signal | Meaning | Typical response |
| Saved source changed after the analysis, or an automatic refresh ran or failed | Save the files and refresh if the response is not current |
| Project references could not be resolved well enough for a complete semantic graph | Restore, then call |
| More matches exist than were returned | Narrow the scope, paginate or raise the documented cap |
Bundle manifest or leading omission note | A token budget removed surrounding types, tests or sibling implementations | Increase the budget or request the missing axis explicitly |
| A name did not resolve to one unique symbol | Repeat the query with a qualified symbol name |
| A value is measured, author-supplied, inferred or not statically knowable | Preserve the uncertainty and verify the relevant runtime configuration when needed |
| The answer came from persisted memory rather than a live Roslyn workspace | Use |
The MCP profile controls automatic refresh. Off only discloses drift,
Reactive refreshes before a reading tool answers and is the normal shipped
setting, while Proactive starts analysis after saved edits settle. Automatic
refresh never sees unsaved editor buffers. Staleness is also different from
reference incompleteness: the first needs a refresh; the second normally needs a
restore followed by a refresh (after a build, a forced one).
AICB also distinguishes unknown from verified absent. Tools such as
assert_absence return confirmed, refuted or indeterminate rather than
turning missing evidence into a false negative.
The question-first architecture, limits and evidence guide explains what lives in memory, how refresh and context selection work, which claims are measured, and where the published scale benchmark stands.
Where it helps
Question | Tool |
Who calls or uses this? |
|
What is the blast radius of a change? |
|
Where is this interface implemented or overridden? |
|
Which tests exercise this symbol? |
|
What gets injected here? |
|
Which code has side effects or calls an external API? |
|
What context does an agent need for this task? |
|
How large would these answers be before I pull them? |
|
Where is this property or resource used in XAML/AXAML? |
|
Which markup bindings cannot be resolved safely? |
|
What changed between two analyzed states? |
|
Does this change set violate a policy or public contract? |
|
Is the claim that this symbol is unused, untested or absent actually supported? |
|
What evidence should a reviewer see for these changed symbols? |
|
Where are concurrency, resource-lifetime or event-subscription risks? |
|
Where did repeated structures, conventions or documentation drift apart? |
|
The default profile exposes every tool in this table except semantic_diff,
diff_review, find_by_concurrency_risk, find_by_resource_leak,
find_structural_twins, check_pattern_drift and check_doc_drift, which need the
Full Select profile, and compare_public_api, which is opt-in (see
Tool sets and Agent Skills).
These tools form a broader capability map rather than a flat search catalog:
Capability | Examples |
Semantic navigation | usages, implementations, overrides, hierarchy, DI and XAML |
Change safety | impact, tests, diagnostics, review context and public API comparison |
Runtime-risk indicators | concurrency, resources, events, external calls and side effects |
Architecture and consistency | layers, cycles, structural twins, pattern drift and documentation drift |
Verification | negative claims, baseline-to-changed claims and change-set policy |
Context economy | task packing, measurement, token budgets and compression |
The desktop app turns code-quality, security, design and architecture findings into an actionable review queue:

AICB is most useful for non-trivial C#/.NET solutions and semantic questions that plain text search cannot answer reliably. It analyzes C#; selected XAML/AXAML relationships supplement that graph. Other programming languages are out of scope.
Multi-targeted projects are loaded once per target framework by default, while
query surfaces generally deduplicate them to one logical project. Setting
analyzePreferredTfmOnly in <Solution>.aicb.json reduces analysis and export work
to the newest target-framework instance. The symbol inventory remains available,
but fan-in edges that exist only in another target can disappear, so this is a
documented precision-versus-cost choice rather than a transparent optimization.
A safe agent workflow
An agent can use AICB without memorizing the tool catalog:
Call
server_infoto verify the connection and detect binary or configuration drift. Uselist_skillsfor the complete capability map ordocs()for the built-in operating manual.Start an edit task with
prepare_taskto collect the named symbols, relevant context, covering tests and likely sibling implementations within one budget.Before changing a symbol that other code names, call
impact_of_change; usefind_tests_forwhen the task bundle does not give enough test evidence.Read uncertainty and completeness signals before treating an empty result as proof. Qualify ambiguous symbol names; restore and force-refresh incomplete projects.
Make and save the change. Then call
refresh_sessionbeforeget_diagnostics. Under the normalReactiveprofile this is usually redundant, but it remains correct in every mode and makes the intended boundary explicit.Use
review_context,evaluate_change_setorverify_claimwhen the task makes a review or policy claim; do not infer absence merely from a short search result.Finish with the repository's real build and test commands.
get_diagnosticsreports Roslyn compiler diagnostics, not third-party analyzer or runtime results.
For several independent read-only questions, batch reuses one session and returns
one bounded response. Use measure first when the likely response size matters.
Reproducible analysis, CI and review
Need | AICB workflow |
Version the portable solution rules | Commit |
Enforce a quality threshold in CI | Run |
Compare an in-place change with a baseline | Call |
Review two live analyzed states |
|
Reuse an analyzed model across processes |
|
Curate context visually | The Windows app adds a solution tree, manual context selection, detail and token controls, AI-Builder-MD preview/export, snapshots, Insights, LLM runs and a source editor. |
In GitHub Actions the gate is one step. The action at the root of this repository sets up
the .NET 10 SDK, installs the published tool, restores the solution and runs
aicb analyze; a failed gate fails the step with exit code 6, and the document is still
written:
- uses: gregordadera/aicb-roslyn-mcp@v0.5.515.1
with:
version: 0.5.515.1
solution: App.sln
output: artifacts/context.md
fail-on: "critical>0 OR debt>120min"The example pins both halves: @v0.5.515.1 fixes the action, version fixes the package it
installs. Without version the action installs the newest published package, and @main
follows every change to the action, so neither belongs in a build you depend on. A release
tag can in principle be moved; the full commit SHA of that tag is the immutable form. Further
inputs are dotnet-version, restore and args for other
aicb analyze options; the outputs are output and exit-code. See
action.yml.
Configuration precedence is axis- and surface-specific. For example, headless layer
mapping can fall back to the sidecar, while headless test detection currently resolves
from the database or built-in rules rather than the sidecar's test axis. The exact
matrix is in the
configuration guide.
A running MCP session keeps the configuration it was analyzed with; after editing the
sidecar, start a new analysis instead of assuming refresh_session re-reads it.
Suppressions hide accepted findings from suppression-aware reading surfaces, but
solution_metrics and the CLI quality gate continue to count them. A shared
suppression is therefore an explicit review decision, not a way to lower the gate.
From semantic engine to human-in-the-loop workspace
The MCP server is currently AICB's most complete and operationally mature integration surface. Its 82 registered tools cover semantic navigation, change impact, dependency injection, test discovery, architecture, quality, context packing, review and session management. Profiles expose a curated 54-tool default or the 72-tool Full Select set, while sessions, staleness signals and bounded responses make the surface practical for coding agents. These numbers describe the available product surface; they are not a published benchmark of agent outcome quality.
The active MCP profile also selects task-oriented facets: each facet connects agent
guidance, a context-template slot and the corresponding tool subset. list_skills
is the runtime source of truth for which tools are exposed, which are callable, and
which additional registered tools sit outside the active pool.
The Windows app complements that agent-facing surface with a visual workspace for people: solution navigation, manual context selection, detail and token controls, AI-Builder-MD preview and export, snapshots, Insights, reusable configuration and manual LLM runs.
Quality and solution-specific analysis profiles
A Quality Profile controls which insight producers run and the thresholds they use, such as method length, cyclomatic complexity and class size. It does not by itself define finding severity or the CLI quality gate.

Each solution also has three independent analysis axes:
Axis | Question it answers | What it controls |
Layer Profile | Where does this code belong architecturally? | Ordered namespace-pattern-to-layer mappings for layers such as Domain, Application and Infrastructure. The first matching rule wins. The profile also determines whether a detected cross-layer violation is |
Exclude Namespaces | What should stay outside the analysis? | Named namespace patterns skipped by the analyzer, using |
Test Profile | What counts as test code? | Project-name rules plus method-attribute markers. The built-in profiles recognize xUnit, NUnit and MSTest conventions, and production-focused tools can exclude the detected test code by default. |
The desktop app presents these three pickers side by side for the selected solution. The Settings pages are the library editors; the Workspace pickers choose which library entry applies to this particular solution. A per-solution choice wins over the global default.

Initialize the three axes
You do not have to set the three axes up by hand. When the desktop Context Builder loads a solution, AICB offers a setup for every axis that is still open and whose automatic setup you have not switched off:
If a
<SolutionName>.aicb.jsonbeside the solution already covers the axis, it offers to take the setting from there, without asking a model.Otherwise, if a default model profile is set up, it asks the model once for a layer map and an exclusion list and shows you the proposal. The request has been sent by the time you see the dialog; you decide whether the proposal is applied. Test code is recognized on your machine, without a model.
An axis can stay open: without a model profile, without detected tests, or when you decline. Choices you already made are never overwritten.
Successful GUI auto-initialization stores the chosen profiles in the local database.
It does not create <SolutionName>.aicb.json automatically. Use Workspace → Profiles → Export Config to write that portable sidecar, then commit it. A later GUI
can restore the supported axes from it without an LLM call; headless consumers apply
the per-axis rules described in the configuration matrix. Initialize Now performs
only an immediate sidecar restore - it does not call a model or analyze the solution.
An agent can guide the same setup explicitly:
solution_config_statusreports which axes are initialized and whether their active values come from the local database, the sidecar or neither.init_solution_configreturns proposal material: declared namespaces for the layer map, referenced namespaces for exclusions, and detected test projects and attributes for the test profile.After reviewing or adapting that proposal,
apply_solution_configcreates and activates the custom entries, marks the axes initialized and writes both the local configuration database and<SolutionName>.aicb.jsonbeside the solution.Commit the sidecar so the portable solution configuration travels with the repository. Each consumer applies the supported axes described above; do not assume every surface resolves every field identically. Later,
check_solution_config_driftreports namespaces or test projects no longer covered by that configuration.
aicb init is a different operation: it connects a repository to the MCP server
and installs the agent skill and optional symbol guard. It does not initialize
these three solution axes or create <SolutionName>.aicb.json.
See profiles and solution configuration for precedence, the sidecar schema and the full initialization behavior.
Context templates and run templates
The two template types have different responsibilities:
Template type | Purpose |
Context Template ( | Defines what goes into an export: prompt, Markdown profile, detail presets, expansion strategies, compression, quality settings and export switches |
Run Template ( | Defines how a task is executed: run type, selected context template, model defaults and run-specific options |
Detail Presets, Markdown Profiles, Expansion Strategies, Compression Rules, Pipeline Profiles and Quality Profiles are reusable building blocks referenced by a context template; a run template selects that context template.

Product direction, not a release commitment
The direction for the desktop app is a human-facing orchestration workspace: a
developer selects and constrains context, inspects intermediate results, approves
decisions and controls what an AI model runs next. Manual is the released run
type today. Iteration is intended to process selected nodes one by one, and
Preselection to let a model narrow the relevant context before the main run;
both are represented in the application but are not released yet. Pipeline
currently exists only as a placeholder in the data model and executes nothing.
Install
Install one form per machine:
You want | Install | Platform |
MCP server and CLI | Windows, Linux, macOS | |
Desktop app plus the same MCP server and CLI | Windows |
The .NET tool needs the .NET 10 SDK. Without .NET 10 it runs on the next newer .NET on the machine and needs that version's SDK. A machine that has only .NET 8 or .NET 9 cannot start it.
dotnet tool install -g aicb-roslyn-mcp
aicb --versionThen register the server with your MCP client. For clients that read .mcp.json:
{
"mcpServers": {
"aicb": {
"command": "aicb",
"args": ["mcp"]
}
}
}aicb init writes this file for you; see Connect a coding agent.
Up to 0.5.465.1 the package was called AIContextBuilder. An update does not cross that
rename: remove the old package first, then install the new one as above.
dotnet tool uninstall -g AIContextBuilderUpdate it later with dotnet tool update -g aicb-roslyn-mcp. For a container, the
repository's Dockerfile
installs the same .NET tool and serves MCP over stdio. The container runs as a non-root
user, and analysis writes obj/ into the mounted solution, so run it as the owner of the
mount and restore the solution inside the container first:
docker run --rm --user "$(id -u):$(id -g)" -v "$PWD:/src" \
-v aicb-nuget:/home/aicb/.nuget/packages --entrypoint dotnet aicb restore /src/App.sln
docker run --rm -i --user "$(id -u):$(id -g)" -v "$PWD:/src" \
-v aicb-nuget:/home/aicb/.nuget/packages aicbThe Dockerfile header explains why the restore has to run in the container.
If the tool reports that MSBuild could not be registered, the .NET it runs on found no SDK it
can use: an SDK newer than that .NET does not count (for example a .NET 10 runtime next to only
the .NET 11 SDK). Install the .NET 10 SDK; beside a .NET 10 runtime the .NET 8 and .NET 9 SDKs
work as well. Or set the environment variable DOTNET_ROLL_FORWARD=LatestMajor so that the tool
runs on the newest .NET - a preview .NET additionally needs
DOTNET_ROLL_FORWARD_TO_PRERELEASE=1.
The Windows downloads are self-contained, but analyzing a solution still needs MSBuild from a .NET SDK or Visual Studio. The installer and the programs are code-signed (publisher: Gregor Dadera); Windows SmartScreen can still warn about a new release until it has enough download history. Every release provides SHA-256 checksums.
Connect a coding agent
Run this from the project you want the agent to work on:
aicb initIt writes .mcp.json, the MCP configuration Claude Code reads, and the
aicb-csharp-context agent skill under .claude/skills/, without overwriting
existing files. If the project uses Codex or OpenCode (a .codex/ or .opencode/
folder), it also registers the server in that client's own configuration and
writes the skill to .agents/skills/, where both read skills. If it detects
Claude Code, Codex or OpenCode project configuration, it also installs a
symbol guard that blocks C# symbol searches by grep and redirects the agent
to the semantic tool. This intentionally changes agent behavior. Opt out with:
aicb init --hooks none--hooks none skips only the guard; the server is still registered. To wire a
client whose folder does not exist yet, name it: aicb init --hooks codex or
aicb init --hooks opencode.
Client-specific status:
Client | MCP setup | Skill and guard |
Claude Code |
| Skill and optional guard installed |
Codex |
| Skill in |
OpenCode |
| Skill in |
Cursor / Cline / other stdio clients | Add command | Use the published skill if the client supports Agent Skills |
Manual .mcp.json configuration for clients that read it:
{
"mcpServers": {
"aicb": {
"command": "aicb",
"args": ["mcp"]
}
}
}Verify the connection by asking the client to call server_info. Every analysis
tool accepts an absolute .sln, .slnx or .slnf path as its session, so no
separate analyze step is required. See the five-minute guide
for setup, first questions and troubleshooting.
Tools
The questions an agent asks most, and the tool that answers each one:
Tool | What it answers |
| Who uses this type, method or property? |
| What else is affected if I change this symbol, and how risky is it? |
| Which types implement this interface? |
| Which tests cover this symbol? |
| What is registered for this service, with which lifetime, and where? |
| Which code touches files, the network or a database, and which code is pure? |
| Which code does nothing reach? |
| Which compiler errors and warnings are there, without a build? |
| Where is the type or member with this name? |
| What does this symbol do, with its source and its surroundings in one answer? |
| How is the solution structured, without reading all of its source? |
| Which XAML bindings name this view-model member? |
| What do I need to read before I edit code for this task? |
| Is the server running, and which version is it? |
All tools with their parameters: docs/TOOLS.md.
Tool sets and Agent Skills
Set | Size | Purpose |
Default MCP profile | 54 tools | Curated semantic and structural tools for normal agent work |
Full Select profile | 72 tools | Default set plus the measured long tail |
Complete server surface | 82 tools | Full Select plus the opt-in session-memory, database and API-comparison tools |
This README names 38 of the 54 default tools; the references below describe all of them.
Start the Full Select profile with
aicb mcp --mcp-profile mcp-profile/full. Set AICB_MCP_TOOLS=all to add
the opt-in tools as well. The generated tool reference documents
the default set; the MCP server manual
documents all 82 tools and their parameters, and alongside them sessions and
staleness, profiles, pools and facets, and what aicb init writes - twelve
chapters in Markdown, readable in the browser and by an agent, and also
published as a PDF.
The three published counts are starting points, not fixed editions. In the
desktop MCP Profiles editor you can create or duplicate a profile, enable only
the task facets you want, and select individual core and facet tools. Every core
tool can be removed except the locked diagnostic server_info, so even a very
small task-specific tools/list is possible. The server's fixed lead-in is
standing agent guidance, not another selectable tool group. For headless setup,
AICB_MCP_TOOLS=methods:<tool>,<tool>,... exposes exactly the named functions;
class lists, lean and all are also supported. Profile and environment changes
take effect at the next server start. list_skills shows the resulting in-pool and
out-of-pool tools. See profiles, pools and facets.
Five Agent Skills ship in skills/:
aicb-csharp-contextroutes semantic C# questions to the right tool.aicb-code-reviewchecks a completed change for correctness.aicb-code-simplifierlooks for unnecessary complexity.aicb-usage-checkreports what this server was actually reached for.aicb-smoke-testruns read-only checks on your own solution, sorts the findings into HIGH, MID and LOW, and drafts reproducible issues for the maintainer. It files nothing until you confirm.
The last four are opt-in: aicb init --skills=all.
Scale, releases and compatibility
AICB has no published hard project-count limit. Initial cost and peak memory are
solution-specific and grow with loaded projects, documents, target-framework
instances and graph density. The first analysis can take seconds to minutes;
subsequent questions reuse the warm graph, and eligible saved-source edits use the
incremental refresh path. For a very large repository, use a .slnf to reduce what
MSBuild loads and optionally set analyzePreferredTfmOnly to avoid analyzing every
target-framework instance. summaryOnly, query scopes and token budgets reduce
response volume; they do not necessarily reduce the underlying solution analysis.
The desktop load-perf.log and MCP usage_report provide local phase and latency
measurements. A standardized cold/warm time and RAM benchmark on three public
.NET solutions (≈ 25k, ≈ 55k and ≈ 1.8M lines of C#) is published in the
architecture and evidence guide;
these controls are still not a universal performance claim, but there is now a
measured boundary.
Public releases currently have no declared LTS window, response-time SLA or promise
that every MCP response and persisted schema remains unchanged across versions.
Operational safeguards are explicit instead: the changelog records releases;
server_info reports version, build and configuration drift; persisted analyses
carry payload-schema and analyzer identities and fall back to a live analysis when
they are incompatible; and the desktop refuses to write a database created by a
newer schema. Database migrations can be one-way, so a reliable rollback means
backing up before an update and using the older build with a separate or restored
pre-migration database. Commercial agreements can define stronger support,
response-time and version-maintenance commitments where required; see
Support.
CLI at a glance
aicb init Connect a project to the MCP server and install the agent skill.
aicb analyze Analyze a solution and emit context Markdown.
aicb export Re-render Markdown from an existing session database.
aicb import Import a constellation JSON.
aicb list List built-in and custom profiles and presets.
aicb mcp Start the stdio MCP server.
aicb call Invoke one MCP tool without an MCP client.Run aicb <command> --help for options.
Local by default
The CLI and MCP server make no outbound network connection of their own and do not modify the source code they analyze. The MSBuild logic and source generators of the analyzed solution run in the same process, so they can reach the network if that solution's own build does (see the last point).
There is no outbound telemetry, analytics, update check, account or license server. The MCP server records its tool calls locally for
usage_reportand the desktop app's MCP Usage page; that log never leaves the machine.The desktop app can contact only an LLM endpoint you configure: for a manual run, a model-profile connection test, or first-load proposals for Layer Profile and Exclude Namespaces when those auto-init flags are armed. The endpoint may be a local model. The Details tab is also a real editor and saves a file only when you explicitly use Save.
Opening a solution runs its MSBuild logic to resolve references, and building its compilation runs the source generators its projects reference, as in an IDE or
dotnet build. Analyze only solutions you trust. AICB does not run third-party Roslyn analyzers, with one opt-in exception:get_diagnosticswithrunSuppressors: trueruns the projects' diagnostic suppressors, as a real build does.
A small number of explicitly named tools can write configuration or an export;
their tool descriptions state this. The complete threat model and private
reporting route are in SECURITY.md.
License at a glance
Use is free for:
private, hobby and educational use by natural persons,
accredited educational institutions for teaching, learning and non-commercial research,
organizations that reach none of these thresholds: 100 employees, EUR 10 million annual turnover, 21 developers.
The thresholds apply to your organization, not to your clients. After first
reaching any one threshold, you have 90 days to agree a commercial license; use
remains free during that period. The 90 days are contractual text only: AICB
starts no license timer, sends no threshold or deadline data, blocks no feature
and does not technically stop working when the period ends. Commercial licenses
start at EUR 25 per licensed developer per month; the exact price and scope depend
on the number of users, the requested support level and any agreed priority for
improvement requests. A commercial agreement can include support, defined response
or maintenance commitments, prioritized consideration or
implementation of improvements - for example, making a generally useful analyzer
handle patterns found in the customer's code more accurately. Such work improves
the general AICB product; it does not create a customer-specific fork or specialize
AICB to one codebase. Customer code is never collected or used for improvement
automatically; examining it requires material or access deliberately provided by
the customer and a separate agreement on scope and confidentiality. Exact
deliverables, priorities and guarantees exist only when written into the individual
agreement. Connecting AICB to MCP clients, agent harnesses, scripts, build systems
and CI through its documented interfaces is permitted. Redistributing, modifying,
repackaging, reselling or offering the AICB binaries as a hosted service is not.
Contact aicb@dadera.de. See the plain-language guide,
LICENSE.txt and the full bilingual EULA.md.
Support and continued development
AICB is under active development: the changelog records every release, and published releases appear on the Releases page.
Support follows the license:
Free | Commercial agreement | |
Who | Everyone below the thresholds | Organizations at or above a threshold, or anyone who wants stronger terms |
Channel | Direct contact plus the public channels | |
Response target | Best effort | ≤ 2 business days |
Security fixes | Shipped through public releases | Fix target ≤ 10 business days for confirmed vulnerabilities |
Version maintenance | Current release | Individually agreed maintenance window |
Improvement requests | Community-driven | Prioritized consideration; agreed priorities are written into the contract |
Source access | None | Code review under NDA can be agreed |
The targets in this table are typical values an individual agreement can include;
they bind only when written into the agreement, and payment alone creates no
unstated SLA. Prices are in License at a glance. Contact
aicb@dadera.de.
Questions and feature requests are welcome in
GitHub Discussions. Report bugs
through GitHub Issues and include
aicb --version and, for MCP problems, the output of server_info. Report security
issues privately as described in SECURITY.md.
Documentation
Everything above is the short version. The three manuals below hold the full reference - 35 chapters, each manual 70,000 to 88,000 words, together about 39 times the length of this README.
Getting started - install, connect and ask the first question
Tool reference - generated reference for the default MCP profile
Architecture, limits and evidence - in-memory model, refresh, context selection, static-analysis boundaries and benchmark status
MCP server manual - the full reference in twelve Markdown chapters: connecting a client,
aicb init, sessions and staleness, profiles and facets, every tool, troubleshootingGeneral reference manual - the full reference in twelve Markdown chapters, with the printable PDF in the same folder
Desktop app reference manual - the full reference in eleven Markdown chapters, with the printable PDF in the same folder
"AIContextBuilder" and "AIContextBuilder for .NET" are product names used by Gregor Dadera; no registration is claimed.
Available Tools
54 toolsanalyze_solutionAnalyze a SolutionA
Analyze a C#/.NET solution (.sln, .slnx or .slnf) with Roslyn and cache the result as an in-memory session - the entry point of this server: it returns the session_id the other tools take as sessionId.
Calling it is optional: every tool that takes a sessionId also accepts the absolute solution path and analyzes on first use, so do not call it just to run a query. Call it to read this answer first or to pass layerProfile / dbPath. After editing .cs files call refresh_session, not this: a second analyze_solution is a full reload into a new session.
Returns JSON: sessionId, solutionPath, solutionName, projectCount, fileSetHash, layerProfile, lastAccessUtc, origin, lineNumbersAvailable - plus, only when they apply, analyzePreferredTfmOnly, incompleteRun, configInit (config axes still to set up; see solution_config_status) and agentWiring (the agent's guard hooks are missing here).
When a scanned project could not bind even its core framework types, the answer carries incompleteRun (projects and repair), because every later answer of the session inherits that shortfall. Its absence is no all-clear: an unrestored project that still binds its framework is not flagged here; get_diagnostics names its errors.
It changes no source or project file: it opens the solution through MSBuild (whose design-time build can write generated files under obj/), reads the files as they are on disk (uncommitted edits included) and keeps the model in server memory - by default for 90 minutes after the last use, at most 8 sessions. A large solution can take minutes; with a client progress token it reports progress. If the client cancels, the analysis still finishes: a same-argument retry gets it if no file changed.
Parameters: normally pass only solutionPath; layer profile, namespace exclusions and test profile then come, per axis, from the server's config DB, else the committed .aicb.json beside the solution, else built-in heuristics. layerProfile overrides the layer axis only; dbPath reads all three from another config DB.
Inputs: solutionPath (required), dbPath, layerProfile.
apply_solution_configApply a Solution ConfigurationADestructive
Apply a proposed Layer Profile + Exclude-Namespaces + Test Profile configuration for a solution. Creates a new custom layer profile / exclusion list / test profile (tagged with the source solution for provenance), activates them, and marks each touched slot initialized. proposalJson is a JSON object {"layerRules":[{"pattern":".Application.","matchType":"Contains","layer":"Application"}],"exclusions":[{"pattern":"System.","matchType":"StartsWith"},{"pattern":"System","matchType":"Exact"}]}; the test axis is given via testProposalJson {"testProjectRules":[{"pattern":".Tests","matchType":"EndsWith"}],"testAttributeNames":["Fact","Theory"]} (rules match PROJECT names) OR via testPreset = xunit|nunit|mstest|default. At least one axis must be non-empty (empty arrays/slots are skipped). matchType is Contains|StartsWith|EndsWith|Exact (an invalid matchType is rejected, not silently defaulted). An exclusion with "keep":true is a keep rule: a namespace it matches is never excluded; exclusions that are all keep rules leave the built-in list in force; the sidecar stores keep rules under "keepNamespaces". Writes to the config/master DB (the given dbPath, or the server's default - the GUI's - if omitted; created if missing) AND to the git-tracked .aicb.json sidecar next to the .sln (returned as sidecarPath - commit it so the config travels with the repo). An axis this call does not name keeps what the existing sidecar holds for it, and keys it does not know stay as they are. An existing sidecar that cannot be read (malformed, locked) is never rewritten: the call is refused before anything is written, and a file that becomes unreadable while the DB is written is left as it is too (the DB keeps the configuration, a warning says so); a keep rule the file puts inside 'exclusions' is moved to 'keepNamespaces'. A keep flag that is not a JSON boolean is rejected.
Inputs: sessionId (required), dbPath, testPreset, proposalJson, +5 more.
architecture_overviewGet Architecture OverviewARead-onlyIdempotent
Orient on a whole solution WITHOUT the firewall of full source. When anything was cut, the document LEADS with a block: how many sections were capped, how many entries are shown of how many in total, and one 'SECTION: shown of total' row per capped section - read it before the sections, because the per-section '+N more' lines below state one section's loss each and never the whole. Absent = nothing was cut. Renders a structural-only AI-Builder-Markdown overview: the macro/architecture graphs (layer map, service & class dependency graphs, role graph, entry points + entry-point flow, architecture flow, interface relations) plus a domain summary and a quality-hotspots section (the most complex methods + most-coupled types) - but NO per-type/per-file source-code blocks, so it stays bounded where export_markdown is a 27-29 MB firehose. BOUNDED AT SCALE: each macro section is capped at 80 entries with a '+N more' note; the layer map lists every type up to 2500 and per-layer counts above, so even a 200+-project monolith returns a usable orientation instead of overflowing the token cap (use export_markdown for the full set, or find_symbol / call_graph / get_context to drill in). PRODUCTION-FOCUSED: test projects are excluded by default (an architecture overview is otherwise dominated by every test method listed as an entry point + its flow trace, which is not part of the production architecture) - set includeTests=true to include .Tests/.Spec projects. Method-level call graphs are deliberately omitted (use call_graph / explain_symbol for a specific symbol). Multi-TFM solutions are deduplicated to one logical project per name, keeping the NEWEST TFM's instance (without it every hotspot/edge appears once per TFM). Recall-safe: works on live AND recalled sessions (on a recalled session quality-hotspot line counts are unavailable, complexity is still shown). Use this FIRST to understand a codebase's shape, then get_context / explain_symbol to drill into a symbol.
Inputs: sessionId (required), lean, summaryOnly, includeTests.
assert_absenceCheck a Negative ClaimARead-onlyIdempotent
Check a NEGATIVE claim against the analyzed model and return confirmed / refuted / indeterminate with evidence. Supported: 'no_tests:' (the symbol has no detected covering test), 'absent:.' (the semantic axis is verified-absent - developer declared 'none', NOT merely unknown), and 'unknown:.' (the axis has no provenance entry at all). is a bare name or the qualified Type.Member form - the same strings find_usages / find_tests_for / impact_of_change resolve, so the pre-edit gate's tools accept one vocabulary; a name that resolves to nothing declared says so ('resolves to no declared type or member'), which is a statement about the name, not a claim the code lacks it. axis is e.g. role/layer/domain/context/responsibility; an axis name not recorded anywhere in the solution returns 'indeterminate' with the actual recorded axes as evidence (a typo'd/invented axis is NOT silently 'confirmed'). Conservative in BOTH directions: a claim the model cannot prove is 'indeterminate', never falsely confirmed - and 'refuted' for a no_tests claim requires at least one STRONG covering test, as find_tests_for defines its tiers; the reason states the split between the strong tiers and the name-only matches, matching find_tests_for's invokesTotal. Tests that merely carry the symbol in their NAME yield 'indeterminate' with that split disclosed, not a refutation - a test named after a symbol is not proof it exercises it. Use to validate 'this service is untested' or 'role is explicitly unset' before relying on it.
Inputs: claim (required), sessionId (required).
batchRun Several Queries at OnceARead-onlyIdempotent
Run several read-only query tools in ONE round-trip against one shared session. measure is the sibling that sizes each answer instead of returning it.
Use it when the questions do not depend on each other's answers; a solution path as sessionId is analyzed once.
queries is an array of {tool, args}: args is that tool's own arguments as a JSON object WITHOUT sessionId (omit it for a tool that takes none). Example: queries=[{"tool":"find_usages","args":{"symbol":"Foo"}},{"tool":"find_tests_for","args":{"symbol":"Foo"}}]. At most 16 per call: more fails the whole call, as does a sessionId that does not resolve.
Returns { results: [{tool, ok, result | error | omitted}], count, okCount, omittedCount (when any) }, in query order. A failing sub-query answers ok:false + error; the rest still return. A JSON tool's result is nested as an object, a Markdown tool's is the string. The answers share about 9000 tokens, small ones whole first. A large answer is never cut: a 'budget' tool's may be rendered smaller if its share reaches that tool's floor (see 'note'), else it carries 'omitted' (it ran; call it directly). Unknown arguments are ignored, named in 'note'.
Dispatchable is a fixed registry of read-only queries about the analyzed code, among them find_*/get_*/impact_of_change/symbol_signature/explain_symbol/calls_external/resolve_injection/symbol_metrics/list_insights. Refused per item, with the registry's names listed - call these directly where the profile exposes them: tools that mutate session state or write (such as analyze_solution/refresh_session/apply_solution_config/save_session/remember_codebase/export_markdown/install_agent_hooks), poor batch members (evaluate_change_set/init_solution_config/prepare_task/review_context), tools needing a second session or snapshot (semantic_diff/diff_review/verify_claim/compare_with_previous) and the session, configuration and server tools. A tool the active MCP profile does not expose is refused per item too, as a direct call would be - also when a refusal's list names it.
Inputs: queries (required), sessionId (required).
call_graphBuild a Call GraphARead-onlyIdempotent
Build a depth-limited call graph rooted at a method NAME. direction='callees' follows project-internal calls outward; direction='callers' follows the inverse (who calls this). Returns (caller, callee) edges using method-lookup keys, cycle-safe and capped at 500 edges (truncated flag). A creation (new X(...)) is an edge to the constructor it binds, keyed 'Ns.X.X(params)', and both directions walk on through a constructor: into what it calls and creates, out to the code that creates the object. A local function's key ends in '@::', character offsets rather than a line ('localFunctionKeyNote' repeats this when the answer holds one). A bare name is rooted at the name, NOT at one symbol: EVERY same-named declaration in the solution is a root and the graph is their union, so a common name (e.g. 'ExecuteAsync') can pull in unrelated members and inflate the payload. The qualified 'Type.Method' form roots only that type's declarations of the name, overloads included. With direction='callers' every node also brings in the callers of the interface and abstract members it implements, at every hop as impact_of_change does (a call through the contract is recorded there); 'viaContract' names those members with their implementation counts (one implementation: exact, several: a union) and the nodes that brought them in ('creditedBy'). 'rootedAt' lists the declarations actually walked - check it when the result looks too big (then re-query a rarer or qualified name) and when it is empty (the name resolved to nothing, which an empty edge list alone does not tell you).
Inputs: method (required), sessionId (required), depth, direction.
calls_externalFind Calls to External APIsARead-onlyIdempotent
Find the code that calls an EXTERNAL member - BCL or package - whose key contains 'pattern'. find_usages answers for solution symbols only; find_by_side_effects classifies the same calls into effect tokens.
Use it for AOT/trimming readiness, security surface or dependency footprint: pass the risky or external API as 'pattern', e.g. 'Process.Start' or 'DateTime.UtcNow'. It is the only search argument; there is no 'symbol'.
Returns scope, pattern, methodsScanned, matches - a capped envelope of at most 200 hits, sorted by type and name, not ranked - and testMatchesFiltered (units the test filter removed, outside methodsScanned; omitted at 0). A hit carries name, declaringType, namespace, matchedCalls (invoked methods and method groups, keys like 'System.IO.File.ReadAllText(string)') and matchedReads (external properties read or written, like 'System.DateTime.UtcNow'). The units scanned are methods plus every accessor and constructor with a body, under their metadata names (get_X, set_X, .ctor), and, unscoped, each top-level-statement file ('Program.cs (top-level)'); an initializer counts toward the constructor that runs it.
The pattern is a case-insensitive substring of the whole key, parameter list included, so a type name also hits the units that only pass that type to another API. It is an open vocabulary (no did-you-mean); empty is strong, not absolute. Not recorded: an external field read ('string.Empty'), a bare 'new X()', operators and conversions, indexers, event subscriptions and compiler-lowered calls (using, foreach, await). Cross-check a surprising empty against the declaring type ('DateTime', not 'DateTime.UtcNow'). A 'note' leads the answer when it is empty - it says when only the test filter emptied it - and when a project's core references did not resolve: an unresolved call records no key.
Read-only; works on a recalled session. Test-project code is excluded by default (includeTests=true includes it). scope is 'solution' or a namespace prefix, matched case-insensitively on segment boundaries.
Inputs: pattern (required), sessionId (required), scope, includeTests.
check_solution_config_driftCheck Solution Configuration DriftARead-onlyIdempotent
Check whether a solution's active Layer Profile, Exclude-Namespaces and Test Profile still FIT the code. solution_config_status tells you WHICH configuration is active and where it comes from.
Use it on a solution configured a while ago, or when layer or exclusion results look off; a stale axis is rebuilt with init_solution_config and apply_solution_config. On a never-configured solution only exclusion.excludedApiAreas is evaluated.
Three conservative signals: layer - declared (own) namespaces that no rule of the active layer profile maps; exclusion - EXTERNAL referenced namespaces that the active exclusion list does not cover; test - projects the default heuristic classifies as test projects that the active custom test profile's rules do not match.
Returns JSON with layer, exclusion and test, each carrying source (db / sidecar / built-in / none), name, status, stale, considered (the denominator), sample (drifted names, items/count/totalFound/truncated, max 50) and reason (the drifted count against considered). status is 'stale' from 3 drifted names on for layer and exclusion, for test above a quarter of considered projects; else 'ok' (an 'ok' with a sample is still worth reading). 'built-in-preset' and 'not-configured': the axis was NOT judged, only a custom config is (a generic preset is a choice, not drift). exclusion.excludedApiAreas, on any status: excluded areas carrying the public API, each with the keepRule to add. sidecarProblem: the .aicb.json could not be read.
It changes no configuration and creates no solution row; a missing dbPath is refused, an empty one is not opened, an older one is upgraded on open (schema, built-in rows). It judges the configuration a plain analyze_solution resolves NOW, not a layerProfile the session was analyzed with. It needs a live session, not a recalled one.
Parameters: normally pass only sessionId (an absolute .sln/.slnx/.slnf path works too). Pass dbPath only if the session was analyzed with it; otherwise this judges a configuration the analysis did not use.
Inputs: sessionId (required), dbPath.
compare_with_previousCompare with a Saved SnapshotARead-onlyIdempotent
Diff the current session against a previously saved snapshot of the SAME solution - the newest by default, or the N-th previous via snapshotsBack (0=newest saved, 1=the one before, ...). Optionally narrow the diff to a single type and/or method. Returns added/removed types plus, per changed type, added/removed methods and signature changes. Requires a sessionId and at least one earlier save_session; dbPath is optional and defaults to the server's standard config DB when omitted. Name-based, syntactic (no method-body diff). A 'note' leads the answer when the baseline was saved with the file-set hash the session still holds and the diff is empty: then it most likely compares one state with itself; with an equal hash saved by another analyzer build it says a difference can come from the analyzer.
Inputs: sessionId (required), dbPath, typeName, methodName, +1 more.
coverage_gapsFind Test Coverage GapsARead-onlyIdempotent
List what is NOT covered in a scope: 'uncovered' - production methods no test code calls directly - and 'verifiedAbsent' - semantic axes a developer explicitly declared as 'none'. find_tests_for asks the opposite question for one symbol.
Use it to find untested code before claiming coverage. THE LIST IS ONE HOP, NOT A COVERAGE MEASUREMENT: a method a test reaches THROUGH another method is listed although it is exercised. So read each entry's testReachDepth: the hops to the nearest directly called method (1 = one of those calls it), ABSENT when no test reaches the method at all. Those are the real gaps. The walk follows interface, abstract and virtual dispatch; an entry reached only that way carries viaDispatch. commandCreditRefused: a test may run its command beside another, so its missing depth is unknown. outerType: the type a nested declaring type sits in.
Returns scope, a 'note' when uncovered is non-empty or no namespace matched, symbolsScanned, uncovered and verifiedAbsent (at most 200 items each), transitivelyReached, judgedUnits, testProjectsExcluded, testFixtureTypesExcluded, reachedOnlyThroughDispatch and commandCreditRefused. uncovered is sorted by type and name, so on a large scope the cap cuts alphabetically: narrow the scope.
What counts: test code is every method of a test project, every test-attributed method and every method or constructor of a type that declares one. Only ordinary methods are judged. A constructor is a hop: new X(...) reaches the bound overload and its callees. A call from an accessor is not read. Abstract and interface declarations are excluded, and a weak test-name match does not count.
THE POPULATION IS DISCLOSED: judgedUnits is the methods the uncovered list is drawn from, out of symbolsScanned; testProjectsExcluded and testFixtureTypesExcluded count what was set aside as test code. symbolsScanned 0 means the scope matched no namespace.
Read-only. scope is 'solution' or a namespace prefix, case-insensitive, matched on segment boundaries. There is no test switch.
Inputs: sessionId (required), scope.
describe_api_surfaceDescribe Public API SurfaceARead-onlyIdempotent
List the PUBLIC API SURFACE of a solution or namespace - every externally-visible type with its externally-visible members (constructors, properties, fields, events, methods, user-defined operators), each as a compact declaration signature. The "header"/contract view: what a consumer of this assembly can actually reference, WITHOUT the source bodies (like a public-API digest, not a firehose). A type is included when its OWN declared accessibility is externally visible - public or the protected family (reachable by a subclass in another assembly); its members likewise. A property renders only the accessors that are themselves visible (a { get; private set; } shows as { get; }), a const carries its value, an enum is listed as a type (its values are not in the model). includeInternal=true additionally surfaces the internal family. PRODUCTION-FOCUSED: test projects are excluded by default (a shipped API surface is production code) - set includeTests=true to include .Tests/.Spec projects. scope='solution' (default) covers everything; a namespace prefix narrows it, and one that matches no namespace gets a 'note' ahead of typesScanned 0. The type list is capped (the true total travels in the envelope; narrow by namespace or filter to see the rest); members per type are whole. Recall-safe (reads persisted accessibility + signature facts) → works on live AND recalled sessions. Known v1 simplifications: accessibility is the type's OWN declared modifier (effective accessibility through the nesting chain is not computed - a public member of a type nested in an internal type is still listed); a type declared with NO access modifier is stored as private by the analyzer, so a modifier-omitted (compiler-internal) top-level type is not surfaced even with includeInternal. Use it to review a public contract before changing it, or as the baseline for a contract comparison - save_session then compare_with_previous, or diff_public_contract, which is outside the default profile's pool.
Inputs: sessionId (required), scope, includeTests, includeInternal.
detect_circular_dependenciesDetect Circular DependenciesARead-onlyIdempotent
Detect circular NAMESPACE dependencies: groups of namespaces that depend on each other in a cycle - a modularity smell the compiler allows, unlike project-reference cycles. The layer-violation insight in list_insights checks the direction rule; this answers the orthogonal 'who is mutually entangled?'.
Use it before splitting an assembly or moving types between namespaces.
Returns scope, namespacesScanned, cyclesFound and cycles, at most 100. Each cycle is a strongly connected component of two or more namespaces and carries namespaces, size, projects, spansMultipleProjects, edgeCount, edgesTruncated and edges: one 'witness' type -> type reference per directed namespace pair (fromNamespace, toNamespace, fromType, toType, typeEdgeCount). Read the witnesses as EXAMPLES, not as a work list: edgeCount counts directed namespace pairs, and typeEdgeCount says how many distinct type references cross that one boundary. Where it is above 1, removing the shown reference leaves the edge standing. Cycles that span assemblies come first, then the largest. Witness edges are capped per cycle, and edgesTruncated says when; the namespace list is complete. A cycle inside one assembly is namespace organisation; one that spans assemblies means a namespace is reused across DLL boundaries.
Edges come from the type references a type's facts record by full name - member, field, parameter, return and typeof types, base class and interfaces among them - so a same-named type in two namespaces is never conflated, and external references are ignored.
cyclesFound 0 under scope 'solution' means no cycle among the scanned namespaces. Under a prefix it only means no cycle touches a namespace starting with that text, and an unknown prefix answers the same zero.
Read-only; works on a recalled session. Test projects are excluded by default (includeTests=true includes them). scope is 'solution' or a plain case-insensitive namespace prefix: it narrows the report, not the graph, and returns a whole cycle when one of its namespaces matches.
Inputs: sessionId (required), scope, includeTests.
docsRead the Operating ManualARead-onlyIdempotent
The aicb operating manual - how to USE this server, as opposed to what it can tell you about your code. Call it with no arguments for the directory (each page with a one-line summary and a token estimate, so you can budget); pass one or more page ids to read them, or the route id 'init' for the whole first run in order. Pages: 'overview' (what aicb is and is not, the three ways to run it), 'install' (getting the dotnet tool, aicb init, and what it writes - the client's .mcp.json entry and the agent skill a tool install cannot deliver; for setting aicb up somewhere else, or finishing a half-done setup), 'first-context' (from a .sln to your first context - and why the full export is rarely the right call), 'navigation' (which tool answers which question about the code, the blast-radius call before editing shared code, and where plain text search is still right), 'solution-config' (layers / exclusions / test detection and the .aicb.json sidecar), 'glossary' (session, facet, MCP profile, snapshot, insight, AI-Builder MD). Start here if you have not used aicb before. No session and no solution needed.
Inputs: topics.
evaluate_change_setCheck an Edit Before Writing ItARead-onlyIdempotent
Advisory policy gate: which code-quality / design-smell / layer-violation findings would this edit INTRODUCE? Ask BEFORE writing it. Cheapest form - the edit you are about to make anyway: changes: [{filePath, oldText, newText}], the snippet being replaced and what it becomes (the shape an editor takes). The full file is rebuilt here from the analyzed document, so the payload is the size of the CHANGE, not of the file, and 'operation' defaults to 'modify'. oldText must occur EXACTLY ONCE - a missing or ambiguous anchor is an error, never a silently different edit; line endings are reconciled for you; an empty newText deletes the snippet. Full-text form still works and is required for add/delete: {filePath, operation: 'modify'|'add'|'delete', newContent}. Findings are a delta against the current session, so pre-existing debt is never blamed. The changeset is applied as an in-memory Roslyn overlay (NO disk write, the session is untouched) and the amended solution is re-analyzed. Verdict: pass / warn / block. Policy: 'block_on_critical' (default) | 'block_on_warning' | 'advisory' (an unknown policy is rejected, not silently treated as advisory). Advisory only - an MCP tool cannot block; the real blocking gate is the CLI '--fail-on'. The findings delta honors the session's explicit layer profile (analyze_solution(layerProfile) / sidecar) and its resolved test-detection profile on both sides; pass dbPath to also use that DB's active QualityProfile (producer thresholds + toggles) and its configured default layer profile (per-solution > app-global; the explicit session profile still wins) - same semantics as list_insights, so the gate judges by the same configured thresholds as the CLI gate. apply_change_set is out of scope. Pass absolute file paths matching the analyzed documents.
Inputs: changes (required), sessionId (required), dbPath, policy.
explain_symbolExplain a SymbolARead-onlyIdempotent
Explain a symbol: its source PLUS a chosen environment, as ONE dense AI-Builder-Markdown slice - the cold-read in a single call instead of 4-6 (get_context + find_usages + find_implementations + find_tests_for + ...). 'include' selects the environment axes (any combination): callers (the members that reference it, with their bodies, and their owner types as structure - not the calling types whole), callees (what it calls/depends on, depth 1), implementations (the types implementing it, if it's an interface), tests (its covering test cases), siblings (naming/file-convention kin), quality (complexity / efferent-coupling hotspot metrics). A partial type renders as ONE block per project it is compiled into (its declaration fragments are folded, a leading comment says which files) instead of one identical block per file; a file compiled into several projects whose block reads the same in each renders once, naming them. An empty include returns just the symbol's source - and then the manifest names the axes you could have asked for, so that answer is a stated choice rather than a silent default; an include token that names no axis is reported as dropped, whether or not a sibling token was valid. A leading manifest comment discloses what each merge axis actually found - so '(none)' is an honest negative (no impl / no caller / no test), not 'not asked' - unless its 'run:' segment says the analysis run was incomplete. Pass a type or method simple name (use find_symbol to disambiguate). Recall-safe (reads persisted facts); prefer it over export_markdown for one symbol's world. Multi-TFM solutions are deduplicated to one logical project per name (NEWEST TFM's instance, matching find_symbol's view).
Inputs: symbol (required), sessionId (required), lean, budget, +1 more.
export_markdownExport Solution as MarkdownADestructiveIdempotent
Render the cached analysis of a session as AI-Builder Markdown. No re-analysis - instant. NOTE: this renders the WHOLE solution - on a large codebase that is many megabytes, and an answer over the server's message ceiling (16,000,000 bytes as JSON unless AICB_MCP_MAX_RESPONSE_BYTES says otherwise) is not sent: the call is refused with its size, because MCP clients drop larger messages. Pass outputPath for anything but a small solution. For a bounded view prefer architecture_overview (whole-solution orientation, structural-only, no per-type source) or get_context / explain_symbol (one symbol's world). When the server has a config DB (the default: a plain 'aicb mcp' resolves the standard one), the active MCP profile drives the render: the chosen facet's template (detail presets, graphs, line numbers, quality metrics) is used; otherwise the full default render is used. Returns the Markdown; if outputPath is given it writes the file and returns a short confirmation (not the full content), so a large render does not flood the response.
Inputs: sessionId (required), facet, format, outputPath.
find_binding_usagesFind XAML Binding UsagesARead-onlyIdempotent
List the {Binding ...} sites in the solution's .xaml and .axaml markup that name a member path, with what each one resolves against. find_usages carries a markup edge for a root member bound against the DataContext; this tool lists every site, redirected bindings included. find_unresolved_bindings checks whether a bound member exists on its view-model.
Use it before renaming or deleting a view-model member.
With a path: sites (view, line, path, form), siteCount, byForm, deeperSegmentSites + deeperSegmentSiteCount, pathlessBindingCount, scannedFileCount, sitesTruncated and a note. form is dataContext (no redirect), elementName, relativeSource or source. A path matches as the whole path or as its ROOT segment: 'Foo' finds {Binding Foo} and {Binding Foo.Bar}. The 'Bar' of {Binding Foo.Bar} is a member of Foo's type and is reported apart in deeperSegmentSites. On an elementName or relativeSource binding a leading 'DataContext.' is the hop to the element's view-model: {Binding DataContext.Cmd, RelativeSource=...} is a site of 'Cmd'.
Without a path: the inventory - members (member, siteCount, byForm), most-bound first, with distinctMemberCount and totalSiteCount. Lists are capped at 200; the counts are true totals. pathlessBindingCount is every binding without a plain member root (an empty {Binding}, {Binding Mode=OneWay}, an attached-property path); it counts the whole solution, whatever scope and form say.
The markup is read from disk at every call, so a markup edit needs no refresh_session. Only the {Binding} markup extension is read ({CompiledBinding} too in .axaml): a binding built in code-behind, element-syntax , {x:Bind}, {TemplateBinding} and a path assembled at runtime are invisible here - zero sites means 'verify before renaming or deleting', not proof.
Read-only; test projects are scanned too. path is a case-sensitive markup path and is not resolved as a symbol. An unknown form matches nothing, and the note says so. scope is 'solution' or a substring of the view path.
Inputs: sessionId (required), form, path, scope.
find_by_attributeFind Symbols by AttributeARead-onlyIdempotent
Find symbols (types, methods, properties or fields) that carry a given ATTRIBUTE - a cross-cutting query that find_symbol (name-only) cannot do. The attribute name is matched format-independently: the last name segment, with an optional 'Attribute' suffix stripped, case-insensitive. So 'Obsolete', 'ObsoleteAttribute' and 'System.ObsoleteAttribute' all match the same symbols (types store the fully-qualified attribute class, methods/properties/fields store the source-syntax name - this reconciles both). The raw stored attribute list travels with each hit so you see the ground truth. kinds = 'all' (default - types + methods + properties + fields) / 'type' / 'method' / 'property' / 'field'. Member scanning surfaces a member-level attribute like a CommunityToolkit '[ObservableProperty]' (on a backing field or a modern partial property) or a '[JsonProperty]' that a type/method-only scan misses. Use it for '[Obsolete]', '[ApiController]', '[Authorize]', test attributes, etc. The 'method' axis includes constructors and accessors with a body (named .ctor / get_X / set_X), so a [JsonConstructor] or an [Obsolete] setter is found. A partial type is one type row; 'fullName' marks namesake type rows (Foo, Foo), 'project' the twin rows of a linked file. Deterministic from Roslyn - works on recalled sessions. Capped at 200.
Inputs: attribute (required), sessionId (required), kinds, scope.
find_by_event_subscriptionFind Event SubscriptionsARead-onlyIdempotent
Find the TYPES that subscribe to a C# event: a '+=' whose left side resolves to an event, anywhere in the type; external events too ('Button.Click'). find_usages on a solution-declared event lists its (un)subscribing and raising METHODS.
Use it for 'who listens to this event?', where handlers are wired, and as a leak hint. A '+=' on a number, string or delegate field is no subscription and is skipped. XAML-wired handlers (Click="OnClick") are 'markupWires' (handler, viewCount, views), owe no '-=', are never 'unreleased'; no event is recorded with them, so eventName cannot match them.
Returns scope, event, typesScanned, subscriptionSitesChecked, subscriptionSitesUnresolved, matches (at most 200 types: name, namespace, events, unreleased, markupWires; sorted by namespace, name) and when they apply 'note', availableEvents, testMatchesFiltered. A partial type is one row ('files' when spread over several); 'fullName' marks namesakes, 'project' linked-file twins. A hit lists ALL event keys ('DeclaringType.EventName') the type subscribes to.
'unreleased' lists each subscription no '-=' of the type removes ('Event <- Handler'). A '-=' counts for the same event AND the same handler on ANY receiver, so an old/new swap reads released; a lambda never matches, so a lambda subscription is always listed; a subscription to an object the type owns is listed although it cannot outlive it. [] means each has a matching '-='; no list means not measured (see the note). A place to look, not a proven leak.
Reading an empty answer: availableEvents lists the events subscribed in scope; read testMatchesFiltered first if present. An EMPTY availableEvents means none was RECORDED - recognition needs a resolved left side. The site counts settle it: '0 of 0' is nothing to see, '0 of 7, 0 unresolved' a real absence, '0 of 7, 7 unresolved' an unmeasured scope.
Read-only; works on a recalled session. eventName matches exactly, never as a substring; omit it for every subscribing type. Test-project types are excluded unless includeTests=true.
Inputs: sessionId (required), scope, eventName, includeTests.
find_by_semanticsFind Symbols by Layer, Role or DomainARead-onlyIdempotent
Find types and methods by their resolved semantics: layer, role, domain, responsibility. Filters combine with AND, and each hit names where a matched value came from.
Use it for 'which types of layer X carry role Y?'. At least one filter is required.
layer and role match the WHOLE value, case-insensitively: role='View' is one view, not every ViewModel. Values that merely contain the query are counted in nearMisses (axis, value, symbols) and named in a leading 'note', never listed as matches. domain and responsibility are free text and match as a case-insensitive substring; responsibility exists on types only, so setting it matches no method. The echoed criteria show the mode ('role=View' vs 'domain~order').
Returns criteria, a 'note' when there are near misses, minConfidence, matches - at most 200 items (kind, name, declaringType, namespace, matched), types first - and when they apply availableAxisValues, unclassifiedAxes, verifiedAbsentAxes and nearMisses. matched lists per filtered axis the field, the value and its source: 'fact', 'ai', 'inferred' or 'none'.
The layer and role values the built-in inference assigns are listed on those parameters; there is no 'Repository' role and an enum gets none. A layer profile or an annotation adds values in its own spelling ('viewmodel-item' beside 'ViewModel'). Nothing infers a domain: that axis matches only where an annotation set one.
On zero matches availableAxisValues lists, for the queried axes, the values present under the same minConfidence (up to 50 per axis). A queried axis no symbol carries a value on at that confidence (so nothing can match it here) is in verifiedAbsentAxes when every symbol declares it 'none' via , otherwise (only some declare it, or no symbol does) in unclassifiedAxes. A queried value missing from availableAxisValues is unknown at that confidence; if it is listed there, the AND of your filters was empty. An axis you did not query is in no list.
Read-only; test-project symbols are matched like production ones.
Inputs: sessionId (required), role, layer, domain, +2 more.
find_by_side_effectsFind Methods by Side EffectsARead-onlyIdempotent
Find code by its side-effect profile: what a method touches in the outside world. Each unit carries effect tokens from a closed set - io, network, database, serialization, logging, cache, messaging, unknown ('unknown' = an external call the analysis could not classify) - or none, which makes it pure. calls_external lists the external calls and property accesses behind a token; an effect that comes from a 'new X()' or is inherited from a callee has no row there.
Use it for 'what hits the database or the network?' and to find pure methods. Effects are propagated along calls and creations: calling a method or building an object that writes a file counts as 'io'.
With neither effect nor purity, only impure units are listed. An effect or purity outside its vocabulary, or effect with purity='pure', is rejected with the valid values, never answered with a silent zero.
Returns scope, effect, purity, methodsScanned, matches - a capped envelope of at most 200 hits (name, declaringType, namespace, effects) - and when they apply 'note', availableEffects, methodsScannedNote and testMatchesFiltered. The units are methods plus every accessor and constructor with a body, under their metadata names (get_X, set_X, .ctor, .cctor), and, unscoped, each top-level-statement file ('Program.cs (top-level)'); the purity='pure' listing is methods-only. A hit does not say which of its effects are direct and which inherited.
On zero matches availableEffects lists the tokens present in scope, so an empty result reads 'valid token, nothing in scope records it'; the note quotes what the token means ('cache' = an external cache API, not an in-memory cache); it is omitted when nothing was scanned. A 'note' leads the answer when a project's core references did not resolve: an unresolved run reports methods as PURE that are not.
Read-only; works on a recalled session. Test-project code is excluded by default (includeTests=true includes it); testMatchesFiltered counts what the filter removed, units methodsScanned does not include.
Inputs: sessionId (required), scope, effect, purity, +1 more.
find_dead_codeFind Dead CodeARead-onlyIdempotent
List dead-code suspects on two axes. kinds='type': types with no incoming reference in the reverse type fan-in, the source find_usages reads. kinds='method' (the default): PRIVATE methods whose recorded caller list is empty. kinds='all': both.
The default axis skips a private method a framework may call ([RelayCommand] or other framework attributes, partial methods, markup or designer event handlers, static Main, record PrintMembers, ShouldSerialize/Reset) or whose callers were not measured (unresolved references, explicit interface implementations). A nameof in a method's attribute (CanExecute) is a call. *.Designer.cs and *.g.cs files are read for references, never judged.
Returns scope, a 'note' first when there is one, methodsScanned, deadMethods and - when the type axis ran - typesScanned and deadTypes: at most 200 items each (methods: name, declaringType, namespace; types: name, kind, accessibility, namespace, and publicApi:true on a library's public type: outside consumers go unseen, see publicApiNote). Counters, each omitted at zero: what was not judged - methodsIneligible and typesIneligible, each split by reason (methodsIneligibleNotPrivate, -FrameworkInvoked, -UnanalyzedCallers; typesIneligibleStructural, -TestScaffolding, -FrameworkActivated, -MarkupReferenced), typesUnexamined (what the type axis would have judged had it run) - and the candidates the test filter removed, testMethodsFiltered and testTypesFiltered.
A listed candidate is a suspect: the note names what a zero fan-in does not prove and how to confirm one. The type axis exempts what it cannot see being used: interfaces, static classes, *Extensions types, test scaffolding, framework- or reflection-activated and markup-referenced types. An empty answer without a note means at least one axis judged an eligible population and found nothing; for the method axis read methodsIneligible against methodsScanned.
Read-only; works on a recalled session. Test projects are excluded by default. Any other non-blank kinds value runs both axes.
Inputs: sessionId (required), kinds, scope, includeTests.
find_implementationsFind ImplementationsARead-onlyIdempotent
List the types that implement an interface, in the solution or external (IDisposable). The interface counterpart to get_type_hierarchy (classes) and find_overrides.
Use it before changing an interface member, to find every type that must follow.
Returns items [{name, isAbstract (absent on interfaces and unknown kinds), kind, isTestProject, namespace, declaration, via, closes, through}], count, totalFound, truncated - at most 100, items without via first, then by name. 'declaration' is a generic implementer's arity form ('PolicyWrap'); via:'back-edge' = known only from the interface's implementer list, 'inherited' = through a solution base class or interface, 'derived-interface' = an interface extending it (implements nothing). 'closes' = the type arguments closing a generic interface, through bases too ('ContentSavedNotification'; an open generic names its own type parameter); 'through' = the base or interface it comes through. Absent when empty or unreadable.
A closed query ('IHandler') keeps the types closing it with those arguments; typeArgumentFilter counts the others closing it otherwise (otherClosings) or only over their own type parameters (openClosings). typeArgumentBase keeps the types closing it with an argument that is, derives from or implements that type ('DeletedNotification'); typeArgumentBaseFilter gives familyTypes and leftOut. An unreadable closing stays, marked closing:'unresolved'.
Test-project implementers are excluded by default; testImplementationsFiltered counts them (absent = none).
An empty answer carries a 'hint' when the name is not an interface or no indexed type carries it (a did-you-mean if one is close), and a 'generatedCodeNote' when a generator that did not run may have added it. A leading 'note' marks the inventory partial when a project's core references did not resolve (restore or build, then refresh_session).
Read-only. interfaceName is the simple name, case-sensitive. Two interfaces sharing a simple name answer as one list.
Inputs: sessionId (required), interfaceName (required), includeTests, typeArgumentBase.
find_overridesFind OverridesARead-onlyIdempotent
List the override-to-base relationships for methods of a given name - the class-inheritance counterpart to find_implementations (which covers interfaces). Each row pairs an overriding method with the nearest ancestor in its base-class chain that declares the virtual/abstract method it overrides: 'Derived.M(params) overrides Base'.
One call answers both 'who overrides this virtual/abstract method?' (impact, before you change a base method) and 'what does this override?' (comprehension). For a type's base and derived types use get_type_hierarchy; for the callers of a method use find_usages.
Returns a capped envelope: items (the rows, sorted), count, totalFound, truncated - at most 100 rows. Empty items means no method of that name is an override. When the overridden base is a BCL/framework virtual outside the solution (object.ToString, Stream.Dispose) the row reads '... overrides (external base)'.
Types render in their DECLARATION FORM ('Strategy.M(...) overrides ResilienceStrategy', the render get_type_hierarchy uses), so a generic and a non-generic namesake stay separate rows. A row carries no namespace: two same-named types that differ only by namespace collapse into one row, so read a row as the relationship, not as one attributed type; 'mergedNamesakes' names each such row and its types.
Read-only: it reads persisted facts and also works on a recalled session. Parameters: a qualified methodName ('Widget.ToString') matches nothing. There is no type filter, and each overload is a row of its own. An empty answer carries 'nearest' when a close declared method name exists - for a qualified methodName the bare method name.
Inputs: sessionId (required), methodName (required).
find_resource_usagesFind XAML Resource UsagesARead-onlyIdempotent
Resource-key fan-in across the solution's XAML/AXAML markup AND its C# sources - the question the C# symbol graph cannot answer because resources are wired by string key, not by symbol. With a key: where it is DEFINED (x:Key, file:line), every REFERENCE (file:line + kind: 'static'/'dynamic' for the markup extension, since a theme-swapped brush must be dynamic; 'theme' for WinUI's {ThemeResource}; 'code' for a C# string literal equal to the key; 'callerMemberName' for a C# member named like the key that reads it through a [CallerMemberName] resource accessor), and same-file duplicate definitions (in a compiled resource dictionary a duplicate silently shadows a neighboring key; a same-named key in DIFFERENT files is usually a theme pair, so only same-file counts as a collision). themeReferenceCount and callerMemberNameReferenceCount appear only when not zero. Without a key: the audit view - every never-referenced key (deletion candidates; deleting a still-referenced key compiles clean and throws only at runtime) + all same-file collisions + every referenced key no markup defines: misspelledKeyCandidates (one slip from a defined key) and notDefinedInSolutionKeys (most often a theme's or the platform's key - unknown, not broken). Reads the CURRENT .xaml/.axaml/.cs files on disk (bin/obj/.vs excluded) - never stale after an edit, no refresh_session needed, works on recalled sessions. STRING keys only (a structural x:Key="{x:Type ...}" is out of scope); the ResourceKey= long form, the element and a markup extension the solution derives from StaticResourceExtension/DynamicResourceExtension are read. XML-commented markup is ignored. Still invisible: a key assembled at runtime, a non-.cs source, a resource-extension subclass from a referenced library, a library template resolving the key from its own dll - zero references means 'verify before deleting', not proof. Lists capped at 200.
Inputs: sessionId (required), key, scope.
find_symbolFind a Symbol by NameARead-onlyIdempotent
Find types, methods, properties, fields, events, enum members and operators whose name contains the query (case-insensitive). Returns each match with kind ('type'/'method'/'property'/'field'/'event'/'enum_member'/'operator'), name, declaring type (a nested type's outer one; a member of a nested type adds 'outerType'), namespace, a signature, and 'project' where two hits would read alike. Use this to locate a symbol before find_usages / call_graph / get_context - every kind listed here is one find_usages / impact_of_change resolve. Types are listed first, then methods, then properties (handwritten + source-generated - a generated property carries a '// source-generated' note in its signature), then fields, events and enum members (an enum member's signature is its qualified Enum.Member form, the string the fan-in tools take), then user-defined operators + conversions. A user-defined operator is found by its METADATA name (query 'op_Equality' / 'op_Addition' / 'op_Implicit'; 'op_' lists them all). Not indexed, so an empty answer is expected for them: local variables, parameters, labels, namespaces, and C# generated into obj/ that carries real declarations (protobuf/Grpc.Tools, T4, Refit), and a Blazor component written only in a .razor file - a zero-hit answer says which in 'generatedSourcesNote'. Results are ranked by match quality FIRST - exact name, then case-insensitive exact, then prefix, then substring - and only within one rank by the kind order above, so the cap never hides an exact match behind weaker substring hits. Returns a capped envelope (items/count/totalFound/truncated), max 100 - if truncated, the exact/prefix matches are the ones you got; narrow the query (a longer substring) to see the weaker rest. If the query matches nothing, the response carries a 'nearest' suggestion - the closest declared name (a likely typo/case-mismatch); a match never carries one. With no nearest either, a 'note' says a missing name is not a missing feature.
Inputs: query (required), sessionId (required).
find_tests_forFind Tests for a SymbolARead-onlyIdempotent
Find the tests that likely cover a symbol: test methods that call it, read it, build it or are named after it. Static evidence (coverage_gaps asks the reverse).
Use it before a change, to see what pins a symbol, and after, to pick tests. Only a method with a test attribute counts (default: Fact, Theory, Test, TestMethod, TestCase(Source)).
Returns items [{testType, testMethod, project, matchReason}] (+ 'testTypeFullName' where two read alike), count, totalFound, truncated (max 50), invokesTotal, dispatchTotal (omitted at 0) and an optional 'note' after the list. matchReason is one of eleven tiers, sorted: 'invokes' (it calls the target), 'invokes-via' (through one method or constructor), 'command' (it executes the target's command), 'command-via', 'accesses' (it reads or writes the target property or field; for a type, a static one), 'accesses-via', 'constructs' (it builds the target type; type and constructor queries only), 'constructs-via', 'dispatch' (it calls an interface, abstract or virtual member the target implements or overrides; a double may have run), 'dispatch-via' and 'name' (a guess: the test's or its class's name mentions the symbol - for Type.Member, both). The first eight are strong; invokesTotal counts them over the result and dispatchTotal the contract rows, the rest are name guesses.
Evidence reaches one call deep at most (the test's body and one method, constructor or getter it calls). The 'note' says so on an empty answer and on a non-constructor one whose strong rows are all '-via', and names other misleading cases: an undeclared name or type segment (name noise), an event or enum member (name tier only), a nested 'Outer.Inner' type, a constructor query, no strong row, contract rows, construction rows (matched by simple name), overloads unioned, tests left out for only reading a constant. 'namesakes': types sharing the name.
Read-only. symbol is case-sensitive: a type's simple name, a member name bare or as 'Type.Member' (only that type's member), or 'Type.Type' for the constructor.
Inputs: symbol (required), sessionId (required).
find_unresolved_bindingsFind Unresolved XAML BindingsARead-onlyIdempotent
List silently broken XAML bindings: a {Binding X} whose root member does NOT exist on its confidently resolved DataContext view-model (typo, removed/renamed member, or wrong DataContext) - neither the compiler nor render-smoke tests catch it.
Use it after renaming or removing view-model members. It judges the ROOT member of a binding path only. Where a member or a resource key is bound: find_binding_usages / find_resource_usages.
Covers WPF/WinUI .xaml (view-model via d:DesignInstance or a unique FooView->FooViewModel convention) and Avalonia .axaml ({Binding}/{CompiledBinding}, only via x:DataType). Conservative BY SKIPPING: a binding whose DataContext scope cannot be typed with certainty (ControlTemplate, any Style but the element's own inline one, runtime DataContext, keyed resources, a DataType-less template unless - WPF only - its host's ItemsSource types it), or whose view-model has an unverifiable external base, is never flagged. Source-generated ([ObservableProperty]/[RelayCommand]) and inherited members count as resolved. Two measured false-report classes are closed: a HierarchicalDataTemplate's ItemsSource pinned onto its content (8/8 on MahApps.Metro), and a d:DesignInstance hint where code-behind sets DataContext = this (25/25 on a ReactiveUI example; now skipped). A hit is evidence to verify at its line, not proof.
Returns JSON: count and unresolvedBindings (view, viewModel, member, line; one entry per site), plus markupFilesScanned and bindingSitesInMarkup from a live disk walk (a clean zero vs no markup at all). The denominator of count: sitesJudged (root member checked on a resolved type) and sitesSkipped, by reason in sitesSkippedBy (both null on a recalled session); judgementNote names any gap to bindingSitesInMarkup. sitesSkippedByView lists each view's skipped sites by reason with their lines (capped at 100 views): a renamed site listed there was never judged.
Read-only; LIVE analysis only: call refresh_session after edits (a recalled snapshot reports none, and a 'note' says so).
Inputs: sessionId (required).
find_usagesFind Usages of a SymbolARead-onlyIdempotent
List who uses a symbol (direct fan-in); impact_of_change adds closure and risk, calls_external covers external members.
Use it before changing or deleting a symbol, not grep. What is listed depends on resolvedKind: 'type' - the types that reference it (a doc cref too: docCrefOnly), plus markup views; 'method' - its callers, overloads unioned ('overloads'; item 'calls': its own); 'property' or 'field' - its readers and writers; 'event' - its raisers and (un)subscribers; 'enum_member' - its references; 'operator' ('op_Equality') - its uses, casts too; 'constructor' ('Type.Type') - the type's construction and injection sites. Test users included.
Returns symbol, resolvedKind, usedBy (capped envelope: items, count, totalFound, truncated; max 100). An item is {name, file, line}, one per user: file is solution-relative, line where its declaration starts (not the reference); 'files' [{file, line}] for a type declared in several; absent if unknown; users sharing a name add 'fullName' (a type), 'project'. Optional members qualify it: 'note' ahead of the list (an empty or constructor answer, unresolved core references), 'namesakes' (several types share it, unioned), 'collision' (a same-named other kind, not counted), 'viaContract' + 'viaContractNote' (a qualified method's contract or virtual-base callers), 'selfReferences' (users on the member's own type), 'textuallyInvisibleUsers' (users not naming it), 'nearest' (on 'not_found': a close name), 'resourceKeyNote' (markup keys the type; those views: find_resource_usages).
An empty usedBy on a resolved symbol is not proof of dead code: entry points, DI, reflection, source generators and unrecorded markup leave no reference. 'not_found' is not 'undeclared': a delegate type and a qualified or nested type name answer it; 'note' may say why.
Read-only. symbol is case-sensitive. A type takes its simple name. A member takes 'Type.Member' for one type's, or a bare name: that answers for ONE kind, the first in the order above with users, unioned over its same-named members.
Inputs: symbol (required), sessionId (required), includeTests.
get_contextGet Code Context for a SymbolARead-onlyIdempotent
Return a dense, token-budgeted AI-Builder-Markdown slice of the code around a symbol: the symbol's source plus its expanded neighborhood (direct dependencies + callees, depth 1). Pass a type or method simple name (use find_symbol first to disambiguate). budget caps the output in tokens (floored at 8000); omit for full bodies. The fastest single-symbol retrieval - prefer it over export_markdown (which renders the WHOLE solution) when you only need one symbol's context. Multi-TFM solutions are deduplicated to one logical project per name, keeping the NEWEST TFM's instance (matching find_symbol's view; export_markdown keeps the unfiltered per-TFM render). Need a CHOSEN environment of one symbol (callers / tests / implementations / quality)? use explain_symbol. Bundling a whole task from a natural-language goal? use pack_for_task (read) or prepare_task (edit-ready: + covering tests & siblings).
Inputs: symbol (required), sessionId (required), lean, budget, +1 more.
get_diagnosticsGet Compiler DiagnosticsARead-onlyIdempotent
Compiler errors and warnings without a build: every C# project of the session is compiled in memory, in seconds. list_insights reports design findings instead.
Use it after editing .cs files: refresh_session, THEN this - the diagnostics come from the session's snapshot, not from disk, and an answer known to be behind the disk leads with verdict:'stale'. After a build call refresh_session(force: true); a restore needs no force.
Returns verdict (only when stale or inconclusive), projectsScanned, errorCount, warningCount, infoCount and diagnostics: at most 200 items (id, severity, message, location, category, project), while the counts cover the full set; with errors, the default lists only them (warningsNotListed). A multi-targeted project reports a diagnostic once; #pragma-suppressed ones are excluded. No analyzer runs, the SDK's included; source generators that load do run.
Disclosures say what the counts leave out:
incompleteProjects, incompleteRatio, suppressedDiagnosticsTotal: projects whose core references did not resolve (e.g. no targeting pack). Their diagnostics are counted, not listed. Read that total as a MAGNITUDE, not as one half of a ratio with errorCount. verdict:'inconclusive' + reliable:false fire only when a strict majority of projects is incomplete.
cascadeFromIncomplete:true on an item: inherited from an incomplete project.
generatedCodeGaps: code a source generator or the WPF markup compiler writes is missing.
designTimeBuildFailures: MSBuild reported a failure loading a project (a warning reads alike); its compiler options may be incomplete.
suppressors: references whose DiagnosticSuppressors a real build runs (see runSuppressors).
Needs a live session (a recalled one is rejected); writes nothing. scope: 'solution' or a case-insensitive substring of an item's location - for a file under the solution folder its relative path, with the OS separator (backslash on Windows). It filters the ANSWER, not the work: every project is compiled either way.
Inputs: sessionId (required), scope, severityFloor, runSuppressors.
get_insightGet Insight DetailsARead-onlyIdempotent
Get the full detail of one insight (incl. the per-item Details list) by its id. Discover ids via list_insights (the producers that found issues). Ids are producer-level. An unknown/typo'd id is rejected with the known producer ids; a registered producer that found nothing this session reports that explicitly (distinct from a typo - list_insights lists only producers WITH findings, so a 0-finding id is not discoverable there). Details default to the top 20 items (a '(... and N more)' marker discloses the rest); pass maxDetails to raise that cap for a full per-item drill-down. Pass dbPath to use the active QualityProfile from that DB (same semantics as list_insights, incl. the explicit-layerProfile precedence). Triage-filtered like list_insights, on BOTH axes: detail lines removed by a suppression ('by design', DB + sidecar) or by a dismissal ('seen', solution record) are gone, and 'suppressedDetailCount' reports the two ADDED TOGETHER (absent = none) - so it is not a count of sidecar entries. A producer silenced outright reports as 'registered but 0 hits'.
Inputs: insightId (required), sessionId (required), dbPath, maxDetails.
get_type_hierarchyGet Type HierarchyARead-onlyIdempotent
Walk one type's inheritance in both directions: baseChain upward, derivedTypes downward, plus the interfaces the type declares. The type-level counterpart to find_overrides; for the types that implement an interface use find_implementations.
Use it before changing a base class.
Returns type, resolvedKind (class, record, struct, interface, enum or 'not_found'), baseChain, interfaces and derivedTypes, a capped envelope (items, count, totalFound, truncated; at most 100). baseChain lists the nearest base first; when the chain leaves the solution the last entry NAMES the external base ('ObservableObject (external)'). derivedTypes is transitive, one entry per type, reading 'Derived : DirectBase'; test-project types are included, unmarked. interfaces holds what the type itself declares - one inherited through a base class is not repeated - and for an interface query its base interfaces. Every link is arity-qualified ('Foo' vs 'Foo'); types sharing a name are written by full name ('Ef.User : Core.User'), adding '[Project]' where the full name is shared too.
Optional members ahead of the lists qualify them: 'mergedNamesakes' (several declared types share the simple name; the view merges them), 'separatedNamesakes' (derivations from a same-named type that is NOT the resolved one, typically a framework base shadowed by an in-solution namesake, were excluded and are counted here), 'hint' (an interface query: derivedTypes covers class inheritance only), 'note' (unresolved core references: partial until restore or build, then refresh_session).
'not_found' answers three empty lists: no type of that name is declared - also for an external base, a delegate type, a namespace-qualified name, a nested 'Outer.Inner' form, or an arity suffix no declaration has. A trailing 'nearest' gives a close declared type name, for a qualified form the simple name.
Read-only; works on a recalled session. typeName is the simple name, case-sensitive. A bare name merges a generic type with its non-generic namesake ('mergedArities' says so).
Inputs: typeName (required), sessionId (required).
impact_of_changeEstimate Impact of a ChangeARead-onlyIdempotent
Estimate the blast radius of changing a symbol: direct users, their dependents and a risk level.
Call it first, before any edit to a symbol other code names (rename, signature, default value): a compile checks names, not what consumers assume.
Returns directCount, transitiveCount (per declaration, tests included), productionImpactCount (transitive, no tests), risk, directImpact (max 20, find_usages items). risk reads productionImpactCount: 0 none, 1-4 low, 5-20 medium, over 20 high; it counts dependents, not what the change means. If one cycle dominates a high closure, 'saturation' leads the counts and risk reads the direct production fan-in.
Per kind: a type - the types referencing it, transitively; a method or operator - its caller closure through interface and abstract dispatch; a property, field, event or enum member - the code touching it (transitive = direct). A qualified method counts its contract and virtual-base callers ('viaContract').
An interface or abstract method or type adds 'implementers' per contract (what a signature change breaks; an entry's 'namesakes' splits merged contracts), and risk reads 'riskBasis'.total = closure + production implementing types (on a type direct ones only, 'indirect' lists the rest; 'riskBasis'.note: no production caller).
Trailing, moving no count: 'registration' (reached only through DI), 'serializers' (a property's serializer sites), 'attachedProperty' (a dependency property's accessor or wrapper markup users).
Ahead of the counts: a 'note' on misleading numbers (directCount 0 on a resolved symbol - DI or reflection may reach it; unresolved core references; no production project; 'not_found': risk 'none' measures nothing, 'nearest' gives a close name; a private member), 'collision' (a same-named other kind, not counted) and 'namesakes' (types sharing the name, counted as one union).
Read-only. symbol is case-sensitive, resolved as in find_usages: a type by simple name, a member bare (one kind) or 'Type.Member'; for a constructor ('Type.Type') use find_usages.
Inputs: symbol (required), sessionId (required).
init_solution_configPropose a Solution ConfigurationARead-onlyIdempotent
Collect the raw material for the guided setup of a solution's Layer Profile, Exclude-Namespaces and Test Profile - step 1 of 2; apply_solution_config is the step that writes.
Use it when solution_config_status or the configInit hint of analyze_solution reports an axis as not initialized, or when check_solution_config_drift reports one as stale. Propose a mapping from the returned material, then call apply_solution_config with it. To only see what is configured, use solution_config_status.
Returns JSON: solutionPath; declaredNamespaces - the solution's own namespaces, the source for layer-mapping rules (test-project and framework namespaces are left out); referencedNamespaces - every namespace a using directive names, the solution's OWN included, heaviest first (a hand-written global using ahead of any file count), each {namespace, documents} (hand-written files whose own using names it), plus generatedDocuments (build-written files, obj/) and globalUsingProjects / implicitGlobalUsing (an SDK implicit using) where they apply - neither says what the code leans on: only the external/framework ones are exclusion material, never exclude your own; testProjects - what the default heuristic classifies as test projects; detectedTestAttributes - the test attributes present in the code; frameworkApiAreas - areas the built-in exclusion list removes although at least 15% of the public method signatures name them (Dapper: System.Data), each with the keepRule to put into exclusions so the rest of the list still applies; proposalFormat - the JSON shape apply_solution_config expects. The first three lists are capped envelopes (items/count/totalFound/truncated, max 300); the other two are plain arrays.
Read-only: it writes no configuration and no DB row and marks nothing as initialized. It walks the warm session workspace (no re-analysis), so it needs a live session; a recalled one is rejected with an error.
Inputs: sessionId (required).
install_agent_hooksInstall Agent HooksADestructiveIdempotent
Install the aicb symbol guard into this project for an agent harness, so a C#-symbol question is REFUSED on a text search and redirected to the tool that answers it. Writes the guard script plus the harness's wiring entry (.claude/settings.json, .codex/hooks.json or opencode.json) and nothing else - no .mcp.json, no skills, and only inside the directory of the session's own solution. Omit 'harness' to install for the harness that is calling. An existing guard or wiring is kept unless force=true. Restart or reconnect the client afterwards, then call refresh_session so the session stops reporting the guard as missing.
Inputs: sessionId (required), force, harness.
instantiation_sitesFind Instantiation SitesARead-onlyIdempotent
Find where a type is constructed and where it is injected: the narrow 'who creates or obtains an instance of X?' that find_usages (every reference) cannot answer. resolve_injection answers the other half - what is registered for a type.
Use it before changing a constructor, and to see whether anything but a test builds a type.
'created': a method, constructor, accessor or top-level code that runs 'new X(...)', a target-typed new or a record 'with'; a field or property initializer is attributed to the constructor the compiler runs it in, so such a site is named '.ctor' ('.cctor' for a static one). 'injected': a type that takes X as a constructor parameter - no container is consulted. A parameter wrapped in another type (Lazy) is recorded under the outer type, and an array of X is not a creation of X.
Returns typeName, a 'note' when there is one, createdByCount, createdByTests, injectedIntoCount, injectedIntoTests, sites - at most 200 items (name, declaringType, namespace, kind, isTestProject), created sites first - and 'nearest'. The four counts are true totals within scope. Test-project sites are included, not filtered: isTestProject marks each. A type the DI container builds has no recorded production site, so 'every site is a test' is not proof.
A 'note' covers two zeros and a test-only answer: with no site at all it names the construction paths this fact cannot see and the markup views naming the type; with injectedIntoCount 0 it names those of its interfaces something is injected under - a DI consumer takes the interface, so query that name. With every site a test (unscoped), it says so and names the markup views. 'nearest' gives a close declared name when no site was found and the name matches no declaration.
Read-only; works on a recalled session. typeName is matched on its simple name: namespace and generic arguments are dropped, namesakes are unioned, and an external type ('List', 'HttpClient') is a valid query. scope is 'solution' or a namespace prefix on the SITE's namespace.
Inputs: typeName (required), sessionId (required), scope.
list_insightsList Code InsightsARead-onlyIdempotent
List code-quality / async / design-smell insights for a session (long methods, fat interfaces, unused types, missing async suffixes, many-parameter methods, ...). Runs against the live analyzed solution; each insight is aggregated per producer, with the count of its detail lines ('detailsCount'; get_insight lists them). Pass dbPath to use the active QualityProfile (producer thresholds + toggles) from that DB; omit it for the default profile (DB-free). Honors the session's active test-detection profile (resolved by analyze_solution(dbPath)) for the production-focused producers. An explicit analyze_solution(layerProfile) drives the layer-violation insight and wins over the DB-configured profile (same precedence as solution_metrics and the CLI gate). THIS ANSWER IS TRIAGE-FILTERED, on BOTH triage axes: findings the solution marked intentional (a suppression - in the DB or in the git-tracked .aicb.json sidecar) AND findings a user dismissed as seen (in the solution record, NOT in the sidecar) are removed, and an insight whose findings are all gone does not appear at all. Every insight that lost lines carries 'suppressed' with the count of what BOTH axes removed together (absent = nothing filtered), so a shrinking number is never mistaken for a codebase that improved - but do not read that count as a sidecar entry count, because some of it is dismissal. Note 'title' is producer text computed BEFORE filtering, so on a partially filtered insight it still names the pre-filter total - 'detailsCount' + 'suppressed' are the real split. 'remediationCostMinutes' is a coarse order-of-magnitude estimate from fixed per-finding rules, not measured effort. Pass files - say the files a change touches - for the findings about the types they declare, with their detail lines.
Inputs: sessionId (required), files, dbPath.
list_mcp_profilesList MCP ProfilesARead-onlyIdempotent
List the MCP profiles in the server's config DB - each with its id, name, the slots that have a template assigned (General appears exactly when a profile-wide template is set), the profile-wide templateId (null = none), whether it carries a skill, its effective tool-set (toolSelection: the class-level CSV, or a 'methods:'-prefixed function-name list when the profile curates individual tool functions; null = lean core), whether it is the active one, AND the render config it applies (tokenBudget / outputFormat / overflowPolicy - a null tokenBudget/overflowPolicy means inherit: the template then the active PipelineProfile decides). Requires the server to be started with --db-path. Use the ids with 'aicb mcp --mcp-profile ' to pin the profile at server start - that is the only way to change it; there is no runtime switch tool.
Inputs: no named parameters.
list_skillsList Tools and FacetsARead-onlyIdempotent
The server's capability map: a tools INDEX naming EVERY tool this server has - each exactly once, grouped into three lists by pool state (inPool.core = the tools every profile exposes, in reach-for-them order; inPool.extras = the rest of the active pool, alphabetical; outOfPool = exists but the active tool set does not expose it) - plus the sections that group them by name: the always-on navigation CORE (listed in no menu because it needs none), the task FACETS (id + guidance + template slot + the facet's tool menu), the cross-cutting guidance styles, and the legacy functional skill bundles. Use it to discover specialized long-tail tools (e.g. get_insight, find_by_concurrency_risk, coverage_gaps, find_unresolved_bindings) beyond that core - and to check whether ANY tool you want is merely in outOfPool rather than nonexistent. Tools outside the active pool become reachable via an MCP profile whose facet menu or bundle names them (see list_mcp_profiles / 'aicb mcp --mcp-profile '), or via the AICB_MCP_TOOLS override. By default the answer omits the per-tool descriptions, which is what makes it cheap; pass detail="full" when you need them. No session needed.
Inputs: detail.
measureMeasure Answer SizesARead-onlyIdempotent
Report how BIG a read-only query's answer would be - its exact token count - WITHOUT returning the answer. Use it to decide before you pull: whether a call is worth making, what 'budget' to pass a slice tool (get_context/explain_symbol/pack_for_task), or whether to narrow 'scope' first. Same shape as batch: pass the shared session_id once plus 'queries', an array of {tool, args} where 'args' is that tool's own arguments WITHOUT the sessionId. Example: queries=[{"tool":"explain_symbol","args":{"symbol":"OrderService","include":["callees"]}}] → {tool, ok, tokens, chars, returned, totalFound, truncated, budgetNote}. 'tokens' is the real tokenizer count (cl100k_base) of the exact text the tool would return; 'returned/totalFound/truncated' are passed through when the tool answers with a capped envelope, so you also see whether you would be seeing everything. The Markdown slice tools (get_context / explain_symbol / pack_for_task) have no such envelope - they disclose budget pruning in prose, so 'budgetNote' carries that disclosure verbatim: if it says types were dropped, the answer you are pricing is already CUT (an axis you asked for can be missing entirely), and a larger 'budget' is what buys it back. IMPORTANT: measure does NOT make the query cheaper to RUN - the server does the full work either way; it makes the answer cheap to INSPECT (you pay ~50 tokens instead of the answer). So measure to DECIDE, then call once - do not measure every call by reflex. Dispatches the same read-only tools as batch, and refuses the same ones for the same four reasons - mutation is only the first of them; the refusal message names which one applies. Like batch, sub-queries follow the ACTIVE POOL: an out-of-pool tool is refused per item. Max 16 queries per call.
Inputs: queries (required), sessionId (required).
pack_for_taskPack Context for a TaskARead-onlyIdempotent
Goal-driven context packing: given a natural-language task, seed on every symbol the goal names (type or method), expand the neighborhood, and return a token-budgeted AI-Builder-Markdown bundle - with goal-aware trimming that keeps the most task-relevant methods when the budget is tight. Name the symbols you care about in the goal (e.g. 'refactor OrderService.Cancel and Validate'). budget caps output tokens (floored at 8000). Multi-TFM solutions are deduplicated to one logical project per name (NEWEST TFM's instance, matching find_symbol's view). For an EDIT-ready bundle that also pulls in the covering tests + naming/file siblings (and, on a profile-aware server, renders through the facet's template), use prepare_task instead.
Inputs: goal (required), sessionId (required), lean, facet, +2 more.
prepare_taskPrepare Context for an EditARead-onlyIdempotent
Build an EDIT-ready context bundle for a task goal: the source of the symbols the goal names, their dependency neighborhood, the test methods that cover them and 1-2 naming/file-convention siblings (the test, the factory, the validator you'd otherwise miss) - as one token-budgeted AI-Builder-MD document.
Prefer this over pack_for_task when you're about to EDIT, not just read. pack_for_task packs the goal-seeded neighborhood alone, without covering tests and siblings. For one symbol use get_context or explain_symbol; for blast radius and test coverage as numbers use review_context.
Returns text, not JSON. It starts with a manifest comment (goal | seeds | tests | siblings | render) so you can see what was covered - and what was NOT ((none)); after a source edit a staleness comment precedes it. 'render' names the path that produced the body. 'template' is the default, because a plain 'aicb mcp' resolves the standard config DB: the chosen facet's template (graphs, detail, line numbers, quality) in the active profile's notation - YAML under the built-in profiles. 'lean' is the fallback of a server without a config DB: tag Markdown. Multi-TFM solutions are deduplicated to one logical project per name (NEWEST TFM's instance, matching find_symbol's view).
Read-only. Parameters: a tight budget drops neighborhood types, tests and siblings first. A type the goal names, or the one owner of a method it names, is never dropped (too large, it renders as structure: members and signatures); the owners of a method name several types share can be dropped. A leading note names the drops. lean and includeQualityMetrics act on the lean render only, so not on a default server.
Inputs: goal (required), sessionId (required), lean, facet, +4 more.
refresh_sessionRefresh a SessionAIdempotent
Re-analyze the session's solution after YOUR code edits - call it once you have changed .cs files, otherwise find_usages / impact_of_change / list_insights / get_context may keep answering from the STALE pre-edit graph (a silent source of wrong results). Cheap to call speculatively: a W0 file-set-hash check skips the expensive Roslyn re-run when nothing actually changed, and a C# file whose timestamp moved while its text stayed the snapshot's (a save without an edit) does not count as a change either. That check reads SOURCE files. A restore that rewrote a project's assets file after the load is noticed anyway and reloads through MSBuild; a plain build is not seen - after one, an analysis that reported unresolved references retries once by itself, and 'force' is the deterministic way to demand a reload. Reuses the warm workspace. Returns { changed, reason, session, mode } - 'mode' names the path the re-analysis took ('incremental' = document texts replayed into the warm snapshot without an MSBuild reload, else 'full-reload') and is absent when nothing was re-analyzed. The incremental path can stop engaging without any visible symptom - answers stay correct, calls just get slow again - so 'mode' is how you notice.
Inputs: sessionId (required), force.
resolve_injectionResolve Dependency InjectionARead-onlyIdempotent
Resolve a Microsoft DI or MEF registration: what is registered for a service, with lifetime and site. Not usage (find_usages), implementers (find_implementations), construction (instantiation_sites).
It reads Add{Singleton,Scoped,Transient}, TryAdd*, descriptor adds built at the call, AddHostedService, project-local helpers registering their own type arguments, and a foreach over a static new(typeof(Service), typeof(Impl)) table. A key, and a closing, is a service of its own (IRepo, IRepo); open IRepo<> is read. Not read: a descriptor built elsewhere, AddHttpClient-style helpers, Scrutor, other containers. Umbraco collection builders are listed apart (collectionBuilders).
Returns JSON: registrations (serviceType, implementationType [= service if self/instance], lifetime, note, location, project, declaringMethod, key, targetFrameworks: a multi-targeted project's call is one row; note 'inferred, not read' = from a package helper's call shape AddSingletonAs().As(): lifetime from its name, never ambiguous or a winner) and: multiRegistration / registrationSites; ambiguous (possible conflict: ONE site registers several implementations of one service, receivers not compared, not consumed as a collection); effectiveRegistration (2+ calls: the winner a single-service resolve gets, read in ONE straight-line site; else null + reason); consumedAsCollection; constructorConsumerCount (plain ctor parameters); otherConsumers (GetService outside a registration factory, [Inject], @inject: per form); optionalDependencies (optional ctor parameters: yes/no/unknown).
An empty list says why: resolvedKind 'not_found' plus 'nearest' = no such name; else a 'note' names the forms not read and which the solution holds - empty means 'not statically visible', never 'not registered'.
Test projects count only with includeTests=true; testRegistrationsFiltered counts what that hid. interface is the SERVICE type (a self-registered class too); a closed form (IRepo) lists that closing and IRepo<> (typeArgumentFilter: the rest).
Inputs: interface (required), sessionId (required), site, includeTests.
review_contextGet Review ContextARead-onlyIdempotent
Assemble review context for a set of changed symbols in ONE call: per symbol its fan-in, the transitive change impact with a risk level, the types that implement it (if an interface) and the test methods that likely cover it. It bundles what find_usages, impact_of_change, find_implementations and find_tests_for answer one symbol at a time.
Use this before reviewing or refactoring to see blast radius + test coverage at a glance. It does not name the callers (use find_usages or impact_of_change for that list) and returns no code (use explain_symbol).
Returns a JSON array ordered by symbol NAME - index it by 'symbol', never by request position. Per entry: symbol, resolvedKind, directUsages, transitiveImpact, risk (none/low/medium/high), implementations (names; test doubles are listed too, find_implementations filters them by default), coveringTests (testType, testMethod, project, matchReason), coveringTestsTotal, coveringTestsTruncated. An unresolved name reads resolvedKind 'not_found' with a 'note' and no risk; a 'note' also flags a risk graded on the direct fan-in or on implementers alone. For a bare name several types share, the counts are their union and no note says so - find_usages names the namesakes.
'coveringTests' is find_tests_for's list: strongest evidence first, capped at 50 per symbol. The cap keeps the 50 strongest rows: name guesses go first, strong rows too once there are more than 50. find_tests_for stops at the same 50, so narrow the symbol rather than re-query. 'invokesTotal' (strong rows) and 'dispatchTotal' (rows reaching the symbol through an interface or abstract member) count the WHOLE list and can exceed the rows shown; coveringTestsTotal minus both is the name guesses. For a qualified method, directUsages and transitiveImpact include callers credited through such a member - see 'viaContract' / 'viaContractNote'.
Read-only. Parameters: symbols must not be empty and takes the strings find_usages resolves - a type's simple name, or a member bare or as Type.Member.
Inputs: symbols (required), sessionId (required).
save_sessionSave a Session SnapshotA
Persist the current session's analyzed model as a named Manual snapshot in an aicb DB, so a later session can diff against it with compare_with_previous.
Use it BEFORE a bulk mechanical change (a rename sweep, a signature migration) and call compare_with_previous afterwards. An ordinary edit needs no snapshot: git diff and the tests cover it.
Returns JSON: snapshotId, sessionId, solutionId, name, lineNumbersAvailable and totalSnapshots (the snapshots that DB now holds for this solution).
It writes to the aicb DB, not to the solution's source or configuration: one new snapshot row, plus the solution's record when the DB does not know the solution yet; a DB file that does not exist is created. Nothing is overwritten: every call adds a snapshot under a fresh id, and name is a label, not a key. The snapshot keeps the line spans, but a session recalled from it reports lineNumbersAvailable false: its lines mean 'as analyzed', not the current working tree.
Parameters: sessionId is a live or recalled session's id or an absolute .sln/.slnx/.slnf path. Omit dbPath to use the server's standard config DB (the same one the GUI uses); when the server resolved none, the call fails with 'dbPath is required'.
Inputs: name (required), sessionId (required), dbPath.
server_infoGet Server InfoARead-onlyIdempotent
Report this SERVER's own identity and health: name, version, build commit, the config DB's schema version, and drift warnings about the server binary. Needs no session - a reachability smoke-check.
Call it first after connecting and after an update or restart: the commit answers the question the version number cannot - 'is the binary I am talking to built from the code I just landed?'. It says nothing about a solution's configuration (solution_config_status: what is active; check_solution_config_drift: does it still fit the code), about which tools exist (list_skills) or about how they were used (usage_report).
Returns plain text, not JSON. Line 1 is the identity: 'aicb MCP server (AIContextBuilder) v (commit )'. Compare the commit against git rev-parse HEAD (the full sha, so a short hash is its prefix): one version number can cover DIFFERENT builds. A build with no resolvable git revision omits the commit. 'Config DB schema: user_version=N ()' follows when a config DB is resolved.
Then, only when they apply: ANALYZER DRIFT - this build's commit against the HEAD of each repo of a solution this server analyzed, per repo with its solution and HEAD (it cannot tell which is yours); also in-sync and ahead; silent with no session; a repo not knowing this commit gets no line. SCHEMA DRIFT - the config DB's schema is newer than this binary: rebuild or reinstall. PENDING MIGRATION - the reverse: the DB is older; the line says how to migrate it. TOOL POOL DRIFT - the active profile lists tools this binary does not register: a newer build wrote it (rebuild or reinstall) or the tool was retired (re-save the profile). That check is tool-NAME level; a new parameter on an existing tool is not detected, the commit is the finer signal. CONFIG DRIFT - the active MCP profile changed since this server started: restart or reconnect to load the current tools and instructions.
Read-only: it reads the config DB's schema version and, for the analyzer line, queries git in the analyzed solution's repository.
Inputs: no named parameters.
solution_config_statusGet Solution Configuration StatusARead-onlyIdempotent
Check whether a solution's Layer Profile, Exclude-Namespaces and Test Profile have been initialized via the guided setup, and which Layer Profile / Exclusion List / Test Profile is currently active. 'initialized' is an explicit marker for all three slots (stamped by apply_solution_config - it is NOT inferred from having an active id, which a user can also set manually via the Settings panel); the testProfile slot additionally reports the effective per-solution active test-detection profile. Pass the session_id; dbPath is optional - omit it to use the server's default config DB (the same one the GUI uses), if the server resolved one. With no DB (or an empty file) the answer comes from the sidecar and the built-in defaults, and no database is opened or created; a dbPath that does not exist is refused. Each slot names the config IN FORCE for the analysis - resolved exactly as analyze_solution resolves it - and its source: 'db' (the per-solution row or the app-global default), 'sidecar' (the committed .aicb.json applies, which it does whenever the DB configured nothing for the axis - a built-in default is not configuration), 'built-in' (the built-in exclusion list, applied when nothing configured one and no sidecar names one) or 'none' (the heuristic). A repo with a valid .aicb.json sidecar but no DB row reads initialized=true, source:sidecar, not a misleading uninitialized. Use this on solution start: if a slot is not initialized, offer to run init_solution_config + apply_solution_config.
Inputs: sessionId (required), dbPath.
solution_metricsGet Solution MetricsARead-onlyIdempotent
The solution-wide quality rollup in one call - the numbers the CLI '--fail-on' quality gate evaluates. symbol_metrics ranks single methods; list_insights shows the findings behind the severity counts.
Use it for a before/after number, or to try a gate expression.
Returns metrics {typeCount, methodCount, complexityMax, complexityAvg, methodsOverComplexityThreshold, ceMax, caMax}, scope (always 'production'), testMethodCount, testTypeCount, severities {critical, warning, info, ok}, debtMinutes, debtApprox, debtRating, complexityThreshold, qualityProfile, layerProfile (when one is in force), knownGateMetrics and, with failOn, gate {expression, passed, violatedClauses}. degradedProducers appears when an insight producer failed; severities and debt are then incomplete. debtMinutes is a coarse estimate from fixed per-finding rules, not measured effort; debtApprox rounds it (1 d = 8 h).
PRODUCTION code only - test projects, per the session's test profile, are excluded from every number, and testMethodCount / testTypeCount give the excluded side. Types are logical (partial fragments merged, multi-TFM deduplicated); methods exclude constructors and accessors. methodsOverComplexityThreshold counts STRICTLY greater than complexityThreshold, while symbol_metrics' minComplexity is inclusive. ceMax / caMax: coupling maxima (caMaxNote: same-named types could raise caMax). severities count insights - one per producer that found something - not findings. The rollup is NOT triage-filtered: suppressed and dismissed findings still count, as in the CLI gate, so debt and severities can be higher than what list_insights shows.
failOn takes the tokens in knownGateMetrics; this gate is advisory, the blocking one is the CLI's.
Profiles: the active QualityProfile of the config DB in force (see dbPath); the session's layer profile, else the one dbPath (or, omitted, the session's analysis DB) configures. Writes nothing of its own; an older dbPath DB is upgraded on open, as the server's own is at startup. Works on a recalled session.
Inputs: sessionId (required), dbPath, failOn.
symbol_metricsMeasure Method ComplexityARead-onlyIdempotent
Report method complexity: McCabe CYCLOMATIC complexity (independent paths) and COGNITIVE complexity (how hard the method is to follow: nesting is penalised, boolean-operator runs collapse), plus the parameter count - for one method by name, or as a ranking of the hotspots. solution_metrics gives the solution-wide rollup.
Use it before touching an unfamiliar method, and to find where the complexity sits. Both metrics measure CODE SHAPE, not runtime cost; for a hot path use a profiler.
With symbol (mode 'symbol'): every method unit of that exact, case-sensitive name - bare for all same-named ones, 'Type.Member' for one type's. scope, includeTests, minComplexity and rankByCognitive are ignored. Accessors, constructors and operators answer by metadata name ('get_Items', '.ctor', 'op_Equality'); '.ctor' is every constructor unit of the solution, 'Type..ctor' one type's. Without symbol (mode 'ranked'): methods only, sorted by cyclomatic complexity descending - by cognitive with rankByCognitive=true - and filtered to complexity >= minComplexity (inclusive; 0 = no floor).
Returns scope, mode, metricMeaning, minComplexity, methodsScanned, methods - at most 200 items (name, declaringType, namespace, cyclomaticComplexity, cognitiveComplexity, parameterCount; project when two projects compile one type) - and when they apply 'note', minComplexityBoundary, 'nearest' (a close declared name when a lookup matched no method) and testMethodsFiltered. In a ranking, test-project methods are excluded by default (includeTests=true includes them); testMethodsFiltered counts those that cleared the floor, and the note names the strongest one when it would have ranked inside the shown list. The note also says when the two axes would have listed different methods.
An empty ranking means nothing cleared the floor - or the scope matched nothing: methodsScanned 0, plus a note. An empty lookup means no METHOD of that name; a bare property or type name gets a note saying so.
Read-only; works on a recalled session.
Inputs: sessionId (required), scope, symbol, includeTests, +2 more.
symbol_signatureGet Symbol SignatureARead-onlyIdempotent
Return a symbol's signature(s) WITHOUT the body: the type declaration, the method/operator signature or a member's declaration (property, field, event, enum member - the last as its qualified Enum.Member form), plus its XML doc and its declaring file + start line - for understanding an API without reading the whole file/body, and for navigating straight to it. 'file' is the fragment's OWN file (for a partial type a method points at the file the METHOD lives in, not at the first type fragment); it is omitted only when the snapshot carries no path. 'line' is where the declaration starts, a leading attribute included; it is null for an enum member and a source-generated property (no line fact is modeled for them). Exact (case-sensitive) name match; multiple results for overloads or name collisions; one declaration compiled in two projects is two results, each with its 'project'. A member also takes 'Type.Member' (that type's only), a type a qualified name ('Ns.Type', 'Outer.Inner') and a constructor 'Type.Type' (kind 'constructor', each declared overload; a primary constructor's line is null, a record's is not listed; on a type that declares none the list is empty and a 'note' says which constructor the type has implicitly, with no 'nearest'). A user-defined operator matches by its METADATA name ('op_Equality' / 'op_Implicit' / …). Leaner than get_context (which returns the full source). Returns a capped envelope (items/count/totalFound/truncated), max 50. Matching is EXACT, so a typo or case-mismatch returns an empty list rather than an error - when nothing matched, the response therefore carries a 'nearest' suggestion (the closest declared name), which distinguishes 'you spelled it differently' from 'this symbol genuinely has no signature' - and, where the run skipped generated C# under obj/, a 'generatedSourcesNote' naming the third possibility: the declaration exists in code this index never read.
Inputs: symbol (required), sessionId (required).
usage_reportReport Tool UsageARead-onlyIdempotent
Report the server-side tool-call telemetry: what the CallTool filter recorded into the config DB's tool_calls table, across every client that used this DB - not this conversation's transcript. list_skills shows the pool; this shows what was called.
Use it to see how often, how reliably and at what cost each tool is used. READ THE SCOPE FIRST: the filter sits on the tools/call pipeline. A one-shot aicb call writes the same row under the client name 'aicb-call' when given a --db-path, and nothing without one. A sub-query inside batch or measure records the PARENT call and no child row. So every count here is a LOWER BOUND, and a tool at 0 was not called THROUGH THIS DOOR rather than not called. A batch parent's row does carry a tally of its sub-queries, returned as subQueries: read it before calling a tool unused.
Returns totals (totalCalls, errorCalls, errorRatePct, distinctTools, distinctSessions, clientSessions, firstTs, lastTs, clients, serverVersions); tools, ranked by calls, each with errors, duration (ms) and result size (chars) as average, p50, p90 and max, plus - when non-zero - errorClasses (exception type names; McpException is a failure the tool raised on purpose), argumentBindingErrors (an argument the tool does not have), ignoredArgumentCalls (the call answered anyway) and aliasApplied; protocolVersions and clientEras (who called, on which protocol revision); facets; subQueries; recordedVia; and poolCoverage (poolSize, poolToolsFired, poolToolsNeverCalled, outOfPoolTools). Read coverage THERE, not from distinctTools: that figure counts every tool called including ones outside the pool, so holding it against the pool size overstates coverage.
The answer carries shapes only - names, counts, exception type names - no code, no argument values and no session reference. Without a readable config DB it answers available:false.
Read-only and session-less. sinceDays narrows every figure to the last N days (1..3650, so 0 means one day; omit it for the whole log); the cutoff comes back as windowSinceIso.
Inputs: topTools, sinceDays.
verify_claimVerify a Claim About a ChangeARead-onlyIdempotent
Verify a structured claim about a change by diffing two analyzed sessions (baseline → changed). Supported claim types (MVP): 'no_new_api' (no types/methods were ADDED AND no method signatures changed) and 'symbol_removed_unused' (the named symbol was removed AND had no usages in the baseline's static fan-in graph). Returns a verdict of confirmed / refuted / indeterminate with a reason + evidence. NOTE THE SCOPE OF 'no_new_api': it is about ADDITIONS and SIGNATURE CHANGES, so a REMOVAL does not refute it - a change set that only deletes public API is legitimately 'confirmed'. Removals are therefore never silent: the reason states the boundary and every removal the same diff records is listed as evidence, prefixed '-'. For the removal question ask verify_claim(symbol_removed_unused), or diff_public_contract (outside the default profile's pool). Conservative in BOTH directions: what the diff cannot prove is never falsely confirmed - and never falsely refuted either, because 'was not removed' is a claim about the change that is only made once the string resolves to something this diff could report on. A name that resolves to nothing, a property/field/event/enum member (the diff models types and methods only), and the simple name of a removed type the diff had to render by its FULL name because it is ambiguous, each return 'indeterminate' naming that cause - not a confident 'was not removed'. A removed method whose only baseline callers called an interface or abstract member it implemented (find_usages' viaContract) is 'indeterminate' too: such a call ran its body only when the instance was of its type, and the reason says whether that credit was exact. Baseline and changed must be two states: the same session twice, or two sessions of one solution with an equal file-set hash and an empty diff, answers 'indeterminate' and says so. Unsupported claim types return indeterminate.
Inputs: claimType (required), changedSessionId (required), baselineSessionId (required), symbol.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 54 tools
The set contains several overlapping clusters: get_context/explain_symbol/pack_for_task/prepare_task all retrieve symbol context, while find_usages/impact_of_change/review_context and find_tests_for/coverage_gaps/assert_absence answer closely related questions. The descriptions carefully distinguish them, but with 54 tools an agent still faces real misselection risk.
Every tool name is snake_case with no camelCase or stylistic mixing. The pattern is mostly verb_noun or clear noun phrases, though not uniformly verb-led; still, names are predictable and readable.
54 tools is far beyond a well-scoped MCP surface and creates substantial choice overhead. Profile filtering mitigates this at runtime, but the listed tool set itself is too large for comfortable selection.
The surface covers an unusually broad C#/.NET analysis lifecycle: loading, diagnostics, navigation, usage/impact, tests, metrics, insights, architecture, DI, snapshots and configuration. Minor gaps exist around build/test execution and direct edit/refactor operations, but these may be intentionally out of scope.
Maintenance
Related MCP Connectors
Deterministic context layer for your codebase: change impact, blast radius, answers with receipts.
Ask a codebase what calls what: search, blast radius, paths between symbols, and diffs.
Code intelligence for coding agents: semantic, AST, graph, and full-text search. 279+ languages.
Search indexed code, trace dependencies, assess change impact, and recall repository memory.
Related MCP Servers
- FlicenseNot gradedqualityBmaintenanceProvides efficient code navigation and graph-based analysis for AI agents, enabling symbol resolution, callers, implementations, and type schemas with minimal token usage.-
- AlicenseNot gradedqualityAmaintenanceEnables AI coding agents and IDEs to query a local structural code graph and retrieve focused, token-bounded context on code entities, Git relationships, callers/callees, impact, paths, and recorded mistakes for more efficient sessions.28 npmMIT
- AlicenseNot gradedqualityAmaintenanceEnables AI coding agents to retrieve bounded, focused context from a local structural code graph, including source entities, Git relationships, and recorded project memory. It supports exploring structure, inspecting callers, callees, and impact, and carrying prior mistakes into later sessions without reading full files.28 npmMIT
- AlicenseNot gradedqualityAmaintenanceEnables AI coding agents to query a local structural code graph and receive focused, token-bounded context about source entities, relationships, Git co-change, and selected project memory without reading full files.28 npmMIT