On this page#
What changes when an agent does this#
A Python MCP server does not make an agent trustworthy. It moves the bottleneck from generating code to controlling context, permissions, validation, and recovery. The server should expose narrow repository operations with explicit evidence, while the agent proposes changes and a human remains responsible for accepting anything that can violate an invariant.
The Model Context Protocol is a standard for connecting AI applications to external capabilities. Its useful property here is separation: the coding agent does not need unrestricted shell access just because it needs to inspect a repository, run approved checks, or prepare a patch.
A production-minded server should expose capabilities such as:
inspect_repository: read bounded paths and return relevant files.search_code: search within the repository, excluding secrets and generated output.propose_patch: produce a reviewable diff without applying it.run_checks: execute an allowlisted validation target.prepare_rollback: identify the exact files and changes that must be reversed.
Do not expose a generic run_command(command: str) tool and call it flexibility. That collapses your security boundary into prompt wording. If the agent can choose arbitrary commands, paths, and arguments, the MCP server is merely a remote shell with better branding.
The architectural boundary matters: an MCP host manages clients, and clients connect to servers that provide capabilities. The official MCP architecture explanation describes those roles, but your repository policy must supply the constraints that the protocol itself cannot know.
Use a policy object that is explicit enough to review:
{
"repository_root": "/workspace/project",
"read_paths": ["src", "tests", "docs"],
"write_mode": "patch_only",
"check_targets": ["lint", "unit", "typecheck"],
"deny_paths": [".env", ".git", "secrets", "vendor"],
"max_changed_files": 8,
"require_human_approval": true
}The number is not a universal best practice; it is a local limit chosen by the repository owner. The important part is that the limit exists outside the model’s prompt and fails closed when exceeded.
Photo by Christina Morillo on Pexels.
The prompt chain#
Use separate prompts for discovery, planning, patching, and verification. Each phase should have one job, a defined output, and a stopping condition. A single “inspect the repo, fix the bug, and test it” prompt hides uncertainty and encourages the agent to convert an early guess into an applied change before anyone sees the reasoning.
Phase 1: establish the contract#
The first prompt should force the agent to identify the requested behavior, repository evidence, and unresolved ambiguity without editing anything. This prevents a plausible implementation from becoming the specification and makes missing information visible before the server grants access to patch generation.
Inspect only the allowed repository paths.
Return:
1. The requested behavior in one sentence.
2. The repository invariants that constrain the change.
3. The files that provide evidence for each invariant.
4. The smallest relevant test surface.
5. Any ambiguity that prevents a safe plan.
Do not propose or apply a patch.
Do not infer requirements that are not supported by repository evidence.This wording separates facts from guesses. Repository invariants might include public API behavior, serialization formats, database boundaries, or error semantics. If the agent cannot cite a file for an alleged invariant, treat it as an assumption.
The official reference server implementations are useful examples of MCP servers, not a substitute for your authorization model. Reference code can show how capabilities are exposed; it cannot decide which operations are safe for your repository.
Phase 2: produce a bounded plan#
The planning prompt should require the smallest coherent change and name every expected file before patching begins. A plan that says “update related code as needed” is not bounded; it gives the agent permission to discover scope while already modifying the repository.
Using only the evidence from the inspection phase, propose a plan.
For each step, state:
- the file to change;
- the invariant preserved;
- the observable behavior changed;
- the check that will validate it.
List assumptions separately.
Stop if the change requires a file outside the allowed paths,
a new dependency, or a public API change.The stop conditions are deliberate. Dependencies and public APIs create review obligations that should not be smuggled through as implementation details.
If your team needs a reusable version of this review discipline, PairFoundry’s Review and Repair is the direct next step. The broader PairFoundry packs overview shows where this workflow sits alongside the other available packs.
Phase 3: generate the patch#
Patch generation should follow the approved plan exactly and return a diff, not mutate the working tree. The agent must explain deviations before making them; otherwise, a narrow plan can silently expand when implementation becomes inconvenient.
Generate a unified diff for the approved plan.
Constraints:
- change only the approved files;
- add no dependency;
- preserve the named invariants;
- include the smallest test that proves the behavior;
- do not weaken or delete an existing assertion to make checks pass.
If the plan is insufficient, return BLOCKED with the missing evidence.
Do not apply the patch.“Do not weaken an assertion” matters because agents can optimize for green checks. A passing suite is meaningless if the patch changed the test to accept the defect.
Phase 4: verify adversarially#
Verification must ask how the patch could be wrong, not merely whether expected checks pass. Run only allowlisted checks through the server, and keep raw results separate from the agent’s interpretation so reviewers can distinguish evidence from narrative.
Review the proposed diff as a hostile maintainer.
Report:
1. Which invariants are preserved, with diff evidence.
2. Which edge case is least well covered.
3. Any behavior changed outside the request.
4. The exact allowlisted checks required.
5. A rollback summary by file.
Do not claim success until every required check returns evidence.For more workflows built around staged agent control, use the PairFoundry workflows hub. If the gating model is still unfamiliar, the free foundations track is the better next move than deploying a server prematurely.
The gate#
The mandatory human gate belongs between a verified proposed diff and any write to the repository. The reviewer must inspect scope, invariants, test meaning, and rollback—not just accept the agent’s summary. Approval is a specific authorization for one patch hash or exact diff, never a reusable “continue” permission.
The reviewer should stop on four questions:
- Does every changed file appear in the approved plan?
- Does the test prove requested behavior rather than reproduce implementation details?
- Did any error handling, authorization boundary, schema, or public interface change?
- Can the patch be reversed without guessing which edits belong to it?
The server should reject application when the approved diff and submitted diff differ. It should also reject writes outside the repository root, denied paths, or the configured file limit.
This is where the MCP client-server boundary helps: capability calls are visible and structurally distinct. But MCP does not turn human approval into a meaningful gate automatically. Your server must bind approval to a concrete artifact and invalidate it when that artifact changes.
Photo by Daniil Komov on Pexels.
What it gets wrong#
Expect confident scope expansion, false completion claims, test gaming, and stale-context edits. These failures are recognizable because the evidence stops matching the narrative: unexplained files appear, checks are summarized without raw results, assertions become weaker, or the patch targets code that repository inspection did not establish as relevant.
| Failure mode | Detection signal | Required response | |---|---|---| | Scope expansion | Unplanned files or dependencies | Reject and return to planning | | False completion | No raw check result | Treat status as unverified | | Test gaming | Deleted or weakened assertion | Reject the patch | | Stale context | Diff no longer applies cleanly | Re-inspect and regenerate | | Invariant loss | Public behavior changes without approval | Escalate to human design review | | Path escape | Absolute, parent-relative, or linked path leaves root | Server must deny the call |
Another common failure is excessive context. Giving the agent the whole repository feels thorough, but it increases irrelevant associations and exposes material it does not need. Start with bounded search and expand only when the inspection phase names missing evidence.
The official protocol specification defines interoperability, not correctness. A valid tool call can still be the wrong operation. Treat schema validation as the beginning of enforcement, not the end.
Rollback#
Rollback should restore the exact pre-change state without asking the agent to reconstruct intent. Keep application atomic, record the approved diff and preconditions, and refuse partial success. If validation fails after application, reverse only that approved change and preserve unrelated work already present in the repository.
A clean rollback sequence is:
- Confirm the working state still matches the patch preconditions.
- Apply the approved diff as one bounded operation.
- Run the required allowlisted checks.
- If any required check fails, apply the recorded inverse diff.
- Re-run the minimum integrity check.
- Report both the original failure and rollback result.
Do not tell the agent to “undo what you changed.” That requires the same fallible system to identify its own edits after state has moved. Rollback data should be produced before application and retained by the server.
Never use broad restoration that can erase a developer’s unrelated changes. The unit of recovery is the approved patch, not the entire working tree. That boundary is the difference between an operational workflow and an agent improvising with source control.
Photo by panumas nikhomkhai on Pexels.
Related reading#
- SQL MCP server — where the agent helps and where it quietly hurts
- API vs MCP — where the agent helps and where it quietly hurts
FAQ#
When should I not use a Python MCP server for repository work?#
Do not build one when a read-only review, an existing constrained integration, or a short manual task already solves the problem. A server adds an authorization surface that must be maintained. It is justified when repeatable, narrow capabilities are safer than giving agents broad filesystem or shell access.
How is this different from following the official MCP examples?#
The official examples demonstrate protocol capabilities and implementation patterns; they do not encode your repository’s invariants, approval rules, or rollback requirements. Use the official reference servers to understand server structure, then add a policy boundary designed for your actual codebase.
What is the biggest team collaboration risk?#
The biggest risk is ambiguous ownership of approval. If anyone can type “continue,” nobody owns the resulting change. Define who may approve patches, what evidence they must inspect, and when approval expires. Bind each approval to the exact diff so another agent turn cannot silently change its scope.
How do we recover if the agent applies a bad patch?#
Reverse the recorded approved patch, not the whole working tree, then verify repository integrity. If the server did not capture the pre-change state and inverse operation before writing, the rollback design was incomplete. Stop further agent writes until a human separates the patch from unrelated changes.
Can an MCP server in Python safely expose arbitrary shell commands?#
No. An arbitrary-command tool defeats the guardrail because the model controls the operation, arguments, and often the target path. Expose named, allowlisted actions with validated parameters instead. If a new command is needed, add it deliberately and review its permissions as server code.