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
This commit is contained in:
Victor Wads 2026-08-19 22:52:47 -03:00
parent 6a78da9eb0
commit 4db24bd124
No known key found for this signature in database
6 changed files with 37 additions and 5 deletions

View file

@ -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)

View file

@ -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',

View file

@ -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<MCPToolCatalogEntry[]>([])
export const disabledMCPTools = useLocalStorage<string[]>(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')

View file

@ -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<AutomationServerHandle | null> {
const executableAvailable = await invoke<boolean>('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<AutomationServerHandle | null> {
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<AutomationServerHandle | null> {
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

View file

@ -5,6 +5,7 @@ import { useI18n } from '@open-pencil/vue'
import {
configurableMCPTools,
disabledMCPTools,
mcpAuthenticationEnabled,
mcpRootDirectory,
setMCPToolCategoryEnabled,
setMCPToolEnabled
@ -94,6 +95,22 @@ function enableAllTools(): void {
</template>
</dl>
<div class="mt-3 border-t border-border pt-3">
<div class="flex items-center justify-between gap-3">
<div>
<p class="text-[10px] font-medium text-surface">{{ dialogs.mcpAuthentication }}</p>
<p class="mt-0.5 text-[10px] leading-relaxed text-muted">
{{ dialogs.mcpAuthenticationDescription }}
</p>
</div>
<AppSwitch
v-model="mcpAuthenticationEnabled"
:label="dialogs.mcpAuthentication"
data-test-id="settings-mcp-authentication"
/>
</div>
</div>
<div class="mt-3 border-t border-border pt-3">
<div class="flex items-center justify-between gap-3">
<div class="min-w-0">

View file

@ -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')