diff --git a/packages/@n8n/api-types/src/schemas/mcp.schema.ts b/packages/@n8n/api-types/src/schemas/mcp.schema.ts index a9b4dda420d..0690bc71b59 100644 --- a/packages/@n8n/api-types/src/schemas/mcp.schema.ts +++ b/packages/@n8n/api-types/src/schemas/mcp.schema.ts @@ -39,7 +39,7 @@ export type McpScope = (typeof MCP_INSTANCE_SCOPES)[number]; export type McpClientType = 'cli' | 'ide' | 'editor' | 'assistant'; /** Known client brands, used by the FE to pick the logo shown next to a client. */ -export type McpClientBrandName = 'claude' | 'cursor' | 'vscode' | 'openai'; +export type McpClientBrandName = 'claude' | 'cursor' | 'vscode' | 'openai' | 'mistral'; /** * Client names are free-form (self-reported at OAuth registration), so known @@ -59,6 +59,7 @@ export const MCP_CLIENT_BRAND_MATCHERS: ReadonlyArray<{ { pattern: /(visual studio code|vs ?code)/i, brand: 'vscode', type: 'editor' }, { pattern: /codex/i, brand: 'openai', type: 'cli' }, { pattern: /chatgpt|openai/i, brand: 'openai', type: 'assistant' }, + { pattern: /mistral|\bvibe\b/i, brand: 'mistral', type: 'assistant' }, ]; export function getMcpClientType(clientName: string): McpClientType | null { diff --git a/packages/@n8n/telemetry/src/events/mcp.ts b/packages/@n8n/telemetry/src/events/mcp.ts index 1a9023124fe..e517915d284 100644 --- a/packages/@n8n/telemetry/src/events/mcp.ts +++ b/packages/@n8n/telemetry/src/events/mcp.ts @@ -8,7 +8,7 @@ import { defineTelemetryEvents } from '../define'; * dialog's client slug is. Null when the name matches no known brand. */ const clientBrand = z - .enum(['claude', 'cursor', 'vscode', 'openai']) + .enum(['claude', 'cursor', 'vscode', 'openai', 'mistral']) .nullable() .describe('Client brand, mirroring MCP_CLIENT_BRAND_MATCHERS in @n8n/api-types'); diff --git a/packages/frontend/editor-ui/src/features/ai/mcpAccess/assets/client-icons/mistral.svg b/packages/frontend/editor-ui/src/features/ai/mcpAccess/assets/client-icons/mistral.svg new file mode 100644 index 00000000000..22bed73f26c --- /dev/null +++ b/packages/frontend/editor-ui/src/features/ai/mcpAccess/assets/client-icons/mistral.svg @@ -0,0 +1 @@ +Mistral AI diff --git a/packages/frontend/editor-ui/src/features/ai/mcpAccess/clients.utils.test.ts b/packages/frontend/editor-ui/src/features/ai/mcpAccess/clients.utils.test.ts index 6200c9486dc..6ce7514c457 100644 --- a/packages/frontend/editor-ui/src/features/ai/mcpAccess/clients.utils.test.ts +++ b/packages/frontend/editor-ui/src/features/ai/mcpAccess/clients.utils.test.ts @@ -12,6 +12,8 @@ describe('getClientBrand', () => { ['Visual Studio Code', 'editor'], ['Codex CLI', 'cli'], ['ChatGPT', 'assistant'], + ['Mistral Vibe', 'assistant'], + ['Vibe', 'assistant'], ['Some Unknown Client', null], ])('derives the type of %s as %s', (name, type) => { expect(getClientBrand(name).type).toBe(type); @@ -20,6 +22,7 @@ describe('getClientBrand', () => { it('resolves a logo for recognized brands and none for unknown clients', () => { expect(getClientBrand('Claude Code').icon).not.toBeNull(); expect(getClientBrand('Cursor').icon).not.toBeNull(); + expect(getClientBrand('Mistral Vibe').icon).not.toBeNull(); expect(getClientBrand('Some Unknown Client').icon).toBeNull(); }); }); diff --git a/packages/frontend/editor-ui/src/features/ai/mcpAccess/clients.utils.ts b/packages/frontend/editor-ui/src/features/ai/mcpAccess/clients.utils.ts index 36cf181c4bf..e1281cb61d4 100644 --- a/packages/frontend/editor-ui/src/features/ai/mcpAccess/clients.utils.ts +++ b/packages/frontend/editor-ui/src/features/ai/mcpAccess/clients.utils.ts @@ -11,6 +11,7 @@ import type { BaseTextKey } from '@n8n/i18n'; import ClaudeIcon from './assets/client-icons/claude.svg?component'; import CursorIcon from './assets/client-icons/cursor.svg?component'; +import MistralIcon from './assets/client-icons/mistral.svg?component'; import OpenAiIcon from './assets/client-icons/openai.svg?component'; import VsCodeIcon from './assets/client-icons/vscode.svg?component'; @@ -25,6 +26,7 @@ const BRAND_ICONS: Record = { cursor: CursorIcon, vscode: VsCodeIcon, openai: OpenAiIcon, + mistral: MistralIcon, }; // Client names are bounded (a user's own registered clients), so memoizing the diff --git a/packages/frontend/editor-ui/src/features/ai/mcpAccess/mcp.clients.catalog.test.ts b/packages/frontend/editor-ui/src/features/ai/mcpAccess/mcp.clients.catalog.test.ts index 8ec3345b375..8b98035be6b 100644 --- a/packages/frontend/editor-ui/src/features/ai/mcpAccess/mcp.clients.catalog.test.ts +++ b/packages/frontend/editor-ui/src/features/ai/mcpAccess/mcp.clients.catalog.test.ts @@ -10,7 +10,7 @@ describe('getMcpClientCatalog', () => { expect(catalog.map((group) => group.id)).toEqual(['cli', 'web', 'ide']); expect(catalog.map((group) => group.clients.map((client) => client.id))).toEqual([ ['claude-code', 'codex', 'gemini-cli'], - ['claude-ai', 'chatgpt'], + ['claude-ai', 'chatgpt', 'mistral-vibe'], ['cursor', 'vscode', 'windsurf'], ]); }); diff --git a/packages/frontend/editor-ui/src/features/ai/mcpAccess/mcp.clients.catalog.ts b/packages/frontend/editor-ui/src/features/ai/mcpAccess/mcp.clients.catalog.ts index f875fb6f206..f2a9ee5f0fa 100644 --- a/packages/frontend/editor-ui/src/features/ai/mcpAccess/mcp.clients.catalog.ts +++ b/packages/frontend/editor-ui/src/features/ai/mcpAccess/mcp.clients.catalog.ts @@ -4,6 +4,7 @@ import ClaudeIcon from './assets/client-icons/claude.svg?component'; import CodexIcon from './assets/client-icons/codex.svg?component'; import CursorIcon from './assets/client-icons/cursor.svg?component'; import GeminiIcon from './assets/client-icons/gemini.svg?component'; +import MistralIcon from './assets/client-icons/mistral.svg?component'; import OpenAiIcon from './assets/client-icons/openai.svg?component'; import VsCodeIcon from './assets/client-icons/vscode.svg?component'; @@ -141,6 +142,13 @@ url = "${serverUrl}"`; addUrl: 'https://chatgpt.com/plugins#settings/Connectors?create-connector=true&redirectAfter=%2Fplugins', }, + { + id: 'mistral-vibe', + name: 'Mistral Vibe', + category: 'web', + icon: MistralIcon, + addUrl: 'https://chat.mistral.ai/connections/marketplace?search=n8n&template=n8n', + }, ], }, {