* Initialize agent-specific MCP servers * Agents can define their own MCP servers in their frontmatter that are additive * to the parent's MCP clients. These servers are connected when the agent starts * and cleaned up when the agent finishes. * * @param agentDefinition The agent definition wit
( agentDefinition: AgentDefinition, parentClients: MCPServerConnection[], )
| 93 | * @returns Merged clients (parent + agent-specific), agent MCP tools, and cleanup function |
| 94 | */ |
| 95 | async function initializeAgentMcpServers( |
| 96 | agentDefinition: AgentDefinition, |
| 97 | parentClients: MCPServerConnection[], |
| 98 | ): Promise<{ |
| 99 | clients: MCPServerConnection[] |
| 100 | tools: Tools |
| 101 | cleanup: () => Promise<void> |
| 102 | }> { |
| 103 | // If no agent-specific servers defined, return parent clients as-is |
| 104 | if (!agentDefinition.mcpServers?.length) { |
| 105 | return { |
| 106 | clients: parentClients, |
| 107 | tools: [], |
| 108 | cleanup: async () => {}, |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | // When MCP is locked to plugin-only, skip frontmatter MCP servers for |
| 113 | // USER-CONTROLLED agents only. Plugin, built-in, and policySettings agents |
| 114 | // are admin-trusted — their frontmatter MCP is part of the admin-approved |
| 115 | // surface. Blocking them (as the first cut did) breaks plugin agents that |
| 116 | // legitimately need MCP, contradicting "plugin-provided always loads." |
| 117 | const agentIsAdminTrusted = isSourceAdminTrusted(agentDefinition.source) |
| 118 | if (isRestrictedToPluginOnly('mcp') && !agentIsAdminTrusted) { |
| 119 | logForDebugging( |
| 120 | `[Agent: ${agentDefinition.agentType}] Skipping MCP servers: strictPluginOnlyCustomization locks MCP to plugin-only (agent source: ${agentDefinition.source})`, |
| 121 | ) |
| 122 | return { |
| 123 | clients: parentClients, |
| 124 | tools: [], |
| 125 | cleanup: async () => {}, |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | const agentClients: MCPServerConnection[] = [] |
| 130 | // Track which clients were newly created (inline definitions) vs. shared from parent |
| 131 | // Only newly created clients should be cleaned up when the agent finishes |
| 132 | const newlyCreatedClients: MCPServerConnection[] = [] |
| 133 | const agentTools: Tool[] = [] |
| 134 | |
| 135 | for (const spec of agentDefinition.mcpServers) { |
| 136 | let config: ScopedMcpServerConfig | null = null |
| 137 | let name: string |
| 138 | let isNewlyCreated = false |
| 139 | |
| 140 | if (typeof spec === 'string') { |
| 141 | // Reference by name - look up in existing MCP configs |
| 142 | // This uses the memoized connectToServer, so we may get a shared client |
| 143 | name = spec |
| 144 | config = getMcpConfigByName(spec) |
| 145 | if (!config) { |
| 146 | logForDebugging( |
| 147 | `[Agent: ${agentDefinition.agentType}] MCP server not found: ${spec}`, |
| 148 | { level: 'warn' }, |
| 149 | ) |
| 150 | continue |
| 151 | } |
| 152 | } else { |
no test coverage detected