Build a config in the browser
mcpg.cloud/configs builds an MCPG gateway config from the plugin catalog, checks it as you type, and encrypts it in your browser before anything is uploaded. You get a URL and a secret; the service stores ciphertext and never holds the key.
Before a config can become a managed gateway, somebody has to write it. mcpg.cloud/configs is where you do that without opening a text editor: a wizard that assembles a gateway config from the plugin catalog, checks it as you go, and hands back an address you can give to a gateway or publish to mcpg.cloud.
It stores none of what you build. The config is encrypted in your browser before anything is uploaded, and the secret that decrypts it never reaches the service.
Start from a template
If the gateway you want is "Notion as MCP tools" or "read-only Postgres for an agent", somebody has already made those decisions. mcpg.cloud/configs/t is a gallery of complete configs — bindings, plugins and trust floor already chosen — each with an article explaining what it exposes and what it needs from you.
Picking one replaces the seven-step wizard with two: a form for the values only you have, and the review. The form is generated from the template's own manifest, so it asks for exactly what the config uses and nothing else — a field the config ignores, and a hole the form never asks about, are both build failures in this repository rather than something you discover at boot.
Each template says which destinations it supports, and the form's answer changes with the one you pick:
- Publish to mcpg.cloud — your secrets are sealed in the browser and stored
as ciphertext. The gateway that runs the config decrypts it at boot; the
service never sees a value.
${env.…}is not available here, because a managed gateway's environment belongs to the platform rather than to you. A value you would rather keep out of the config altogether — so it can be rotated without a publish — is a registered secret, referenced as${secret.NAME}in the editor. - Run it yourself — secrets are written as
${env.NAME}and are not in the config at all. You get theexportlines to run before starting the gateway, and aconfig.yamlyou can commit.
Switch between them and the preview re-renders under your own answers, so you
can see ${cred://…} become ${env.…} before you commit to either.
Secrets are treated differently from everything else you type. They are never written into the preview, never into the editor, never into the draft your browser keeps, and never into a URL — the address of a template page carries its slug and nothing more. The one render that contains a real secret exists for as long as it takes to encrypt it, and the values are cleared once that is done.
Start from nothing
If no template fits, open the wizard and pick a starting point:
- From scratch — an empty config. Every step adds only what you choose.
- From a starter — the same configs
mcpg config initships, comments and all. - Paste what you already have — an existing config is taken verbatim. Comments, key order, and any key no form on the site knows about survive untouched.
From there it walks you through Endpoint (transport and bind address), Identity (who a caller is, and how sure the gateway is), Plugins, Tools (plus prompts and resources), Governance (default trust floor and audit), and Review. Skip ahead at any point; a draft is kept in your browser and nowhere else.
The plugin step is a searchable, class-filtered view of the same catalog the
rest of the platform ships — 95 plugins across 14 categories. Add one and the binding editor
knows which backend.kind values it registers, so a tool you write can only
point at a backend this config actually loads. That is worth more than it
sounds: a binding whose kind nothing provides lists fine and fails on every
call, which is the most expensive kind of config bug because nothing complains
at boot.
What it checks while you type
Two of the panels alongside the editor are worth watching.
Trust ceiling. A binding's trust floor defaults to header_asserted, so a
config with a tool and no identity is exactly the config that lists nothing —
the binding is filtered out of tools/list and rejected on call, and nothing
fails at boot to tell you. The wizard mirrors the gateway's own ceiling
calculation and names any binding sitting above what the current identity
posture can produce.
Validation. Errors block publishing; warnings do not, but you have to
acknowledge them. It also scans for literal credentials and tells you to
reference them with ${cred://<plugin-id>/<target>} from a credential plugin
you own, so the value is fetched at runtime instead of living in the file.
${env.NAME} is the other form and it works on a gateway you run yourself —
but not on one published to mcpg.cloud, where it would expand against the
platform's pod environment rather than yours. The review step refuses it for
that reason, so the scanner leads with the form that works in both places. On
mcpg.cloud the counterpart is ${secret.NAME}: a value you
register once for the gateway name, which the platform
delivers to that gateway alone. Whether a key is registered is a fact only the
platform holds, so the review step does not judge ${secret.…} — publishing
does, and mcpg cloud publish --dry-run does earlier.
These checks are a fast pre-flight, not the gateway's own validator.
mcpg config check is the authoritative one — it resolves plugins and validates
every backend spec against the plugin that owns it, which a browser cannot do.
The service itself can check nothing, because it cannot read your config.
Publishing: a URL and a secret
Publishing generates a 256-bit secret in the page, derives an AES-256-GCM key from it with HKDF-SHA256, seals the config, and uploads the ciphertext. The service receives the salt and a SHA-256 digest of two derived tokens. It never receives the secret or the key.
You get back three things:
| What it is | |
|---|---|
| Config URL | https://mcpg.cloud/configs/c/<id>. Safe to store anywhere. On its own it decrypts nothing. |
| Secret | mcpg_sk_ followed by 52 characters. Treat it like a private key. |
| Link with the secret | The URL with #k=<secret> appended. One string that opens and decrypts in a browser — and full access for anyone who receives it. |
The secret is shown once, and that is not a policy choice. It was generated in your browser and never left it, so there is nothing on our side to show you a second time. Lose it and the config is gone: no reset, no recovery, no support path, because there is nothing to recover it from. Save it to a password manager or a secret store before you leave the page.
If the secret leaks, treat the config as read. Re-encrypting under a new secret stops the old one working from that version onward, but a blob somebody already fetched stays decryptable under the secret they fetched it with. Publish a new config and delete the old one.
The receipt also offers the plaintext config and the secret as file downloads — the path you want when the config is going to the managed cloud or into a Kubernetes Secret.
Opening it again
Go to the config URL. The page loads metadata only; the ciphertext is not touched until you submit a secret, which is what stops a link scanner or a chat unfurl from consuming a one-time config.
The URL alone is not enough. The address names the record; the secret
decrypts it, in your browser, after the ciphertext arrives. If you opened a link
that carried #k=, the secret is stripped from the address bar once it has done
its job, so a screen share or a glance over your shoulder does not hand it over.
Once unlocked you get the same editors as the wizard, plus what the service knows about the record: version, size, read count, created, updated, expires. Re-publishing keeps the same URL and the same secret and bumps the version.
Encryption hides the config, not its shadow. The service can see how large it
is, when it changed, and how often it was fetched — never a hostname, a tool
name, a plugin, or a credential inside it. Its real name travels inside the
ciphertext too, because acme-prod-stripe-gateway gives away plenty on its own.
The controls at publish time
| Control | What it does |
|---|---|
| Name | A label for you, sealed inside the ciphertext. A checkbox also stores it in a column the service can read — off by default, because that makes the config easier for you to recognise and easier for anyone with the database to profile. |
| Lifetime | One hour, one day, seven days, or thirty days; seven is the default. At expiry the ciphertext is deleted. You can move the deadline later, up to an absolute 180-day cap measured from when the config was created. |
| One-time read | The ciphertext is destroyed once a reader confirms a successful decrypt. For handing a config to one person and nobody else — not for a config a gateway boots from repeatedly. |
The expiry is inside the authentication tag, so the service refuses an out-of-range value rather than adjusting one. A clamped expiry would no longer be the value the blob was sealed under, and every later read would fail authentication and present as "wrong secret". A refusal names the bound.
An expired address is kept as a metadata-only tombstone for a while, so a reader is told the config expired instead of being told the link was wrong. A one-time config that is fetched but never confirmed has a backstop, so it does not stay readable forever if a client walks away mid-decrypt.
Handing it to the managed cloud
Publish to mcpg.cloud on the receipt — and on any config you re-open later that is not one-time — opens the control-plane console with this config already loaded, on the gateway-create form where you pick the region, the size and the name.
One-time configs are the exception, and the reason is the hand-off itself: the console fetches the address, and opening a one-time config here has already spent its single read. The editor says so in place of the button. Publish without the one-time flag when the address is going to the cloud.
The config itself does not travel. The link carries the address, and the secret rides in the URL fragment, which browsers never send to a server: the control plane's servers, proxies and access logs never see the key. The console fetches the ciphertext and decrypts it in your browser, exactly as this site's own opener does, and only the resulting YAML is posted to the API. That also means anyone you forward the link to can read the config, for the same reason the link with the secret is dangerous anywhere else.
It sends what is stored, not what is on screen. Re-publish an edit before handing it over, or the gateway comes up on the previous version.
Or download the config from the receipt and publish it from a terminal:
mcpg cloud publish edge --config gateway.yaml --region eu --size s
Either way it is the ordinary publish flow — the wizard replaces the authoring step, not the platform.
A cloud-published config must carry auth. A managed gateway is exposed on the public edge, so the control plane refuses a config with no token verifier rather than standing up an endpoint that accepts a self-asserted identity. It wants one of these:
governance.access.oidc_oauth— the OIDC / OAuth option on the wizard's identity step.governance.access.jwks— the Static JWKS option.cloud.allow_anonymous: true— the deliberate opt-out, for a gateway you mean to be public.
Nothing else counts. Anonymous establishes nothing, and header-asserted
takes a proxy's word for who the caller is — precisely the self-asserted
identity the guard exists to keep off a public endpoint. An identity plugin
does not satisfy it either, even though the wizard's ceiling panel will read
verified for one: the guard looks under governance.access, and a plugin is
not there.
You do not have to discover this at the control plane. The review step runs the
same checks the control plane runs — the auth requirement, plus the constructs a
tenant config may not carry on a shared platform (${env.X}, env://,
file://, an opt-in to private backends, a federation upstream pointing at a
private address) — and names what is missing while the config is still open in
front of you. The hand-off button is replaced by that list until it is clear.
The control plane remains the authority; this is only the same answer, delivered
somewhere you can act on it. The one gate it cannot mirror is whether every
${secret.KEY} names a key registered for the gateway: only
the platform holds that list, so an unregistered key is refused at publish (or
by a dry run), not here.
Or point a self-hosted gateway straight at it
A gateway can boot from the address itself, fetching and decrypting on its own:
export MCPG_CONFIG_SECRET_FILE=/run/secrets/mcpg-config-secret
mcpg --config mcpg+enc:https://mcpg.cloud/configs/c/<id>
A file, not an argument: an argument is visible in ps to every user on the
host and lands in your shell history. In Kubernetes a Secret mounts as exactly
this file. Append /v/<n> to the address to pin a version you have already
seen — every version is authentic for its own number, so an older one verifies
perfectly and nothing in the cipher can flag a rollback.
Agents can drive all of it
https://config.mcpg.cloud/mcp is an MCP endpoint — an address of its own,
not part of the app under /configs — and it is worth saying
plainly what it is: an MCPG gateway, running the same software this platform
sells, fronting the config service's own API as six tools —
config_cloud.publish, describe, fetch_ciphertext, update, delete, and
burn. An agent that can hold a key can build, publish, read, revise and burn
configs without a browser.
Callers present an API key as Authorization: Bearer or X-Api-Key. Two tiers
exist, and the difference is visible in tools/list rather than in an error: a
read-only key sees three tools, a read/write key sees six, and no key sees none.
An agent holding a read key is never tempted to attempt a write it cannot make.
Two properties are worth knowing before you point an agent at it:
- The gateway holds nothing that could be stolen from it. Every write is authorised by a value the caller supplies as a tool argument, derived from the publishing secret. The API keys authorise reaching the gateway; they decrypt nothing.
- The agent does the crypto. It mints the address, chooses the expiry, and
seals the payload locally before calling
publish— the same construction the browser uses, for the same reason. There is also no list or search tool, by design: an address is 128 random bits and the service publishes no index, and that is what lets every read route be unauthenticated.
Where to go next
- Publish a config — turn the config into a running managed gateway.
- Secrets — the
${secret.NAME}values a managed gateway reads, and how to rotate them. - Versions and rollback — history and revert, once it is published.
- What is mcpg.cloud? — the managed service around it.
- Configure the gateway — the reference behind every field the wizard writes.
- Config templates — what a template is on disk, and how to run one on a gateway you host yourself.