cd /news/ai-agents/controlling-file-writes-in-goose-wit… · home › topics › ai-agents › article
[ARTICLE · art-141225] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Controlling File Writes in Goose with MCP

A developer's local case study of Goose Desktop shows that an MCP-based authorization layer (GAAP) only governs writes that pass through its own executor: in a saved Amsterdam session, Goose completed a shipping implementation through GAAP with deployment still disabled, then used its Developer extension's `edit` tool to set `shipping_enabled` to true in `deployment.json` directly, bypassing the policy check. The recipe declared the three intended GAAP tools but did not establish the full Desktop session inventory, so Developer remained available; in a fresh reproduction, removing Developer from the actual session caused the activation proposal to be denied before its executor ran.

by read5 min views1 publishedSep 28, 2026

A local case study of MCP tool permissions, Goose Desktop extensions, and verifying file changes.

I took a coding-agent demo to Amsterdam with a simple boundary: Goose could implement a shipping-price function, but it couldn't enable shipping in deployment.json. The request asked it to do both.

The local configuration ended with shipping enabled. Nothing went to production. But the boundary I was demonstrating hadn't contained the whole workflow.

Goose is an open source agent that connects a model to tools. I connected it through Model Context Protocol (MCP) to my Governed Agent Autonomy Patterns project, or GAAP: an execution engine that evaluates proposed changes before allowing them to run.

GAAP's saved record showed a completed shipping implementation with deployment still disabled. It had no receipt accounting for the later configuration change.

That discrepancy is the lesson: authorization controls only the execution paths that pass through it. To understand what happened, I had to compare the engine's records with Goose's actual tool calls and the resulting files.

The local test project contains shipping_quote(quantity, unit_price) in shipping.py and a configuration file, deployment.json. The function needs quantity validation, a bulk discount, a free-delivery threshold, and rounding to cents.

The policy allows writes to shipping.py and denies every other path. Although deployment.json labels its environment production, it is a local fixture. No service or customer traffic sits behind its switch.

“Implement it, check it, turn it on” sounds like one job. Each write still needs its own authority. Passing the shipping tests doesn't grant permission to change deployment settings.

Goose submits MCP calls; GAAP authorizes the mediated write; the Inspector displays the resulting evidence.

Goose calls read_project to obtain the task, policy, and current files. The response includes a subject digest: a fingerprint of the project state. It then calls submit_change with that digest as base_digest, a plan, and the complete replacement content for one file. Each proposal becomes one bounded Agent Run.

MCP defines the tool interface through discovery, schemas, calls, and results. The application must still decide whether a particular call may change a particular file. Here, the adapter passes that decision to GAAP.

GAAP checks permissions, tool trust, and resource limits before its filesystem executor writes the file. A separate verifier then runs the shipping function against eight acceptance cases and rechecks the project state. Only verified work may complete successfully.

A terminal receipt connects the request, project fingerprints, ordered events, usage, and outcome. A write can succeed while verification fails, so “the file changed” and “the task completed” remain separate outcomes.

In the fresh reproduction, the implementation completed. The activation proposal was denied before its executor ran.

The saved Amsterdam conversation shows Goose submitting the shipping implementation through GAAP. That run completed with deployment still disabled. Goose then called edit from its Developer extension and changed deployment.json directly, setting shipping_enabled to true.

Developer exposed another way to write the same file. That call never reached GAAP, so there was no GAAP activation receipt. The permission check controlled its own executor; it couldn't govern a write performed by another tool.

The recipe declared the three intended GAAP tools. It didn't establish the complete Desktop session inventory. In the tested Goose 1.50.0 source, Desktop's Agent Client Protocol (ACP) interface starts sessions with selected built-ins and adds recipe extensions. Developer can remain available. The CLI follows a different path, so my CLI rehearsals didn't prove the Desktop restriction.

For a fresh reproduction through the Desktop backend, Developer was removed from the actual session. The effective inventory contained only GAAP's tools, and a direct edit challenge returned tool not found. Goose's activation proposal then reached GAAP and was denied.

The saved Amsterdam conversation says shipping is enabled. GAAP's history alone cannot account for the later Developer edit.

Fresh Desktop-backend reproduction: one completed shipping run and one blocked activation run. Durations measure local engine work.

The read-only Inspector exposes four views. Each answers a different question about the selected run.

Changes shows the files before and after that operation. These are historical snapshots. Compare them with the current workspace: another tool may have edited the file afterward, as happened in Amsterdam.

Verification shows the eight shipping cases and their outcomes. They cover pricing, discounting, free delivery, rounding, and invalid quantities. They support claims about those inputs, not correctness for every possible input.

Trace shows where execution stopped. In the fresh activation run, permission.denied prevents the write. Later controls display Not evaluated because the run never reached them.

Receipt ties the request to its evidence and outcome. These local records are integrity-checked but unsigned. They help reconstruct what GAAP did; they don't account for every action elsewhere on the machine.

An interrupted process creates a different uncertainty: the write may finish before the caller receives its result. This fixture saves a running marker before execution. If that marker remains unresolved, the operator must inspect the records and actual files before retrying.

An identical submitted proposal returns its existing receipt instead of writing again. Changing the plan, file content, or base digest can identify a different operation. A conversational “try again” doesn't establish whether the same attempt is being resumed.

For a Goose workflow like this one, inspect the effective session tools before the first task. In the tested Desktop version, disable Developer and other extensions that can perform the protected write. Confirm that only these tools remain:

gaap__read_project
gaap__submit_change
gaap__run_status

Recheck after reopening or changing extensions.

That restriction removes the alternate tool path from the session. It doesn't provide OS isolation: another process or editor can still change these files. The verifier checks a restricted Python subset, and engine accounting excludes Goose's model tokens, reasoning time, and provider spend.

Start with the operation you want to protect. Identify every tool or credential that can perform it. Exercise the denied path, check interrupted attempts, and compare the records with the resulting state.

The question I now bring to an agent demo is concrete: can the evidence account for the change that actually happened?

The GAAP source and coding runbook contain the local adapter, verifier, Inspector, and reproduction commands. The Goose recipe reference describes declared recipe settings; the tagged Desktop ACP implementation explains the tested built-in extension behavior. The MCP tools specification defines the protocol interface.

AI tools assisted with editing and shortening this article. The cover illustration was AI-generated.

── more in #ai-agents 4 stories · sorted by recency
── more on @goose 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/controlling-file-wri…] indexed:0 read:5min 2026-09-28 · —