# How OpenWork MCP Server Configuration Handles Remote and Local Types

> Learn how OpenWork MCP server configuration prioritizes remote MCPs over local ones using a three-layer hierarchy for efficient management.

- Repository: [Different AI/openwork](https://github.com/different-ai/openwork)
- Tags: how-to-guide
- Published: 2026-08-06

---

**OpenWork MCP server configuration manages MCP definitions through a three-layer hierarchy where remote MCPs (marked with `"type": "remote"`) stored in runtime configurations always override local MCPs (marked with `"type": "local"`) defined in static configuration files.**

The `different-ai/openwork` repository implements a sophisticated MCP (Managed Connector Provider) server configuration system that distinguishes between remote and local MCP types. This architecture allows developers to manage connectors through both static configuration files and dynamic runtime storage, with clear precedence rules governing how conflicts are resolved in [`dev/apps/server/src/mcp.ts`](https://github.com/different-ai/openwork/blob/main/dev/apps/server/src/mcp.ts).

## The Three-Layer Configuration Hierarchy

The configuration system organizes MCP definitions across three distinct layers, each with specific storage locations and source identifiers.

### Global Configuration Layer

Stored in `~/.config/opencode/config.jsonc` (or the path returned by `resolveGlobalOpenCodeConfigPath()`), the global layer contains MCP definitions available across all workspaces. Entries from this layer receive the source label `"config.global"` and typically use `"type": "local"`.

### Project Configuration Layer

Located at `<workspace>/.opencode/config.jsonc`, the project layer provides workspace-specific MCP definitions. These entries carry the source label `"config.project"` and also default to `"type": "local"`.

### Remote Runtime Layer

Remote MCPs reside in the workspace's runtime Opencode config, stored as JSON under the server's data directory and accessed via `readRuntimeOpencodeConfig()`. These entries use the source label `"config.remote"` and explicitly set `"type": "remote"`.

## Configuration Merging and Precedence Rules

The core merging logic in [`dev/apps/server/src/mcp.ts`](https://github.com/different-ai/openwork/blob/main/dev/apps/server/src/mcp.ts) implements strict precedence when combining layers. The `listMcpFromRuntimeSnapshot` function processes entries in the following order:

1. **Global entries** not overridden by project entries
2. **Project entries** not overridden by runtime entries  
3. **Runtime (remote) entries**—always taking highest precedence

```ts
// Global entries that are not overridden by a project entry
for (const [name, entry] of Object.entries(globalMcpMap)) {
  if (Object.prototype.hasOwnProperty.call(projectMcpMap, name)) continue;
  items.push({ name, config: entry, source: "config.global", … });
}

// Project entries that are not overridden by a runtime entry
for (const [name, entry] of Object.entries(projectMcpMap)) {
  if (Object.prototype.hasOwnProperty.call(runtimeMap, name)) continue;
  items.push({ name, config: entry, source: "config.project", … });
}

// Runtime (remote) entries – always win
for (const [name, entry] of Object.entries(runtimeMap)) {
  items.push({ name, config: entry, source: "config.remote", … });
}

```

This ensures **remote MCPs always win over local definitions**, while local MCPs serve as fallbacks when no remote entry exists.

## Type Validation and Runtime Storage

Both `addMcp` and `setMcpEnabled` functions call `validateMcpConfig` to ensure every MCP entry contains a valid `type` field—either `"remote"` or `"local"`.

When adding a new MCP through the server API, the system stores the entry in the runtime snapshot:

```ts
export async function addMcp(serverConfig, workspaceId, name, config) {
  validateMcpName(name);
  validateMcpConfig(config);
  const runtimeConfig = await readRuntimeOpencodeConfig(serverConfig, workspaceId);
  const mcpMap = { …runtimeMcpMap(runtimeConfig) };
  mcpMap[name] = config;                  // ← stored in runtime → “remote”
  await writeRuntimeOpencodeConfig(serverConfig, workspaceId, cur => ({ …cur, mcp: mcpMap }));
}

```

This automatic storage in the runtime configuration marks the MCP as remote, regardless of the original type specified.

## UI Representation and User Interface Handling

The settings page in [`dev/apps/app/src/react-app/domains/settings/pages/mcp-view.tsx`](https://github.com/different-ai/openwork/blob/main/dev/apps/app/src/react-app/domains/settings/pages/mcp-view.tsx) renders the MCP type to users:

```tsx
<span className="text-dls-text">
  {props.entry.config.type === "remote" ? t("mcp.type_cloud") : t("mcp.type_local")}
</span>

```

Remote MCPs display as **Cloud** while local MCPs display as **Local**. The UI also conditionally exposes connection URLs only for remote entries:

```tsx
url={detailConnectMcp.config.type === "remote" ? detailConnectMcp.config.url : undefined}
oauth={detailConnectMcp.config.type === "remote"}

```

## Practical Implementation Examples

### Adding a Remote MCP

To programmatically add a remote MCP through the server API:

```ts
await addMcp(serverConfig, "ws_123", "my-remote-mcp", {
  type: "remote",
  url: "https://example.com/mcp",
  enabled: true,
  oauth: {}                     // optional OAuth config
});
// → stored as source "config.remote"

```

### Defining a Local MCP in Static Config

Create or edit `<workspace>/.opencode/config.jsonc`:

```jsonc
{
  "mcp": {
    "my-local-mcp": {
      "type": "local",
      "command": ["node", "my-mcp.js"]
    }
  }
}

```

This requires no server-side calls and appears as `"config.project"`.

### Listing All MCPs with Merged View

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

const items = await listMcp(serverConfig, "ws_123", workspaceRoot);
items.forEach(item => {
  console.log(`${item.name} – ${item.source} – ${item.config.type}`);
});
/* Example output:
   my-remote-mcp – config.remote – remote
   my-local-mcp  – config.project – local
*/

```

### Disabling an MCP

```ts
await setMcpEnabled(serverConfig, "ws_123", "my-remote-mcp", false);

```

## Summary

- OpenWork MCP server configuration uses a **three-layer hierarchy**: global (`~/.config/opencode/config.jsonc`), project (`<workspace>/.opencode/config.jsonc`), and remote runtime configs.
- **Remote MCPs** (`type: "remote"`) stored in runtime configs via `writeRuntimeOpencodeConfig` always take precedence over **local MCPs** (`type: "local"`) from static files.
- The `validateMcpConfig` function enforces type safety, accepting only `"remote"` or `"local"` values in [`dev/apps/server/src/mcp.ts`](https://github.com/different-ai/openwork/blob/main/dev/apps/server/src/mcp.ts).
- Server-side operations like `addMcp` automatically store entries as remote MCPs in the runtime snapshot, marking them with `source: "config.remote"`.
- The UI in [`mcp-view.tsx`](https://github.com/different-ai/openwork/blob/main/mcp-view.tsx) distinguishes types visually, showing "Cloud" for remote and "Local" for static configurations, while only exposing URL fields for remote types.

## Frequently Asked Questions

### What determines whether an MCP is classified as remote or local in OpenWork?

The classification depends on storage location rather than connection method. MCPs stored in the runtime Opencode config (managed through `addMcp` or server APIs) become remote with `type: "remote"`, while those defined in `~/.config/opencode/config.jsonc` or `<workspace>/.opencode/config.jsonc` are local with `type: "local"`.

### Can a local MCP override a remote MCP with the same name?

No. According to the merging logic in [`dev/apps/server/src/mcp.ts`](https://github.com/different-ai/openwork/blob/main/dev/apps/server/src/mcp.ts), runtime (remote) entries always win. The `listMcpFromRuntimeSnapshot` function checks for existing runtime entries before including project or global definitions, ensuring remote MCPs maintain highest precedence regardless of where local definitions exist.

### How does the system validate MCP type definitions?

The `validateMcpConfig` function validates every MCP configuration before storage, ensuring the `type` field contains either `"remote"` or `"local"`. This validation occurs in both `addMcp` and `setMcpEnabled` functions to prevent invalid type assignments and maintain configuration integrity.

### Where are remote MCP configurations physically stored?

Remote MCPs reside in the workspace's runtime Opencode config, accessed through `readRuntimeOpencodeConfig()` and written via `writeRuntimeOpencodeConfig()` in [`dev/apps/server/src/runtime-opencode-config-store.ts`](https://github.com/different-ai/openwork/blob/main/dev/apps/server/src/runtime-opencode-config-store.ts). This JSON file lives under the server's data directory and maintains the `source: "config.remote"` identifier throughout the system.