Roamer MCP — Technical Architecture

Project truth,
composed live.

Roamer MCP is a hosted Model Context Protocol server that gives AI coding assistants a persistent, structured memory of a project — registry state, behavioral specs, and engineering threads, served as live database queries instead of markdown files that quietly go stale.

Runtime
.NET 10 / ASP.NET Core, on Azure Container Apps
Protocol
Model Context Protocol — local stdio bridge + streamable HTTP
Database
Azure SQL, schema managed via EF Core migrations
Auth
Entra ID device-code flow via Cloudflare Access Managed OAuth (PKCE, public client)
Client bridge
roamer-device-auth (npm), spawned via npx from a local stdio script
Edge / gateway
Cloudflare Access + Cloudflare Tunnel
Billing
Stripe — Checkout + Billing Portal, per-seat
Distribution
Agent Skills plugin (Claude Code, GitHub Copilot, etc.) — dedicated plugin repo, 6 skills
CI/CD
GitHub Actions → Azure Container Apps

Repository Structure

The system is split across two repositories: a server repo (RoamerMcp) that owns the hosted MCP server and its data model, and a distribution repo (RoamerMcpPlugin) that packages a stdio bridge into an installable Agent Skills plugin — a format multiple AI coding clients can consume as-is, from Claude Code to GitHub Copilot, not a Claude-specific integration. The server never ships client-side install logic; the plugin never touches the database directly.

RoamerMcp (server repo) ├── RoamerMcp/ │ ├── Models/ # Project, BranchSession, Blocker, ProjectEnvironment, │ │ # Spec, SpecArea, SpecItem, EngineeringThread, BrainSection │ ├── Tools/ # 16 MCP tools — get_registry, log_spec_item, log_thread, │ │ # create_checkout_session, etc. │ ├── Resources/ # BrainResources, RegistryResources, SpecResources — │ │ # serve the roamer:// URI scheme │ ├── Brain/ # BrainComposer — expands {{live:*}} tokens at request time │ ├── Prompts/ # WorkflowPrompts — 6 server-side MCP prompts │ └── Migrations/ # EF Core schema migrations │ ├── docs/ARCHITECTURE.md # Architecture reference for the server itself └── infra/main.bicep # Azure Bicep IaC RoamerMcpPlugin (distribution repo) ├── skills/ # 6 skills mirroring the server's workflow prompts ├── scripts/roamer-bridge.sh # Wraps `npx roamer-device-auth` — the stdio bridge ├── .mcp.json # MCP server registration — the Agent Skills plugin manifest └── marketplace.json # Plugin marketplace listing, publicly served

Both repos' bridge scripts are kept in lockstep by hand and pinned to the same published version of the roamer-device-auth package — a change to the auth flow lands as a coordinated commit across both repos, never one alone.

Core Systems

MCP Tools & Resources

16 tools cover the full surface — registry (get_registry, update_project), specs (list_specs, get_spec_area, log_spec_item), threads (get_threads, log_thread), blockers, and billing. The same data is also exposed read-only as MCP resources under a roamer:// URI scheme — roamer://project/{name}.md, roamer://spec/{name}/{area}.md — so a client can @-reference project state directly without a tool round trip.

Dynamic Brain

The ecosystem overview a session opens with isn't a file — it's BrainSection rows composed at request time by BrainComposer. Sections that mirror the registry, specs, or threads use {{live:*}} tokens that expand against the database on every read, so the parts most likely to drift — project counts, open thread lists, spec item totals — structurally can't. Editing is a tool call (log_brain_section); there is no rebuild or redeploy step.

Behavioral Specs

Spec / SpecArea / SpecItem model each project's requirements as versioned, queryable records instead of markdown a human has to remember to update. Every item carries a priority and a status (draftreadycode-complete-*), which is what lets "done" be computed rather than asserted — see The Arc Model, below.

Engineering Threads

EngineeringThread holds the open questions, live incidents, and deferred decisions that don't yet belong in a formal spec — cross-cutting, tied to one or more projects, and explicitly separate from spec items so exploratory reasoning never gets forced into a done/not-done shape before it's ready.

Auth & Transport

Clients authenticate through a local stdio bridge — a device-code flow against Entra ID, brokered at the edge by Cloudflare Access Managed OAuth (PKCE, public client, no secret to leak). The claude.ai browser connector instead talks HTTP directly, using its own separately-registered Cloudflare OAuth client.

