Skip to content

feat: add MCP server support for Electric Agents#4165

Draft
balegas wants to merge 20 commits intomainfrom
feat/mcp-server-support
Draft

feat: add MCP server support for Electric Agents#4165
balegas wants to merge 20 commits intomainfrom
feat/mcp-server-support

Conversation

@balegas
Copy link
Copy Markdown
Contributor

@balegas balegas commented Apr 24, 2026

Summary

  • Adds new @electric-ax/agents-mcp package that bridges the official MCP TypeScript SDK into the existing AgentTool interface
  • Supports stdio and Streamable HTTP transports with OAuth 2.1 + PKCE auth flow
  • Connection pooling with lazy connect and idle timeout
  • Tool namespacing (mcp__server__tool) following Claude Code conventions
  • MCP resources exposed as agent tools (mcp__list_resources, mcp__read_resource)
  • Conversational config management via Horton tools (mcp__manage__add_server, etc.)
  • Config stored in .electric-agents/mcp.json with ${VAR} env var expansion
  • OAuth tokens persisted in .electric-agents/mcp-auth.json (gitignored, mode 0600)
  • Per-entity-instance overrides via spawn args
  • Server instructions injected into agent system prompt
  • Output size limiting (default 25K chars) with truncation
  • 44 tests across 8 test files

Architecture

New package @electric-ax/agents-mcp plugs into agents-runtime via the existing AgentTool interface. The runtime stays MCP-agnostic; MCP is opt-in. The agents package wires MCP into Horton at bootstrap time.

McpClientPool → McpClient → @modelcontextprotocol/sdk
     ↓                ↓
 bridgeMcpTools    OAuthClientProvider
     ↓                ↓
 AgentTool[]      TokenStore (.electric-agents/mcp-auth.json)

Design docs

  • Spec: docs/superpowers/specs/2026-04-24-mcp-server-support-design.md
  • Plan: docs/superpowers/plans/2026-04-24-mcp-server-support.md

Test plan

  • Unit tests for env var expansion (9 tests)
  • Unit tests for config store (6 tests)
  • Unit tests for token store (6 tests)
  • Unit tests for tool bridge with namespacing + truncation (5 tests)
  • Unit tests for resource bridge (3 tests)
  • Unit tests for connection pool with mocked client (7 tests)
  • Unit tests for config management tools (4 tests)
  • Integration test for full flow with mocked MCP client (4 tests)
  • Manual test with a real MCP server (e.g., @modelcontextprotocol/server-github)
  • Manual test with OAuth flow against a remote MCP server

🤖 Generated with Claude Code

balegas and others added 20 commits April 24, 2026 08:58
Design for @electric-ax/agents-mcp package — MCP client integration
for Electric Agents entities. Covers stdio + Streamable HTTP transports,
OAuth 2.1 auth flow, connection pooling, tool/resource bridging, and
Horton-based config management.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Design for running coding agents (Claude Code, Codex) inside isolated
sandbox environments with full session streaming via durable streams
and agent-session-protocol parsing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds env var expansion, arbitrary headers, startup/tool timeouts,
output size limits, mcp__ tool namespacing, SDK OAuthClientProvider
integration, server instructions, exponential backoff reconnection,
graceful stdio shutdown, and enabled toggle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
15-task plan covering package scaffolding, types, config/token stores,
env var expansion, OAuth provider, MCP client/pool, tool/resource bridges,
config management tools, integration factory, and Horton wiring.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Introduces McpServerConfig, McpConfig, McpOverrides, McpIntegration,
McpServerState, McpDiscoveredTool, McpDiscoveredResource, McpServerStatus,
and MCP_DEFAULTS constants. Re-exports all from src/index.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds expandEnvVars (supports \${VAR} and \${VAR:-default} syntax) and
expandConfigValues (recursively expands strings in any config object),
with full test coverage via vitest.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…s/mcp.json

Supports loading with optional env-var expansion, saving with auto-created
.gitignore, and addServer/removeServer helpers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…fiers

