CodeCharter ships an MCP server so an AI coding assistant can analyze your code and help you write rules without leaving the chat. The assistant calls a small set of tools; you stay in control of what gets saved.
Installing the server in your AI tool
Prerequisites: the CodeCharter CLI must be installed and on your PATH, and a
valid license is required — both codecharter mcp and codecharter mcp install
exit with a license error otherwise. The install command only writes the
client's config file; the AI tool itself is needed to actually use the server.
codecharter mcp install --client <claude-code|claude-desktop|cursor|windsurf|gemini>
By default the command writes the CodeCharter MCP server into a project-level
config file in the current directory (for example .mcp.json for Claude Code);
pass --scope user for the user-level config instead. Claude Desktop and
Windsurf only support user scope, so pass --scope user for them. Use
--dry-run to print the resulting diff without writing anything, and --force
to overwrite an existing CodeCharter entry. Run the command once per tool you use:
codecharter mcp install --client claude-code
codecharter mcp install --client cursor
Supported --client values are claude-code, claude-desktop, cursor,
windsurf, and gemini (Google's Gemini CLI). The Windows installer
can register the server for the tools it detects, so on Windows you usually do
not need to run this by hand.
For an MCP client that is not in the list, configure it to run codecharter mcp,
which starts the server on stdio. An HTTP transport is also available; see
the next section.
HTTP transport
For clients that connect over HTTP instead of stdio, start the server with
--transport http:
codecharter mcp --transport http
By default the server listens on http://127.0.0.1:7777/mcp and prints the
listening address to stderr. It exposes two endpoints:
POST /mcp— the client sends JSON-RPC requests here.GET /mcp— a server-sent-events (SSE) stream for server-to-client notifications.
| Flag | Default | Purpose |
|---|---|---|
--port |
7777 |
TCP port to listen on. |
--bind |
127.0.0.1 |
IP address to bind to. |
--token |
auto-generated | Bearer token (literal value). |
--token-file |
— | File whose content is used as the Bearer token. |
--allow-public |
off | Required to bind a non-loopback address. |
--cors-origin |
same-origin only | CORS origin to whitelist; can be repeated. |
Authentication
Bearer-token authentication is mandatory: every request must carry an
Authorization: Bearer <token> header. A missing or wrong token gets a
401 Unauthorized response. Provide the token either literally with
--token or via --token-file, whose file content (trimmed of whitespace)
becomes the token. If you pass neither, the server generates a random token
at startup and prints it once to stderr:
[codecharter mcp] Bearer token: <generated-token>
Network exposure
The default bind address 127.0.0.1 keeps the server reachable from the
local machine only; localhost and other loopback addresses work the same
way. Binding to a non-loopback address is refused unless you also pass
--allow-public — a deliberate safety latch so the server is never exposed
to the network by accident. With --allow-public the server starts and
prints a security warning to stderr.
The server speaks plain http. If you need to reach it from beyond the local
machine, put a TLS-terminating reverse proxy in front of it and keep the token
secret.
CORS
By default the server answers same-origin browser requests only.
Pass --cors-origin once per origin you want to whitelist, for example
--cors-origin https://app.example.com.
Example
Start the server with a fixed token:
codecharter mcp --transport http --port 8080 --token my-secret-token
Then point your MCP client at http://127.0.0.1:8080/mcp with the header
Authorization: Bearer my-secret-token. To verify the connection by hand:
curl -X POST http://127.0.0.1:8080/mcp \
-H "Authorization: Bearer my-secret-token" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'
The tools
The server exposes six groups of tools: analysis tools that run rules against
real code, a coverage tool that runs the test-coverage gate, graph tools that
expose the codebase as a coupling graph, a rule search tool over the portal's
rule catalog, authoring tools that help you draft and verify a new rule, and
a configuration tool that edits your .codecharter/config.yml.
Alongside the tools it also exposes MCP resources — docs://getting-started,
docs://dsl-grammar, docs://predicates, docs://rule-examples, and
rules://catalog — for clients that surface resources.
Analysis
| Tool | What it does |
|---|---|
analyze_solution |
Run the active rule set against a whole .sln / .slnx / .csproj and return the findings. |
analyze_file |
Run the rules against a single file. Fast feedback while editing. |
analyze_diff |
Run the rules against a diff (e.g. the staged changes), so the assistant sees only what changed. |
Every finding carries the rule's recommendation alongside the message, so the
assistant can propose a fix without a second explain_rule call.
All three analysis tools accept a min_severity parameter that filters the
findings: one of info, warn, or error — note the spelling warn, not
warning; findings also report their severity as warn. When omitted, all
severities are returned.
Per-tool notes:
analyze_solutionalso acceptsinclude_pathsandexclude_paths, each a list of glob patterns such assrc/**/*.csortests/**. Wheninclude_pathsis omitted, all files are included; exclusions are applied after inclusions. While iterating the source files the tool sends MCP progress notifications, so a client can show progress on large solutions. If the client cancels the call, the tool returns the findings collected so far as a partial result with itsisCancelledflag set.analyze_difftakes the changes as exactly one of two parameters:diff(raw unified diff text, e.g. the output ofgit diff) orgit_ref(a ref range such asmain..HEAD, which the server resolves by runninggit diffitself). Passing both, or neither, is an error.analyze_diffwithinclude_context: trueadditionally returns findings in diff-touched files whose reported line lies outside the changed lines, for example a missing-documentation finding on a newly added method when the diagnostic points at a line outside the changed range. These indirect findings arrive in a separatecontextFindingscollection, never overlap with the direct findings, and respectmin_severity. The default (include_context: false) keeps the previous behavior.
Coverage
| Tool | What it does |
|---|---|
run_coverage |
Run the test-coverage gate and return the result: the coverage summary, the per-project test outcome, and every uncovered region with file, method, line numbers, and source snippet. The equivalent of codecharter coverage. |
The threshold comes from .codecharter/config.yml exactly as it does on the
command line; min_coverage overrides it for one call. The optional root,
skip_tests, and results_root parameters mirror the CLI arguments. While the
tests run the tool sends progress notifications, so the client does not time the
call out on a large solution.
The result names its outcome (success, below-threshold, tests-failed,
no-data, no-test-projects, or a usage/configuration error) instead of
leaving the assistant to parse prose, and a failed test project comes with the
relevant tail of the test output. This closes the loop: the assistant writes
code, runs the gate, and gets the exact lines that still need a test.
Set git_ref to a range such as main..HEAD to gate only the lines that range
changed instead of the whole workspace; the result then carries a
diffCoverage section with the changed-line numbers and the regions that miss
a test. Set affected_by to the same range as git_ref to additionally narrow
the run to only the test projects reachable from the changed files — the
equivalent of --affected-by on the coverage command,
with the same rules: it requires git_ref naming the exact same range and is
incompatible with skip_tests. The narrowed selection shows up in the result's
coverageScope (which suites ran, which were skipped), and a changed file left
with no coverage data at all still fails the run — a narrower selection never
renders as a passing gate over data it never measured.
Graph
| Tool | What it does |
|---|---|
graph_overview |
A solution-wide view of the codebase's coupling graph, aggregated to project or namespace granularity: node counts and the top weighted edges between them. |
graph_neighborhood |
The resolved focus type with its full member signatures, then its neighbors grouped by relation (inherits/inherited-by, implements/implemented-by, uses/used-by, calls/called-by) up to a requested depth. |
graph_impact |
With only target: its transitive inbound closure ("who depends on this type"), grouped by distance. With source too: the shortest dependency paths between them. |
graph_cycles |
Every dependency cycle in the coupling graph (strongly connected components), aggregated to type, namespace (default), or project granularity. Each cycle reports its members, a representative shortest cycle, and every closing edge annotated with whether removing that edge alone would break the cycle. |
graph_hubs |
The most-coupled nodes in the coupling graph, ranked by fan-in, fan-out, or instability, aggregated to type (default), namespace, or project granularity. Degrees are computed from the graph's own edges, not from the rule-analysis metrics, so the numbers stay available and consistent regardless of which rules are active. |
graph_paths |
Whether every path from from to to passes through an optional must_pass_through chokepoint (or, without it, plain reachability), at type (default) or method granularity. A tri-state answer — true/false are only ever returned once the search is exhaustive; a truncated search reports undecided instead of a possibly-wrong verdict. A false verdict carries a concrete counterexample path. |
graph_diff |
Compares the coupling graph between two git refs: added/removed nodes, added/removed/changed edges, and a per-aggregate coupling-change summary. base is required; head defaults to the working tree. |
graph_neighborhood, graph_impact, graph_cycles, and graph_paths resolve target/from/to/must_pass_through (and graph_impact's optional source) the same way at type granularity: an exact or case-insensitive full type name, a name suffix, or a source file path. An ambiguous or unresolved target returns a candidate list instead of guessing. Edges come from declared inheritance, interface implementation, member/parameter/return/field/event types, and — by default — method-body invocations that resolved to a unique method symbol, attributed to the callee's declaring type, applied attributes, and object-creation expressions that resolved to a unique constructor symbol; every graph tool accepts edge_kinds to narrow which of the six kinds are included (graph_paths at level: "methods" ignores edge_kinds; see below). All seven tools share the analysis tools' warm per-workspace cache: the first call after a change is slower, subsequent calls are fast — except graph_diff's base side, described below. The CLI equivalent, worked examples, and the full list of limits live on codecharter graph.
graph_paths at level: "methods" resolves from/to/must_pass_through against a method's canonical signature, its declaring-type-qualified name, or a bare method-name suffix instead, and traverses an on-demand induced subgraph of method call edges rather than the persisted type-level graph — see paths and method-level honesty for why a true verdict there carries an under-approximation caveat that type-level verdicts do not.
Unlike the other graph tools, graph_diff's base side is not served from the warm cache: a cache miss there costs a dotnet restore plus a full solution analysis, so the first call for a given base/head pair returns isPending: true immediately instead of waiting on it. Call again with the same base, head, and path to get the finished diff, or to keep waiting if it is close to done; an already-analyzed base is served from a fast per-commit cache like any other graph tool. Node and edge ids carrying an ordinal #n disambiguation suffix are excluded from the diff, since that suffix is not guaranteed stable across two independently analyzed revisions.
Rule search
| Tool | What it does |
|---|---|
search_rules |
Full-text, faceted search over the rule catalog on the portal: your own rules, or the curated example corpus. The equivalent of the codecharter rules search command. |
search_rules complements list_rules and explain_rule, which only see the
rules already active in your workspace: run search_rules before
scaffold_rule to check whether a near-duplicate rule already exists in the
catalog, so you extend or adapt it instead of writing the same thing twice.
Pass a free-text query, and narrow with scope (example for the curated
CodeCharter rules, or mine for your own authored rules — example is the
default), categories, severities, and tags (a hit must carry every tag
you pass). in_profile restricts to rules used or unused in a profile.
Results are paginated with page and page_size (25 per page by default, 100
at most), and the response includes facet counts (per category, severity, and
tag) alongside the matches, so an assistant can suggest a narrower search
instead of guessing. This tool calls out to the portal, so it needs the same
license/portal connectivity as any other portal-backed command.
Authoring
| Tool | What it does |
|---|---|
scaffold_rule |
Turn a one-line description into a ready-to-edit .ccr skeleton, with the frontmatter filled in and the closest worked example to adapt. |
get_authoring_docs |
Fetch the DSL grammar, predicate catalog, or worked examples as text, so the assistant drafts against the real reference instead of guessing. |
list_rules |
List the rules currently in scope, with their ids and severities. Each entry also reports whether your workspace actually enforces the rule (enabled) and the severity it reports with after your config.yml overrides (effective_severity, next to the severity the rule declares). Disabled rules are listed and flagged by default; pass include_disabled=false to leave them out. |
explain_rule |
Explain what a given rule matches and why, in plain language. On an unknown rule_id it answers with did-you-mean suggestions listing the closest known rule ids. |
validate_rule |
Parse a draft rule, check its frontmatter for completeness, and statically resolve every property/method reference in the query against the DSL schema (including chained access and lambda parameters). Parse errors include a hint pointing at the matching get_authoring_docs topic, an unknown property/method comes back as an error with a did-you-mean suggestion when a close match exists, and a thin frontmatter surfaces as warnings. The equivalent of codecharter validate. |
dry_run_rule |
Run a draft rule against real code without saving it, to see what it would flag. Returns up to 50 matches by default (raise max_results for more) and flags when the result is truncated. Accepts the same include_paths / exclude_paths globs as analyze_solution, and severity_override (info, warn, error) replaces the rule's declared @severity for this run only. |
test_rule_spec |
Run a draft rule against a .spec.md of hits and misses. The equivalent of codecharter test. |
save_rule |
Write a validated draft into your workspace ./rules directory. Refuses a broken draft, and will not overwrite an existing rule unless you pass overwrite=true. The file name comes from rule_id; when omitted it defaults to a slug derived from the rule's @name directive (lowercase, hyphen-separated). |
Configuration
| Tool | What it does |
|---|---|
config_mutate |
Apply a single .codecharter/config.yml mutation — set, unset, add, remove, or promote — with the same validation and write behavior as the CLI's config commands. |
config_mutate lets the assistant adjust your configuration the same way you
would from the command line, while keeping you in control of what lands:
- Pick the mutation with
operation:set/unseta dotted key (params.<rule>.<name>,overrides.<rule>.severity,profiles.<slug>);add/removea list entry in asection(exclude,profiles,ignore,include) vialist_value(or a local bundlepathfor a profile); orpromotea key fromconfig.local.ymlup to the committedconfig.yml. Anignore/includeentry can be narrowed within_namespaceandmatch. - It defaults to a dry run. With
dry_run: true(the default) the tool returns the unified diff of the edit instead of writing, so the assistant — and you — see the exact change first. Passdry_run: falseto actually write the file. - Pass
use_local: trueto targetconfig.local.ymlinstead of the committedconfig.yml. Forpromote,allpromotes the whole local overlay andkeepcopies instead of moves (leaving the entry inconfig.local.yml). - Rule and parameter references are validated against the same active rule set the analysis tools see, so a mutation naming an unknown rule or an out-of-range parameter is rejected before it is written — exactly as the CLI rejects it.
The authoring loop
Writing a rule typically follows this sequence:
- Scaffold —
scaffold_ruleturns a one-line description into a.ccrskeleton with the frontmatter filled in. - Validate —
validate_rulechecks that the draft parses, that the frontmatter is complete, and that every property/method reference in the query resolves against the DSL schema. Syntax mistakes surface here with a hint pointing at the matchingget_authoring_docstopic; an unknown property or method (e.g. a typo) is now caught here too, instead of only atdry_run_ruleortest_rule_spectime. - Dry-run —
dry_run_ruleruns the draft against your real code without saving it, so you can see over- and under-matching before committing. - Spec —
test_rule_specruns the draft against a.spec.mdof explicit hits and misses. A passing spec means the rule does what you intended. - Save —
save_rulewrites the validated draft to the./rulesdirectory in your workspace, wherecodecharter analyzepicks it up. A broken draft is rejected.
All steps before save_rule are in-memory. Nothing touches your rule set until
you explicitly save. Use get_authoring_docs at any point to pull the DSL
grammar or predicate catalog into the conversation.
Which rules the MCP server sees
The server resolves its rules directory in the same order as
codecharter analyze: an explicitly given rules path wins, otherwise a
rules folder in the workspace. codecharter mcp has no flag for an
explicit path, so in practice the analysis tools use the rules folder next
to the solution or project they locate, while list_rules and
explain_rule use the rules folder in the directory the server was
started from.
Related
- codecharter validate and codecharter test
are the CLI equivalents of
validate_ruleandtest_rule_spec. - codecharter config is the CLI equivalent of
config_mutate. - Writing rules covers the DSL the assistant drafts in.