Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions packages/mcp-config-schema/CLIENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ This document provides a comprehensive overview of all supported MCP clients, th
| **Goose** | User-configurable | HTTP native | Token, OAuth DCR | No | macOS, Linux, Windows |
| **JetBrains AI Assistant** | User-configurable | HTTP native | Token | No | macOS, Linux, Windows |
| **Junie (JetBrains)** | User-configurable | stdio only | None | Yes (for HTTP) | macOS, Linux, Windows |
| **OpenCode** | User-configurable | HTTP native | Token, OAuth DCR | No | macOS, Linux, Windows |
| **Visual Studio Code** | User-configurable | HTTP native | Token, OAuth DCR | No | macOS, Linux, Windows |
| **Windsurf** | User-configurable | HTTP native | Token, OAuth DCR | No | macOS, Linux, Windows |

Expand Down Expand Up @@ -957,6 +958,103 @@ extensions:

---

### OpenCode

- **Configuration**: User-configurable
- **Connection Type**: Native HTTP support
- **Documentation**: [Link](https://opencode.ai/docs/mcp-servers/)
- **Supported Platforms**: macOS, Linux, Windows
- **Auth Support**: Token, OAuth DCR
- **OAuth Redirect**: `http://127.0.0.1:*`
- **Configuration Paths**:
- **macOS/Linux**: `$HOME/.config/opencode/opencode.json`
- **Windows**: `%USERPROFILE%\.config\opencode\opencode.json`

<details>
<summary><strong>Internal Configuration Schema</strong></summary>

```json snippet=configs/opencode.json
{
"id": "opencode",
"name": "opencode",
"displayName": "OpenCode",
"description": "OpenCode with native HTTP and stdio support",
"userConfigurable": true,
"documentationUrl": "https://opencode.ai/docs/mcp-servers/",
"transports": ["stdio", "http"],
"supportedPlatforms": ["darwin", "linux", "win32"],
"configFormat": "json",
"configPath": {
"darwin": "$HOME/.config/opencode/opencode.json",
"linux": "$HOME/.config/opencode/opencode.json",
"win32": "%USERPROFILE%\\.config\\opencode\\opencode.json"
},
"configStructure": {
"serversPropertyName": "mcp",
"httpPropertyMapping": {
"typeProperty": "type",
"urlProperty": "url",
"headersProperty": "headers"
},
"stdioPropertyMapping": {
"typeProperty": "type",
"commandProperty": "command",
"argsProperty": "command",
"envProperty": "environment"
}
},
"supportedAuth": ["token", "oauth:dcr"],
"oauth": {
"dcr": {
"redirect_uri_patterns": ["http://127.0.0.1:*"]
}
}
}
```

</details>

<details>
<summary><strong>HTTP Configuration</strong></summary>

```json snippet=examples/configs/http/opencode.json
{
"mcp": {
"example": {
"type": "remote",
"url": "https://api.example.com/mcp"
}
}
}
```

</details>

<details>
<summary><strong>stdio Configuration</strong></summary>

```json snippet=examples/configs/stdio/opencode.json
{
"mcp": {
"example": {
"type": "local",
"command": [
"npx",
"-y",
"@example/mcp-server"
],
"environment": {
"EXAMPLE_API_KEY": "your-api-key"
}
}
}
}
```

</details>

---

### Visual Studio Code

- **Configuration**: User-configurable
Expand Down Expand Up @@ -1177,6 +1275,7 @@ Clients that can connect directly to HTTP MCP servers without additional tooling
- Gemini CLI
- Goose
- JetBrains AI Assistant
- OpenCode
- Visual Studio Code
- Windsurf

Expand Down
36 changes: 36 additions & 0 deletions packages/mcp-config-schema/configs/opencode.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"id": "opencode",
"name": "opencode",
"displayName": "OpenCode",
"description": "OpenCode with native HTTP and stdio support",
"userConfigurable": true,
"documentationUrl": "https://opencode.ai/docs/mcp-servers/",
"transports": ["stdio", "http"],
"supportedPlatforms": ["darwin", "linux", "win32"],
"configFormat": "json",
"configPath": {
"darwin": "$HOME/.config/opencode/opencode.json",
"linux": "$HOME/.config/opencode/opencode.json",
"win32": "%USERPROFILE%\\.config\\opencode\\opencode.json"
},
"configStructure": {
"serversPropertyName": "mcp",
"httpPropertyMapping": {
"typeProperty": "type",
"urlProperty": "url",
"headersProperty": "headers"
},
"stdioPropertyMapping": {
"typeProperty": "type",
"commandProperty": "command",
"argsProperty": "command",
"envProperty": "environment"
}
},
"supportedAuth": ["token", "oauth:dcr"],
"oauth": {
"dcr": {
"redirect_uri_patterns": ["http://127.0.0.1:*"]
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"mcp": {
"example": {
"type": "remote",
"url": "https://api.example.com/mcp"
}
}
}
15 changes: 15 additions & 0 deletions packages/mcp-config-schema/examples/configs/stdio/opencode.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"mcp": {
"example": {
"type": "local",
"command": [
"npx",
"-y",
"@example/mcp-server"
],
"environment": {
"EXAMPLE_API_KEY": "your-api-key"
}
}
}
}
133 changes: 133 additions & 0 deletions packages/mcp-config-schema/src/builders/OpenCodeConfigBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { BaseConfigBuilder } from './BaseConfigBuilder.js';
import { MCPConnectionOptions, OpenCodeMCPConfig, MCPServersRecord } from '../types.js';

function isOpenCodeMCPConfig(
config: OpenCodeMCPConfig | MCPServersRecord
): config is OpenCodeMCPConfig {
return typeof config === 'object' && config !== null && 'mcp' in config;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Distinguish wrapped and flat OpenCode config shapes

getNormalizedServersConfig accepts either a wrapped { mcp: ... } object or a flat MCPServersRecord, but this guard only checks whether the key mcp exists. A flat servers map can validly include a server named mcp (for example when includeRootObject: false and serverName is "mcp"), in which case the function misclassifies the input as wrapped and then normalizes server fields (type, command, etc.) as if they were server entries, producing corrupted normalized output.

Useful? React with 👍 / 👎.

}

/**
* Config builder for OpenCode which uses { mcp: {...} } format
* with "local"/"remote" type values and combined command arrays.
*/
export class OpenCodeConfigBuilder extends BaseConfigBuilder<OpenCodeMCPConfig> {
protected buildStdioConfig(
options: MCPConnectionOptions,
includeRootObject: boolean = true
): Record<string, unknown> {
const serverName = this.buildServerName({
transport: 'stdio',
serverName: options.serverName,
});

const serverConfig: Record<string, unknown> = {
type: 'local',
command: ['npx', '-y', this.serverPackage],
};

const env = this.getEnvVars(options);
if (env) {
serverConfig.environment = env;
}

if (!includeRootObject) {
return {
[serverName]: serverConfig,
};
}

return {
mcp: {
[serverName]: serverConfig,
},
};
}

protected buildHttpConfig(
options: MCPConnectionOptions,
includeRootObject: boolean = true
): Record<string, unknown> {
if (!options.serverUrl) {
throw new Error('HTTP transport requires a server URL');
}

const resolvedUrl = this.substituteUrlVariables(options.serverUrl, options.urlVariables);

const serverName = this.buildServerName({
transport: 'http',
serverUrl: options.serverUrl,
serverName: options.serverName,
});

if (this.config.transports.includes('http')) {
const serverConfig: Record<string, unknown> = {
type: 'remote',
url: resolvedUrl,
};

const headers = this.buildHeaders(options);
if (headers) {
serverConfig.headers = headers;
}

if (!includeRootObject) {
return {
[serverName]: serverConfig,
};
}

return {
mcp: {
[serverName]: serverConfig,
},
};
} else {
throw new Error(`Client ${this.config.id} doesn't support HTTP server configuration`);
}
}

protected buildHttpCommand(options: MCPConnectionOptions): string | null {
if (this.commandBuilder?.http) {
return this.commandBuilder.http(this.config.id, options);
}

return null;
}

protected buildStdioCommand(options: MCPConnectionOptions): string | null {
if (this.commandBuilder?.stdio) {
return this.commandBuilder.stdio(this.config.id, options);
}

return null;
}

/**
* @deprecated This method is deprecated and will be removed in the next major version.
* Consumers should use buildConfiguration() directly and handle the output format
* based on the includeRootObject option.
*/
getNormalizedServersConfig(config: OpenCodeMCPConfig | MCPServersRecord): MCPServersRecord {
const servers: MCPServersRecord = isOpenCodeMCPConfig(config) ? config.mcp : config;

const normalized: MCPServersRecord = {};

for (const [name, value] of Object.entries(servers)) {
const openCodeConfig = value as Record<string, unknown>;
if (typeof openCodeConfig === 'object' && openCodeConfig !== null) {
const commandArray = openCodeConfig.command as string[] | undefined;
normalized[name] = {
type: openCodeConfig.type === 'local' ? 'stdio' : 'http',
command: commandArray?.[0],
args: commandArray?.slice(1),
env: openCodeConfig.environment,
url: openCodeConfig.url,
headers: openCodeConfig.headers,
};
}
}

return normalized;
}
}
1 change: 1 addition & 0 deletions packages/mcp-config-schema/src/builders/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export * from './CursorConfigBuilder';
export * from './GeminiConfigBuilder';
export * from './GenericConfigBuilder';
export * from './GooseConfigBuilder';
export * from './OpenCodeConfigBuilder';
export * from './VSCodeConfigBuilder';
3 changes: 3 additions & 0 deletions packages/mcp-config-schema/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export const CLIENT = {
JUNIE: 'junie',
JETBRAINS: 'jetbrains',
GEMINI: 'gemini',
OPENCODE: 'opencode',
} as const;

/**
Expand All @@ -32,6 +33,7 @@ export const CLIENT_DISPLAY_NAME = {
JUNIE: 'Junie (JetBrains)',
JETBRAINS: 'JetBrains AI Assistant',
GEMINI: 'Gemini CLI',
OPENCODE: 'OpenCode',
} as const;

/**
Expand Down Expand Up @@ -61,6 +63,7 @@ export function getDisplayName(clientId: ClientIdConstant): ClientDisplayName {
[CLIENT.JUNIE]: CLIENT_DISPLAY_NAME.JUNIE,
[CLIENT.JETBRAINS]: CLIENT_DISPLAY_NAME.JETBRAINS,
[CLIENT.GEMINI]: CLIENT_DISPLAY_NAME.GEMINI,
[CLIENT.OPENCODE]: CLIENT_DISPLAY_NAME.OPENCODE,
};
return mapping[clientId];
}
1 change: 1 addition & 0 deletions packages/mcp-config-schema/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export { GooseConfigBuilder } from './builders/GooseConfigBuilder.js';
export { CursorConfigBuilder } from './builders/CursorConfigBuilder.js';
export { ClaudeCodeConfigBuilder } from './builders/ClaudeCodeConfigBuilder.js';
export { CodexConfigBuilder } from './builders/CodexConfigBuilder.js';
export { OpenCodeConfigBuilder } from './builders/OpenCodeConfigBuilder.js';
7 changes: 7 additions & 0 deletions packages/mcp-config-schema/src/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
ClaudeCodeConfigBuilder,
CodexConfigBuilder,
GeminiConfigBuilder,
OpenCodeConfigBuilder,
} from './builders/index.js';
import chatgptConfig from '../configs/chatgpt.json';
import claudeCodeConfig from '../configs/claude-code.json';
Expand All @@ -31,6 +32,7 @@ import windsurfConfig from '../configs/windsurf.json';
import junieConfig from '../configs/junie.json';
import jetbrainsConfig from '../configs/jetbrains.json';
import geminiConfig from '../configs/gemini.json';
import opencodeConfig from '../configs/opencode.json';
const allConfigs = [
chatgptConfig,
claudeCodeConfig,
Expand All @@ -45,6 +47,7 @@ const allConfigs = [
junieConfig,
jetbrainsConfig,
geminiConfig,
opencodeConfig,
];

export class MCPConfigRegistry {
Expand Down Expand Up @@ -89,6 +92,10 @@ export class MCPConfigRegistry {
'gemini' as ClientId,
GeminiConfigBuilder as new (config: MCPClientConfig) => BaseConfigBuilder
);
this.builderFactories.set(
'opencode' as ClientId,
OpenCodeConfigBuilder as new (config: MCPClientConfig) => BaseConfigBuilder
);
// Other clients will use GenericConfigBuilder by default
}

Expand Down
Loading