An MCP server that turns AI agents into reactive and proactive software engineering assistants.
Codebase Guardian watches a Git repository, pushes events the moment something important happens, and gives the agent structured Agent Skills and tools. The agent can then review commits, triage failing checks, respond to leaked secrets and keep dependencies healthy without constant polling or a human prompting each step.
Every event that has a natural follow-up names a suggested skill. A skill is a packaged workflow that the agent loads
(skills/get + resources/read, verified against a SHA-256 digest) and then carries out with the Guardian's tools.
It is built on the Model Context Protocol revision, the MCP C# SDK 2.2.0 and .NET 10:
- MCP Events:
events/list,events/poll, andevents/streampush delivery - Skills extension (
io.modelcontextprotocol/skills, SEP-2640) - Tools and Resources, with the Tasks extension (
io.modelcontextprotocol/tasks) for long-running scans - Stateless request/response core: stdio and stateless Streamable HTTP
Status: Feature complete for v1 (see docs/backlog). The design is in docs/specs/2026-10-04-codebase-guardian-design.md.
Traditional AI coding assistants are reactive: they act only when a human asks. Engineering teams need assistants that notice problems and start on them.
| Problem | How Codebase Guardian solves it |
|---|---|
| Agents have to keep polling for new commits, failing checks or leaked secrets | MCP Events: the server pushes a notification over events/stream when something changes. Clients without Events use the poll_events tool with the same semantics |
| Complex workflows (security audit, bug triage, dependency upgrades) are hard to prompt reliably | Ships five ready-to-use Agent Skills that encode the procedure step by step and name the exact tools and resources to use |
| An event alone does not say what to do next | Each event carries a suggestedSkill URI, so the agent knows which workflow to load |
| Skill content could be tampered with in transit | Every skill file is listed with its size and SHA-256 digest, so clients can verify what they load |
| Long-running analysis blocks the conversation | The Tasks extension: full_scan (and, optionally, run_checks and audit_dependencies) return a task handle instead of blocking |
| Scaling MCP servers used to require sticky sessions | Built on the **stateless ** protocol: request/response methods need no session affinity |
| Every team reinvents the same "watch this repo" logic | One reusable MCP server that any MCP client can connect to |
What it does today:
- When a commit lands, the agent is told (
repo.commit.created) and can review it with thepr-reviewskill. - When a commit contains something that looks like a credential,
security.secret_detectedfires with redacted findings, and thesecurity-auditskill walks through revoke, rotate and clean-up. - When a dependency manifest changes, the
dependency-hygieneskill audits for vulnerable and outdated packages. - When the build or tests fail (
checks.failed), thebug-triageskill reads the stored log, reproduces the failure and isolates the commit that caused it. - When a
full_scanfinishes (scan.completed), the event links the Markdown report and names the skill to load next. - With a GitHub token, new issues, PR comments and failed CI runs arrive as events, and the agent can file an issue, comment on a PR or open a pull request from an already pushed branch. Every outward-facing action asks for confirmation first.
This is the difference between an AI that answers questions and an AI that helps run the engineering process.
| MCP feature | How the Guardian uses it |
|---|---|
| Tools | Inspect the repository, summarize diffs, run checks, scan for secrets, audit dependencies, full_scan, GitHub actions, poll_events fallback |
| Resources | guardian:// live repo status, recent commits, check runs and logs, scan reports; skill:// skill files |
Skills (io.modelcontextprotocol/skills) |
skills/list, skills/get, resources/directory/read; five bundled skills |
Tasks (io.modelcontextprotocol/tasks) |
full_scan always runs as a task; run_checks and audit_dependencies may |
| Events (Triggers & Events, draft) | events/list, events/poll, events/stream, events/subscribe (webhooks, authenticated HTTP only) |
| Stateless core | stdio and stateless Streamable HTTP |
| Skill | Use it for | Triggered by |
|---|---|---|
guardian |
Load first: what the server watches, how to subscribe, the event-to-skill map, safety rules | — |
security-audit |
Triage secret findings by rule, confirm with scan_secrets, revoke/rotate first, rewrite history only with consent |
security.secret_detected |
dependency-hygiene |
Run audit_dependencies, fix critical/high vulnerabilities first, major vs minor upgrade policy, verify with run_checks |
repo.dependencies.changed |
bug-triage |
Read the check log, reproduce, isolate with recent_commits and diff_summary, classify, write an issue report |
checks.failed |
pr-review |
Review a commit's diff against a checklist covering correctness, tests, security, naming and docs | repo.commit.created |
New skills are a directory with a SKILL.md; point --skills-dir at your own directories to add them.
- Latest Protocol version, verified with the official MCP conformance suite: all three Skills scenarios pass (see docs/reference/conformance.md).
- stdio and stateless Streamable HTTP transports.
- Works with any MCP client, for example Claude Code or VS Code. Clients that do not implement the draft
events/*methods get the same events through thepoll_eventstool.
Requires the .NET SDK 10.0 and git on the PATH.
dotnet build
dotnet run --project src/CodebaseGuardian -- --repo .The server speaks MCP on stdio (stdout is reserved for the protocol; logs go to stderr). Options:
| Option | Meaning |
|---|---|
--repo <path> |
Repository to watch (default: current directory) |
--transport stdio|http |
Transport (default stdio) |
--urls <url> |
HTTP listen address (default http://127.0.0.1:5199) |
--auto-checks |
Run the configured checks after each new commit |
--no-watch |
Disable the repository watcher (no pushed events) |
--skills-dir <path> |
Skill directories (default: skills next to the binary) |
Every option is also a Guardian:* setting (appsettings, GUARDIAN__* environment variables).
Claude Code
claude mcp add codebase-guardian -- dotnet run --project <path>/src/CodebaseGuardian -- --repo <repo>VS Code (.vscode/mcp.json)
{
"servers": {
"codebase-guardian": {
"type": "stdio",
"command": "dotnet",
"args": ["run", "--project", "<path>/src/CodebaseGuardian", "--", "--repo", "${workspaceFolder}"]
}
}
}HTTP mode (stateless Streamable HTTP, endpoint /mcp)
dotnet run --project src/CodebaseGuardian -- --transport http --repo . --urls http://127.0.0.1:5199{ "servers": { "codebase-guardian": { "type": "http", "url": "http://127.0.0.1:5199/mcp" } } }At startup the server logs the URL to give clients (MCP endpoint: http://127.0.0.1:5199/mcp). The root URL that Kestrel
reports as Now listening on answers 404.
Full contract: spec section 5.
Tools
| Tool | Purpose |
|---|---|
repo_status |
Branch, HEAD, ahead/behind, staged/unstaged/untracked files |
recent_commits |
Last N commits (default 10, max 100), optional branch |
diff_summary |
Per-file stats for a range plus a truncated patch |
run_checks |
Run the build/test command, store the log, emit events |
scan_secrets |
Scan working tree, staged changes or a commit range; output is redacted |
audit_dependencies |
Outdated and vulnerable packages (NuGet and npm) |
full_scan |
Secrets, dependencies and checks in one run; stores a Markdown report. Runs as an MCP task |
create_issue, comment_on_pr, open_pull_request |
GitHub actions; each asks the user to confirm first (needs GitHub) |
poll_events |
Fallback for clients without events/* |
Resources: guardian://repo/status, guardian://repo/commits/recent, guardian://checks/latest,
guardian://checks/{runId}/log, guardian://scans/{scanId}/report, and the skill files under skill://<skill>/<file>.
Events
| Event | Trigger | Suggested skill |
|---|---|---|
repo.commit.created |
New commit on a local branch | pr-review |
repo.branch.changed |
HEAD moved to another branch | none |
repo.files.changed |
Working-tree edits (debounced) | none |
repo.dependencies.changed |
A dependency manifest changed | dependency-hygiene |
checks.completed |
A check run finished | none |
checks.failed |
A check run failed | bug-triage |
security.secret_detected |
Secret found in a new commit or scan | security-audit |
scan.completed |
A full_scan finished |
security-audit with secrets, else dependency-hygiene with vulnerable packages, else none |
github.issue.opened |
A new issue on the GitHub repository | bug-triage |
github.pr.comment.created |
A comment on a pull request | pr-review |
github.ci.failed |
A GitHub Actions run failed | bug-triage |
The three github.* events exist only when GitHub is enabled. scan.completed carries scanId, reportUri,
secretFindings, vulnerablePackages, checksPassed (null when no checks ran) and, when there is something to act on,
suggestedSkill.
Skills: guardian (load first; event-to-skill map), security-audit, dependency-hygiene, bug-triage, pr-review.
Long-running tools use the MCP Tasks extension (io.modelcontextprotocol/tasks). The client opts in by declaring that
extension in its initialize capabilities (capabilities.extensions["io.modelcontextprotocol/tasks"]) and calls the tool
as a task; the server answers with a task handle and the client polls tasks/get (and can tasks/cancel).
| Tool | Task mode |
|---|---|
full_scan |
Required: a client without the extension gets error -32021 (missing required client capability) |
run_checks, audit_dependencies |
Optional: a plain call returns the result directly, a task call returns a handle |
| everything else | Synchronous |
full_scan(includeChecks = true) scans the working tree for secrets, audits dependencies (outdated ones included) and runs
the check command, in that order, reporting progress secrets, dependencies, checks. A step that fails is recorded under
## Errors in the report and the scan continues. The result is {scanId, reportUri, secretFindings, vulnerablePackages, outdatedPackages, checksPassed, durationMs}; read reportUri (guardian://scans/{scanId}/report, text/markdown) for the
details. Secret values in the report are always redacted. The last 20 reports are kept in memory, and scan.completed is
published when a scan ends. If the client has no task support, call scan_secrets, audit_dependencies and run_checks
instead.
GitHub support is on by default but does nothing without a token and a github.com origin remote. The token comes from the
GITHUB_TOKEN environment variable, otherwise from gh auth token (run gh auth login once). Settings:
| Setting | Default | Meaning |
|---|---|---|
Guardian:GitHub:Enabled |
true |
false removes the github.* events and makes the GitHub tools fail with a clear error |
Guardian:GitHub:PollEnabled |
true |
Poll GitHub for issues, PR comments and failed runs |
Guardian:GitHub:PollIntervalSeconds |
60 |
Poll interval, at least 15 |
Guardian:GitHub:Owner, Guardian:GitHub:Repository |
from origin |
Override the repository |
Guardian:GitHub:ApiBaseUrl |
https://api.github.com |
For GitHub Enterprise |
GITHUB_TOKEN=<token> dotnet run --project src/CodebaseGuardian -- --repo .
# or: gh auth login, then run as usual; disable with --Guardian:GitHub:Enabled=falsecreate_issue, comment_on_pr and open_pull_request ask the user to confirm (MCP elicitation, or an explicit confirm: true
argument for clients without it) before anything is sent, and text that leaves the machine is refused if it contains a secret.
Besides events/stream and events/poll, a client can have events POSTed to its own HTTPS endpoint with events/subscribe
(Standard Webhooks signatures, endpoint verification, no redirects, private addresses blocked). Webhooks are offered only on
the HTTP transport and only with at least one API key, so a client without credentials cannot make the server call out.
export GUARDIAN__Http__ApiKeys__ci="$(head -c 48 /dev/urandom | base64 | tr -d '=+/')" # principal "ci", at least 32 characters
dotnet run --project src/CodebaseGuardian -- --transport http --repo . --urls http://127.0.0.1:5199Clients then send Authorization: Bearer <key>. When a key is configured every HTTP request needs it. Settings:
| Setting | Default | Meaning |
|---|---|---|
Guardian:Http:ApiKeys:<principal> |
none | API key per principal, at least 32 characters |
Guardian:Webhooks:Enabled |
true |
Set to false to stop offering webhooks even with API keys |
Guardian:Webhooks:AllowInsecureLoopback |
false |
Development only: allows http:// callback URLs and loopback or private targets |
Warning: never set
Guardian:Webhooks:AllowInsecureLoopback=trueon a shared or production host. It turns off the HTTPS requirement and the private-address (SSRF) guard for webhook callbacks.
samples/GuardianWatch is a console MCP client that runs the whole loop: it starts the server over stdio, prints the
server instructions, the skill names and the event names, opens one events/stream per event (repo.commit.created,
repo.dependencies.changed, checks.failed, security.secret_detected, request ids watch-<name>), and for every event
with a suggestedSkill calls skills/get, reads SKILL.md with resources/read, verifies its SHA-256 digest and size, and
prints skill <name> verified (<size> bytes) with the first lines.
dotnet run --project samples/GuardianWatch -- --repo /path/to/repo
# optional: --server <path to CodebaseGuardian.dll or to the project directory>Then commit something in that repository, for example a fake secret built at runtime (no secret-shaped literal in any file):
printf 'AWS_ACCESS_KEY_ID=%s%s\n' AKIA IOSFODNN7EXAMPLE > .env.prod && git add -f .env.prod && git commit -m "add env"Ctrl+C sends notifications/cancelled for each stream and exits 0. (With SDK 2.2.0 over stdio, cancelling the token of a
request does not reach the server, so the client sends the notification itself; see
docs/reference/sdk-2.2-notes.md.)
Spec section 7.
- No shell execution: every external process (
git,dotnet,npm) runs with an argument list; model-supplied revisions are validated. - Repo-relative paths are validated against
..escapes; resource reads resolve only catalog entries and known run ids. - Secret values are redacted everywhere: tool results, events, logs.
- HTTP is loopback-only by default (binds
127.0.0.1). Binding another address needsGuardian:HttpAllowRemote=trueand at least one API key. Without any key the HTTP transport has no authentication: anyone who can reach the port can read the repository and run its checks. API keys are compared as hashes and never logged. - Webhook callbacks are HTTPS-only, never follow redirects and cannot target private addresses, unless the development flag
Guardian:Webhooks:AllowInsecureLoopbackis set (see Webhooks). - GitHub actions ask for confirmation, and outgoing text containing a secret is refused.
- DNS-rebinding guard: a request whose
HostorOriginis not loopback is answered with 403 (remote mode drops only theHostrestriction; a non-loopbackOriginis still refused).
- Events are a draft (Triggers & Events working group sketch); the wire format may change.
- State (event log, check runs, scan reports, task store, webhook subscriptions) lives in a single process and is lost on restart.
- Dependency auditing covers NuGet and npm only.
- GitHub support covers github.com repositories and polls for events; there is no inbound GitHub webhook.
- The
create_issue,comment_on_prandopen_pull_requesttools cannot run as tasks (SDK 2.2.0 cannot combine them with elicitation). - Conformance: the three Skills scenarios of the MCP conformance suite pass; the base suite passes everything that does not need the suite's own fixtures. Details: docs/reference/conformance.md.
| Epic | Delivers | State |
|---|---|---|
| 1. Core Guardian (local) | Git layer, repo tools and resources, Skills, Events (poll/stream), repo watcher, checks, secret scan, dependency audit, stateless HTTP, demo client | Done |
| 2. GitHub + webhooks | GitHub client, create_issue / comment_on_pr / open_pull_request with confirmation, github.issue.opened / github.pr.comment.created / github.ci.failed events, HTTP auth, signed webhook delivery via events/subscribe |
Done |
| 3. Tasks | Tasks extension, task-capable run_checks and audit_dependencies, full_scan with a report resource and scan.completed |
Done |
Non-goals for v1: shared state across instances, ecosystems beyond NuGet and npm, auto-fixing code or pushing commits. The agent makes changes with its own tools; the Guardian only opens PRs from branches that already exist on the remote.
flowchart LR
Client[MCP client / GuardianWatch] <-->|stdio or Streamable HTTP| Host[Hosting: StdioHost, HttpHost]
Host --> Tools[Tools]
Host --> Resources[Resources guardian://]
Host --> Skills[Mcp.Skills: skills/list, skills/get]
Host --> Events[Mcp.Events: events/list, poll, stream]
Tools --> Git[Git: process runner and git wrapper]
Tools --> Checks[Checks: build/test runner]
Tools --> Security[Security: secret scanner]
Tools --> Deps[Dependencies: auditor]
Tools --> Scan[Scanning: full_scan and report store]
Scan --> Security
Scan --> Deps
Scan --> Checks
Scan -->|publish| Events
Resources --> Git
Resources --> Checks
Watcher[Watching: repository watcher] --> Git
Watcher --> Security
Watcher -->|publish| Events
Checks -->|publish| Events
Skills --> SkillFiles[(skills/ directories)]
Mcp.Skills and Mcp.Events are generic libraries that never reference the application.
dotnet test # xunit.v3; no network needed, git tests use real temporary repositoriessrc/Mcp.Skills Skills extension library
src/Mcp.Events Events extension library
src/CodebaseGuardian the server (Hosting, Git, Watching, Checks, Security, Dependencies, Scanning, GitHub, Tools, Resources, skills)
samples/GuardianWatch demo client
tests/ CodebaseGuardian.Tests (includes the end-to-end scenario test)
docs/ spec, backlog, reference notes
MIT © 2026 Islam Umarov. The bundled skills are also MIT-licensed (license: "MIT" in each SKILL.md).