diff --git a/core/config/yaml/loadYaml.ts b/core/config/yaml/loadYaml.ts index 44af239df88..40821bb04ca 100644 --- a/core/config/yaml/loadYaml.ts +++ b/core/config/yaml/loadYaml.ts @@ -9,7 +9,6 @@ import { ModelRole, PackageIdentifier, RegistryClient, - TEMPLATE_VAR_REGEX, unrollAssistant, validateConfigYaml, } from "@continuedev/config-yaml"; @@ -237,22 +236,6 @@ export async function configYamlToContinueConfig(options: { sourceFile: doc.sourceFile, })); - config.mcpServers?.forEach((mcpServer) => { - if ("args" in mcpServer) { - const mcpArgVariables = - mcpServer.args?.filter((arg) => TEMPLATE_VAR_REGEX.test(arg)) ?? []; - - if (mcpArgVariables.length === 0) { - return; - } - - localErrors.push({ - fatal: false, - message: `MCP server "${mcpServer.name}" has unsubstituted variables in args: ${mcpArgVariables.join(", ")}. Please refer to https://docs.continue.dev/hub/secrets/secret-types for managing hub secrets.`, - }); - } - }); - // Prompt files - try { const promptFiles = await getAllPromptFiles(ide, undefined, true); diff --git a/core/context/mcp/MCPConnection.ts b/core/context/mcp/MCPConnection.ts index 4090e92c581..88d70970377 100644 --- a/core/context/mcp/MCPConnection.ts +++ b/core/context/mcp/MCPConnection.ts @@ -2,6 +2,10 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { Transport } from "@modelcontextprotocol/sdk/shared/transport.js"; import { fileURLToPath } from "url"; +import { + decodeSecretLocation, + getTemplateVariables, +} from "@continuedev/config-yaml"; import { SSEClientTransport, SseError, @@ -163,6 +167,24 @@ class MCPConnection { } } + const vars = getTemplateVariables(JSON.stringify(this.options)); + const unrendered = vars.map((v) => { + const stripped = v.replace("secrets.", ""); + try { + return decodeSecretLocation(stripped).secretName; + } catch { + return stripped; + } + }); + + if (unrendered.length > 0) { + this.errors.push( + `${this.options.name} MCP Server has unresolved secrets: ${unrendered.join(", ")}. +For personal use you can set the secret in the hub at https://hub.continue.dev/settings/secrets. +Org-level secrets can only be used for MCP by Background Agents (https://docs.continue.dev/hub/agents/overview) when \"Include in Env\" is enabled.`, + ); + } + this.connectionPromise = Promise.race([ // If aborted by a refresh or other, cancel and don't do anything new Promise((resolve) => { diff --git a/extensions/cli/src/services/MCPService.ts b/extensions/cli/src/services/MCPService.ts index 0f8a4b5bc89..01f449eed9d 100644 --- a/extensions/cli/src/services/MCPService.ts +++ b/extensions/cli/src/services/MCPService.ts @@ -1,3 +1,7 @@ +import { + decodeSecretLocation, + getTemplateVariables, +} from "@continuedev/config-yaml"; import { type AssistantConfig } from "@continuedev/sdk"; import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"; @@ -311,7 +315,24 @@ export class MCPService this.connections.set(serverName, connection); this.updateState(); + const vars = getTemplateVariables(JSON.stringify(serverConfig)); + const secretVars = vars.filter((v) => v.startsWith("secrets.")); + const unrendered = secretVars.map((v) => { + return decodeSecretLocation(v.replace("secrets.", "")).secretName; + }); + try { + if (unrendered.length > 0) { + const message = `${serverConfig.name} MCP Server has unresolved secrets: ${unrendered.join(", ")} +For personal use you can set the secret in the hub at https://hub.continue.dev/settings/secrets or pass it to the CLI environment. +Org-level secrets can only be used for MCP by Background Agents (https://docs.continue.dev/hub/agents/overview) when \"Include in Env\" is enabled for the secret.`; + if (this.isHeadless) { + throw new Error(message); + } else { + connection.warnings.push(message); + } + } + const client = await this.getConnectedClient(serverConfig, connection); connection.client = client;