Tracks the two files that were sitting untracked in the working tree: the original Agentic Bridle MCP design doc, and CLAUDE.md carrying the repo's git/Gitea, SonarQube, and contribution conventions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RZerbsHGF9Ka3bKhCjJ9m
200 lines
12 KiB
Markdown
200 lines
12 KiB
Markdown
# Agentic Bridle MCP
|
|
|
|
## Purpose
|
|
|
|
A self-hosted MCP server that acts as the single point of contact between coding agents (Claude Code, Codex, Kimi, DeepSeek, and others) and a project's tools, memory, and rules. The goal: switching which coding agent you're using should not change the behavior, context, or constraints that agent operates under. The intelligence and consistency live in this server, not in each agent's local config file.
|
|
|
|
**Design principle:** Bridle is project-agnostic infrastructure — a control plane other projects plug into, not something built around or dependent on any single project. RunicGateway (or any other project) is an early adopter/consumer of Bridle, not a dependency of it.
|
|
|
|
Deployed self-hosted via Docker Compose. Designed to be usable by others, not just a single homelab: Gitea is assumed to already exist (it's the one thing wired in as a pure external integration), but every other supporting service — Kanboard, Qdrant, LM Studio-equivalent embedding, etc. — ships as an *optional installable* component in the compose file. Someone who already runs their own Kanboard or vector DB points the gateway at their existing instance via env vars; someone who doesn't can spin the bundled version up alongside everything else with one `docker compose up`.
|
|
|
|
---
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─────────────────────┐
|
|
Agents ───────► │ Gateway (Rust) │ ← single MCP entrypoint
|
|
(Claude Code, │ - auth/session │
|
|
Codex, Kimi, │ - routing │
|
|
DeepSeek, ...) │ - token vault │
|
|
└──────────┬───────────┘
|
|
│
|
|
┌──────────────┬───────┴────────┬───────────────┬────────────────┐
|
|
▼ ▼ ▼ ▼ ▼
|
|
┌───────────┐ ┌────────────┐ ┌─────────────┐ ┌─────────────┐ ┌──────────────┐
|
|
│ Rules │ │ Memory │ │ Gitea MCP │ │ Kanboard MCP │ │ Web Panel │
|
|
│ Server │ │ Service │ │ (existing) │ │ (custom) │ │ (admin UI) │
|
|
│ (custom) │ │ (custom) │ │ │ │ │ │ │
|
|
└─────┬──────┘ └──────┬──────┘ └──────────────┘ └──────────────┘ └──────┬───────┘
|
|
│ │ │
|
|
└────────► Qdrant (vector store) ◄───────────────────────────────────┘
|
|
```
|
|
|
|
All admin actions — from any surface — go through the Gateway's admin API. The Web Panel is a thin client against that API, not a separate integration point per service.
|
|
|
|
---
|
|
|
|
## Components
|
|
|
|
### 1. Gateway (custom, Rust)
|
|
|
|
- Single MCP endpoint all agents connect to.
|
|
- Identifies connecting agent (via MCP handshake client info or explicit config) and tags requests with agent type.
|
|
- Routes requests to backend services, aggregates responses into a consistent shape.
|
|
- **Owns all secrets/tokens centrally.** Backend services never hold their own credentials — the gateway injects them per-request. Agents and sub-services never see raw tokens.
|
|
- Token storage: Docker secrets for v1 (file-mounted, not env vars); consider SOPS if secrets need to be version-controlled in Gitea; Vault only if this scales beyond single-host/single-user.
|
|
- Exposes an admin API that the Web Panel consumes for full control of every subsystem.
|
|
- Maintains an audit log distinguishing the source of every change: user-edited (via panel), agent-proposed (via MCP tool, requires approval), or vendor-seeded (via guardrail suggestion feed, requires approval).
|
|
|
|
### 2. Rules Server (custom)
|
|
|
|
Stores one canonical ruleset per project — the source of truth that gets rendered into each agent's native format.
|
|
|
|
**Canonical document structure:**
|
|
```yaml
|
|
project:
|
|
overview: ...
|
|
stack: ...
|
|
conventions: ...
|
|
|
|
guardrails:
|
|
- id: no-unrequested-refactors
|
|
statement: "Do not perform unrelated refactoring outside the requested task."
|
|
severity: guidance | hard-instruction | enforcement
|
|
applies_to: [all_agents]
|
|
provenance:
|
|
source: user | agent | vendor
|
|
agent_session: ... # if agent-proposed
|
|
approved_by: ...
|
|
approved_at: ...
|
|
|
|
patterns:
|
|
standard: [MVC, MVVM, hexagonal, ...]
|
|
custom:
|
|
- name: "modified-mvc"
|
|
description: ...
|
|
example_repos:
|
|
- url: ...
|
|
reference_tag: "modified-mvc-example-1"
|
|
arch_tag: "modified-mvc"
|
|
last_indexed: <timestamp>
|
|
```
|
|
|
|
Guardrail severity tiers:
|
|
- **guidance** — model can deviate, shouldn't
|
|
- **hard-instruction** — agent is required to follow it. For scope-expansion rules specifically, this means the agent evaluates its own next action against the rule and calls a `request_scope_approval` MCP tool to check in before proceeding — not the gateway intercepting tool calls in the live path.
|
|
- **enforcement** — the gateway itself gates the action (e.g. rules-approval, secret access) rather than relying on the agent to self-report.
|
|
|
|
**Guardrails vs. agent profiles — kept separate:** project guardrails (above) are project policy, with their own provenance and lifecycle. This is deliberately distinct from vendor-published best-practice guidance, tracked separately per agent:
|
|
|
|
```yaml
|
|
agent_profiles:
|
|
claude:
|
|
capabilities: [...]
|
|
instruction_format: "CLAUDE.md conventions"
|
|
vendor_guardrail_suggestions: [...] # sourced from Anthropic docs, pending review
|
|
codex:
|
|
capabilities: [...]
|
|
instruction_format: "AGENTS.md"
|
|
vendor_guardrail_suggestions: [...]
|
|
kimi: {...}
|
|
deepseek: {...}
|
|
```
|
|
|
|
A vendor suggestion only becomes project policy once explicitly approved via the Web Panel — at which point it's promoted into the canonical `guardrails:` list with its own provenance record. This keeps vendor documentation from silently becoming project policy.
|
|
|
|
**Permissions:** each agent's authority over Bridle itself is explicit, not implied — e.g. an agent may be permitted to *propose* rule changes without being permitted to *approve* them:
|
|
```yaml
|
|
permissions:
|
|
claude:
|
|
gitea: {read: true, write: true}
|
|
kanboard: {read: true, write: true}
|
|
rules: {propose: true, approve: false}
|
|
secrets: {read: false}
|
|
```
|
|
|
|
**Per-model rendering:** `get_rules(project_id, agent_type)` renders the canonical doc into the target format:
|
|
- Claude → `CLAUDE.md`, native heading conventions
|
|
- Codex → `AGENTS.md`
|
|
- Kimi / DeepSeek → `AGENTS.md`-style, but formatted with more explicit, step-by-step phrasing to compensate for weaker instruction-following at the guardrail level
|
|
|
|
**Full document delivered, not a dynamic subset.** The rendered rules doc is always sent in full — no path-based or task-based filtering of what an agent receives. Some models (e.g. Opus-tier) need a fuller, more complete leash to reliably stay on task than others; how much a given model needs is itself a per-model concern handled by the renderer, not a universal token-saving optimization applied to everyone. Dynamic subsetting was considered and rejected for this reason.
|
|
|
|
**Guardrail sourcing:** a periodic or manually-triggered job pulls each vendor's published agent-guidance docs and proposes guardrail entries into a review queue. Nothing auto-merges — proposals require approval via the Web Panel before becoming part of the canonical doc.
|
|
|
|
**Editing paths:**
|
|
- Web Panel — primary surface for reviewing vendor suggestions, editing guardrails/patterns directly, managing example repos.
|
|
- Agent-initiated — MCP tools (`propose_guardrail`, `add_pattern`, `update_rules`) let an agent suggest changes mid-session. These are gated behind a confirmation step (surfaced in the Web Panel's approval queue) rather than auto-applied.
|
|
|
|
### 3. Memory Service (custom)
|
|
|
|
- Wraps Qdrant (self-hosted) for vector storage.
|
|
- Embeddings generated locally via LM Studio (RTX 3060).
|
|
- Two primary use cases sharing the same Qdrant instance in separate collections/namespaces:
|
|
- **Session memory** — decisions, context, and history from agent sessions.
|
|
- **Pattern example RAG** — indexed code from example repos linked to architecture patterns.
|
|
|
|
**Pattern example indexing:**
|
|
- Triggered once when a user adds an example repo to a pattern entry (clone → chunk → embed → upsert).
|
|
- Payload metadata per chunk: `reference_tag`, `arch_tag`, `source_repo`, `file_path`, `chunk_type`, `last_indexed`.
|
|
- **No automatic re-fetching.** Re-indexing only happens on explicit trigger — via the Web Panel or by telling an agent to refresh (`refresh_pattern_example(reference_tag)`), which re-runs the fetch/chunk/embed/upsert flow and replaces prior vectors for that tag.
|
|
- Exposed as an MCP tool: `search_pattern_examples(query, arch_tag?)` — semantic search optionally filtered to a specific named pattern.
|
|
|
|
### 4. Kanboard MCP (custom wrapper)
|
|
|
|
- Wraps Kanboard's existing REST API as MCP tools for task/plan management.
|
|
- Kanboard itself is not replaced — this is a thin translation layer.
|
|
|
|
### 5. Gitea MCP (existing)
|
|
|
|
- Uses an existing Gitea MCP server implementation rather than a custom-built wrapper.
|
|
- Exposes issues, PRs, commits, diffs as tools, authenticated via a gateway-issued token.
|
|
|
|
### 6. Web Panel (custom)
|
|
|
|
- Full admin surface — this project is user/developer-first, and the panel is not an add-on but the primary control interface.
|
|
- Talks only to the Gateway's admin API (not directly to individual backend services), mirroring the centralized-token principle.
|
|
- Controls:
|
|
- Gateway: connected agents/sessions, routing, agent-identity mapping
|
|
- Secrets: add/rotate/revoke (raw values never displayed after storage)
|
|
- Rules: edit canonical doc, approve/reject agent-proposed and vendor-seeded guardrails, manage patterns and example repos, trigger re-indexing
|
|
- Memory: browse/search indexed content, manual purge/re-index
|
|
- Integrations: Gitea/Kanboard connection status and project mappings
|
|
- Audit log: full change history with source attribution
|
|
|
|
---
|
|
|
|
## Deployment
|
|
|
|
- Docker Compose, self-hosted (dev instance on Darrow/Proxmox or Lucifer/Unraid).
|
|
- WireGuard for secure agent access when off local network.
|
|
- Pangolin if external reachability is needed.
|
|
|
|
**Optional/bundled services:** everything except Gitea is toggleable in the compose file — bundled via Compose profiles (e.g. `--profile with-kanboard`, `--profile with-qdrant`) so each service is either:
|
|
- **Bundled**: spun up as part of the stack for someone with no existing instance, or
|
|
- **External**: pointed at an existing self-hosted instance via env vars (`KANBOARD_URL`, `QDRANT_URL`, etc.), skipping the bundled container entirely.
|
|
|
|
Gitea is the exception — treated as a required external integration (via the existing Gitea MCP server) rather than something bundled, since it's assumed the user already runs one.
|
|
|
|
---
|
|
|
|
## Build Phases
|
|
|
|
1. Gateway skeleton + Rules Server (prove single-source-of-truth rendering across at least two agent formats)
|
|
2. Qdrant integration for session memory
|
|
3. Kanboard MCP wrapper
|
|
4. Gitea MCP integration (existing server, wired through gateway auth)
|
|
5. Pattern-example RAG pipeline (fetch/chunk/embed/tag/search)
|
|
6. Web Panel — full admin surface across all of the above
|
|
7. Guardrail vendor-sourcing feed + approval queue
|
|
8. Audit log and multi-project support
|
|
|
|
---
|
|
|
|
## Open Questions
|
|
|
|
- Exact schema/versioning strategy for the canonical rules document as it evolves (e.g. schema migrations when new sections are added).
|
|
- Whether Web Panel auth is single-user or needs multi-user roles (currently designed single-user/developer-first).
|
|
- Long-term: whether to integrate with agentglass for session observability once the gateway is routing all agent traffic.
|