Multi-Tenancy & Billing

Tenant isolation, Owner/Member roles, and per-seat entitlement checks are enforced server-side on every call, not left to the client to respect. create_checkout_session and create_billing_portal_session wrap Stripe Checkout and the Billing Portal directly, gated to tenant Owners.

Data Layer

Project tracks exactly where a project sits on its arc — spec-item status, branch and session history, environments, and both resolved and unresolved blockers — backed by an Azure SQL schema managed through EF Core migrations.

Deployment & Edge

The container sits behind a Cloudflare Tunnel and Cloudflare Access, so every request — from a person or from another service — is authenticated at the edge before it ever reaches the app.

The Arc Model

An arc is the unit of work Roamer MCP actually trusts. Not a status someone typed into a ticket, but something computed live from real data — so "done" means done, not "I think it's done" or "it compiled." It's the mechanism that lets an AI-native workflow claim completion without asking a person to go double-check the claim by hand.

Formal definition
1

One arc = one spec area — a feature, migration, or fix, tracked from its first spec item through genuine completion, however many threads or branches it touches along the way.

2

An arc is finished when every blocker, critical, and major item in its area reaches a code-complete status — driven to zero, not just mostly done.

3

Minor and unprioritized items don't block the arc. They're real backlog — just not the bar for calling the work done.

Code-complete isn't the finish line

A spec item's status describes the code and its test coverage — nothing more. Roamer MCP separately tracks whether that code actually shipped: a project's real deployment state is compared against its arc position, so work that's "code-complete" but sitting unmerged, or merged but never deployed, doesn't get to call itself done.

Why an arc, not a checklist
  • Closure is pulled, not pushed — a loose thread tied to a finished piece of work surfaces for review on its own, instead of being quietly forgotten.
  • Identity survives reorganization — splitting or merging a spec area never duplicates or desyncs an item's status.
  • Idle work resurfaces on its own — nothing goes stale silently; it gets flagged for a fresh judgment call instead of aging out of sight.
  • Coverage is a gate, not an afterthought — done means tested, not just written.

Request Flow

Every client — Claude Code, VS Code, Cursor, Claude Desktop — talks to the same hosted server through one of two paths, chosen by whether the client can spawn a local process. Stdio-first was a deliberate choice over building per-client OAuth/Dynamic Client Registration for each one.

Interactive clients — stdio bridge, cached after first run
1

The client spawns a local process (npx roamer-device-auth) over stdio — it never opens the HTTPS connection itself.

2

First run only: the bridge starts an Entra ID device-code flow, persisting the pending flow to disk so a respawned process resumes it instead of minting a new code.

3

Cloudflare Access validates the resulting token at the edge before any request reaches the container.

4

Subsequent tool calls reuse the cached token — no repeated browser round trips.

Browser-hosted connector

claude.ai's connector talks HTTP directly and authenticates via its own, separately-registered Cloudflare Managed OAuth client — it never touches Entra or the stdio bridge.

Why stdio-first, not per-client OAuth
  • One device-code flow, cached locally, works identically across Claude Code, VS Code, and Cursor.
  • No per-client Dynamic Client Registration to build or maintain going forward.
  • Cloudflare Access still gates every request at the edge, regardless of transport.
  • The browser-hosted connector is the one deliberate exception — it already has its own working OAuth path.

Design Principles

01

Docs are live queries, not files

The brain overview, registry, and specs are composed from the database on every read. There is no markdown file that a human has to remember to update, and none that can quietly fall out of sync with what's actually true.

02

One arc, one spec area, gated by priority

A feature counts as done when every blocker, critical, and major item in its spec area reaches code-complete status — directly computable from the data, not a status someone declared in standup. See The Arc Model, above, for the full definition.

03

stdio-first authentication

Any client that can spawn a local process authenticates once via a cached, reactively-invalidated device-code flow. Only the browser-hosted connector — which already has its own working OAuth path — talks HTTP with server-brokered auth directly.

04

Threads for what specs can't yet formalize

Open questions, live incidents, and deferred decisions get their own tracked, project-linked record instead of disappearing into chat history or getting force-fit into a spec item before they're ready to be one.

05

Multi-tenant from the start

Owner/Member roles, per-seat Stripe billing, and tenant-scoped data isolation are enforced server-side as core parts of the data model — not a retrofit bolted onto a server that was originally built single-tenant.