Skip to content

Repository files navigation

codex-git-unleash-mcp

Local MCP server for a narrow, policy-constrained set of Git and GitHub operations that Codex can call with fewer repeated shell and sandbox approval prompts.

It exists to handle a small approved workflow through MCP tools: inspect repository state, stage and commit changes, fetch, pull, and push the current branch, create or switch local branches in a constrained way, and open draft pull requests.

This is especially useful with OpenAI Codex sandbox, where protected-path behavior still applies to paths such as .git. In practice, shell Git operations that write repository metadata can still be blocked or require approval, while direct GitHub network mutations may still be allowed or approval-gated depending on runtime policy.

The MCP tool metadata in this server is explicit about which tools are read-only, which ones are additive mutations, and which ones may replace existing local or remote state. That can help Codex make better approval decisions, but it does not bypass Codex approval policy or higher-level safety monitoring. Repository mutations may still prompt for approval or be cancelled by the client/runtime.

See OpenAI Codex docs: Protected paths in writable roots.

For the suggested repository workflow used in this repo (and in general), see AGENTS.md.

Current tool surface:

  • config_bootstrap
  • config_upsert_repo
  • git_repo_policy
  • git_status
  • git_add
  • git_stage
  • git_commit
  • git_fetch
  • git_sync_base
  • git_pull_current_branch
  • git_worktree_add
  • git_push
  • git_branch_create_and_switch
  • git_branch_switch
  • gh_pr_create_draft

The server is intentionally not a generic git or gh proxy. Inputs are structured, commands are fixed, and unsupported operations are denied by default.

License

MIT. See LICENSE.

Prerequisites

  • Node.js
  • npm
  • git 2.23 or newer
  • gh

git_branch_switch uses git switch rather than git checkout, so Git 2.23+ is the supported baseline for branch switching.

Install

From this repository:

npm install

Configure

Use a config file at ~/.config/codex-git-unleash-mcp.yaml.

If that file does not exist yet, the server still starts and exposes the full tool surface. Global config is optional at runtime: repositories can also be authorized through a fixed repo-local policy file at .git-unleash.yaml.

config_bootstrap and config_upsert_repo can still create or update the YAML when you want an external allowlist.

Configuration trust model

config_bootstrap and config_upsert_repo are administrative conveniences, not security controls. A caller that can invoke them can create or widen repository policy, and runtime tools will use the changed policy on their next call. Hosts should expose these tools only to trusted callers; otherwise, omit them from the available tool set or require host-side approval for each invocation.

Example:

defaults:
  allowed_branch_patterns:
    - "^main$"
  feature_branch_pattern: "<user>/<feature-name>"
  git_worktree_base_path: /tmp/git-worktrees
  allow_draft_prs: true
  workflow_mode: worktree
  allowed_workflow_modes:
    - feature_branch
    - worktree

repositories:
  - path: ~/projects/codex-git-unleash-mcp
    default_remote: origin
  - path: ~/projects/another-repo
    allowed_branch_patterns:
      - "^feature/[a-z0-9._-]+$"
    feature_branch_pattern: "feature/<feature-name>"
    allow_draft_prs: false
    workflow_mode: feature_branch
    allowed_workflow_modes:
      - feature_branch

