# How the OpenWork Gateway Runtime Manages MCP Connections: Architecture and Implementation

> Discover how the OpenWork gateway runtime dynamically manages MCP connections using configuration-driven endpoints, hierarchical merges, and JSON-RPC for efficient communication.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: architecture
- Published: 2026-08-16

---

**The OpenWork gateway runtime treats MCP (Model Context Protocol) connections as configuration-driven, dynamically-registered endpoints that are discovered through a hierarchical merge of project, global, and runtime configs, validated against tool-policy denies, and invoked via a minimal JSON-RPC transport layer.**

The `different-ai/openwork` repository implements a sophisticated gateway server under `apps/server` that orchestrates MCP lifecycles for cloud-provider integrations. This article examines how the runtime discovers, validates, and communicates with MCP endpoints using the source code from the `dev` branch.

## The Three-Stage Connection Lifecycle

The gateway manages MCP connections through a pipeline that separates configuration management from transport execution.

### Stage 1: Loading and Merging Configuration

The runtime constructs a unified view of available MCPs by reading three distinct configuration sources in [`apps/server/src/mcp.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/mcp.ts). First, it loads the **project-level** [`opencode.json`](https://github.com/different-ai/openwork/blob/main/opencode.json) from the workspace root. Second, it resolves the **global** OpenCode config via `resolveGlobalOpenCodeConfigPath()`, typically located at `~/.config/openwork`. Third, it retrieves **runtime-registered** MCPs from the workspace-specific runtime Opencode config (a JSONC file managed through `writeRuntimeOpencodeConfig`).

The merging follows a precedence hierarchy: **runtime entries override project settings, which override global defaults**. This allows dynamically added MCPs to supersede static configurations without modifying files on disk. The `listMcp` and `listMcpFromRuntimeSnapshot` functions orchestrate this merge, parsing all files with JSONC support to allow comments.

### Stage 2: Diagnosing Tool Policy Denies

Before marking an MCP as available, the runtime evaluates security policies through `diagnoseMcpToolDeniesFromConfigs`. This function aggregates deny rules from both legacy `tools` maps and modern `permission` objects found in project and global configurations.

The helper functions `collectToolsDenyArray` and `collectPermissionRulesetDenies` flatten these configurations into a `ToolPolicyMap`. Each MCP's tools are then checked against glob-style deny patterns using `minimatch`. **Project-level allow rules can override global denies**, but only when the pattern matches the exact tool ID. Denied MCPs surface in the `toolDenies` array, allowing the UI to disable or hide affected endpoints while keeping the configuration intact.

### Stage 3: Runtime JSON-RPC Interaction

When a client requests a resource, the gateway initiates transport in [`apps/server/src/connect-mcp-transport.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/connect-mcp-transport.ts). The `readMcpResourceText` function executes a strict handshake sequence:

1. **Initialize**: Sends the `initialize` request and records the `mcp-session-id` header
2. **Notify**: Transmits `notifications/initialized` as a silent confirmation
3. **Read**: Issues `resources/read` for the target URI

The `readMcpPayload` parser handles both standard JSON responses and `text/event-stream` payloads for streaming tool output. All transport calls respect the `enabled` flag and abort immediately if tool-policy denies are present, ensuring disabled MCPs never receive network traffic.

## Configuration Hierarchy and MCP Discovery

The discovery process begins with reading JSONC files using `readJsoncFile` with `allowInvalid: true` for resilience:

```typescript
const { data: config } = await readJsoncFile(opencodeConfigPath(workspaceRoot), {}, { allowInvalid: true });
const { data: globalConfig } = await readJsoncFile(resolveGlobalOpenCodeConfigPath(), {}, { allowInvalid: true });

```

The `getMcpConfig` utility extracts the top-level `mcp` object from each config, while `runtimeMcpMap` parses the dynamic runtime store. The final inventory construction iterates through global, project, and runtime maps in order, attaching any discovered denies:

```typescript
const toolDenies = diagnoseMcpToolDeniesFromConfigs({ projectConfig: config, globalConfig, name });
items.push({
  name,
  config: entry,
  source,
  disabledByTools: toolDenies.length > 0 || undefined,
  ...(toolDenies.length ? { toolDenies } : {}),
});

```

## Tool Policy Enforcement and Deny Logic

The permission system supports both legacy boolean tool maps and granular policy objects. The runtime creates a flattened view of all deny rules, then evaluates each tool identifier against glob patterns. This allows administrators to block broad categories (e.g., `filesystem/*`) while permitting specific exceptions at the project level.

When `diagnoseMcpToolDeniesFromConfigs` identifies conflicts, it populates the `toolDenies` metadata with the specific matched patterns. This diagnostic information propagates to the MCP Management UI in the desktop application, providing transparency about why certain cloud-provider tools are unavailable.

## Runtime Transport and Resource Reading

The transport layer implements the MCP specification through a minimal JSON-RPC client. The `mcpPost` function manages the underlying HTTP communication, while `readMcpResourceText` handles the stateful conversation flow.

To read a resource from an active MCP:

```typescript
import { readMcpResourceText } from "./connect-mcp-transport.ts";

const text = await readMcpResourceText({
  config: { url: "https://api.mycloud.example/v1", enabled: true },
  uri: "workspace:/myfile.txt",
  fetcher: fetch,
  clientName: "openwork-desktop",
});

if (text) {
  console.log("File contents:", text);
}

```

