diff --git a/server/modules/providers/services/mcp.service.ts b/server/modules/providers/services/mcp.service.ts index 9ab31bad..54592b60 100644 --- a/server/modules/providers/services/mcp.service.ts +++ b/server/modules/providers/services/mcp.service.ts @@ -1,7 +1,18 @@ +import os from 'node:os'; + import { providerRegistry } from '@/modules/providers/provider.registry.js'; import type { LLMProvider, McpScope, ProviderMcpServer, UpsertProviderMcpServerInput } from '@/shared/types.js'; import { AppError } from '@/shared/utils.js'; +/** Cursor MCP is not supported on Windows hosts (no Cursor CLI integration). */ +function includeProviderInGlobalMcp(providerId: LLMProvider): boolean { + if (providerId === 'cursor' && os.platform() === 'win32') { + return false; + } + + return true; +} + export const providerMcpService = { /** @@ -83,7 +94,7 @@ export const providerMcpService = { const scope = input.scope ?? 'project'; const results: Array<{ provider: LLMProvider; created: boolean; error?: string }> = []; - const providers = providerRegistry.listProviders(); + const providers = providerRegistry.listProviders().filter((p) => includeProviderInGlobalMcp(p.id)); for (const provider of providers) { try { await provider.mcp.upsertServer({ ...input, scope }); diff --git a/server/modules/providers/tests/mcp.test.ts b/server/modules/providers/tests/mcp.test.ts index 30e9ab68..495f1027 100644 --- a/server/modules/providers/tests/mcp.test.ts +++ b/server/modules/providers/tests/mcp.test.ts @@ -255,7 +255,8 @@ test('providerMcpService global adder writes to all providers and rejects unsupp workspacePath, }); - assert.equal(globalResult.length, 4); + const expectCursorGlobal = process.platform !== 'win32'; + assert.equal(globalResult.length, expectCursorGlobal ? 4 : 3); assert.ok(globalResult.every((entry) => entry.created === true)); const claudeProject = await readJson(path.join(workspacePath, '.mcp.json')); @@ -267,8 +268,10 @@ test('providerMcpService global adder writes to all providers and rejects unsupp const geminiProject = await readJson(path.join(workspacePath, '.gemini', 'settings.json')); assert.ok((geminiProject.mcpServers as Record)['global-http']); - const cursorProject = await readJson(path.join(workspacePath, '.cursor', 'mcp.json')); - assert.ok((cursorProject.mcpServers as Record)['global-http']); + if (expectCursorGlobal) { + const cursorProject = await readJson(path.join(workspacePath, '.cursor', 'mcp.json')); + assert.ok((cursorProject.mcpServers as Record)['global-http']); + } await assert.rejects( providerMcpService.addMcpServerToAllProviders({ diff --git a/server/routes/settings.js b/server/routes/settings.js index 7eee2454..e2ce0885 100644 --- a/server/routes/settings.js +++ b/server/routes/settings.js @@ -273,4 +273,14 @@ router.post('/push/unsubscribe', async (req, res) => { } }); +// Host OS for UI (e.g. hide Cursor agent when the backend runs on Windows). +router.get('/server-env', async (req, res) => { + try { + res.json({ platform: process.platform }); + } catch (error) { + console.error('Error reading server environment:', error); + res.status(500).json({ error: 'Failed to read server environment' }); + } +}); + export default router; diff --git a/src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx b/src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx index 9eaf690e..7c4f21c2 100644 --- a/src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx +++ b/src/components/chat/view/subcomponents/ProviderSelectionEmptyState.tsx @@ -1,6 +1,7 @@ -import React from "react"; +import React, { useEffect, useMemo } from "react"; import { Check, ChevronDown } from "lucide-react"; import { useTranslation } from "react-i18next"; +import { useServerPlatform } from "../../../../hooks/useServerPlatform"; import SessionProviderLogo from "../../../llm-logo-provider/SessionProviderLogo"; import { CLAUDE_MODELS, @@ -115,6 +116,23 @@ export default function ProviderSelectionEmptyState({ setInput, }: ProviderSelectionEmptyStateProps) { const { t } = useTranslation("chat"); + const { isWindowsServer } = useServerPlatform(); + + const visibleProviders = useMemo( + () => + isWindowsServer + ? PROVIDERS.filter((p) => p.id !== "cursor") + : PROVIDERS, + [isWindowsServer], + ); + + useEffect(() => { + if (isWindowsServer && provider === "cursor") { + setProvider("claude"); + localStorage.setItem("selected-provider", "claude"); + } + }, [isWindowsServer, provider, setProvider]); + const nextTaskPrompt = t("tasks.nextTaskPrompt", { defaultValue: "Start the next task", }); @@ -166,8 +184,10 @@ export default function ProviderSelectionEmptyState({ {/* Provider cards — horizontal row, equal width */} -
- {PROVIDERS.map((p) => { +
= 4 ? "sm:grid-cols-4" : "sm:grid-cols-3"}`} + > + {visibleProviders.map((p) => { const active = provider === p.id; return (