Notes:

  • path must be an absolute path or start with ~/
  • config_bootstrap creates a minimal valid YAML config file and refuses to overwrite an existing file
  • config_upsert_repo adds or updates one repository entry in the YAML config and matches existing entries by canonical repository path
  • config changes are reloaded from disk on the next tool call, so a server restart is not required after config_bootstrap or config_upsert_repo
  • top-level defaults are optional and may define allowed_branch_patterns, feature_branch_pattern, git_worktree_base_path, default_remote, allow_draft_prs, workflow_mode, and allowed_workflow_modes
  • repository values override top-level defaults field-by-field
  • feature_branch_pattern is an optional suggested naming template for new feature branches; it is advisory metadata and does not grant permission to use a branch name that fails allowed_branch_patterns
  • allowed_branch_patterns and feature_branch_pattern support a dedicated <user> placeholder, resolved from USER, then USERNAME, then the system account username; other environment-variable expansion is intentionally not supported
  • git_worktree_base_path is inherited or overridden per repository and, when configured, constrains git_worktree_add.path to stay under that base
  • for Codex workflows, prefer a repo-specific in-repository worktree base such as .worktrees/ when you want linked worktrees to stay under the same trusted project root; add that directory to .gitignore
  • workflow_mode is optional preference metadata for agents; it tells them which Git setup path to try first for this repository. Supported values are worktree, feature_branch, and current_branch
  • allowed_workflow_modes is the explicit authorization boundary for setup tools and accepts worktree, feature_branch, or current_branch
  • when both fields are present, agents should treat workflow_mode as the preferred starting flow and allowed_workflow_modes as the list of setup flows they may actually use
  • when allowed_workflow_modes is omitted but workflow_mode is set, setup tools derive the allowed mode from workflow_mode for backward compatibility
  • when both allowed_workflow_modes and workflow_mode are omitted, setup tools fail closed and ask the caller to inspect git_repo_policy
  • current_branch cannot be combined with feature_branch or worktree in allowed_workflow_modes
  • when both fields are set, workflow_mode must be included in allowed_workflow_modes
  • worktree means git_worktree_add is authorized
  • feature_branch means git_branch_create_and_switch and git_branch_switch are authorized
  • current_branch means do not create a new worktree or feature branch; work directly on the current allowed branch
  • branch patterns are full-match regexes against the current branch name
  • keep branch patterns simple: advanced group syntax, backreferences, and nested quantifiers are rejected at config load time
  • each repository must end up with at least one effective allowed branch pattern, either from the repo entry or inherited defaults
  • git_repo_policy returns the configured branch patterns and related repository defaults for an authorized repository, including feature_branch_pattern, git_worktree_base_path, the preferred workflow_mode, allowed_workflow_modes, the policy source, whether repo overrides were applied, and the repo-local config path when applicable
  • git_add, git_stage, git_commit, git_sync_base, git_pull_current_branch, git_push, and gh_pr_create_draft require the current branch to match one of the configured patterns
  • repository-mutating Git and GitHub tools are serialized per worktree inside the MCP process, and allowed-branch mutations re-check the current branch before returning success
  • that process-local lock does not stop a different process from changing the worktree concurrently, but branch drift is reported as an error instead of being reported as a clean success
  • git_fetch only requires the repository to be authorized, fetches from the resolved remote, updates local fetch metadata and remote-tracking state only, and uses an explicit branch when provided or the detected base branch otherwise
  • git_sync_base requires a clean worktree, fetches the detected remote base branch, merges only that remote-tracking ref into the current allowed branch, and aborts the merge before returning an error if a conflict occurs
  • git_pull_current_branch requires a clean worktree, fetches the current branch from the resolved remote, merges only that remote-tracking ref into the current allowed branch, and aborts the merge before returning an error if a conflict occurs
  • git_worktree_add requires an explicit absolute target path, validates the requested new branch name against allowed_branch_patterns, creates a linked worktree from an explicit or detected upstream base branch, and requires worktree in the effective allowed workflow modes
  • when git_worktree_base_path is configured, git_worktree_add.path must resolve under that base path
  • git_branch_create_and_switch and git_branch_switch require a clean worktree
  • git_branch_create_and_switch also requires the requested new branch name to match allowed_branch_patterns, and requires feature_branch in the effective allowed workflow modes
  • git_branch_switch also requires the requested branch name to match allowed_branch_patterns, and requires feature_branch in the effective allowed workflow modes
  • remote resolution prefers configured default_remote when present and valid, then the current branch's remote, then origin
  • branch creation and PR base resolution prefer the remote HEAD branch and fall back to GitHub default-branch detection when needed
  • git_status only requires the repository to be authorized

Policy precedence:

  • if .git-unleash.yaml exists, it is the base policy for that repository
  • if the global config has a matching repository entry, that repository entry overrides the repo-local base field-by-field
  • otherwise, top-level defaults are the base policy
  • if the global config has a matching repository entry, that repository entry overrides the defaults base field-by-field
  • for backward compatibility, an explicit workflow_mode with no allowed_workflow_modes also sets the effective allowed workflow modes to that single workflow

Repo-Local Policy

Repositories can opt into zero-setup authorization with a fixed repo-local file at .git-unleash.yaml in the repository root.

Example:

allowed_branch_patterns:
  - "^<user>/.*$"