Stores access/refresh tokens, PKCE code verifiers, and client info in
.electric-agents/mcp-auth.json (which is gitignored by ConfigStore).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements the MCP SDK's OAuthClientProvider interface with persistent
credential storage via TokenStore. Handles the authorization code flow
by spinning up a temporary local HTTP server for the OAuth redirect
callback, with a 5-minute timeout.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wraps the MCP SDK Client with transport creation (StdioClientTransport
for command-based servers, StreamableHTTPClientTransport for URL-based
servers), auth wiring (OAuth, Bearer token, custom headers), automatic
tool/resource discovery, and listChanged notification handling.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… interface

Bridges MCP discovered tools into the AgentTool interface with mcp__
prefix namespacing, output formatting, and truncation support.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ources

Creates AgentTool wrappers for mcp__list_resources and
mcp__read_resource that interact with connected MCP servers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Manages McpClient instances with lazy connect, idle timeout, and
acquire/release semantics. Bridges tools and resources via the bridge
modules and supports dynamic server add/remove and per-call overrides.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…server management

Implements mcp__manage__add_server, mcp__manage__remove_server,
mcp__manage__list_servers, and mcp__manage__list_tools as AgentTools,
with full test coverage.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Provides a unified entry point that wires ConfigStore, McpClientPool,
and config tools together, exposing getTools, getServerInstructions,
getServerSummary, and close via the McpIntegration interface.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds exports for createMcpIntegration, McpClient, TokenStore,
bridgeMcpTools, createResourceTools, createConfigTools, and
env-expand utilities so consumers have a single import surface.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add @electric-ax/agents-mcp workspace dependency
- Create MCP integration in bootstrap, pass to Horton
- Inject MCP config tools + bridged tools into Horton's tool set
- Add MCP server summary section to system prompt
- MCP is fully optional — existing code works without it

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…mocked McpClient

Covers tool bridging, config management tools, server summary generation,
and override-based server exclusion in an end-to-end flow.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…d server name through onAuthUrl

- Write auth token file with mode 0o600 instead of default 0644
- Remove unused zod dependency from package.json
- Change onAuthUrl signature to (serverName, url) throughout pool and integration

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@balegas
Copy link
Copy Markdown
Contributor Author

balegas commented Apr 24, 2026

Code Review Follow-up Items

The following issues were identified during the final code review. Items 1-3 are deferred to a follow-up PR; items 4-7 are minor and can be addressed as needed.

Should address (follow-up PR)

1. Exponential backoff reconnection
The pool's acquire() has no retry logic — a single connection failure marks the server as failed and throws. The spec calls for 5 attempts with 1s base delay and 2x growth.

  • File: packages/agents-mcp/src/pool.ts (connect method)
  • Risk: Transient network issues or slow-starting stdio processes will prevent server use until the next handler invocation.

2. stdio process crash detection
No onclose handler is registered on the SDK Client or transport. If a stdio child process crashes, the pool entry stays in connected state and subsequent tool calls fail silently instead of triggering reconnection.

  • File: packages/agents-mcp/src/client.ts
  • Fix: Register client.onclose to emit a status change that the pool can react to.

3. Truncation uses per-block limit instead of running budget
truncateOutput() checks total chars across blocks but applies maxChars independently per block. Multiple blocks each just under the limit won't trigger truncation even if the total exceeds it.

  • File: packages/agents-mcp/src/bridge/tool-bridge.ts
  • Fix: Implement truncation as a running char budget across blocks.

Minor / acceptable for v1

4. getTools() connects eagerly on every handler wake
Every Horton wake calls mcp.getTools() which iterates all servers and calls acquire(). Consider caching discovered tools and only refreshing on listChanged notifications.

5. No config validation on load
ConfigStore.load() casts JSON.parse output directly. Malformed config files produce confusing runtime errors. Basic structural validation would help.

6. OAuth redirect port mismatch when callbackPort: 0
The redirectUrl getter returns a hardcoded port (9876) before the callback server starts, but the actual listening port may differ. This could cause issues with strict redirect URI validation during DCR.

7. console.log in OAuth provider fallback
Uses console.log instead of the project's pino logger when no onAuthUrl callback is provided.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant