Skip to content

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:

  1. 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.
  2. 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:

  1. Decrypts the configured env vars from connectors.config_encrypted.
  2. Calls your command with your args, stdin/stdout piped, env injected.
  3. Sends initialize then tools/list and caches the manifest.
  4. 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_facts from 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 command values 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