Bring your own MCP server¶
Gaby's connectors are all Model Context Protocol servers spawned as stdio subprocesses. The five we ship (Postgres, Keycloak, Redis, Sentry, Stripe) are convenience, not the whole story. Any MCP server that speaks the standard handshake works.
Two ways to plug one in:
- Pick one from the curated list — community MCPs we know work end-to-end with Gaby's host. The Connectors UI pre-fills the command and arguments; you just paste the secrets.
Custom MCP— the escape hatch. You provide command, args, and env. Gaby spawns it, runs the handshake, and exposes its tools to the investigation loop.
How the spawn works¶
When Gaby starts a connector, it:
- Decrypts the configured env vars from
connectors.config_encrypted. - Calls your command with your args, stdin/stdout piped, env injected.
- Sends
initializethentools/listand caches the manifest. - Hands those tools to the planner.
The restart supervisor in mcp_host.py will restart your subprocess up to
5 times in a 5-minute window. After that the connector goes DEGRADED and
the dashboard surfaces the failure.
Adding a custom MCP from the UI¶
Settings → Connectors → Add → Custom MCP.
| Field | What goes in it |
|---|---|
Name |
A label you'll recognize. Free text. |
Command |
The binary on the backend's PATH (e.g. npx, uvx, node, python, a path). |
Args |
JSON list of arguments (e.g. ["-y", "@scope/server"]). |
Env |
Key/value pairs the subprocess gets in its environment. Secrets go here. |
Everything in Env is encrypted at rest with envelope encryption (same key
the rest of connectors.config_encrypted uses). The plaintext only lives in
the subprocess's environment, never on disk.
Scopes still apply¶
Even with a fully custom MCP, the safety pipeline runs on every tool call:
- The planner sees
applicable_factsfrom the memory graph, not raw arg values. - PII redaction runs on tool outputs before they reach the next LLM call.
- Money-touching tool patterns (
refund_*,charge_*,pay_*) are hard-excluded from the agent's tool surface regardless of what your MCP advertises. - The approval queue gates anything outside the connector's
default_scopes.
You can tighten further by editing the row's scopes JSON after creation
({"read": {"allow": ["fetch_*"]}, "write": {"allow": []}}).
Foot-gun warning¶
command is executed as the backend user. A malicious or buggy MCP server
can do anything that user can — read files, hit the network, eat memory.
This is intentional (Gaby is self-hosted; you trust the binaries you run).
But:
- Don't paste random
commandvalues from the internet without reading the source. - For untrusted connectors, run Gaby in a container with a restricted user + read-only filesystem (see Kubernetes deployment).
- Custom MCPs that need network egress should be reviewed the same way you'd review any other backend dependency.
Writing your own MCP¶
A reference implementation lives at connectors/example-mcp/. Two tools
(echo, list_files), a healthcheck, scope markers, ~150 lines of Python.
Copy it as a starting point.
The protocol itself is documented at https://modelcontextprotocol.io/specification.
Troubleshooting¶
| Symptom | Likely cause | Fix |
|---|---|---|
DEGRADED after 5 restarts |
Subprocess crashing on startup | Run the command manually with the same env; read stderr |
mcp_custom: __command is required |
Empty Command field | Fill it in |
mcp_custom: __args must be a JSON list |
Args not valid JSON |
Use a JSON array like ["-y", "pkg"] |
| Tools call but return errors | MCP-level success, content-level failure | Look at the connector's own logs; Gaby surfaces these as tool output |
Related¶
- Curated community MCPs
- One MCP vs many: the opinionated take on architecture.