Zum Inhalt springen

AI-assisted rule authoring via the CodeCharter MCP server

Drive CodeCharter from an AI assistant via the MCP server and the AI-assisted rule-authoring loop.

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_solution also accepts include_paths and exclude_paths, each a list of glob patterns such as src/**/*.cs or tests/**. When include_paths is 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 its isCancelled flag set.
  • analyze_diff takes the changes as exactly one of two parameters: diff (raw unified diff text, e.g. the output of git diff) or git_ref (a ref range such as main..HEAD, which the server resolves by running git diff itself). Passing both, or neither, is an error.
  • analyze_diff with include_context: true additionally 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 separate contextFindings collection, never overlap with the direct findings, and respect min_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.

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 / unset a dotted key (params.<rule>.<name>, overrides.<rule>.severity, profiles.<slug>); add / remove a list entry in a section (exclude, profiles, ignore, include) via list_value (or a local bundle path for a profile); or promote a key from config.local.yml up to the committed config.yml. An ignore/include entry can be narrowed with in_namespace and match.
  • 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. Pass dry_run: false to actually write the file.
  • Pass use_local: true to target config.local.yml instead of the committed config.yml. For promote, all promotes the whole local overlay and keep copies instead of moves (leaving the entry in config.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:

  1. Scaffoldscaffold_rule turns a one-line description into a .ccr skeleton with the frontmatter filled in.
  2. Validatevalidate_rule checks 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 matching get_authoring_docs topic; an unknown property or method (e.g. a typo) is now caught here too, instead of only at dry_run_rule or test_rule_spec time.
  3. Dry-rundry_run_rule runs the draft against your real code without saving it, so you can see over- and under-matching before committing.
  4. Spectest_rule_spec runs the draft against a .spec.md of explicit hits and misses. A passing spec means the rule does what you intended.
  5. Savesave_rule writes the validated draft to the ./rules directory in your workspace, where codecharter analyze picks 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.