feat(vtex): read-only by default with opt-in write mode - #538
Open
guitavano wants to merge 1 commit into
Open
Conversation
Add a `writeMode` config prop to the VTEX MCP state. The MCP is now read-only by default: read tools (annotated readOnlyHint: true) always run, while any create/update/delete tool is refused unless the connection opts in via `writeMode: true` (or VTEX_WRITE_MODE=true for local dev). A tool is treated as a write unless explicitly annotated readOnlyHint: true, so un-annotated mutations can never slip through the gate. The gate is enforced per-request at execute time (not by filtering tools/list), because @decocms/runtime caches tool registrations for the process lifetime while configuration state is delivered per-request (multi-tenant). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
hugo-ccabral
approved these changes
Aug 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds a
writeModeconfig prop to the VTEX MCP state. The MCP is now read-only by default: read tools always run, while any create/update/delete tool is refused unless the connection opts in viawriteMode: true(orVTEX_WRITE_MODE=truefor local dev).How read vs. write is decided
A single, fail-safe signal: a tool is a read only if it is explicitly annotated
readOnlyHint: true. Everything else is treated as a write — including tools with no annotations at all.This matters: of the ~705 registry tools, only ~303 carry
readOnlyHint: trueand ~321 have no annotation, and those un-annotated ones are overwhelmingly writes (budget/allocation POSTs, etc.). Treating "no annotation" as write means an un-annotated mutation can never slip through the gate in read-only mode. All genuine reads (including the 5 custom read tools) already carryreadOnlyHint: true; the 2 custom writes carry none and are correctly gated.Why gate at execute time, not by filtering
tools/list@decocms/runtimeresolves and caches tool registrations once for the process lifetime (tools.tsif (cached) return cached), while configuration state is delivered per-request (multi-tenant — the same reasontool-adapter.tsalready reads credentials only fromruntimeContext). Filtering the list bystate.writeModewould freeze whatever the first request sent for every subsequent tenant. So the gate reads state per-request at execute time, exactly like credential resolution.Consequence: write tools still appear in
tools/list, but return a clear error if invoked without write mode. The security intent (read-only default, write opt-in) is fully enforced. If deployment is single-tenant, we could additionally hide writes from the list safely — happy to follow up.Changes
server/lib/write-mode.ts(new) —resolveWriteMode,isReadOnlyTool,assertWriteModeEnabled.server/types/env.ts—writeMode: z.boolean().optional()onStateSchema.server/lib/tool-adapter.ts— every generated tool is gated: non-readOnlyHinttools callassertWriteModeEnabledinexecute.server/tools/custom/{reorder-collection,update-product-specifications}.ts— the 2 custom writes gated (they bypass the adapter).app.json—writeModeadded toconfigSchema(boolean, defaultfalse).README.md,.env.example— docs.server/lib/write-mode.test.ts(new) — 11 tests.Testing
bun test→ 118 pass / 0 fail.tsc --noEmit→ no errors in project files (the only 2 errors are internal to@decocms/runtimeinnode_modules, pre-existing).🤖 Generated with Claude Code
Summary by cubic
Default VTEX MCP to read-only with opt-in write mode. Previously all tools ran when authorized; now only tools annotated
readOnlyHint: truerun by default, and any unannotated or destructive tool requireswriteMode: true. Write tools still appear intools/listbut return a clear error in read-only mode. Enforcement happens per request at execute time to avoid@decocms/runtimeregistration caching issues.writeModeto state schema andapp.json(defaultfalse) andVTEX_WRITE_MODEenv override for local dev.resolveWriteMode,isReadOnlyTool, andassertWriteModeEnabled; integrated gating intool-adapter.tsand the two custom write tools.readOnlyHint: trueas read; all other tools are writes.Rollout/Migration
writeMode: truein the connection configuration orVTEX_WRITE_MODE=truefor local development.Written for commit 0d6dde4. Summary will update on new commits.