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:
parent
6a78da9eb0
commit
4db24bd124
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
Loading…
Reference in a new issue