From febdcef3923e4048cc32856d9ae38db841aa0aea Mon Sep 17 00:00:00 2001 From: Victor Wads Date: Wed, 19 Aug 2026 22:22:02 -0300 Subject: [PATCH] feat(mcp): configure desktop root directory - Persist an optional MCP filesystem root selected from Settings - Default file-scoped tools to the user home directory - Apply the configured root when the desktop MCP server restarts --- CHANGELOG.md | 2 +- packages/vue/src/i18n/messages/dialogs.ts | 6 +++ src/app/automation/mcp/preferences.ts | 2 + src/app/automation/mcp/spawn.ts | 12 +++--- .../settings/mcp/MCPSettingsPanel.vue | 39 +++++++++++++++++++ 5 files changed, 53 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d3cbbf0b2..b8fb9200e 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 and choose individual, inspection, or modification tools it exposes from Settings. +- Monitor and restart the local MCP server, configure its root directory, 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 d9937ce1d..092ed3172 100644 --- a/packages/vue/src/i18n/messages/dialogs.ts +++ b/packages/vue/src/i18n/messages/dialogs.ts @@ -253,6 +253,12 @@ export const dialogMessageDefaults = { mcpPort: 'Port', mcpAddress: 'Address', mcpVersion: 'Version', + mcpRootDirectory: 'MCP root directory', + mcpRootDirectoryDefault: 'User home directory (default)', + mcpChooseRootDirectory: 'Choose folder', + mcpUseDefaultRoot: 'Use default', + mcpRootDirectoryDescription: + 'File tools are limited to this folder. Restart the MCP server to apply changes.', mcpTools: 'Available tools', mcpInspectionTools: 'Inspection tools', mcpModificationTools: 'Modification tools', diff --git a/src/app/automation/mcp/preferences.ts b/src/app/automation/mcp/preferences.ts index eeabe62b9..020fb687d 100644 --- a/src/app/automation/mcp/preferences.ts +++ b/src/app/automation/mcp/preferences.ts @@ -3,10 +3,12 @@ import { useLocalStorage } from '@vueuse/core' import { MCP_TOOL_CATALOG, 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' export const configurableMCPTools = MCP_TOOL_CATALOG.filter((tool) => tool.availability !== 'eval') export const disabledMCPTools = useLocalStorage(DISABLED_TOOLS_STORAGE_KEY, []) +export const mcpRootDirectory = useLocalStorage(ROOT_DIRECTORY_STORAGE_KEY, '') export function setMCPToolEnabled(name: string, enabled: boolean): void { const disabled = new Set(disabledMCPTools.value) diff --git a/src/app/automation/mcp/spawn.ts b/src/app/automation/mcp/spawn.ts index 1e4e26bde..c5eea38ca 100644 --- a/src/app/automation/mcp/spawn.ts +++ b/src/app/automation/mcp/spawn.ts @@ -8,7 +8,7 @@ import { decodeTauriStderr } from '@/app/shell/ui' import { resolvePlatformCommand } from '@/app/tauri/command' import { isTauri } from '@/app/tauri/env' -import { disabledMCPToolsCSV } from './preferences' +import { disabledMCPToolsCSV, mcpRootDirectory } from './preferences' export interface AutomationHealth { status: 'ok' | 'no_app' @@ -273,12 +273,10 @@ async function startMCPIfNeeded(): Promise { // Cache only after MCP startup is confirmed healthy. const { Command } = await import('@tauri-apps/plugin-shell') - // Set OPENPENCIL_MCP_ROOT to the user's home directory so file-scoped - // tools (open_file, save_file, export_*) operate on paths inside ~, - // which is naturally writable and matches user expectations for "my files." - // The app bundle directory (Tauri executableDir) is read-only and would - // cause EACCES errors on every file write. - const mcpRoot = await resolveTauriHomeDir() + // Scope file tools to the configured root, defaulting to the user's home. + // The app bundle directory (Tauri executableDir) is read-only and cannot be + // used as a safe fallback for open_file, save_file, or export tools. + const mcpRoot = mcpRootDirectory.value.trim() || (await resolveTauriHomeDir()) const resolved = resolvePlatformCommand(MCP_EXECUTABLE) const command = Command.create(resolved.command, resolved.args, { env: { diff --git a/src/components/settings/mcp/MCPSettingsPanel.vue b/src/components/settings/mcp/MCPSettingsPanel.vue index 3440c3350..7099bed65 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, + mcpRootDirectory, setMCPToolCategoryEnabled, setMCPToolEnabled } from '@/app/automation/mcp/preferences' @@ -45,6 +46,12 @@ function restart(): void { void restartMCPRuntime() } +async function chooseRootDirectory(): Promise { + const { open } = await import('@tauri-apps/plugin-dialog') + const directory = await open({ directory: true, multiple: false }) + if (typeof directory === 'string') mcpRootDirectory.value = directory +} + function isToolEnabled(name: string): boolean { return !disabledToolNames.value.has(name) } @@ -86,6 +93,38 @@ function enableAllTools(): void {
{{ mcpRuntime.version }}
+ +
+
+
+

{{ dialogs.mcpRootDirectory }}

+

+ {{ mcpRootDirectory || dialogs.mcpRootDirectoryDefault }} +

+
+
+ + +
+
+

+ {{ dialogs.mcpRootDirectoryDescription }} +

+