feature_branch_pattern: "<user>/<feature-name>"
git_worktree_base_path: .worktrees
workflow_mode: worktree
allowed_workflow_modes:
  - feature_branch
  - worktree

Repo-local policy rules:

  • .git-unleash.yaml replaces top-level defaults when it exists
  • a matching repository entry in the global config may still override repo-local values field-by-field
  • global config still works as a fallback when a repository does not define .git-unleash.yaml
  • for repo-local policy, git_worktree_base_path may be relative to the repository root
  • repo-local policy must not set default_remote
  • runtime tools compare the repo-local policy in the local remote-tracking base ref, index, and working tree, and fail closed on any divergence
  • when the trusted base copy is missing locally, runtime tools fetch that base branch and retry the trust check before failing closed
  • in a fork, the fork's own base branch is authoritative for repo-local policy
  • this prevents locally widened repo-local policy from being used for MCP operations

Example opt-in override from a matching global repository entry:

Global config:

defaults:
  feature_branch_pattern: "defaults-do-not-win/<feature-name>"

repositories:
  - path: /Users/alice/project
    feature_branch_pattern: "bob/<feature-name>"

Repo-local .git-unleash.yaml:

allowed_branch_patterns:
  - "^[a-zA-Z0-9.]/.*$"
feature_branch_pattern: "<user>/<feature-name>"

Effective policy:

allowed_branch_patterns:
  - "^[a-zA-Z0-9.]/.*$"
feature_branch_pattern: "bob/<feature-name>"

In that example, the matching repository entry overrides feature_branch_pattern, while the global defaults.feature_branch_pattern is ignored because repo-local policy replaces defaults for that repository.

Workflow Summary

The intended happy path is:

  1. If the config file does not exist yet, call config_bootstrap to create it.
  2. Call config_upsert_repo to add or update an allowlisted repository entry when needed.
  3. Or check in .git-unleash.yaml to authorize the repository through repo-local policy instead of the global YAML.
  4. Call git_repo_policy or git_status to inspect the authorized repository.
  5. Prefer the setup flow advertised by git_repo_policy.workflow_mode as the way to start work in that repository.
  6. Before creating a branch, use git_repo_policy to confirm the configured allowed_branch_patterns, then choose a new branch name that matches that policy.
  7. Call git_branch_create_and_switch when the preferred or selected setup flow is feature_branch and you need a new local branch in the current worktree.
  8. Call git_worktree_add when the preferred or selected setup flow is worktree and you need a separate linked worktree on a new allowed branch at an explicit absolute path.
  9. Call git_sync_base when you need to bring the detected remote base branch into the current allowed branch without exposing generic merge controls.
  10. Call git_pull_current_branch when you need to bring the current branch's resolved remote branch into the current allowed branch without exposing generic merge controls.
  11. Call git_add or git_stage with explicit repository-relative paths.
  12. Call git_commit with a normal commit message.
  13. Call git_push to push the current branch to the resolved remote.
  14. Call gh_pr_create_draft to open a draft PR against an explicit base or the detected default base branch.

Each step stays inside a fixed policy boundary. There is no arbitrary checkout, no arbitrary push refspec, no amend flow, and no non-draft PR creation.

Run Locally

Start the server over stdio with the config path as the first argument:

npm run dev -- ~/.config/codex-git-unleash-mcp.yaml

You can also provide the config path through GIT_UNLEASH_MCP_CONFIG:

GIT_UNLEASH_MCP_CONFIG=~/.config/codex-git-unleash-mcp.yaml npm run dev

If the config path points to a file that does not exist yet, the server still starts. Runtime tools can still operate on repositories that are authorized through .git-unleash.yaml; otherwise they behave as unauthorized until you create or update the global YAML.

Build

npm run build

Test

npm run typecheck
npm test

Register In Codex

Register the MCP server with the wrapper script:

codex mcp add git_unleash -- ~/projects/codex-git-unleash-mcp/scripts/run-mcp.sh ~/.config/codex-git-unleash-mcp.yaml

Then verify:

codex mcp list

The Purpose of the Wrapper Script

The wrapper is there so the MCP server can inherit or reconstruct the SSH agent socket when Git operations need it.

