Skip to main content

Flotilla MCP

A self-developing fleet of MCP servers, with guardrails.

License: Apache-2.0 Contributions welcome

Flotilla MCP is a fleet of Model Context Protocol servers that can read GitHub issues, edit its own source, open pull requests, and — once a human merges the change — redeploy itself, without ever being able to take the fleet down or disarm its own safety controls. The parts of the system that could do real damage (the deploy pipeline, the permission layer, the rule that defines what's off-limits) are structurally walled off from the part that writes code, and enforced at more than one independent layer.

This repository is the foundation of that system: the protected-core enforcement engine, the Self-Dev MCP server, and the Deploy Watcher's blue/green deploy and rollback machinery. It is real, tested code, not a prototype — see Status below for exactly what's built and what isn't yet.

Install

Self-Dev MCP only:

pip install flotilla-mcp

Self-Dev MCP plus the Deploy Watcher (needs the Docker SDK):

pip install "flotilla-mcp[watcher]"

Or run it without installing anything, via uvx:

uvx flotilla-mcp

This installs three console scripts:

  • flotilla-mcp and flotilla-self-dev (identical — two names for the same entry point) — the Self-Dev MCP server: flotilla-self-dev --transport stdio|http (default stdio; --transport http serves /health on port 8080 for container health checks); --version prints the installed version.
  • flotilla-watcher — the Deploy Watcher. Needs pip install "flotilla-mcp[watcher]"; without the docker package installed, it prints flotilla-watcher needs the Docker SDK. Install it with: pip install "flotilla-mcp[watcher]" and exits rather than crashing with an import traceback.

Self-Dev MCP reads its configuration from environment variables (see .env.example and flotilla_mcp/self_dev_mcp/config.py). SELF_DEV_GITHUB_TOKEN is the only variable you have to set — run it from inside a git checkout with a GitHub origin remote and everything else is detected:

Variable Required Meaning
SELF_DEV_GITHUB_TOKEN yes fine-grained token (or bot token): contents, pull requests, and issues read/write on the target repo
SELF_DEV_REPO_REMOTE no (auto-detected) git remote URL used for the clone/branch/commit/push workflow. When unset, detected by running git remote get-url origin in the server's working directory.
GITHUB_REPO_FULL_NAME no (auto-detected) owner/repo. When unset, parsed from SELF_DEV_REPO_REMOTE (explicit or detected). A non-GitHub remote (or a local path) parses to nothing — the editing tools (start_issue, read_file, write_file, run_tests) still work; the GitHub-backed tools (list_assigned_issues, check_pr_status, submit_pr) return an ERROR: no GitHub repository configured string instead of failing to start.
FLEET_MANIFEST_PATH no path to the fleet manifest. When unset, resolved in order: fleet_manifest.yaml in the current directory, then fleet_manifest.yaml at the git repo root. If none of those exist either, the server starts with an empty manifest (no services declared) and logs one warning — only the built-in always-protected paths apply until a manifest exists. An explicit FLEET_MANIFEST_PATH that doesn't exist is still a hard error.
SELF_DEV_MAX_ATTEMPTS no (default 5) per-issue write attempt cap
SELF_DEV_TEST_TIMEOUT_SECONDS no (default 600) run_tests timeout, in seconds

An explicitly set environment variable always wins over auto-detection. If SELF_DEV_REPO_REMOTE is unset and no origin remote can be detected, the server exits with a one-line error naming both fixes: run it inside a git repository with an origin remote, or set SELF_DEV_REPO_REMOTE.

Read SECURITY.md#preconditions-before-pointing-a-live-agent-at-a-repo before pointing this at a repository you care about.

Use with an MCP client

Both snippets below point uvx at the flotilla-mcp PyPI package and pass configuration through environment variables — see SECURITY.md#preconditions-before-pointing-a-live-agent-at-a-repo first.

Claude Desktop

Add to your Claude Desktop config (claude_desktop_config.json). The token is the only variable that's required — as long as the server's working directory is inside a git checkout with a GitHub origin remote, the repo remote and owner/repo are auto-detected (see Install above):

{
  "mcpServers": {
    "flotilla-mcp": {
      "command": "uvx",
      "args": ["flotilla-mcp"],
      "env": {
        "SELF_DEV_GITHUB_TOKEN": "<fine-grained token>"
      }
    }
  }
}

If your client doesn't run the server with a working directory inside the target repo (or you want to be explicit), set SELF_DEV_REPO_REMOTE (and optionally GITHUB_REPO_FULL_NAME, FLEET_MANIFEST_PATH) in env the same way as before -- an explicit value always overrides auto-detection.

Claude Code

The claude CLI wasn't available in the environment this doc was written in, so its exact claude mcp add ... -e KEY=VALUE flag syntax couldn't be verified against a real claude mcp add --help; run that yourself before scripting the command form. The JSON form above is not client-specific — paste the same mcpServers block into whatever configuration surface your claude CLI version reads for MCP servers.

Why Flotilla MCP

Most "self-modifying agent" demos either can't touch production or can touch too much of it. Flotilla MCP is built around one design principle: the agent reasons, MCP servers provide capabilities, and guardrails sit in between, independent of the agent's own judgment.

  • The agent (any LLM-backed orchestrator, via the MCP gateway) decides what to build or fix.
  • MCP servers like Self-Dev MCP expose narrow, auditable primitives — read a file, write a file, run tests, open a PR — not blanket shell access.
  • Guardrails (the fleet manifest, workspace containment, the attempt cap, GitHub branch protection, credential scoping) sit between the agent's intent and anything irreversible. They work in two layers:
    • the tool-level checks stop a cooperative agent, one that only edits through write_file, from touching protected paths or .git/;
    • GitHub branch protection plus a separate bot identity is the real backstop against a hostile (e.g. prompt-injected) agent. run_tests executes code the agent wrote, with the self-dev token, so that code can write protected files into its PR branch or call the GitHub API directly. What stops it from reaching main is a required CI check and a required human (code-owner) review that the bot identity can't give itself. In either case Self-Dev MCP never holds a Docker socket.

The result is an agent that can fix its own bugs and extend its own fleet, while every change that could take the system down or weaken its own oversight has to pass a human review on GitHub, provided the preconditions are in place.

Key features

Implemented today:

  • Protected-core enforcement (flotilla_mcp/common/manifest.py): a fleet manifest defines which services and paths are off-limits. A hardcoded core is protected no matter what the manifest says: the manifest, its loader, flotilla_mcp/__init__.py, requirements.txt, requirements-dev.txt, docker-compose.yml, pyproject.toml, .gitattributes, .gitignore, and the whole flotilla_mcp/common/ and .github/ trees. Path checks canonicalize separators, .. segments, NTFS aliases and case, and are applied to the symlink-resolved target too.
  • Self-Dev MCP server: a FastMCP server exposing start_issue, read_file, write_file, run_tests, submit_pr, list_assigned_issues, and check_pr_status. Every write is checked against the fleet manifest and confined to an ephemeral, per-issue workspace before it touches disk. Absolute paths, drive letters, UNC paths, .. escapes and any .git path are refused, and each refusal is audit-logged. Every git call runs with hooks and fsmonitor disabled and pushes with an explicit, non-forcing refspec. A per-issue attempt cap (default 5) stops runaway writes; a refused write never consumes an attempt. These checks bind a cooperative agent; see the safety model for what backs them up.
  • Limited blast radius: the Self-Dev MCP has no Docker socket access, no deploy credentials, and no merge code path. It can open and update PRs. Making them live takes a human review enforced by GitHub branch protection, which is a precondition, not something this code can enforce.
  • Deploy Watcher — polls main for new commits, builds each non-protected service that has a container entry in the manifest from a fresh checkout of that commit, and performs a blue/green deploy: the new container must pass 3 consecutive health checks before it's promoted, and then survives a 30-minute probation window during which a single failed check triggers an automatic rollback to the last proven image. An image is only recorded as known-good after it clears probation, so a rollback can never target a build that hasn't actually been proven to work.
  • Resumable, non-destructive self-dev cycles — re-invoking start_issue for an issue that already has an open PR resumes the same selfdev/issue-N branch instead of branching again, so follow-up commits land on the same PR. There is no force-push anywhere in the pipeline.
  • Docker Compose + per-service Dockerfilesdocker-compose.yml brings up the fixture service, Self-Dev MCP, and the Deploy Watcher on a dedicated mcp-fleet network, each with its own non-root Dockerfile and only the credentials/mounts it actually needs (see Quickstart).
  • Self-Dev MCP HTTP transport: --transport http serves Self-Dev MCP over SSE (/sse, /messages/) plus GET /health on port 8080, which the compose healthcheck probes. Tools run in worker threads, so a long run_tests never blocks /health. --transport stdio (the default) is unchanged for local/CLI use. For this release self-dev-mcp is compose-managed (docker compose up --build), not redeployed by the Deploy Watcher.

Roadmap (not built yet — see Status):

  • MCP gateway and permission manager (the layer that sits between an orchestrating agent and every capability server, including Self-Dev MCP)
  • model-adapters-mcp and credentials-manager, and the chat-side "Model Manager" flow for adding support for a new LLM backend on request

How it fits together

flowchart TD
    subgraph gh["GitHub (system of record)"]
        issue["Issue labeled self-dev"]
        pr["Pull Request"]
        ci["CI status checks"]
        branchprot["Branch protection + CODEOWNERS"]
        main["main branch"]
    end

    subgraph host["Fleet host"]
        selfdev["Self-Dev MCP<br/>(git/PR ops only —<br/>no Docker, no deploy creds)"]
        watcher["Deploy Watcher<br/>(PROTECTED — Docker socket,<br/>blue/green, rollback)"]
        gateway["MCP Gateway +<br/>Permission Manager<br/>(PROTECTED — planned)"]
        registry["Service registry +<br/>known-good store"]
    end

    subgraph fleet["Watcher-deployed containers"]
        svc1["fixture-hello-mcp"]
        svcN["... other unprotected services<br/>with a container entry"]
    end

    issue -->|start_issue| selfdev
    selfdev -->|read_file / write_file<br/>manifest-checked| selfdev
    selfdev -->|run_tests, informational| selfdev
    selfdev -->|submit_pr| pr
    pr --> ci
    ci --> branchprot
    branchprot -->|human merge only| main
    main -->|poll| watcher
    watcher -->|build + health-check +<br/>blue/green swap| fleet
    watcher --> registry
    gateway -.orchestrates calls to.-> selfdev
    gateway -.orchestrates calls to.-> svcN

See ARCHITECTURE.md for the full component breakdown, trust boundaries, and the rollback state machine.

The protected core and the safety model

A small set of things can never be edited by the Self-Dev MCP, no matter what an agent's reasoning concludes it should do:

  • The fleet manifest (fleet_manifest.yaml), the manifest loader (flotilla_mcp/common/manifest.py) and everything else in flotilla_mcp/common/, plus the build/dependency files (flotilla_mcp/__init__.py, requirements.txt, requirements-dev.txt, docker-compose.yml, pyproject.toml, .gitattributes, .gitignore) and CI/CODEOWNERS (.github/). These are hardcoded-protected, so write_file refuses them even if the manifest were rewritten to claim they're safe.
  • Git metadata (.git/): no tool can read or write it, and every git call runs with hooks and fsmonitor disabled and an explicit push refspec.
  • The Deploy Watcher, permission manager (planned), and MCP gateway (planned) services — marked protected: true in the manifest. These hold real authority (Docker socket access, routing, auth), so they are off-limits at the tool layer and would additionally require CODEOWNERS sign-off at the GitHub layer — the workflow and CODEOWNERS file exist; enforcement requires branch protection, which the repo admin enables — see CONTRIBUTING.md#enabling-branch-protection.

On top of that:

  • Workspace containment: every read_file/write_file/run_tests call is resolved against the current issue's ephemeral workspace. Absolute paths, drive letters, UNC paths, .. segments, and symlinks or junctions that lead to a protected or outside path are refused before anything touches disk.
  • No merge code path: Self-Dev MCP can open and update a pull request, and nothing in this codebase calls a merge API. The self-dev token could merge through the API, though, so merge-by-a-human-only is enforced by GitHub branch protection and CODEOWNERS. The workflow and CODEOWNERS file exist; the repo admin enables branch protection (see CONTRIBUTING.md#enabling-branch-protection).
  • No deploy authority in Self-Dev MCP — it holds no Docker socket, no deploy credentials, and never imports docker. Only the Deploy Watcher — a separate, protected service — can build an image or swap a container.
  • Known-good floor — the Deploy Watcher only records an image as known-good after it survives a full probation window with zero failed checks. Rollback always targets that proven image, never a build that merely passed its first three health checks.

This is defense in depth, not a single check: even a bug in the manifest parser doesn't expose the manifest file or the parser itself, because those paths are hardcoded rather than manifest-driven.

What the tool checks do not stop. They bind a cooperative agent. run_tests executes code the agent wrote, with the self-dev token. That code can write any file (protected ones included), which submit_pr's git add -A then commits to the PR branch, and it can call the GitHub API directly. The backstop is GitHub branch protection (required test check, required review, code-owner review, enforce_admins) plus a separate bot identity for self-dev. With the owner's own PAT, self-dev PRs are authored by the owner, and a sole owner can't approve their own PR under enforce_admins. See SECURITY.md.

Quickstart

Run the test suite (works today)

git clone https://github.com/OmPrakashSingh1704/flotilla-mcp.git
cd flotilla-mcp
python -m venv .venv && source .venv/bin/activate   # or .venv\Scripts\activate on Windows
pip install -r requirements-dev.txt
python -m pytest tests -m "not docker" -v -W error::DeprecationWarning -W error::pytest.PytestUnhandledCoroutineWarning

requirements-dev.txt pulls in requirements.txt plus the packaging tools (build, twine, hatchling, hatch-fancy-pypi-readme) that tests/test_packaging.py needs to build a wheel as part of the suite.

(-m docker runs the Docker integration test separately; it needs a reachable Docker daemon.)

This runs the full suite for the protection engine, the Self-Dev MCP tools, and the Deploy Watcher — including the adversarial tests that assert a protected-path write is refused and a probation failure triggers rollback.

Run the fleet locally

Precondition before pointing a live agent at a repo: enable branch protection on main (required test check, required review, code-owner review, enforce_admins) and give self-dev its own bot identity (a machine user or GitHub App), not your own PAT. On a private repo, branch protection needs GitHub Pro, Team or Enterprise. See SECURITY.md. Running the stack with placeholder values to look around is fine.

cp .env.example .env
# edit .env: set SELF_DEV_GITHUB_TOKEN, WATCHER_GITHUB_TOKEN,
# GITHUB_REPO_FULL_NAME, REPO_REMOTE, SELF_DEV_REPO_REMOTE
docker compose up --build

SELF_DEV_GITHUB_TOKEN (fine-grained: contents, pull requests and issues read/write on this repo) and WATCHER_GITHUB_TOKEN (contents read-only) are separate tokens. The old shared GITHUB_TOKEN variable is no longer read. Git authenticates with them through a credential helper, so never put a token in a remote URL.

This brings up three containers on the mcp-fleet network:

  • fixture-hello-mcphttp://localhost:8081/health — a minimal Flask service used as the Deploy Watcher's local smoke-test target.
  • self-dev-mcphttp://127.0.0.1:8082/health (bound to loopback only, not published to other hosts — see SECURITY.md) — the Self-Dev MCP server over --transport http. Compose-managed: after merging a change to it, update it with docker compose up --build self-dev-mcp. The Deploy Watcher does not redeploy it.
  • deploy-watcher — no published port; it only talks outbound to GitHub and to the Docker daemon via the mounted docker.sock.

With placeholder .env values (or real ones that just don't resolve), fixture-hello-mcp and self-dev-mcp still start and their /health checks still pass — GitHub credentials are only resolved lazily, on the first tool call that actually needs them. Any Self-Dev MCP tool that talks to GitHub (list_assigned_issues, submit_pr, check_pr_status, ...) will return an "ERROR: ..." string until SELF_DEV_GITHUB_TOKEN and GITHUB_REPO_FULL_NAME are set to real values.

Repo layout

flotilla_mcp/
  common/manifest.py        fleet manifest loader + protected-path engine
  self_dev_mcp/              Self-Dev MCP: git ops, workspace, tools, server,
                                Dockerfile
  deploy_watcher/             Deploy Watcher: checkout, build, health, blue/green,
                                rollback, service registry, known-good store,
                                Dockerfile, entrypoint.sh
  fixture_hello_mcp/          fixture Flask service + Dockerfile, used as the
                                Deploy Watcher's local smoke-test target
tests/                      unit tests, mirroring the flotilla_mcp/ layout
docs/design/                design documents (historical / planned; code and
                              ARCHITECTURE.md are authoritative)
fleet_manifest.yaml         the fleet manifest (protected)
docker-compose.yml          local fleet: mcp-fleet network + all three services (protected)
.env.example                template for the .env docker-compose reads secrets from

Status / Roadmap

Component Status
Fleet manifest + protected-path engine Built
Self-Dev MCP (7 tools, workspace containment, attempt cap) Built
Deploy Watcher (blue/green, probation, rollback, known-good floor) Built
Docker Compose + per-service Dockerfiles Built
Self-Dev MCP HTTP transport (--transport http) Built
CI workflow + CODEOWNERS Built (CODEOWNERS names a real owner, @OmPrakashSingh1704; branch protection must still be enabled by the repo admin for that review to be enforced, rather than advisory; see CONTRIBUTING.md#enabling-branch-protection)
MCP gateway Planned
Permission manager Planned
model-adapters-mcp Planned
credentials-manager Planned
Model Manager chat flow Planned

Nothing in this table is aspirational marketing — if it says "Built," it has passing tests in this repository today. If it says "Planned" or "In progress," there is no working code for it yet.

The one thing "Built" doesn't cover automatically: a real run against your own GitHub repo (a live PR, CI, and merge). See docs/acceptance-checklist.md for that manual verification pass.

Documentation

License

Apache License 2.0 — see LICENSE and NOTICE.

Release files for flotilla-mcp 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for flotilla-mcp 0.1.1
File Size Uploaded
flotilla_mcp-0.1.1.tar.gz 94.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for flotilla-mcp 0.1.1
File Interpreter ABI Platform
flotilla_mcp-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 139.7 kB

Release files / flotilla_mcp-0.1.1.tar.gz

Download URL flotilla_mcp-0.1.1.tar.gz
Size 94.6 kB
Tags Source
SHA-256 checksum
How to use checksums
7ad58d6058e51b5f88829be0ed3e21bc00571ae7781322de3be5f6b0955aec4e
BLAKE2b-256 checksum
How to use checksums
81e84161ef0a5b954a4da9770ac554e5b4c86ba2c2406bd7c8afefab18d02f77
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release files / flotilla_mcp-0.1.1-py3-none-any.whl

Download URL flotilla_mcp-0.1.1-py3-none-any.whl
Size 45.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1fef7623a47bc7bf8b32593c7577a70efcf577e04e6ea616253bca05caef388d
BLAKE2b-256 checksum
How to use checksums
1d8f2c42a8ef8fb8b65e1b4c8094198b5a26cfc4276366c2bd5bb1d2565f93e6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 20, 2026.

Transparency log

Release history Release notifications | RSS feed

0.1.2

2 release files

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page