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
16 changes: 15 additions & 1 deletion packages/cli/src/detect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import * as path from "node:path";
import * as os from "node:os";
import * as fs from "node:fs";
import { generators } from "@getmcp/generators";
import type { AppIdType, AppMetadata } from "@getmcp/core";
import { supportsBothScopes } from "@getmcp/core";
Expand Down Expand Up @@ -91,10 +92,23 @@ export function detectApps(): DetectedApp[] {
const hasBothScopes = supportsBothScopes(generator.app);
const globalConfigPath = hasBothScopes ? getConfigPath(generator.app, "global") : undefined;

// For dual-scope apps, prefer the project config when it exists; otherwise
// fall back to the global config so that doctor/list reflect the config
// the user is actually using. This matters for apps like OpenCode and
// Claude Code where many users only keep a global config.
let effectiveConfigPath = configPath;
if (hasBothScopes && globalConfigPath) {
const projectExists = fs.existsSync(configPath);
const globalExists = fs.existsSync(globalConfigPath);
if (!projectExists && globalExists) {
effectiveConfigPath = globalConfigPath;
}
}

results.push({
id: generator.app.id,
name: generator.app.name,
configPath,
configPath: effectiveConfigPath,
exists: generator.detectInstalled(),
supportsBothScopes: hasBothScopes,
...(globalConfigPath ? { globalConfigPath } : {}),
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/tests/detect.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe("detectApps", () => {

it("dual-scope apps have supportsBothScopes true", () => {
const apps = detectApps();
const dualScopeIds = ["claude-code", "cursor", "codex"];
const dualScopeIds = ["claude-code", "cursor", "codex", "opencode"];
for (const app of apps) {
if (dualScopeIds.includes(app.id)) {
expect(app.supportsBothScopes).toBe(true);
Expand All @@ -67,7 +67,7 @@ describe("detectApps", () => {

it("single-scope apps have supportsBothScopes false", () => {
const apps = detectApps();
const singleScopeIds = ["claude-desktop", "vscode", "cline", "goose", "opencode"];
const singleScopeIds = ["claude-desktop", "vscode", "cline", "goose"];
for (const app of apps) {
if (singleScopeIds.includes(app.id)) {
expect(app.supportsBothScopes).toBe(false);
Expand Down
12 changes: 11 additions & 1 deletion packages/generators/src/opencode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,18 @@ export class OpenCodeGenerator extends BaseGenerator {
id: "opencode",
name: "OpenCode",
description: "Open-source AI coding agent by Anomaly",
// Project-level config. OpenCode accepts both opencode.json and opencode.jsonc;
// we write .json because it is the most widely compatible default.
configPaths: "opencode.json",
globalConfigPaths: null,
// Global config. Per OpenCode docs, global settings live in
// ~/.config/opencode/opencode.jsonc (JSONC to allow comments).
// On Windows the equivalent location is %AppData%\opencode\.
// See https://opencode.ai/docs/config/
globalConfigPaths: {
darwin: "~/.config/opencode/opencode.jsonc",
win32: "%AppData%/opencode/opencode.jsonc",
linux: "~/.config/opencode/opencode.jsonc",
},
configFormat: "jsonc",
docsUrl: "https://opencode.ai/docs/mcp-servers/",
};
Expand Down