Connecting Glion to your infrastructure
Why connecting is the hard part, how Glion knows which page mints each token, the methods ranked, and what happens to a credential.
This is the document to read if you are adding support for a new system, or if you want to know why connecting feels the way it does.
The four questions
Before an agent can do anything useful on infrastructure it did not build, four questions have to be answered. They are separate problems and conflating them is why this is usually harder than it should be.
| question | who answers it | status | |
|---|---|---|---|
| 1 | What is out there? | server/src/discovery/ |
done |
| 2 | May I touch it, and how do I get permission? | server/src/onboarding/ |
this document |
| 3 | How do I call it, safely and cheaply? | server/src/connectors/, server/src/mcp/ |
native shipped, MCP shipped |
| 4 | What does the model need to know? | server/technologies/<signature>/SKILL.md |
27 scaffolds |
Question 2 is the one that decides whether the product works. A scan of one real network found twenty-six services in under a minute. Connecting to them means twenty-six admin interfaces, each minting a differently-named credential in a different place — “API token”, “API key”, “access key”, “service account”. Nobody works through that list from memory.
MCP does not help here. An MCP server assumes you already hold the credential and have already configured it. That is the entire problem handed back to you.
What Glion does about it
Glion knows what a service is and where it lives, so it also knows which page mints its token. That turns “go and find your API settings” into a link into the operator’s own instance and a single field to paste into.
The knowledge lives in server/src/onboarding/recipes.ts, one recipe per
discovery signature:
{
signature: 'coolify',
service: 'Coolify',
options: [
{
method: 'native', driverKind: 'coolify', recommended: true,
label: 'Coolify API',
why: 'Glion speaks it directly. Nothing to install…',
credential: {
kind: 'bearer',
label: 'An API token',
where: 'Keys & Tokens → API tokens → Create New Token',
path: '/security/api-tokens',
scope: 'read-only is enough unless you want Glion to deploy or restart things',
looksLike: '1|abcdef… (a number, a pipe, then a long string)',
docs: 'https://coolify.io/docs/api-reference/authorization',
},
},
SSH_OPTION,
],
}
path is joined to the address discovery found, so the console renders a real
link. where has to stand on its own without it, because not every service has
a stable settings URL.
Three rules, and why
Never ask for a login password. Every recipe asks for a scoped, revocable token. A password is the operator’s identity; a token is a grant they can withdraw without changing anything else. This is enforced by a test, and it is not theoretical — a root password was once pasted into a session on this instance, which put it in the trace and sent it to a model vendor.
Two services genuinely have no API-credential concept (Nginx Proxy Manager is one). Their recipes say so explicitly and tell the operator to create a second, limited user rather than hand over their own login.
Say the least privilege that works. scope is not a disclaimer, it is an
instruction: “Give it PVEAuditor on / — that is read-everything,
change-nothing.” A sentence like that changes what people click.
Never promise a driver that does not exist. withDriverTruth() reconciles
every recipe against the drivers actually compiled into the build, in both
directions: a recipe cannot offer a native option that would fail, and a recipe
cannot go on saying “driver planned” after one ships. The code is the authority;
the prose is not.
Where Glion ships nothing, the recipe says so and offers the honest alternative. An explicit “not yet” costs less trust than a button that half-works.
Some things are deliberately not offered
Two recipes exist purely to refuse:
- Apache Guacamole stores working logins for every machine it can reach, and an administrator can read those passwords in clear text. The inventory of what it reaches is useful; the credentials behind it are not something an agent should hold.
- Docker Engine API on 2375 has no authentication at all. If a scan found it open, that is a finding to act on, not a connector to configure.
Both are surfaced with their reasoning, so they read as decisions rather than missing features.
Choosing a connection method
Ranked, best first. The console shows all of them with their trade-offs and lets the operator choose.
1. Native driver — the default
Glion speaks the service’s own REST API, in-process. See
server/src/connectors/native/.
- Nothing to install. No sidecar, no daemon, no second thing to keep running and updated.
- Classifications are declared, not guessed. The author of the call and the
author of the read/write/destructive label are the same person, in the same
diff.
classification_sourceisdeclared, which outranksannotationandheuristic; only an operator’s own label wins over it. The approval gate keys on a fact rather than an inference over a tool’s name. - Responses are projected.
GET /api/v1/applicationson Coolify returns about ninety fields, two of which arehttp_basic_auth_usernameandhttp_basic_auth_password. A driver names the fields it exposes — an allowlist, so a secret a future release adds is dropped by default rather than exposed by default. - Small surface. 13 curated tools at ~72 tokens each, against ~250 tokens per tool for a typical MCP server publishing everything it can do.
1b. Capability manifest — a native driver as data
For a service with an HTTP API and no driver in Glion. A manifest says what a compiled driver says — tools, labels, how each call is made, which fields each answer may pass — as JSON, and the host runs it: no host field, an allowlist at every level, no expression language. Import the service’s OpenAPI document on the Manifests screen, or paste a manifest; it is a draft that runs nothing until an admin signs it. A connector on a signed manifest is a native connector in every way that matters: its tools are granted, gated and approved like any other, and its probes notice when the service changes shape under it.
What it cannot do is compute: an “overview” that calls three endpoints and summarises them is a compiled driver.
2. MCP — the escape hatch
For anything Glion ships no driver for and where a server already exists.
Grafana is the good case: Grafana Labs maintain mcp-grafana with a
--disable-write flag enforced server-side.
The costs are real. It is one more container to deploy and update. Its
descriptions and schemas are third-party text spliced into a system prompt, so
normalize() treats them as untrusted and classify() has to infer safety from
a name. And it cannot redact a response.
3. SSH — the universal fallback
Reaches anything, including services with no API at all. It is never
recommended, because a shell has exactly one operation — “run this” — so the
policy engine cannot tell df -h from rm -rf. Every call is judged by a
human or not judged at all.
Adding a new service
- A discovery signature in
server/src/discovery/signatures.ts. It must have aconfirm()that sees the service name itself — a port number is never an answer. - A skill scaffold in
server/technologies/<signature>/SKILL.md, its header’ssignaturethe directory’s name. Tests require a## Read-only orientationsection, a destructive section, and a## Verifyingsection naming where that stack keeps its own machine-readable schema. - A connection recipe in
server/src/onboarding/recipes.ts. - Optionally a driver. Prefer a manifest in
server/technologies/<signature>/manifest.json: it is loaded at boot, linted bymanifest-lint.test.ts, and needs no code. Write a compiled driver inserver/src/connectors/native/only when a manifest’s closed forms cannot say what the tool does — implementNativeDriver, add it toDRIVERS, and write the projection allowlist first.
Steps 1–3 are enforced against each other by tests: every signature must have a scaffold, no scaffold may point at a signature that does not exist, and no recipe may promise a driver that is not compiled in.
Write the driver against a running instance, not from memory. Every skill
scaffold tells agents to do this and the same applies to us. It is how the
Coolify driver learned that restart, start and stop are POST: Coolify
answers a GET with 405 and the message “This endpoint has changed to a POST
request.”
What happens to the credential
- Posted to
/api/onboarding/connectover the console’s authenticated session. - Encrypted with
encrypt()before it touches a row; onlycredential_hint(a few masked characters) is ever readable. - Decrypted at the moment of a call, handed to the driver for that call, and never stored on the object it came from.
- Never logged, never returned to the console, never placed in a URL, and never included in an audit row — the audit records the service and the method only, so a credential cannot be reconstructed from the audit log.
Verification is part of the ceremony rather than a separate step. A saved
credential that has never been used is a claim; one that has just returned a
list of six applications is a fact. discoverConnector() installs the declared
tools and calls the service, and the operator sees what came back.