You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Parent epic: #9 Status: Implemented — all deliverables complete, E2E release-gate pending Priority: High
Background
data-studio-mcp exposes ~63 database tools across ES, MongoDB, DynamoDB (via dockit) and PostgreSQL, MySQL, SQL Server, SQLite (via sqlkit). These tools have different risk levels — reading data vs. creating indexes vs. deleting tables. We need a permission model that:
Confirms dangerous operations in the agent client (Claude Code, Cursor, OpenCode, Codex, Pi) — the UI the user is actually in
Enforces policy server-side at the bridge — the final safety boundary, even if the client fails to intercept
Research: How the MCP ecosystem actually handles this
The MCP spec (2025-06-18) has NO built-in confirmation/permission mechanism.
Tools are "model-controlled"; the protocol does not mandate any user-interaction model
Spec security recommendations only say clients SHOULD prompt for confirmation on sensitive operations
elicitation is a server→client request for structured input (usernames, tokens) — not a confirmation gate; servers MUST NOT use it to collect sensitive info
No mainstream MCP server uses a two-step confirm/execute tool pair — that pattern is not recognized by the ecosystem
What the ecosystem actually does — two separated layers:
Layer
Mechanism
Examples
UX confirmation (client)
Client intercepts tools/callbefore sending, shows native dialog that stays open until the user decides
Key insight: Confirmation UX is 100% client-side and happens before the server ever receives the call. The server's job is (a) to expose honest risk metadata via ToolAnnotations, and (b) to enforce policy statically so a client that fails to intercept (bypass mode, malicious client) is still blocked.
Desktop apps (dockit/sqlkit) do not participate in MCP confirmations — they only execute. The built-in AI assistant has its own existing confirmation flow (ConfirmMap + tool-confirmation-card) and is out of scope.
Solution Design
1. Risk vocabulary: ToolAnnotations (MCP server)
Map each capability's RiskLevel to the standard ToolAnnotations in tools/list:
RiskLevel
readOnlyHint
destructiveHint
idempotentHint
openWorldHint
Safe (read/query/metadata)
true
false
true
false
Elevated (insert/update/create)
false
false
false
true
Destructive (delete/drop/truncate)
false
true
false
true
Bridge already exposes metadata.riskLevel per tool in /tools — MCP server maps it to annotations
destructiveHint: true is what makes Claude Code / Cursor / OpenCode show their native confirm dialogs
Stored per app (.store.dat via tauri-plugin-store), read on every /tools and /invoke
confirm_destructive is a policy hint surfaced in /tools policy payload so the MCP server can strengthen annotations when needed — the actual confirmation UI remains the client's job
/tools additionally filters tool list by mode when running in read-only mode:
/tools?readonly=true (or policy.mode == ReadOnly)
→ only capabilities whose riskLevel == Safe are listed
→ MCP server never sees write/delete tools (Neon-style)
3. Optional read-only mode (MCP server)
Neon-style flag: --readonly (or env DATA_STUDIO_MCP_READONLY=1) — MCP server filters backend tools to riskLevel == Safe before advertising them. Defense in depth for "query-only" deployments.
4. Client-side documentation (per agent)
Users configure which tools require confirmation in their client. We document the exact configs:
Remaining: E2E release-gate test (real dockit/sqlkit + real client — destructive tool prompts in client UI; deny blocks; bypass-mode client still gets 403 from bridge).
[Phase 4] MCP Permission Model — client-driven confirmation + server-enforced policy
Parent epic: #9
Status: Implemented — all deliverables complete, E2E release-gate pending
Priority: High
Background
data-studio-mcp exposes ~63 database tools across ES, MongoDB, DynamoDB (via dockit) and PostgreSQL, MySQL, SQL Server, SQLite (via sqlkit). These tools have different risk levels — reading data vs. creating indexes vs. deleting tables. We need a permission model that:
Research: How the MCP ecosystem actually handles this
The MCP spec (2025-06-18) has NO built-in confirmation/permission mechanism.
elicitationis a server→client request for structured input (usernames, tokens) — not a confirmation gate; servers MUST NOT use it to collect sensitive infoWhat the ecosystem actually does — two separated layers:
tools/callbefore sending, shows native dialog that stays open until the user decidespermissions.askrules, OpenCodepermissionconfig, CursormcpAllowlist+ auto-review, Codexapproval_modeToolAnnotationsintools/list:readOnlyHint,destructiveHint,idempotentHint,openWorldHint— clients use these to decide when to promptreadonly=truehides write tools)Key insight: Confirmation UX is 100% client-side and happens before the server ever receives the call. The server's job is (a) to expose honest risk metadata via ToolAnnotations, and (b) to enforce policy statically so a client that fails to intercept (bypass mode, malicious client) is still blocked.
Architecture
Desktop apps (dockit/sqlkit) do not participate in MCP confirmations — they only execute. The built-in AI assistant has its own existing confirmation flow (ConfirmMap + tool-confirmation-card) and is out of scope.
Solution Design
1. Risk vocabulary: ToolAnnotations (MCP server)
Map each capability's
RiskLevelto the standardToolAnnotationsintools/list:truefalsetruefalsefalsefalsefalsetruefalsetruefalsetruemetadata.riskLevelper tool in/tools— MCP server maps it to annotationsdestructiveHint: trueis what makes Claude Code / Cursor / OpenCode show their native confirm dialogs2. Server-enforced policy: McpPolicy (bridge)
.store.datvia tauri-plugin-store), read on every/toolsand/invokeconfirm_destructiveis a policy hint surfaced in/toolspolicy payload so the MCP server can strengthen annotations when needed — the actual confirmation UI remains the client's jobDecision flow in
/invoke:/toolsadditionally filters tool list by mode when running in read-only mode:3. Optional read-only mode (MCP server)
Neon-style flag:
--readonly(or envDATA_STUDIO_MCP_READONLY=1) — MCP server filters backend tools toriskLevel == Safebefore advertising them. Defense in depth for "query-only" deployments.4. Client-side documentation (per agent)
Users configure which tools require confirmation in their client. We document the exact configs:
settings.json:"permissions": { "ask": ["mcp__data-studio__*delete*", "mcp__data-studio__*drop*", "mcp__data-studio__*truncate*"] }(orask: ["mcp__data-studio__*"]for everything)opencode.json:"permission": { "mcp__data-studio__*": "ask" }(or granular per-tool)permissions.json: omit frommcpAllowlist→ routed to auto-review/safety classifier;beforeMCPExecutionhooks as neededconfig.toml:default_tools_approval_mode = "writes"+ per-tool overridesImplementation Plan
1. data-studio-agent — permission types ✅ (done)
McpPermissionMode,McpPolicy,ConnectionMcpOverride+allows(),is_connection_allowed(),is_connection_read_only()+ 6 unit tests (PR #20).confirm_destructiveadded withdecide()(Allow/Ask/Deny),deny_reason(),policy_notice()— 90 unit tests (v0.1.5).2. MCP server — ToolAnnotations + read-only mode (TypeScript)
3. Bridge — McpPolicy enforcement (dockit + sqlkit)
4. Settings UI (dockit + sqlkit)
5. Documentation — client permission configs
Per-agent setup guide (Claude Code / OpenCode / Cursor / Codex) so dangerous tools prompt in the client UI.
Testing
/invoke403 paths (mode, allowlist, override);/toolsread-only filteringDeliverables
McpPermissionMode,McpPolicy,ConnectionMcpOverridetypes + tests (PR feat: MCP permission model — PolicyAction, ToolAnnotations, --readonly, unified server-side Deny #20 → master, v0.1.5)confirm_destructivefield toMcpPolicy+ tests (incl.PolicyActionAllow/Ask/Deny,deny_reason(),policy_notice())RiskLevel→ToolAnnotationsintools/list(ANNOTATIONS_BY_RISKin tools.ts)--readonlymode flag (filter non-Safe tools) — published as npm 0.1.4/invoke(mode → allowlist → override → 403 with actionabledeny_reason()guidance)/tools+policy_notice()appended to surviving tool descriptionsPolicyActionthree-value decision + server-sideDenyshort-circuit in the built-in agent loop (should_denyin loop_runner)McpActionper-connection action allowlist (read/write/delete), supersedes the booleanread_onlyoverrideRemaining: E2E release-gate test (real dockit/sqlkit + real client — destructive tool prompts in client UI; deny blocks; bypass-mode client still gets 403 from bridge).
References