It also plays nicely with child-process PATH fallback inside the Node server. By default, command execution now preserves the inherited PATH and appends common system locations, including Homebrew paths on macOS such as /opt/homebrew/bin, so gh and similar tools are found more reliably even when the MCP host starts with a minimal environment.

This matters in two common cases:

  • git_commit when Git is configured for SSH-based commit signing
  • git_fetch and git_push when the repository remote uses SSH authentication

If your shell already has a working SSH_AUTH_SOCK, start Codex from that shell so the MCP server inherits it.

If Codex does not inherit the socket automatically, set one of these before starting Codex:

export GIT_UNLEASH_SSH_AUTH_SOCK="$SSH_AUTH_SOCK"

or:

export SSH_AUTH_SOCK=/path/to/ssh-agent.sock

On macOS, the wrapper will also try launchctl getenv SSH_AUTH_SOCK before failing.

For GUI-launched Codex on macOS, a stable SSH agent socket is usually more reliable than the system SSH_AUTH_SOCK. The system socket can be dynamic, and launchctl setenv values do not survive a reboot. If you use an agent with a stable socket, configure the MCP server with that socket explicitly.

For example, 1Password's SSH agent exposes this socket on macOS:

/Users/<user>/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock

Add the socket to your Codex config:

[mcp_servers.git_unleash.env]
SSH_AUTH_SOCK = "/Users/<user>/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"
GIT_UNLEASH_SSH_AUTH_SOCK = "/Users/<user>/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock"

Then verify that the agent exposes the key Git is configured to use for SSH signing:

SSH_AUTH_SOCK="/Users/<user>/Library/Group Containers/2BUA8C4S2C.com.1password/t/agent.sock" ssh-add -L
git config --get user.signingkey

user.signingkey may be either a .pub file path or Git's inline key::ssh-ed25519 ... format. In both cases, the public key shown by ssh-add -L must match the public key configured by user.signingkey. The comments may differ, but the key type and key body must match.

If you use Apple's built-in SSH agent and Keychain integration, keep in mind that this SSH config:

Host *
    AddKeysToAgent yes
    UseKeychain yes
    IdentityFile ~/.ssh/id_ed25519

does not necessarily load identities into the agent at login. It can be lazy and load the key only after an SSH operation runs. That means a preflight like ssh-add -L can still report no identities immediately after a reboot. In that setup, either load the key before starting Codex or use a stable agent socket such as 1Password, Secretive, or another dedicated SSH agent.

Troubleshooting checklist:

  • If startup fails with SSH_AUTH_SOCK is not set, the MCP host did not pass a socket and the wrapper could not recover one through GIT_UNLEASH_SSH_AUTH_SOCK or launchctl getenv SSH_AUTH_SOCK.
  • If startup fails with ssh-agent is reachable ... but it is not returning any identities, the socket is valid but ssh-add -L cannot see a loaded key.
  • If Terminal Git works but MCP startup fails after reboot, check whether Terminal lazily loaded the key after the MCP server had already started.
  • If you use a stable agent socket, prefer setting both SSH_AUTH_SOCK and GIT_UNLEASH_SSH_AUTH_SOCK in the MCP server config so the wrapper and child Git processes agree on the same agent.

If you need an exact PATH instead of the built-in fallback behavior, you can still register the server with an explicit environment override:

codex mcp add git_unleash --env PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" -- ~/projects/codex-git-unleash-mcp/scripts/run-mcp.sh ~/.config/codex-git-unleash-mcp.yaml

What Codex Can Do Through This MCP Server

