# How Opencode JSONC Configuration Works with MCP Servers in OpenWork

> Learn how opencode.jsonc configures MCP servers in OpenWork. Define endpoints, auth, and connection params declaratively for runtime access via getConfig().

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

---

**OpenWork uses `opencode.jsonc`—a JSON-with-comments configuration file—to declaratively define MCP server endpoints, authentication, and connection parameters, which the runtime loads on startup and exposes via `getConfig()` for the MCP client to consume.**

The `opencode.jsonc` format is central to how OpenWork connects to its Managed Capabilities Platform (MCP). This approach separates infrastructure concerns from application logic, allowing teams to switch MCP environments, rotate credentials, or adjust timeouts without modifying source code.

## Understanding the Opencode JSONC Format

OpenWork adopted **JSONC** (JSON with Comments) as its configuration language. The `.jsonc` extension signals that C-style comments are permitted, making it easier to document configuration choices inline.

According to the OpenWork source code, the configuration loader resides in [`src/utils/opencode.ts`](https://github.com/different-ai/openwork/blob/main/src/utils/opencode.ts). This module handles:
- Parsing JSONC files with comment stripping
- Deep-merging environment-specific overlays (e.g., `opencode.dev.jsonc`)
- Resolving `envVar` references to inject secrets at runtime

The loader never persists resolved secrets to disk or logs, ensuring credentials remain in memory only.

## MCP Server Configuration Structure

The MCP server is defined under the `servers.mcp` key in `opencode.jsonc`:

```jsonc
{
  "servers": {
    "mcp": {
      "url": "https://api.openworklabs.com/mcp/agent",
      "auth": {
        "type": "bearer",
        "envVar": "OPENWORK_MCP_TOKEN"
      },
      "timeoutMs": 30000
    }
  }
}

```

### Configuration Fields Explained

- **`url`**: The base endpoint for all MCP operations. Switch this value to point at staging, production, or local mock servers.
- **`auth.type`**: Currently supports `"bearer"` token authentication. The `auth` block describes *how* to authenticate, not the secret itself.
- **`auth.envVar`**: Names the environment variable containing the actual token. The loader reads `process.env.OPENWORK_MCP_TOKEN` at startup and injects its value.
- **`timeoutMs`**: Optional network timeout in milliseconds. Defaults to 15000 if omitted.

## Configuration Loading and Runtime Flow

The startup sequence in [`src/main.ts`](https://github.com/different-ai/openwork/blob/main/src/main.ts) orchestrates configuration initialization:

```ts
// src/main.ts — simplified startup flow
import { loadOpencode } from '@/utils/opencode';

async function bootstrap() {
  const config = await loadOpencode(); // reads opencode.jsonc + overlay
  // config.servers.mcp now contains resolved URL and token
  initializeMcpClient(config.servers.mcp);
  // ... remainder of application startup
}

```

### Step-by-Step Loading Process

1. **Base file read**: `opencode.jsonc` is parsed from the repository root
2. **Overlay detection**: If `NODE_ENV=development`, the loader looks for `opencode.dev.jsonc`
3. **Deep merge**: Overlay values recursively replace base values
4. **Secret injection**: All `envVar` references are resolved from `process.env`
5. **Validation**: The configuration object is checked for required MCP fields
6. **Export**: `getConfig()` returns a frozen configuration singleton

The [`src/config/index.ts`](https://github.com/different-ai/openwork/blob/main/src/config/index.ts) module exposes the loaded configuration:

```ts
// src/config/index.ts
import { readOpencode } from '@/utils/opencode';

let cachedConfig: ReturnType<typeof readOpencode> | null = null;

export const getConfig = () => {
  if (!cachedConfig) {
    cachedConfig = readOpencode();
  }
  return cachedConfig;
};

```

## MCP Client Implementation

The `McpClient` class in [`src/mcp/client.ts`](https://github.com/different-ai/openwork/blob/main/src/mcp/client.ts) consumes the configuration to build a typed API wrapper:

```ts
// src/mcp/client.ts
import { getConfig } from '@/config';

interface McpConfig {
  url: string;
  auth: { type: string; token: string };
  timeoutMs?: number;
}

export class McpClient {
  private baseUrl: string;
  private token: string;
  private timeout: number;

  constructor() {
    const cfg = getConfig().servers.mcp as McpConfig;
    this.baseUrl = cfg.url;
    this.token = cfg.auth.token; // injected from envVar at load time
    this.timeout = cfg.timeoutMs ?? 15000;
  }

  async request<T>(path: string, init?: RequestInit): Promise<T> {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), this.timeout);

    try {
      const resp = await fetch(`${this.baseUrl}${path}`, {
        ...init,
        headers: {
          ...init?.headers,
          Authorization: `Bearer ${this.token}`,
        },
        signal: controller.signal,
      });

      if (!resp.ok) {
        throw new Error(`MCP request failed: ${resp.status} ${resp.statusText}`);
      }

      return await resp.json() as T;
    } finally {
      clearTimeout(timeoutId);
    }
  }
}

```

All MCP operations—skill discovery, plugin execution, policy fetching—route through this client, ensuring consistent authentication, timeout handling, and error propagation.

## Environment-Specific Overrides

One of the strongest features of the Opencode configuration system is **overlay support**. Teams can maintain multiple environment configurations without modifying the base file.

### Local Development Example

Create `opencode.dev.jsonc` in the repository root:

```jsonc
{
  "servers": {
    "mcp": {
      "url": "http://localhost:4000/mcp/agent"
      // auth and timeoutMs inherited from base opencode.jsonc
    }
  }
}

```

Start the application with `NODE_ENV=development`, and the MCP client automatically connects to your local server. The token is still injected from `OPENWORK_MCP_TOKEN`, which can point to a development credential.

### Staging and Production Patterns

- **Staging**: `opencode.staging.jsonc` with `url` pointing to staging MCP gateway
- **Production**: No overlay; production values live in the base `opencode.jsonc`
- **CI/CD**: Override via environment variables injected by your deployment pipeline

## Security Considerations

The Opencode design explicitly avoids credential exposure:

| Risk | Mitigation |
|------|------------|
| Secrets in version control | `envVar` references only; actual tokens never committed |
| Secret logging | Loader excludes `auth.token` from serializations |
| Token leakage in error messages | `McpClient` sanitizes headers before throwing |

When debugging configuration, use `getConfig()` but never log the full `auth` object. The OpenWork codebase includes utility functions to redact sensitive fields.

## Complete Working Example

Here's an end-to-end pattern for integrating MCP operations with Opencode configuration:

```ts
// src/skills/loader.ts
import { McpClient } from '@/mcp/client';

interface Skill {
  id: string;
  name: string;
  version: string;
  capabilities: string[];
}

export class SkillLoader {
  private client: McpClient;

  constructor() {
    this.client = new McpClient();
  }

  async fetchAvailable(): Promise<Skill[]> {
    return this.client.request<Skill[]>('/v1/skills');
  }

  async execute(skillId: string, input: unknown): Promise<unknown> {
    return this.client.request<unknown>(`/v1/skills/${skillId}/execute`, {
      method: 'POST',
      body: JSON.stringify(input),
      headers: { 'Content-Type': 'application/json' },
    });
  }
}

```

This module requires zero hardcoded URLs or credentials—it inherits everything from `opencode.jsonc` through the `McpClient` constructor chain.

## Summary

- **Opencode JSONC** is OpenWork's declarative configuration format, supporting comments and environment overlays
- **MCP server configuration** lives under `servers.mcp` in `opencode.jsonc`, specifying `url`, `auth`, and `timeoutMs`
- **Secret injection** happens at runtime via `envVar` references, keeping credentials out of source control
- **Configuration loading** in [`src/utils/opencode.ts`](https://github.com/different-ai/openwork/blob/main/src/utils/opencode.ts) handles parsing, merging, and validation
- **McpClient** in [`src/mcp/client.ts`](https://github.com/different-ai/openwork/blob/main/src/mcp/client.ts) consumes the resolved configuration to provide authenticated, timeout-aware API access
- **Environment-specific overlays** enable seamless switching between local, staging, and production MCP endpoints

## Frequently Asked Questions

### What is the difference between JSON and JSONC configuration files?

JSONC is a superset of JSON that allows C-style (`//`) and block (`/* */`) comments. OpenWork uses the `.jsonc` extension to indicate that parsers should strip comments before JSON validation. This lets teams document configuration decisions directly in the file without breaking parsers.

### How does OpenWork handle MCP authentication tokens securely?

The `opencode.jsonc` file never contains actual tokens. Instead, it references an environment variable name via `auth.envVar`. When the configuration loads, [`src/utils/opencode.ts`](https://github.com/different-ai/openwork/blob/main/src/utils/opencode.ts) reads `process.env[envVar]` and injects the value into the configuration object in memory only. The resolved token is never written to logs, caches, or serialized output.

### Can I use multiple MCP servers with different configurations?

The current OpenWork schema supports a single MCP server under `servers.mcp`. For multi-server scenarios, you would define additional server blocks under `servers` with different keys (e.g., `servers.mcpStaging`), then modify [`src/mcp/client.ts`](https://github.com/different-ai/openwork/blob/main/src/mcp/client.ts) to accept a server key parameter. The configuration structure supports this extension without breaking changes.

### What happens if `OPENWORK_MCP_TOKEN` is not set at runtime?

The `loadOpencode()` function in [`src/utils/opencode.ts`](https://github.com/different-ai/openwork/blob/main/src/utils/opencode.ts) will throw a configuration validation error during startup, preventing the application from initializing with an incomplete MCP setup. This fail-fast behavior ensures that authentication failures are caught early rather than surfacing as mysterious 401 errors during operation.