From b223d71a7f07a9719879b85c6ae0f9db59a90797 Mon Sep 17 00:00:00 2001 From: Daniel Riccio Date: Wed, 6 Aug 2025 11:06:41 -0500 Subject: [PATCH] fix: update MCP configuration to use direct object format in YAML - Updated schema to accept { mcp: { included: [...] } } directly as a GroupEntry - Modified McpSelector to create the direct object format instead of tuple - Updated getGroupName and getGroupOptions helpers to handle the new format - Updated MCP server section to properly extract included list from both formats This resolves the nested array issue in YAML generation when toggling MCP checkbox and selecting specific servers. The YAML now correctly generates: - mcp: included: [...] Instead of the previous nested structure. --- packages/types/src/mode.ts | 12 +++- src/core/prompts/sections/mcp-servers.ts | 30 +++++++--- src/shared/modes.ts | 19 ++++++- .../src/components/modes/McpSelector.tsx | 57 +++++++------------ webview-ui/src/components/modes/ModesView.tsx | 8 ++- 5 files changed, 77 insertions(+), 49 deletions(-) diff --git a/packages/types/src/mode.ts b/packages/types/src/mode.ts index 28c5b45927..4a0900d624 100644 --- a/packages/types/src/mode.ts +++ b/packages/types/src/mode.ts @@ -40,7 +40,17 @@ export type GroupOptions = z.infer * GroupEntry */ -export const groupEntrySchema = z.union([toolGroupsSchema, z.tuple([toolGroupsSchema, groupOptionsSchema])]) +export const groupEntrySchema = z.union([ + toolGroupsSchema, + z.tuple([toolGroupsSchema, groupOptionsSchema]), + // Allow direct mcp configuration object + z.object({ + mcp: z.object({ + included: z.array(z.string()), + description: z.string().optional(), + }), + }), +]) export type GroupEntry = z.infer diff --git a/src/core/prompts/sections/mcp-servers.ts b/src/core/prompts/sections/mcp-servers.ts index 62b3a5f6d6..3b2f15e018 100644 --- a/src/core/prompts/sections/mcp-servers.ts +++ b/src/core/prompts/sections/mcp-servers.ts @@ -54,18 +54,34 @@ export async function getMcpServersSection( if (currentMode) { // Find MCP group configuration const mcpGroup = currentMode.groups.find((group: GroupEntry) => { + // Handle tuple format: ["mcp", { mcp: { included: [...] } }] if (Array.isArray(group) && group.length === 2 && group[0] === "mcp") { return true } + // Handle direct object format: { mcp: { included: [...] } } + if (typeof group === "object" && !Array.isArray(group) && "mcp" in group) { + return true + } return getGroupName(group) === "mcp" }) - // If MCP group configuration is found, get mcpIncludedList from mcp.included - if (mcpGroup && Array.isArray(mcpGroup) && mcpGroup.length === 2) { - const options = mcpGroup[1] as { mcp?: { included?: unknown[] } } - mcpIncludedList = Array.isArray(options.mcp?.included) - ? options.mcp.included.filter((item: unknown): item is string => typeof item === "string") - : undefined + // Extract mcpIncludedList based on the format + if (mcpGroup) { + let mcpOptions: { mcp?: { included?: unknown[] } } | undefined + + if (Array.isArray(mcpGroup) && mcpGroup.length === 2) { + // Tuple format + mcpOptions = mcpGroup[1] as { mcp?: { included?: unknown[] } } + } else if (typeof mcpGroup === "object" && !Array.isArray(mcpGroup) && "mcp" in mcpGroup) { + // Direct object format + mcpOptions = mcpGroup as { mcp?: { included?: unknown[] } } + } + + if (mcpOptions) { + mcpIncludedList = Array.isArray(mcpOptions.mcp?.included) + ? mcpOptions.mcp.included.filter((item: unknown): item is string => typeof item === "string") + : undefined + } } } @@ -77,7 +93,7 @@ export async function getMcpServersSection( connectedServers = `${filteredServers .map((server) => { const tools = server.tools - ?.filter((tool) => tool.enabledForPrompt !== false) + ?.filter((tool) => tool.enabledForPrompt !== false) ?.map((tool) => { const schemaStr = tool.inputSchema ? ` Input Schema: diff --git a/src/shared/modes.ts b/src/shared/modes.ts index f68d25c682..f7fb6a5eca 100644 --- a/src/shared/modes.ts +++ b/src/shared/modes.ts @@ -23,13 +23,26 @@ export function getGroupName(group: GroupEntry): ToolGroup { if (typeof group === "string") { return group } - - return group[0] + if (Array.isArray(group)) { + return group[0] + } + // Handle direct MCP object format + if (typeof group === "object" && "mcp" in group) { + return "mcp" as ToolGroup + } + return group as ToolGroup } // Helper to get group options if they exist function getGroupOptions(group: GroupEntry): GroupOptions | undefined { - return Array.isArray(group) ? group[1] : undefined + if (Array.isArray(group)) { + return group[1] + } + // Handle direct MCP object format - return the object itself as options + if (typeof group === "object" && "mcp" in group) { + return group as GroupOptions + } + return undefined } // Helper to check if a file path matches a regex pattern diff --git a/webview-ui/src/components/modes/McpSelector.tsx b/webview-ui/src/components/modes/McpSelector.tsx index 50ce0a1f74..a2a1b57e78 100644 --- a/webview-ui/src/components/modes/McpSelector.tsx +++ b/webview-ui/src/components/modes/McpSelector.tsx @@ -69,45 +69,28 @@ const McpSelector: React.FC = ({ setMcpIncludedList(included) }, [currentMode]) // Handle save - function updateMcpGroupOptions(groups: GroupEntry[] = [], group: string, mcpIncludedList: string[]): GroupEntry[] { - let mcpGroupFound = false - const newGroups = groups - .map((g) => { - if (Array.isArray(g) && g[0] === group) { - mcpGroupFound = true - return [ - group, - { - ...(g[1] || {}), - mcp: mcpIncludedList.length > 0 ? { included: mcpIncludedList } : undefined, - }, - ] as GroupEntry - } - if (typeof g === "string" && g === group) { - mcpGroupFound = true - return [ - group, - { - mcp: mcpIncludedList.length > 0 ? { included: mcpIncludedList } : undefined, - }, - ] as GroupEntry - } - return g - }) - .filter((g) => g !== undefined) + function updateMcpGroupOptions(groups: GroupEntry[] = [], _group: string, mcpIncludedList: string[]): GroupEntry[] { + // Filter out any existing "mcp" entries (both string and object forms) + const filteredGroups = groups.filter((g) => { + if (typeof g === "string") { + return g !== "mcp" + } + if (Array.isArray(g) && g[0] === "mcp") { + return false + } + if (typeof g === "object" && g !== null && !Array.isArray(g) && "mcp" in g) { + return false + } + return true + }) - if (!mcpGroupFound && group === "mcp") { - const groupsWithoutSimpleMcp = newGroups.filter((g) => g !== "mcp") - groupsWithoutSimpleMcp.push([ - "mcp", - { - mcp: mcpIncludedList.length > 0 ? { included: mcpIncludedList } : undefined, - }, - ]) - return groupsWithoutSimpleMcp as GroupEntry[] - } else { - return newGroups as GroupEntry[] + // Add the new MCP configuration if there are selected servers + if (mcpIncludedList.length > 0) { + // Directly add the mcp object without wrapping in an array + return [...filteredGroups, { mcp: { included: mcpIncludedList } }] as GroupEntry[] } + + return filteredGroups as GroupEntry[] } // Handle save diff --git a/webview-ui/src/components/modes/ModesView.tsx b/webview-ui/src/components/modes/ModesView.tsx index f7e4739fe1..1eb2b8bd97 100644 --- a/webview-ui/src/components/modes/ModesView.tsx +++ b/webview-ui/src/components/modes/ModesView.tsx @@ -62,7 +62,13 @@ type ModesViewProps = { // Helper to get group name regardless of format function getGroupName(group: GroupEntry): ToolGroup { - return Array.isArray(group) ? group[0] : group + if (Array.isArray(group)) { + return group[0] + } + if (typeof group === "object" && "mcp" in group) { + return "mcp" as ToolGroup + } + return group as ToolGroup } const ModesView = ({ onDone }: ModesViewProps) => {