Once registered, Codex should be able to use:

  • config_bootstrap to create the initial YAML config file when it does not exist yet; it writes a minimal valid config and the runtime tool handlers will see it on their next call
  • config_upsert_repo to add or update one repository entry in the YAML config; it validates the resulting file against the existing schema, matches existing repos by canonical path, and the runtime tool handlers will see the updated policy on their next call
  • git_repo_policy to inspect the configured path, canonical path, allowed branch patterns, suggested feature-branch pattern, configured worktree base path, workflow preference, allowed workflow modes, default remote, draft-PR setting, and policy source for an authorized repository
  • git_status for an authorized repository
  • git_add for repository-relative paths inside an authorized repository; it rejects absolute paths and repository-escaping paths like ../x
  • git_stage as a discoverability alias for git_add when the caller is looking for "stage" rather than "add"
  • git_commit with a normal commit message on an allowed branch; it rejects empty commit messages and empty commits
  • git_fetch to fetch a plain branch name from the detected remote; it updates local fetch metadata and remote-tracking state only, does not allow arbitrary fetch arguments or refspecs, and uses an explicit branch when provided or the detected base branch otherwise
  • git_sync_base to merge the detected remote base branch into the current allowed branch; it requires a clean worktree, does not allow arbitrary refs or merge flags, and aborts on conflict before returning an error
  • git_pull_current_branch to fetch and merge the current branch from the detected remote into the current allowed branch; it requires a clean worktree, does not allow arbitrary refs or merge flags, and aborts on conflict before returning an error
  • git_worktree_add to create a linked worktree for a new allowed branch at an explicit absolute path; it fetches the explicit or detected base branch first, does not allow arbitrary refs, and enforces git_worktree_base_path when configured
  • git_branch_create_and_switch to create a local branch from an explicit or detected upstream base and switch to it; it rejects requested branch names that do not match the configured allowed branch patterns
  • git_branch_switch to switch to an existing local branch when the worktree is clean; it does not create branches or allow detached checkouts
  • git_push to push the current branch to the detected remote; it only pushes HEAD to refs/heads/<current-branch> and does not allow arbitrary refspecs, force-like behavior, delete pushes, or unrelated branch pushes
  • gh_pr_create_draft to create a draft PR for the current branch using an explicit base or the detected default branch; it is draft-only and requires a non-empty title

Mutating tools reject detached HEAD.

When the global config file is missing, runtime tools remain registered. They can still authorize repositories through .git-unleash.yaml, and once the YAML exists, the next tool call reloads it from disk.

Remote And Base Resolution

Some operations resolve defaults at runtime instead of requiring everything to be pinned in config.

  • git_fetch, git_sync_base, git_pull_current_branch, git_push, git_worktree_add, git_branch_create_and_switch, and gh_pr_create_draft resolve the remote by preferring configured default_remote, then the current branch remote, then origin
  • git_fetch, git_worktree_add, git_branch_create_and_switch, and gh_pr_create_draft accept an explicit branch or base input
  • git_fetch, git_sync_base, git_worktree_add, git_branch_create_and_switch, and gh_pr_create_draft resolve their default branch or base by preferring the remote HEAD branch and falling back to the GitHub repository default branch when no explicit input is provided

This keeps the tools constrained while still working across repositories that use different default branches or remotes.

Example Config For This Repo

If you want to branch from main but only allow mutations on personal feature branches:

defaults:
  allowed_branch_patterns:
    - "^main$"
  feature_branch_pattern: "<user>/<feature-name>"
  workflow_mode: worktree
  allowed_workflow_modes:
    - feature_branch
    - worktree

repositories:
  - path: ~/projects/codex-git-unleash-mcp
  - path: ~/projects/codex-git-unleash-mcp-enterprise
    allowed_branch_patterns:
      - "^feature/[a-z0-9._-]+$"
    workflow_mode: feature_branch
    allowed_workflow_modes:
      - feature_branch

In that example, workflow_mode tells agents to start with the worktree setup flow by default, while allowed_workflow_modes keeps both worktree and feature_branch available when the caller needs either one.

If you want some repositories to stay on their base branch instead of creating a worktree or feature branch:

repositories:
  - path: ~/projects/dm-cv-on-steroids
    workflow_mode: current_branch
    allowed_workflow_modes:
      - current_branch
    allowed_branch_patterns:
      - "^main$"
  - path: ~/dot.files
    workflow_mode: current_branch
    allowed_workflow_modes:
      - current_branch
    allowed_branch_patterns:
      - "^main$"

If you want to bootstrap the config and then add this repository incrementally, a minimal progression is:

Use this progression only when the caller is trusted to change MCP policy. Exposing either configuration mutation tool delegates that authority to its caller.

  1. Call config_bootstrap with defaults such as feature_branch_pattern, the preferred workflow_mode, or allowed_workflow_modes.
  2. Call config_upsert_repo with repo_path, and optionally git_worktree_base_path, default_remote, allowed_branch_patterns, the preferred workflow_mode, or allowed_workflow_modes.
  3. Use the Git tools against that repository; they will reload the YAML on each call.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages