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
This commit is contained in:
Victor Wads 2026-08-19 22:22:02 -03:00
parent d807828270
commit febdcef392
No known key found for this signature in database
5 changed files with 53 additions and 8 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 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)

View file

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

View file

@ -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<string[]>(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)

View file

@ -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<AutomationServerHandle | null> {
// 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: {

View file

@ -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<void> {
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 {
<dd class="font-mono text-surface">{{ mcpRuntime.version }}</dd>
</template>
</dl>
<div class="mt-3 border-t border-border pt-3">
<div class="flex items-center justify-between gap-3">
<div class="min-w-0">
<p class="text-[10px] font-medium text-surface">{{ dialogs.mcpRootDirectory }}</p>
<p class="mt-0.5 truncate font-mono text-[10px] text-muted">
{{ mcpRootDirectory || dialogs.mcpRootDirectoryDefault }}
</p>
</div>
<div class="flex shrink-0 gap-1.5">
<button
v-if="mcpRootDirectory"
type="button"
class="rounded border border-border px-2 py-1 text-[10px] text-muted hover:bg-hover hover:text-surface"
@click="mcpRootDirectory = ''"
>
{{ dialogs.mcpUseDefaultRoot }}
</button>
<button
type="button"
class="rounded border border-border px-2 py-1 text-[10px] text-surface hover:bg-hover"
data-test-id="settings-mcp-root-directory"
@click="chooseRootDirectory"
>
{{ dialogs.mcpChooseRootDirectory }}
</button>
</div>
</div>
<p class="mt-1.5 text-[10px] leading-relaxed text-muted">
{{ dialogs.mcpRootDirectoryDescription }}
</p>
</div>
</div>
<p