# How antinomyhq/forgecode Implements the Model Context Protocol (MCP): Architecture and Limitations

> Discover how antinomyhq/forgecode implements the Model Context Protocol (MCP) as a dynamic tool provider. Explore its architecture and understand its limitations regarding timeouts and streaming.

- Repository: [Forge Code/forgecode](https://github.com/antinomyhq/forgecode)
- Tags: architecture
- Published: 2026-04-08

---

**Forgecode implements MCP as a lazy-loading dynamic tool provider that bridges remote STDIO and HTTP/SSE servers into locally namespaced functions, though it imposes fixed timeouts, requires manual cache refreshing, and lacks support for streaming responses.**

Forgecode's approach to the Model Context Protocol (MCP) centers on treating external servers as dynamic tool sources that integrate seamlessly with its agentic workflow. The `antinomyhq/forgecode` implementation spans three architectural layers—Domain, Service, and Infrastructure—and emphasizes deterministic caching and transport abstraction to minimize reconnection overhead. This article examines how the codebase transforms MCP tools into executable functions and details the concrete limitations you will encounter when extending its capabilities.

## MCP Architecture Overview

The codebase organizes MCP functionality into three distinct layers that separate configuration from execution. The **Domain** layer in [`crates/forge_domain/src/mcp.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_domain/src/mcp.rs) defines the data structures for server configurations and OAuth settings. The **Service** layer in [`crates/forge_services/src/mcp/service.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_services/src/mcp/service.rs) orchestrates lazy initialization, connection management, and tool registration. Finally, the **Infrastructure** layer in [`crates/forge_infra/src/mcp_client.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_infra/src/mcp_client.rs) handles low-level transport concerns including STDIO child processes, HTTP/SSE streams, and OAuth token negotiation.

## Domain Layer: Configuration and Cache Keys

Located in [`crates/forge_domain/src/mcp.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_domain/src/mcp.rs), the domain layer provides the type system that governs how MCP servers are declared and indexed.

### Server Configuration Types

The system supports two primary transport mechanisms through the `McpServerConfig` enum (lines 25‑55):

```rust
pub enum McpServerConfig {
    Stdio(McpStdioServer), // exec a local binary  (lines 25‑38)
    Http(McpHttpServer),   // HTTP / SSE endpoint (lines 41‑55)
}

```

*STDIO* servers require a command, arguments, and optional environment variables, while *HTTP* servers specify a URL, headers, request timeout, and OAuth settings. This distinction determines which transport logic the infrastructure layer invokes during connection establishment.

### OAuth Flexibility

The `McpOAuthSetting` enum (lines 58‑73) supports three authentication strategies:

```rust
#[derive(Default)]
pub enum McpOAuthSetting {
    #[default] AutoDetect,          // try standard HTTP first, then OAuth if 401
    Disabled,                       // never use OAuth
    Configured(McpOAuthConfig),    // explicit client‑id/secret, scopes, etc.
}

```

Deserialization is tolerant: the value `false` maps to `Disabled`, `true` or `null` to `AutoDetect`, and a full configuration object to `Configured`. This allows configuration files to toggle OAuth without restructuring the entire server definition.

### Deterministic Cache Keys

To avoid unnecessary reconnections, `McpConfig::cache_key()` (lines 92‑103) computes a hash of the entire `BTreeMap` of servers. Because `BTreeMap` guarantees stable ordering, the hash changes **if and only if** the logical configuration changes—not merely the file modification time. This key drives the lazy-reload logic in the service layer.

## Service Layer: Lazy Initialization and Tool Registration

The `ForgeMcpService` in [`crates/forge_services/src/mcp/service.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_services/src/mcp/service.rs) acts as the orchestrator, ensuring that MCP connections are established only when needed and cached aggressively.

### Lazy Initialization with Hash-Based Caching

The `init_mcp` method (lines 49‑51) checks whether the configuration has changed since the last invocation:

```rust
async fn init_mcp(&self) -> anyhow::Result<()> {
    let mcp = self.manager.read_mcp_config(None).await?;
    if !self.is_config_modified(&mcp).await { return Ok(()); }
    self.update_mcp(mcp).await
}

```

If the cache key matches the stored hash, **no reconnection occurs** (lines 100‑104), allowing the system to skip expensive process spawns or HTTP handshakes during repeated tool calls.

### Tool Discovery and Namespacing

When configuration changes are detected, `update_mcp` iterates over enabled servers and spawns a future per server via the `connect` method (lines 17‑31). Upon successful connection, the system registers every remote tool with a generated name to prevent collisions:

```rust
let tools = client.list().await?;
for mut tool in tools {
    let generated_name = ToolName::new(
        format!("mcp_{server_name}_tool_{}", tool.name.into_sanitized())
    );
    tool.name = generated_name.clone();
    tool_map.insert(generated_name, ToolHolder { … });
}

```

This namespacing strategy (lines 62‑68) guarantees uniqueness by prefixing each tool with `mcp_<server>_tool_<sanitized_original_name>`, though this mangling is irreversible and replaces the original identifier.

### Tool Call Forwarding

When the core engine invokes a tool, `ForgeMcpService::call` first ensures the MCP setup is current by calling `self.init_mcp().await?`, then looks up the generated tool name and forwards the request to the stored `McpExecutor` (lines 73‑80). Errors from the remote server are captured and returned as structured `ToolOutput` instances.

## Infrastructure Layer: Transport and Connection

`ForgeMcpClient` in [`crates/forge_infra/src/mcp_client.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_infra/src/mcp_client.rs) implements the actual network and process management, handling template resolution and protocol selection.

### STDIO Transport

For STDIO servers, the client spawns a child process using `TokioChildProcess::builder` and immediately drains `stderr` to avoid deadlocks (lines 18‑33). This ensures that MCP servers writing diagnostic logs to standard error do not block when their buffers fill.

### HTTP/SSE and OAuth Handling

HTTP connections follow a fallback pattern. The client first attempts `create_standard_http_connection` (lines 78‑89). If this fails with an authentication error (401/unauthorized) and the server configuration uses `AutoDetect` mode, the client automatically retries with `create_oauth_connection` (lines 46‑71).

When OAuth is explicitly `Disabled`, the client never attempts OAuth (lines 36‑39). When an explicit `McpOAuthConfig` is supplied, the client connects directly using stored credentials without interactive prompts (lines 40‑44). Notably, the client **never** launches an interactive browser flow itself; authentication must be performed out-of-band using `forge mcp login`.

### Template Resolution

Before connecting, `get_resolved_config` (lines 57‑66) expands Mustache placeholders in HTTP headers and URLs using environment variables. This occurs once at client creation, meaning changes to environment variables after initialization do not affect active connections.

## Key Limitations of the MCP Implementation

While the architecture provides clean separation of concerns, several constraints affect production deployments and developer experience.

### Fixed 5-Minute Timeout

All HTTP and SSE connections use a **hard-coded 5-minute timeout** (`tokio::time::Duration::from_secs(300)`) when accepting TCP listeners (lines 408‑410, 640‑642). This value is not exposed in `McpHttpServer` configuration, preventing users from tuning timeouts for long-running operations or high-latency networks.

### Out-of-Band OAuth Authentication

The MCP client never initiates an interactive OAuth flow during normal operation. Users must run `forge mcp login` separately to acquire and store tokens before the service can authenticate. This decouples authentication from the tool invocation path, which can confuse end-users expecting automatic browser prompts when a 401 occurs.

### No Automatic Reconnection

Once a server fails during `update_mcp`, it is recorded in `failed_servers` (lines 33‑46) and excluded from subsequent tool calls. The service never retries the connection until `refresh_cache` is invoked or the process restarts, meaning transient network failures require manual intervention to resolve.

### Limited Transport Support

The transport layer is strictly limited to STDIO and HTTP/SSE protocols (matching on `McpServerConfig::Stdio` or `Http`). Custom transports such as WebSocket or gRPC are unsupported without modifying [`crates/forge_infra/src/mcp_client.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_infra/src/mcp_client.rs).

### Irreversible Tool-Name Mangling

The generated tool name pattern (`mcp_<server>_tool_<sanitized>`) replaces the original tool identifier during registration (lines 62‑68). The original name is not preserved, which may break tools that expect specific identifiers in their metadata or logging contexts.

### No Streaming Support

The `McpExecutor` forwards tool calls and waits for a single `ToolOutput` response. MCP servers that stream incremental results cannot be leveraged, as the client aggregates all content before returning.

### Manual Cache Invalidation

The only mechanism to detect changes to an MCP server’s tool set is the `refresh_cache` method (lines 84‑92), which must be invoked explicitly (e.g., via `mcp reload`). There is no filesystem watcher or automatic polling for configuration updates.

### Limited Error Propagation

Connection errors are stored as formatted strings in `failed_servers` for UI display (lines 37‑44). Higher-level layers cannot programmatically distinguish between connection refused, authentication failure, or timeout errors without parsing these strings.

### No Per-Tool Configuration

All tools from a single server share the same connection pool, timeout, and headers. Individual tools cannot request custom timeouts or additional headers, as the `McpExecutor` receives the entire client configuration rather than per-tool overrides.

### Static Environment Resolution

Environment variable expansion via `get_resolved_config` occurs only once during client creation. If environment variables change at runtime (e.g., rotating API keys), the client does not re-render the URL or headers until manually reinitialized.

## Practical Usage Example

Calling an MCP tool through Forgecode requires using the mangled tool name constructed by the service layer:

```rust
// Assuming an MCP config file defines a server named "mytools"
let call = ToolCallFull {
    name: ToolName::new("mcp_mytools_tool_generate_image".into()),
    arguments: json!({ "prompt": "sunrise over mountains" }),
};

let output = forge_mcp_service.call(call).await?;
println!("Image URL: {}", output.result);

```

**Execution flow**:
1. `call` triggers `ForgeMcpService::call`, which invokes `init_mcp` to verify the cache key.
2. If the config changed, `update_mcp` establishes a connection via `ForgeMcpClient::connect`, selecting STDIO or HTTP transport.
3. For HTTP servers, the client tries standard headers first, falling back to OAuth if it receives a 401 and `AutoDetect` is enabled.
4. The tool list is fetched, namespaced, and the call is forwarded to the remote server via the cached `McpExecutor`.

## Summary

- **Lazy initialization**: Forgecode uses deterministic hashing to avoid reconnecting to MCP servers unless configuration changes occur.
- **Namespace isolation**: Tools are prefixed with `mcp_<server>_tool_` to prevent naming collisions across different servers.
- **Transport flexibility**: Supports both STDIO child processes and HTTP/SSE endpoints with optional OAuth fallback.
- **Operational constraints**: Fixed 5-minute timeouts, manual cache invalidation via `refresh_cache`, and no support for streaming responses limit high-availability scenarios.
- **Authentication model**: OAuth requires pre-authentication via CLI commands; the client does not handle interactive flows during tool execution.

## Frequently Asked Questions

### Does antinomyhq/forgecode support WebSocket or gRPC MCP servers?

No. As implemented in [`crates/forge_infra/src/mcp_client.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_infra/src/mcp_client.rs), the transport layer strictly handles STDIO child processes and HTTP/SSE connections. The `McpServerConfig` enum only contains variants for `Stdio` and `Http`, meaning WebSocket or gRPC transports require modifying the infrastructure layer source code.

### How does forgecode handle OAuth authentication for MCP servers?

Forgecode supports three OAuth modes—`AutoDetect`, `Disabled`, and `Configured`—defined in [`crates/forge_domain/src/mcp.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_domain/src/mcp.rs). However, the client in [`crates/forge_infra/src/mcp_client.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_infra/src/mcp_client.rs) never initiates an interactive browser flow. Users must run `forge mcp login` to acquire tokens, which the client then reads from the environment store. When `AutoDetect` is enabled, the client retries with OAuth only after receiving a 401 from the initial standard HTTP attempt.

### Can I configure different timeouts for individual MCP tools?

No. The timeout is hard-coded to 5 minutes (300 seconds) in [`crates/forge_infra/src/mcp_client.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_infra/src/mcp_client.rs) (lines 408‑410) and applies to the entire HTTP transport layer. There is no per-tool or per-server timeout override in the current `McpHttpServer` configuration structure.

### What happens if an MCP server crashes during a session?

If a server fails during the initial `update_mcp` call, it is added to the `failed_servers` list in [`crates/forge_services/src/mcp/service.rs`](https://github.com/antinomyhq/forgecode/blob/main/crates/forge_services/src/mcp/service.rs) (lines 33‑46) and excluded from subsequent tool lookups. The service does not automatically retry failed connections. To recover, you must invoke `refresh_cache` (lines 84‑92) or restart the Forgecode process to trigger a fresh discovery cycle.