If the MCP is disabled or denied, the function returns `null` immediately without network overhead.

## Managing MCPs Programmatically

The server exposes lifecycle APIs in [`apps/server/src/mcp.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/mcp.ts) for dynamic management. To add a new cloud provider at runtime:

```typescript
import { addMcp } from "./mcp.ts";

await addMcp(serverConfig, workspaceId, "my-cloud-mcp", {
  url: "https://api.mycloud.example/v1",
  headers: { Authorization: "Bearer …" },
  enabled: true,
});

```

The `addMcp` function validates the name via `validateUserMcpName` and the schema via `validateMcpConfig` before persisting to the runtime config. Disabling an MCP prevents further transport calls:

```typescript
import { setMcpEnabled } from "./mcp.ts";

await setMcpEnabled(serverConfig, workspaceId, "my-cloud-mcp", false);

```

Listing all MCPs with their diagnostic status:

```typescript
import { listMcp } from "./mcp.ts";

const mcps = await listMcp(serverConfig, workspaceId, "/path/to/workspace");
console.log(mcps.map(m => ({
  name: m.name,
  source: m.source,
  disabled: !!m.disabledByTools,
  denies: m.toolDenies?.map(d => d.matched) ?? [],
})));

```

## Key Source Files and Architecture

The implementation spans several critical files in the `different-ai/openwork` repository:

- **[`apps/server/src/mcp.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/mcp.ts)**: Core logic for configuration merging, tool-policy diagnosis, and lifecycle management (`addMcp`, `removeMcp`, `setMcpEnabled`)
- **[`apps/server/src/connect-mcp-transport.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/connect-mcp-transport.ts)**: JSON-RPC transport implementation including `readMcpResourceText` and `readMcpPayload` for SSE handling
- **[`packages/types/src/den/runtime-opencode-config-store.ts`](https://github.com/different-ai/openwork/blob/main/packages/types/src/den/runtime-opencode-config-store.ts)**: Persistence layer for runtime MCP registrations
- **[`packages/types/src/den/mcp-connection-action.ts`](https://github.com/different-ai/openwork/blob/main/packages/types/src/den/mcp-connection-action.ts)**: TypeScript definitions for `McpItem`, `McpToolDeny`, and transport interfaces
- **[`apps/server/src/mcp.remote-connect.e2e.test.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/mcp.remote-connect.e2e.test.ts)**: End-to-end validation of the full MCP lifecycle

## Summary

- The OpenWork gateway runtime manages MCP connections through a **three-stage pipeline**: configuration merging, tool-policy validation, and JSON-RPC transport execution.
- Configurations merge in precedence order (runtime → project → global), allowing dynamic overrides without static file modifications.
- The `diagnoseMcpToolDeniesFromConfigs` function enforces security policies using glob-pattern matching against legacy and modern permission schemas.
- Transport operations in [`connect-mcp-transport.ts`](https://github.com/different-ai/openwork/blob/main/connect-mcp-transport.ts) follow the MCP specification with initialize-notify-read sequencing and support for both JSON and SSE response parsing.
- Lifecycle APIs (`addMcp`, `setMcpEnabled`) persist changes to a workspace-specific runtime config distinct from version-controlled [`opencode.json`](https://github.com/different-ai/openwork/blob/main/opencode.json) files.

## Frequently Asked Questions

### How does the OpenWork gateway handle conflicting MCP configurations between project and global scopes?

Runtime-registered MCPs take highest precedence, followed by project-level configurations, then global settings. When `listMcp` merges these sources in [`apps/server/src/mcp.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/mcp.ts), later entries supersede earlier ones in the iteration sequence. This hierarchy ensures that dynamically added cloud providers via the UI or API override static definitions, while project-specific settings can customize global defaults without affecting other workspaces.

### What happens when an MCP tool matches a deny pattern in the configuration?

The runtime evaluates deny rules during the discovery phase through `diagnoseMcpToolDeniesFromConfigs`. If a tool ID matches a glob pattern in `tools.deny` or the permission ruleset, the function populates the `toolDenies` array for that MCP. The transport layer checks this metadata before executing `readMcpResourceText`, immediately returning `null` if denies are present. The UI uses this diagnostic information to display disabled states and provide transparency about policy restrictions.

### Can the OpenWork gateway runtime manage MCPs added after the server has started?

Yes, the architecture fully supports dynamic registration through the runtime Opencode config. The `addMcp` function validates new configurations via `validateMcpConfig` and persists them via `writeRuntimeOpencodeConfig` to a JSONC file separate from the static project config. These runtime entries are loaded through `listMcpFromRuntimeSnapshot` and immediately become available for transport connections without requiring a server restart, though they are scoped to the specific workspace.

### What transport protocol does the OpenWork gateway use for MCP communication?

The gateway implements a **minimal JSON-RPC transport** over HTTP as defined in [`apps/server/src/connect-mcp-transport.ts`](https://github.com/different-ai/openwork/blob/main/apps/server/src/connect-mcp-transport.ts). The `readMcpResourceText` function executes a strict initialization handshake (initialize request, initialized notification, then resource/read), maintaining session state through the `mcp-session-id` header. The parser handling in `readMcpPayload` supports both standard JSON responses and `text/event-stream` formats for streaming tool outputs, ensuring compatibility with diverse cloud-provider implementations.