From 4db24bd124792b73b4f09a728161d36ad4f29de5 Mon Sep 17 00:00:00 2001 From: Victor Wads Date: Wed, 19 Aug 2026 22:52:47 -0300 Subject: [PATCH] feat(mcp): allow disabling local authentication - Persist whether the desktop MCP server requires a bearer token - Start localhost MCP without a generated token when authentication is disabled - Warn in Settings and require a server restart to apply the preference --- CHANGELOG.md | 2 +- packages/vue/src/i18n/messages/dialogs.ts | 3 +++ src/app/automation/mcp/preferences.ts | 2 ++ src/app/automation/mcp/spawn.ts | 8 ++++---- .../settings/mcp/MCPSettingsPanel.vue | 17 +++++++++++++++++ tests/e2e/settings/credentials.spec.ts | 10 ++++++++++ 6 files changed, 37 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b8fb9200e..2f5638a19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ - Run Pi through AI SDK HarnessAgent as a configurable desktop provider with multiple saved model profiles, secure credentials, existing MCP design tools, and per-profile thinking and permission settings. - Open multiple selected design files in separate tabs. - Let Figma API scripts and automation combine components into variant sets. -- Monitor and restart the local MCP server, configure its root directory, and choose individual, inspection, or modification tools it exposes from Settings. +- Monitor and restart the local MCP server, configure its root directory or authentication, and choose individual, inspection, or modification tools it exposes from Settings. - Add deterministic two-browser collaboration coverage for bidirectional edits, awareness, departure cleanup, partitioned-peer convergence, and reconnect synchronization without public network dependencies. (#530) - Import, render, edit, resize, select, and export Figma text-on-path layers while preserving their curved glyph layout. - Show Figma-style temporary distance measurements between selected and Option/Alt-hovered layers. (#491) diff --git a/packages/vue/src/i18n/messages/dialogs.ts b/packages/vue/src/i18n/messages/dialogs.ts index 092ed3172..a5630cfd2 100644 --- a/packages/vue/src/i18n/messages/dialogs.ts +++ b/packages/vue/src/i18n/messages/dialogs.ts @@ -253,6 +253,9 @@ export const dialogMessageDefaults = { mcpPort: 'Port', mcpAddress: 'Address', mcpVersion: 'Version', + mcpAuthentication: 'Require authentication', + mcpAuthenticationDescription: + 'Protect the localhost MCP endpoint with a bearer token. Disable only on a trusted machine. Restart the server to apply changes.', mcpRootDirectory: 'MCP root directory', mcpRootDirectoryDefault: 'User home directory (default)', mcpChooseRootDirectory: 'Choose folder', diff --git a/src/app/automation/mcp/preferences.ts b/src/app/automation/mcp/preferences.ts index cc38476e7..841d621d7 100644 --- a/src/app/automation/mcp/preferences.ts +++ b/src/app/automation/mcp/preferences.ts @@ -5,11 +5,13 @@ import type { MCPToolCatalogEntry } from '@open-pencil/mcp/tools' const DISABLED_TOOLS_STORAGE_KEY = 'open-pencil:mcp:disabled-tools' const ROOT_DIRECTORY_STORAGE_KEY = 'open-pencil:mcp:root-directory' +const AUTHENTICATION_ENABLED_STORAGE_KEY = 'open-pencil:mcp:authentication-enabled' export const configurableMCPTools = ref([]) export const disabledMCPTools = useLocalStorage(DISABLED_TOOLS_STORAGE_KEY, []) export const mcpRootDirectory = useLocalStorage(ROOT_DIRECTORY_STORAGE_KEY, '') +export const mcpAuthenticationEnabled = useLocalStorage(AUTHENTICATION_ENABLED_STORAGE_KEY, true) export function setMCPToolCatalog(tools: MCPToolCatalogEntry[]): void { configurableMCPTools.value = tools.filter((tool) => tool.availability !== 'eval') diff --git a/src/app/automation/mcp/spawn.ts b/src/app/automation/mcp/spawn.ts index c7087e423..3e715209a 100644 --- a/src/app/automation/mcp/spawn.ts +++ b/src/app/automation/mcp/spawn.ts @@ -9,7 +9,7 @@ import { decodeTauriStderr } from '@/app/shell/ui' import { resolvePlatformCommand } from '@/app/tauri/command' import { isTauri } from '@/app/tauri/env' -import { disabledMCPToolsCSV, mcpRootDirectory } from './preferences' +import { disabledMCPToolsCSV, mcpAuthenticationEnabled, mcpRootDirectory } from './preferences' export interface AutomationHealth { status: 'ok' | 'no_app' @@ -271,7 +271,7 @@ async function startMCPIfNeeded(): Promise { const executableAvailable = await invoke('mcp_executable_available') if (!executableAvailable) return rememberStartupError(missingMCPError()) - const authToken = randomHex(32) + const authToken = mcpAuthenticationEnabled.value ? randomHex(32) : null // Cache only after MCP startup is confirmed healthy. const { Command } = await import('@tauri-apps/plugin-shell') @@ -283,7 +283,7 @@ async function startMCPIfNeeded(): Promise { const command = Command.create(resolved.command, resolved.args, { env: { PORT: String(AUTOMATION_HTTP_PORT), - OPENPENCIL_MCP_AUTH_TOKEN: authToken, + OPENPENCIL_MCP_AUTH_TOKEN: authToken ?? '', OPENPENCIL_MCP_CORS_ORIGIN: window.location.origin, OPENPENCIL_MCP_TCP: '1', OPENPENCIL_MCP_ROOT: mcpRoot, @@ -333,7 +333,7 @@ async function startMCPIfNeeded(): Promise { assertCompatibleMCPVersion(health) const discoveryPath = await resolveDiscoveryPath(health.discoveryPath) const discovered = await readDiscoveryToken(discoveryPath) - const token = discovered ?? authToken + const token = health.authRequired ? (discovered ?? authToken) : null spawnedToken = token runtimeAutomationAuthToken = token runtimeAutomationStartupError = null diff --git a/src/components/settings/mcp/MCPSettingsPanel.vue b/src/components/settings/mcp/MCPSettingsPanel.vue index d627e0b72..63a6cb810 100644 --- a/src/components/settings/mcp/MCPSettingsPanel.vue +++ b/src/components/settings/mcp/MCPSettingsPanel.vue @@ -5,6 +5,7 @@ import { useI18n } from '@open-pencil/vue' import { configurableMCPTools, disabledMCPTools, + mcpAuthenticationEnabled, mcpRootDirectory, setMCPToolCategoryEnabled, setMCPToolEnabled @@ -94,6 +95,22 @@ function enableAllTools(): void { +
+
+
+

{{ dialogs.mcpAuthentication }}

+

+ {{ dialogs.mcpAuthenticationDescription }} +

+
+ +
+
+
diff --git a/tests/e2e/settings/credentials.spec.ts b/tests/e2e/settings/credentials.spec.ts index b799041d1..e2db9b696 100644 --- a/tests/e2e/settings/credentials.spec.ts +++ b/tests/e2e/settings/credentials.spec.ts @@ -82,6 +82,16 @@ test('MCP automation settings filter and persist tool availability', async ({ pa await page.getByTestId('app-settings-trigger').click() await page.getByTestId('settings-section-mcp').click() + const authentication = page.getByTestId('settings-mcp-authentication') + await expect(authentication).toHaveAttribute('data-state', 'checked') + await authentication.click() + await page.reload() + await canvas.waitForInit() + await page.getByTestId('app-settings-trigger').click() + await page.getByTestId('settings-section-mcp').click() + await expect(authentication).toHaveAttribute('data-state', 'unchecked') + await authentication.click() + const search = page.getByTestId('settings-mcp-tool-search') await search.fill('create_shape') await expect(search).toHaveValue('create_shape')