Connections
A connection plugs a remote MCP server’s tools into your agent the same way everything else plugs in: one data-only file, identity = filename.
# app/agent/connections/crm.yml
url: https://mcp.example-crm.com/mcp
auth:
type: bearer # or `header` with a `header:` name, or omit
credential: crm.mcp_token # a PATH into Rails credentials — never the secret
approval: once # never (default) | once | always
effect: at_most_once # at_most_once (default) | idempotent
Restart, and the server’s tools appear to the model namespaced
crm__search, crm__create_note, … — alongside your local tools, running
through the same Ledger.
The rules the file encodes
- Credentials are paths, not secrets.
credential: crm.mcp_tokenresolvesRails.application.credentials.dig(:crm, :mcp_token)at call time. The YAML is committable; a missing credential fails loudly with the path named. - Approval and effect mode are per-connection, enforced by the same Ledger
as local tools.
approval: alwaysparks every remote call for a human;onceapproves an identical (tool, arguments) pair once per session. transactional!does not exist here, by design. A remote call can’t join your database transaction, so the honest ceiling isat_most_once(an ambiguous crash parks in-doubt for a person) oridempotent(you’re asserting the remote op is safe to repeat).- Transport is HTTP (v1). Filename is the namespace:
crm.yml→crm__*.
Boot-time discovery, fail-loud
Each connection’s tool list is fetched once at boot and cached
(tools/list). A misconfigured or unreachable connection raises at boot,
never inside a turn — the same posture as the Registry’s tool validation. The
remote tool names and schemas are model-visible state, so they’re part of the
definitions digest: if the remote server changes its toolset, parked turns
from before the change fail loudly on resume (NondeterminismError) instead
of resuming against a different toolset. Settle parked turns before pointing a
connection at a changed server.
Testing
config.mcp_client_factory = ->(connection) { fake_client } injects a fake
client per connection — the seam the gem’s own specs use.
The other direction
Silas can also serve your agent’s tools as an MCP server (Silas::Mcp::Server,
bound to config.mcp_server_host) — the “mount your tools as MCP” seam, so
other MCP clients can call the tools you wrote for your agent.