An MCP connector that gives a chat hands on your own Google Cloud project.
The whole system at a glance: the console and MCP surfaces split off one image, the encrypted git store and data lake, where the keys live, and the models you can wire in. Full-size diagram · release + architecture PDF · security review PDF.
Point any MCP client at it -- Claude, Grok, anything that speaks the protocol -- and ask for software. It builds the container, deploys it to Cloud Run in your project, and reports back the HTTP code it got when it checked. Your project, your bill, your key, and the credential never leaves the control plane.
It installs in one command. Agents propose, you commit, and then it builds the next version of itself.
./install.sh
One command, no arguments, and a GCP project with billing enabled. The installer runs enables APIs, creates Firestore, mints three service accounts, generates every secret into Secret Manager, deploys the console, the MCP service and the gated executor, walks you end to end with nobody watching, and then tests itself. If the self-test fails it says so and exits non-zero — a green run means it works, not that nothing errored.
Apache-2.0. Your project, your bill, your key.
Serverless MCP behind Google IAP — and the reason there are two services.
IAP eats the Authorization header, so an MCP client can never live behind it. One image,
one 93-route table, deployed twice: IAP on for humans, off for machines. Scale-to-zero on
both. A route that maps to neither surface throws at boot rather than shipping broken.
Two MCP protocol eras on one endpoint.
The 2026 stateless revision and the 2025 handshake answer on the same POST /mcp, routed as
a pure function of one request's bytes — no connection state, no cache, no clock.
A git server with no git binary and no disk. Isomorphic-git over Firestore and Cloud Storage — refs get real serializable compare-and-swap in a transaction, objects get atomic whole-packfile writes. Serverless git that scales to zero. No force push. Ever.
X-Wing sealing everything at rest. The hybrid post-quantum KEM — ML-KEM-768 and X25519, 1120 bytes of ciphertext — so an attacker has to break the lattice and the elliptic curve. The same envelope covers the git objects and the data lake.
Your work is not trapped in a chat session. Memory, journal, history, files and git all live in your project, encrypted. Hit a usage limit, move to another Claude Max plan, paste the bootstrap, and the agent resumes with its full history intact. Portable agent state is the whole payoff of putting the crypto underneath it.
A billing boundary on the built-in chat.
The connector itself makes no model calls -- your client is the model. The console ships its
own chat as well, for when you want one without leaving the project, and models.fleet_mode
decides what that chat may spend: home makes no model call of any kind, dual runs on your
own keys, work is keyless and employer-billed on Vertex with the keyed transport refused
outright in code rather than remembered by a human. Anything unset, unreadable or
unrecognised fails safe to home. NOTE, because earlier releases described this as governing
a whole fleet: 12.0 deleted every runner and the autonomous loop with them, 12.5 removed the
last references to them, and this switch now governs one thing -- the console chat.
Agent identity we built ourselves, then made interoperable.
Every strain is a first-class identity with its own scope, journal lane and memory, and
cannot read another's — a leaked key leaks a role, not the system. Each one publishes an A2A
agent card at /agents/{role}/.well-known/agent-card.json, discoverable by anything that
speaks Agent2Agent.
Your own MCP server, not a tenant on someone else's. ~55 tools over full OAuth 2.1 — work items, a memory graph, a journal, file storage, git, execution, infra, messaging — and the source ships with it. Change a tool, cut a release, redeploy. It also ships as an Agent Plugins package, so any client that reads the format can connect.
It ships the pipelines that rebuild it. Cloud Build configs, a release generator with leak gates, route audits and structural checks, and the git and deploy tools an agent uses to modify the thing it is running inside. The generator fails the build outright when it catches you — including twice in the week this release was cut, correctly.
Client-agnostic on purpose, and proven on more than one. The session key travels as a tool argument, not as a client-specific header, so anything that can pass an argument on every call can drive this. Measured, not asserted: the same install has been driven end to end from Claude and from Grok, on the same GCP project, through the same MCP tools, against the same internal git store. Claude is the reference client, not a dependency. The console also ships its own chat -- Claude or Gemini, wired straight into GCP -- for when you want one inside the project.
An agent stages its privileged work, and every stage is recorded.
On a default install install.sh sets PC_AUTO_APPROVE=1: a job an agent stages is signed and
executed in the same call, and the journal records who staged it, what the command was and what
it returned. That is the deliberate posture. This is built to ACCELERATE security-minded
agentic engineering, and a human tap in front of every command does not make a system safer --
it makes it slower and teaches everyone to click through.
Set PC_AUTO_APPROVE=0 and a staged job goes to pending and does not run -- not on a
timer, not on a retry, not ever. Nothing in the product will come along and run it for you.
There is no approval queue and no per-job tap: the console page that offered one was deleted,
so with that switch on the honest description is that the product STOPS, not that it asks.
The executor refuses more than it accepts.
A staged job is signed with a Cloud KMS asymmetric key and executed in the same call. The
signature covers the job id and the command digest, the control plane holds the private half,
and the executor holds only the public half -- so it verifies a signature it could not produce,
refuses a command edited after signing, claims each approval exactly once in a transaction,
bounds its age, and runs it under a PATH jail.
install.sh ships PC_GUARDRAILS=0, so the two RUNTIME refusals are off by decision.
PC_GUARDRAILS=1 restores them and a destructive or lockout-class body is handed back to you
in chat rather than run. Note what that switch does NOT touch: every check that can fail a
release CUT is unconditional and stays exactly where it is.
Identity-Aware Proxy guards the console, not the job.
IAP is the front door: you sign in to your Google account at its prompt, and the control
plane admits a verified IAP identity on the approver allow-list (WA_APPROVER_EMAILS) on
every request, checking the assertion's signature and audience itself rather than trusting a
header. The console issues one credential of its own -- a session cookie signed under
WA_SESSION_SECRET that expires after WA_SESSION_MIN minutes -- and enrols nothing else.
That decides who reaches the browser surface, not what runs. It is not a per-command
approval, and this document will not describe it as one.
Jobs run inside a binary jail.
The approved script executes with PATH restricted to an enumerated set, so an unlisted
binary simply does not resolve. What authorises the work in the first place is that a human
approved that exact command, and the executor refuses any script whose hash does not match
the approval.
Memory that survives the session. A knowledge graph where corrections supersede rather than overwrite, so you can always tell a corrected claim from a current one.
Built the way it runs: Claude Max plans through Cowork, subagents fanned out across the engineering work in parallel, moving between plans when one ran hot — and never losing the thread, because the state was never in the chat.
A thing that builds the things.
The connector does not care which model you use, and that turns out to be worth money.
This is how this fleet is actually run, and it is the reason the client-agnostic property is a headline feature rather than a footnote. Two $100-a-month plans, each doing the thing it is best at, both pointed at the same Google Cloud project:
- Claude Max — the architect. Design, the calls that are expensive to get wrong, release cuts, and the security review. Opus 5 on high effort is what reads the diff and asks what breaks. It is the chat that decides what should be true.
- Grok — the engineering loop. Long mechanical passes over a large tree: inventories, refactors, hunting every call site of a thing being removed. It is stellar at this and it is patient in a way that is expensive to buy from a reasoning model.
- Claude in Chrome is the bridge. Cowork drives a browser tab logged into
grok.com, so the architect hands work to the engineer and reads the result back without a human copy-pasting between two windows.
Two sets of eyes on every line, and they are not the same eyes. Grok writes and Claude reviews, or the reverse, and a disagreement between two vendors' models is the most useful signal either one produces -- it is the one thing a single model cannot give you no matter how much you spend on it. More than one defect in this release was found because the second model refused to agree with the first.
And the token economics are the point. Reasoning tokens are the expensive ones. Spending them on architecture and security review while a second plan absorbs the long grinding passes is what makes a two-plan month cost less than doing all of it on one, and finish more.
What makes it work is that both chats authorise into the same place. Neither model holds a Google credential. Each one presents a session key to the same OAuth-connected MCP connector, the control plane resolves it to a role server-side, and from there they share everything that matters: the same GCP project, the same MCP tool surface, the same internal git store with its objects sealed under X-Wing, the same data lake, the same journal. Work one model does is visible to the other because it was never in a chat window to begin with -- it is in the project.
The Flow Hood is the control panel for that. It is where strains are created and managed, and where the per-strain session paste is generated for each chat harness -- so getting a second model onto the same fleet is a paste, not an integration.
Ask a strain in the console chat for a service, and it builds the container and deploys it to Cloud Run in your project — then verifies the result anonymously and reports the HTTP code back.
Everything configurable lives on one panel: theme, the model substrate (Claude or Gemini, 3.8 Flash or 3.1 Pro), key rotation, the per-strain session pastes that give a chat its identity, and the Google accounts allowed to authorise an MCP connector.
The installer deploys one built image as two Cloud Run services, and the separation between them is a security boundary rather than a packaging detail.
paracoding-control-plane is the console: the browser pages a signed-in operator works in.
Step 8 puts it behind Identity-Aware Proxy, grants the installing account
roles/iap.httpsResourceAccessor, and removes its public invoker binding, so an anonymous
request is refused at Google's edge before your container is reached.
paracoding-mcp is the machine-facing surface: the MCP transports, OAuth 2.1 and its
discovery documents, the agent cards, and the token-authenticated agent API. It is publicly
invokable and IAP is off in front of it. It has to be -- IAP consumes the Authorization
header and an MCP client has no Google identity to present, so with IAP on, POST /mcp is
refused at the edge and no client can connect at all. IAP on Cloud Run is one switch per
service with no path-level carve-out, which is why one service cannot serve both purposes.
Separating them means a compromise of the machine-facing surface does not reach the human console. It is also how you get in at all: IAP runs the Google sign-in, and the identity it verifies is the identity the console admits. There is no second sign-in and nothing to enrol. The console keeps its service name deliberately -- the IAP audience the control plane checks names that service, and renaming it would change the audience and every URL you have bookmarked.
The two URLs are not interchangeable, and the installer prints both when it finishes:
- the console URL, at
/harness, is the one you open in a browser - the MCP URL, at
/mcp, is the one you give an MCP client
Underneath IAP the console is still guarded by the application's own session check, and
step 8 asserts that guard against your live deployment in the moment before IAP goes in front
of it: /harness must answer an anonymous caller 401 with the locked page. It stops rather
than putting IAP in front of a console that would be readable by anyone the moment IAP came
off. If it cannot enable IAP it says so and prints the command to enable it yourself, rather
than reporting a clean install. uninstall.sh removes both services.
The 401 that used to meet you on the very first login is fixed in this release, and it is worth saying what it was. You would authenticate with Google, land on the console, and get a black locked page saying no. A browser refresh got you straight in, and it looked random. It was not random and it was not your setup: IAP verifies its assertion against Google's public keys, the fetch of those keys was fire-and-forget, and the first request after a cold start arrived before the keys did -- so a caller carrying a perfectly valid assertion was read as having no identity at all. Per Cloud Run INSTANCE, which is exactly why it seemed to come and go. Boot now waits for the first key fetch before it starts listening, bounded by a five-second race so a slow fetch cannot hang the service, and an empty key set is refused rather than cached -- one malformed response used to install a key map that satisfied the one-hour freshness check and matched nothing, locking every caller out for a full hour with no recovery but a redeploy. If you have seen the black page on an older install, that was this, and upgrading ends it.
bash upgrade.sh upgrade the install in your current project
bash upgrade.sh --project P --region R say which one explicitly
install.sh has always been the upgrade path and still is -- it adopts every resource it
finds, describes before it creates, and never rotates a secret, so running a newer release
over a live install is the supported move rather than a trick. The version marker carries
<version> <commit>, so the installer decides for itself whether a run is an upgrade, the
same build again, or a downgrade it refuses by name.
upgrade.sh therefore owns nothing install.sh owns -- it delegates to it. Two copies of
a provisioning rule is how the two drift. What it adds is the three things install.sh cannot
do, because install.sh does not know it is upgrading anything:
- It refuses when there is nothing to upgrade. No install marker in the project means
install.shwould build a NEW install there -- correct forinstall.sh, and a surprise deployment with a bill for something calledupgrade. More than one marker is refused too, rather than guessing which lane you meant. - It reads the region off your running services instead of defaulting. Cloud Run is
regional and the marker is not, so a guessed region passes every other check and then
builds a second fleet somewhere else. An explicit
--regionthat disagrees with where the install actually runs is refused with both names, because that is a typo, not an instruction. - It writes down the way back first, and verifies the result off the services. The revisions serving right now are printed as named rollback commands BEFORE anything moves, which is when they are easy to find rather than at 02:00. Afterwards it re-reads the serving revision of each surface and checks it carries this release's commit -- because a deploy message is a statement of intent, not evidence. Exit 31 means it did not.
It does not judge your version ordering. install.sh refuses a downgrade with both versions
named, and that refusal lives in exactly one place.
Once, and you may press ENTER through it. Step 6d asks which OTHER Google accounts may sign in -- the account you install with is very often not the account your Claude app signs in with, and that is cheaper to answer here than to discover as a refusal afterwards. It is also editable later in Settings, so an empty answer costs you nothing.
Nothing else stops. There is no workstation question because 12.x builds no VM, and there is no credential to register because the Google sign-in IAP runs is the only sign-in there is. The installer does not refuse to start without a terminal, which means an unattended run -- Cloud Shell you walked away from, or a script -- reaches the end.
Open Cloud Shell and point the installer at a project that has billing linked -- Cloud Shell
already provides gcloud, python3, openssl, curl and an interactive terminal. Step 0
checks everything it needs and stops with the exact missing permission rather than the
symptom.
12.x is a GCP MCP connector a person drives from an MCP client on their own machine. There
is no workstation VM, no autonomous loop, and nothing at all that runs unattended. install.sh
does not build a VM at step 9/10 and does not leave one stopped -- both halves of that older
claim are false.
Work items, the memory graph and the journal remain as a shared list and shared memory that a human or an agent reads and writes -- not a queue anything claims.
The connector authenticates in tool arguments, so it is client-agnostic. Claude is the
reference cockpit, not a dependency: the same install works from Claude, from Grok, and
from any MCP client that can pass a session key as the agent argument on every call.
uninstall.sh still deletes a leftover workstation instance if one exists from an earlier
release, because dropping a name from the teardown sweep is how a machine keeps billing
after an operator believes they have deleted everything.
Two providers, two transports each, and Vertex is the default for both. Nothing needs an API key to work.
Claude Vertex (default) <- rides the service's own identity, no key
api.anthropic.com <- set CHAT_CLAUDE_PROVIDER=anthropic, needs a stored key
Gemini Vertex (default) <- token auth, no key
generativelanguage.google <- set CHAT_GEMINI_PROVIDER=studio, needs a stored key
Settings shows which one is live, per provider, in a line under each key box: what it resolved to, the region, the model, and -- when something is wrong -- the blocker and the one setting that moves you to the other transport. An empty key box does not mean the chat is unconfigured. On a default install it means Vertex is carrying it and no key is required. Read the line, not the box.
Region, and the global endpoint. CHAT_VERTEX_REGION (default us-east5) picks where Claude is
called; CHAT_VERTEX_GEMINI_REGION (default global) does the same for Gemini. Some published
models are served ONLY on the global endpoint, and both providers now detect that and override the
configured region rather than failing -- the override is logged with the model that caused it.
That detection exists because the failure is deceptive. Asking a regional host for a global-only model does not 404. It answers:
HTTP 429 Quota exceeded ... base model: anthropic-claude-opus-5
which reads as "ask Google for more quota", and the quota tables agree with that story, because
online_prediction_input_tokens_per_minute_per_base_model enumerates REGIONAL buckets only and has
no row for a model that is not served regionally. The model is fine, the quota is fine, and the
endpoint is wrong. If you hit a 429 naming a base model, check the endpoint before you file a quota
request.
Model ids. CHAT_API_OPUS and CHAT_API_SONNET pin the two Claude entries. Genuinely stale ids
(claude-opus-4, claude-opus-4-1, the whole claude-opus-3 family) are still force-upgraded to
claude-opus-5 so old configuration cannot drag the chat backwards -- but only those. Newer members
of the 4-x family are yours to pin, which matters because the regional quota a project actually has
is often largest on those.
The console ships six palettes -- purple (the default), green, orange, blue, dark and light -- and you pick one in the console under Settings. Mushroom mode is on, as it always has been.
A palette is a list of values, not a code change. Every colour the chrome uses reads a CSS
custom property declared once in control-plane/src/harness.html, and a palette is a block that
overrides the ones it wants:
html[data-theme="light"]{ --ink:#1a1c1c; --glass:rgba(255,255,255,.86); ... }
Two things about that are worth knowing before you change any of it.
The hue is a token; the alpha stays where it is. Colours that vary in transparency are written
rgba(var(--ink-rgb),.28) rather than collapsed into one flat token. The accent glow appears at
five different alphas in this file, the recessed surfaces at eight, and the ink at twenty-one.
Collapsing each family to a single token is the obvious refactor and it silently restyles panel
depth and border weight everywhere while looking like a no-op. Keep the split.
The logo is data too. --logo-filter, --logo-radius, --logo-fit-*, --logo-w-*,
--logo-h-*, --logo-maxw and --logo-shadow-* carry the treatment. The shipped mark is a 96x96
square that the stock filter chain paints gold, so the defaults crop to a circle and recolour. If
you drop in a wider lockup, say so in the tokens -- --logo-filter:none, --logo-radius:0,
--logo-w-hdr:auto, --logo-maxw:190px -- rather than editing the rules. A finished asset handed
to the stock defaults gets cropped to its first two letters and repainted.
Whimsy is a switch. window.PC_BRAND.whimsy = false turns mushroom mode off and hides its
four toggles, and it OVERRIDES the stored preference rather than seeding a default -- anyone who
has ever clicked the toggle carries pc_mush=1, so changing only the default would leave it on
for exactly the people who use it. The lexicon keeps running in the professional direction either
way, because the authored markup contains mushroom-isms that it is what rewrites.
Strings in the chrome are still authored in the document rather than read from data. Changing them
means editing harness.html, and an install that does that is carrying a patch it will have to
re-apply on upgrade.
agent-plugin/ is an Agent Plugins package — the vendor-neutral
format for wrapping an MCP server into a portable directory. Point any client that reads the
format at that directory. agent-plugin/mcp.json ships with a placeholder URL because your
control plane's address does not exist until you install; the installer prints the exact
value to substitute, and writes a resolved copy next to it that is not part of the manifest.
Point a Gemini Enterprise custom connector at the /mcp URL and a Gemini chat gets the
full tool surface over OAuth 2.1 — the same tools every other client gets, against your
project, your bill, your key. No model holds a Google credential: the connector presents its
binding and the control plane resolves it to a role server-side. Consent is CSRF-protected,
and a signed-in user's session is re-verified against Google inside its lifetime rather than
trusted for seven days on one check.
Identity resolves from the connector binding — oauth_client_id + oauth_email — mapped
to a strain on the server, every turn. What that means for a team is worth stating plainly:
- By default, everyone shares one identity. With the keyless flat-rate config
(
PC_NOCARD_EXEC=1,PC_NOCARD_ALL=1) that makes a chat interface painless, every user you add to the Gemini Enterprise application inherits the single Google account that consented to the connector and executes as one bound strain carrying the full tool surface. Powerful, shared, and right for a single operator — andSECURITY.mddocuments the exposure of that posture in full rather than dressing it up. - Multi-user is a binding, not a mode. Bind each teammate's Google account (
oauth_email) to their own strain, and each person resolves to their own isolated identity — its own memory, journal, permissions andtool_classes. A leaked account leaks one role, not the workspace. That per-user binding is the designated way to open the door to a team; restricting the shared strain is not.
To build an agent for it: connect and consent, bind the account to a strain, arm the tools it
needs with load_skill up front (in a GE chat each fleet tool is its own skill and only a
couple are preloaded — phrase every call as one sentence with the tool bound to the verb),
fence it with tool_classes, and let other agents find it through the Agent2Agent card
every strain publishes at /agents/{role}/.well-known/agent-card.json.
FEATURES.md— the full feature inventorywiki/— architecture pages, including the five diagramsSECURITY.md— the security policy and how to reportdocs/architecture.png— the full architecture diagram (shown at the top)docs/release-architecture.pdf— the release and architecture write-up, print-readydocs/security-review.pdf— the v15.0 security review
Apache-2.0. Keep the copyright headers and the NOTICE file.
