Connecting External MCP Servers to WorkingAgents

WorkingAgents needs to proxy tools from external MCP servers – Stripe, GitHub, a customer’s internal API, a specialist AI model – and make them available to agents through the same permission and audit layer as built-in tools. This document describes how that works.


The Architecture

External MCP server connections are stored in SQLite, managed through the same MCP tools / REST API as everything else, and each connection is a first-class entity in the permission system.

Data Model

CREATE TABLE mcp_connections (
  id           INTEGER PRIMARY KEY,  -- ms timestamp
  updated_at   INTEGER NOT NULL,
  name         TEXT NOT NULL UNIQUE,
  transport    TEXT NOT NULL,         -- "http" | "stdio" | "sse" | "websocket"
  url          TEXT,                  -- for http/sse/websocket transports
  command      TEXT,                  -- for stdio transport
  args_json    TEXT,                  -- JSON array of command args
  env_json     TEXT,                  -- JSON map of env vars
  auth_json    TEXT,                  -- encrypted: type, token, etc.
  permission_key INTEGER NOT NULL,    -- the key required to use this server's tools
  status       TEXT DEFAULT 'active', -- active | disabled
  last_seen_at INTEGER,               -- when connection was last healthy
  tool_count   INTEGER DEFAULT 0      -- cached from last discovery
);

Each connection row represents one external MCP server. The permission_key is a runtime-assignable integer that gates access – exactly like every other module in the system.

Permission Model

Each external server gets a dedicated permission key assigned at connection time. The convention is to use integers in the 200_001+ range for external connections. The admin grants this key to specific users or roles:

mcp_connection_add name=stripe url=https://mcp.stripe.com auth_token=sk_live_... permission_key=200001 transport=http
access_control_grant user=alice permissions=[200001]
access_control_grant role=finance permissions=[200001]

Alice and all finance-role users can now call Stripe tools. Bob cannot. If Stripe’s server misbehaves, mcp_connection_disable name=stripe removes all access immediately for all users – no config files, no restart.


How MCPConnectionRegistry Works

MCPConnectionRegistry is a GenServer that owns the mcp_connections SQLite table. It is the sole process that reads and writes that database.

On startup: Loads all status = 'active' rows, starts a Hermes.Client.Supervisor for each under a DynamicSupervisor, discovers the tool list from each server, and caches it in memory.

On tool list: When a user requests tools/list, MyMCPServer calls MCPConnectionRegistry.tools_for_permissions(permissions). The registry returns only tools whose connection’s permission_key is present in the user’s permission map. A user without the Stripe permission does not see Stripe tools.

On tool call: When an agent calls a tool name that isn’t a built-in, MyMCPServer falls through to MCPConnectionRegistry.call_tool(name, args, permissions). The registry finds which connection owns that tool name, checks the permission, and proxies the call through the live hermes_mcp client.

On add/disable/remove: The registry updates the database and immediately starts or stops the corresponding hermes_mcp client. No restart required.


Adding a New External Connection

HTTP server with bearer auth

mcp_connection_add name=stripe transport=http url=https://mcp.stripe.com auth_token=sk_live_... permission_key=200001

The connection is live immediately. Tools are discovered and cached. Grant the permission key to relevant users.

stdio server

mcp_connection_add name=github transport=stdio command=npx args=[-y, @modelcontextprotocol/server-github] env={GITHUB_PERSONAL_ACCESS_TOKEN: ghp_...} permission_key=200002

WorkingAgents spawns the process, connects via stdin/stdout, and discovers the tool list.


Audit Trail

Every proxied tool call goes through the same telemetry path as built-in tools. The audit log records: user, tool name, external server name, timestamp, result status. An admin can query which users called Stripe tools in the last 24 hours and what the result was.


Available MCP Tools

Tool Description
mcp_connection_add Register a new external server
mcp_connection_list List registered servers with status
mcp_connection_enable Re-enable a disabled connection
mcp_connection_disable Disable a connection (tools removed immediately)
mcp_connection_remove Delete a connection permanently

Implementation

Three modules implement this:

MCPConnection – pure functional module. Owns the mcp_connections table schema, CRUD operations, and AES-256-CTR encryption/decryption of the auth_json credential field.

MCPConnectionRegistry – GenServer. Sole owner of the database. On startup connects to all active servers. Manages a DynamicSupervisor that runs one Hermes.Client.Supervisor per active connection. Exposes tools_for_permissions/1 and call_tool/3 to the MCP server layer.

Permissions.MCPConnection – AccessControlled wrapper. Gates admin operations (add, list, enable, disable, remove) behind permission key 140_001. Individual connections each have their own permission key for end-user access.

The client library is hermes_mcp, already in the dependency tree. The transport and client process for each connection are started dynamically via Hermes.Client.Supervisor, registered in a dedicated Elixir Registry with via-tuple names so they can be looked up and terminated by connection name.


Behavior at Runtime

Action Effect
mcp_connection_add Row inserted, client started, tools cached
mcp_connection_disable Row updated, client stopped, tools removed from all lists
mcp_connection_enable Row updated, client restarted, tools re-cached
mcp_connection_remove Row deleted, client stopped
User permission revoked User no longer sees or can call those tools (next request)
External server down Client retries, tools remain cached, calls fail gracefully