feat: configure built-in MCP access

Built-in MCP servers were always granted to supported runtimes without per-server controls. They now have persistent enablement and runtime assignments for direct models, managed OpenCode, and Continue, while DeepSeek Harness remains visibly unsupported and cannot be assigned.

MCP settings are reorganized into Built-in MCP, Direct model, Custom MCP, and Computer control tabs. Direct-model web search now uses the same accessible collapsible inventory pattern as other tool groups.

Release note: 内置 MCP 现在可分别启停并分配给直连模型、OpenCode 和 Continue;MCP 设置分类与直连模型工具列表也更清晰,DeepSeek Harness 会明确显示为暂不支持。
This commit is contained in:
mesalogo
2026-08-17 00:25:46 +08:00
parent b751c70d75
commit 792d80e67c
21 changed files with 1012 additions and 218 deletions
+114 -7
View File
@@ -18,6 +18,9 @@ import {
browserProfileIdSchema,
browserProfileNameSchema,
browserProfilesSummarySchema,
builtinMcpAssignmentsSchema,
builtinMcpServerIdSchema,
builtinMcpServerStateSummarySchema,
capabilityDiagnosticReportSchema,
capabilityAssignmentsSchema,
computerCapabilityConfigSummarySchema,
@@ -25,6 +28,7 @@ import {
mcpServerIdSchema,
mcpServerInputSchema,
mcpServerSummarySchema,
runtimeTargetSchema,
skillIdSchema,
skillSummarySchema,
webSearchCapabilitySchema,
@@ -32,6 +36,7 @@ import {
type CapabilityDiagnosticReport,
type CapabilitySnapshot,
type BrowserProfilesSummary,
type BuiltinMcpServerId,
type ComputerCapabilityId,
type McpServerInput,
type McpServerSummary,
@@ -105,6 +110,21 @@ const skillStateSchema = z
})
.strict()
const builtinMcpServerStateSchema = z
.object({
enabled: z.boolean(),
assignments: builtinMcpAssignmentsSchema
})
.strict()
const builtinMcpServerStatesSchema = z
.object({
'knowledge-base': builtinMcpServerStateSchema,
'magic-notes': builtinMcpServerStateSchema,
'goodbuddy-config': builtinMcpServerStateSchema
})
.strict()
const encryptedSecretSchema =
encryptedSettingsCredentialSchema.optional()
@@ -193,10 +213,15 @@ const storedCapabilitiesV3Schema = z
})
.strict()
const storedCapabilitiesSchema = storedCapabilitiesV3Schema.extend({
const storedCapabilitiesV4Schema = storedCapabilitiesV3Schema.extend({
version: z.literal(4)
})
const storedCapabilitiesSchema = storedCapabilitiesV4Schema.extend({
version: z.literal(5),
builtinMcpServers: builtinMcpServerStatesSchema
})
type StoredCapabilitiesV1 = z.infer<typeof storedCapabilitiesV1Schema>
type StoredCapabilities = z.infer<typeof storedCapabilitiesSchema>
type StoredMcpServer = z.infer<typeof storedMcpServerSchema>
@@ -254,12 +279,25 @@ function defaultComputerCapabilityStates(): StoredCapabilities['computerCapabili
}
}
function defaultBuiltinMcpServerStates(): StoredCapabilities['builtinMcpServers'] {
const defaultState = (): z.infer<typeof builtinMcpServerStateSchema> => ({
enabled: true,
assignments: ['model', 'opencode', 'continue']
})
return {
'knowledge-base': defaultState(),
'magic-notes': defaultState(),
'goodbuddy-config': defaultState()
}
}
function emptyStoredCapabilities(
webSearchEnabled = true
): StoredCapabilities {
return {
version: 4,
version: 5,
skills: {},
builtinMcpServers: defaultBuiltinMcpServerStates(),
mcpServers: [],
webSearch: { enabled: webSearchEnabled },
computerCapabilities: defaultComputerCapabilityStates()
@@ -728,7 +766,7 @@ export class CapabilityService {
let shouldPersist = false
try {
const raw = JSON.parse(await readFile(this.filePath, 'utf8')) as unknown
assertSupportedSettingsVersion(raw, 4, (version) =>
assertSupportedSettingsVersion(raw, 5, (version) =>
`当前 GoodBuddy 不支持能力设置版本 ${version},请升级应用后重试`
)
const version = z
@@ -737,7 +775,8 @@ export class CapabilityService {
z.literal(1),
z.literal(2),
z.literal(3),
z.literal(4)
z.literal(4),
z.literal(5)
])
})
.passthrough()
@@ -746,8 +785,9 @@ export class CapabilityService {
const legacy: StoredCapabilitiesV1 =
storedCapabilitiesV1Schema.parse(raw)
loaded = {
version: 4,
version: 5,
skills: legacy.skills,
builtinMcpServers: defaultBuiltinMcpServerStates(),
mcpServers: legacy.mcpServers,
webSearch: { enabled: true },
computerCapabilities: defaultComputerCapabilityStates()
@@ -757,7 +797,8 @@ export class CapabilityService {
const legacy = storedCapabilitiesV2Schema.parse(raw)
loaded = {
...legacy,
version: 4,
version: 5,
builtinMcpServers: defaultBuiltinMcpServerStates(),
webSearch: { enabled: true }
}
shouldPersist = true
@@ -765,7 +806,16 @@ export class CapabilityService {
const legacy = storedCapabilitiesV3Schema.parse(raw)
loaded = {
...legacy,
version: 4
version: 5,
builtinMcpServers: defaultBuiltinMcpServerStates()
}
shouldPersist = true
} else if (version === 4) {
const legacy = storedCapabilitiesV4Schema.parse(raw)
loaded = {
...legacy,
version: 5,
builtinMcpServers: defaultBuiltinMcpServerStates()
}
shouldPersist = true
} else {
@@ -891,6 +941,12 @@ export class CapabilityService {
? -1
: 1
),
builtinMcpServers: builtinMcpServerIdSchema.options.map((id) =>
builtinMcpServerStateSummarySchema.parse({
id,
...state.builtinMcpServers[id]
})
),
mcpServers: state.mcpServers.map((server) =>
this.toMcpSummary(server)
),
@@ -1564,6 +1620,43 @@ export class CapabilityService {
})
}
setBuiltinMcpServerEnabled(
serverId: BuiltinMcpServerId,
enabled: boolean
): Promise<CapabilitySnapshot> {
return this.updateBuiltinMcpServerState(serverId, { enabled })
}
setBuiltinMcpServerAssignments(
serverId: BuiltinMcpServerId,
assignments: CapabilityAssignments
): Promise<CapabilitySnapshot> {
return this.updateBuiltinMcpServerState(serverId, {
assignments: builtinMcpAssignmentsSchema.parse(assignments)
})
}
private updateBuiltinMcpServerState(
serverId: BuiltinMcpServerId,
update: Partial<z.infer<typeof builtinMcpServerStateSchema>>
): Promise<CapabilitySnapshot> {
return this.queue(async () => {
const id = builtinMcpServerIdSchema.parse(serverId)
const state = await this.load()
await this.persistUserChange({
...state,
builtinMcpServers: {
...state.builtinMcpServers,
[id]: {
...state.builtinMcpServers[id],
...update
}
}
})
return this.getSnapshot()
})
}
private updateSkillState(
skillId: string,
update: Partial<z.infer<typeof skillStateSchema>>
@@ -1801,4 +1894,18 @@ export class CapabilityService {
assigned.map((server) => this.getResolvedMcpServer(server.id))
)
}
async getEnabledBuiltinMcpServerIds(
target: RuntimeTarget
): Promise<BuiltinMcpServerId[]> {
const runtime = runtimeTargetSchema.parse(target)
if (runtime === 'deepseek-harness') {
return []
}
const state = await this.load()
return builtinMcpServerIdSchema.options.filter((id) => {
const server = state.builtinMcpServers[id]
return server.enabled && server.assignments.includes(runtime)
})
}
}