Skip to content

feat: enterprise multi-org GitHub App auth, repo denylist, and write guards#9

Open
tlongwell-block wants to merge 566 commits intomainfrom
feature/block-custom-features
Open

feat: enterprise multi-org GitHub App auth, repo denylist, and write guards#9
tlongwell-block wants to merge 566 commits intomainfrom
feature/block-custom-features

Conversation

@tlongwell-block
Copy link
Owner

Summary

Enterprise extensions to the GitHub MCP Server for organizations that need multi-org GitHub App authentication, repository access controls, and fine-grained write guards. All features are additive and backward-compatible with existing PAT authentication.

Rebased on upstream main (github/github-mcp-server).


Features

Multi-Org GitHub App Authentication

  • MultiOrgClientFactory routes API calls to the correct GitHub App installation based on repository or organization owner
  • Owner extraction middleware resolves the target owner from tool call parameters (owner, org, repo, etc.)
  • Per-org lockdown cache with TTL-based expiration
  • CLI flags and env vars: --gh-app-id, --gh-installation-id, --gh-private-key, --gh-org-installations

Repository Denylist

  • RepoDenylist data structure supporting org-wildcard (org/*) and exact-repo (org/repo) matching
  • MCP middleware blocks tool calls targeting denied repos/orgs before they reach the GitHub API
  • Search query rewriting strips denied repos/orgs from search_code and search_repositories queries
  • Completion filtering excludes denied repos from autocomplete suggestions
  • Case-insensitive matching throughout

Write Guards

  • WritePrivateOnlyMiddleware restricts write operations to private repositories only
  • Visibility check via GitHub API with caching
  • Per-toolset read/write modes via CLI flag (e.g., --toolsets repos:rw,issues:ro)
  • ExcludeTools support for disabling individual tools by name (e.g., --exclude-tools search_repositories,search_code)

Server Bootstrap

  • Unified config validation for PAT vs GitHub App auth modes
  • All middleware wired into server initialization with correct ordering
  • Integration tests covering the full middleware chain

Configuration

All new features are opt-in via CLI flags or environment variables:

Flag Env Var Description
--gh-app-id GITHUB_APP_ID GitHub App ID
--gh-installation-id GITHUB_INSTALLATION_ID Default installation ID
--gh-private-key GITHUB_PRIVATE_KEY App private key content
--gh-private-key-path GITHUB_PRIVATE_KEY_FILE_PATH Path to private key file
--gh-org-installations GITHUB_ORG_INSTALLATIONS Org-to-installation mapping (org1:id1,org2:id2)
--repo-denylist GITHUB_REPO_DENYLIST Denied repos (org/*,org/repo)
--write-private-only GITHUB_WRITE_PRIVATE_ONLY Restrict writes to private repos
--toolsets GITHUB_TOOLSETS Toolset modes (repos:rw,issues:ro)
--exclude-tools GITHUB_EXCLUDE_TOOLS Disable specific tools by name

Testing

Comprehensive test coverage across all new packages, including:

  • Unit tests for denylist matching, query parsing, owner extraction
  • Integration tests for middleware chain ordering and interaction
  • Edge cases: case sensitivity, wildcard matching, bypass attempts, cache behavior

mattdholloway and others added 30 commits December 18, 2025 16:00
- Remove Sizes field from octicons.Icons() to fix compatibility with older MCP clients
- Older clients like Cursor expect sizes to be a string, not an array
- The 2025-11-25 MCP spec changed sizes from string to array
- Omitting the optional Sizes field makes icons compatible with all clients
- Update tests and toolsnaps to reflect the change

Fixes github#1644

Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
The issue was that after PR github#1640 switched from closure-based deps to context-based deps,
the stdio server was missing middleware to inject ToolDependencies into the request context.
This caused tools to panic with "ToolDependencies not found in context" when called.

Added middleware in NewMCPServer() that wraps all requests with github.ContextWithDeps(),
ensuring deps are available to tool handlers via MustDepsFromContext().

Also added tests to verify server creation and toolset resolution logic.

Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
* add aliases for new actions tools

* generate docs
…1570)

* Add raw client error annotation and annotate GetFileContents

* Track response.

* add raw errors to context

* add raw api error test

* Update pkg/errors/error.go

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add blank line after Error() method for readability

* add NewGitHubRawAPIErrorResponse back

---------

Co-authored-by: Matt Holloway <mattdholloway@pm.me>
Co-authored-by: Matt Holloway <mattdholloway@github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Add DestructiveHint: true to the delete_project_item tool to be
consistent with other delete operations (delete_file and
delete_workflow_run_logs) that properly indicate destructive behavior.

This helps LLMs better understand that this tool permanently removes
data and should be used with appropriate caution.

Co-Authored-By: Claude <noreply@anthropic.com>
Add destructiveHint: true to the snapshot file to match the code change.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
* get_file_contents improvements

* Return custom errors when main ref is supplied

* Remove test for short sha

* Apply Copilot suggestion
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.11.1 to 3.12.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](docker/setup-buildx-action@e468171...8d2750c)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: 3.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
…b#1583)

* fix: handle architecture-specific license differences

The licenses script now:
- Generates separate license reports per GOOS/GOARCH combination
- Groups identical reports together (comma-separated arch names)
- Adds a Table of Contents at the top of each platform file
- Handles cases where different architectures have different dependencies
  (e.g., x/sys/unix vs x/sys/windows, mousetrap on Windows only)

This addresses the issue discovered in cli/cli where some deps changed
which changed the mod graph for different GOARCH and affected the
exported licenses because go-licenses tries to find common ancestors.

* fix: make license script portable and deterministic

Address review feedback:
- Remove bash 4.0+ associative array requirement for macOS compatibility
- Add cross-platform hash function (md5sum on Linux, md5 on macOS)
- Ensure deterministic iteration order using sorted groups file
- Add better error handling for failed go-licenses commands
- Fix grammar: 'architecture(s)' -> 'architectures'
- Add documentation for third-party/ being a union of all architectures
- Use file-based state instead of associative arrays for portability

* fix: update licenses-check to use new architecture-aware format

- Check now regenerates using ./script/licenses and compares
- Add GOROOT/PATH setup in CI to fix go-licenses module info errors
- Check both license files AND third-party directory for changes
- See: google/go-licenses#244

* fix: use LC_ALL=C for consistent sorting across systems

The sort command uses locale-specific ordering which can differ between
systems. Use LC_ALL=C to ensure consistent ordering in CI and locally.

* feat: auto-fix license files on PRs and improve CI reliability

Changes:
- Pin go-licenses version in CI for reproducibility (commit 5348b744)
- Add GOROOT/PATH setup for 'Package does not have module info' fix
- Update license-check.yml to auto-fix and push to PR branches
- Add CI=true env var to use pinned go-licenses version
- Add dependabot exclusion from auto-fix workflow
- Add code-scanning exclusion for third-party files

* feat: auto-close PRs that only needed license updates

After the bot pushes license fixes, check if the PR now only contains
license file changes. If so, close it automatically with a comment
explaining that the license updates are complete.

This prevents stale PRs from accumulating when someone creates a PR
just to fix licenses, or when all other changes were already merged
to the base branch.

* feat: auto-create/manage license fix PRs for failing PRs

Creates stacked PRs to fix license issues:
- Detects when a PR needs license updates
- Creates child PR: main <- PR:feature <- PR:license-fix
- Tracks PRs with metadata and hash of license changes
- Auto-closes if user fixes licenses manually
- Auto-closes and recreates if dependencies change
- Prevents multiple fix PRs for same base PR

Rules:
- Only targets PRs against main (not stacked PRs)
- Only runs on ready-for-review PRs (not drafts)
- Skips bots and forks
- Hash-based detection avoids unnecessary work

* fix: allow auto-fix workflow to run on dependabot PRs

Dependabot PRs frequently need license updates and can't be merged until
fixed. The auto-fix workflow helps by creating a child PR with the
license changes, making it easy to merge both together.

* fix: address Copilot review comments

- Remove dependabot exclusion (we want to support dependabot PRs)
- Comment indentation already fixed
- CI env var already set for reproducibility

* refactor: move base branch filter to on: block

Moved the 'targets main' check from job if: to workflow on.pull_request.branches.
This prevents the workflow from even triggering for PRs targeting other branches,
saving CI resources.

Draft check is implicit in the types list (opened + ready_for_review).
Fork check must stay in if: condition (can't be filtered in on: block).

* refactor: merge auto-fix into license-check workflow

Combines both workflows into one with two jobs:
1. license-check: Checks licenses, fails if needed, sets outputs
2. auto-create-fix-pr: Creates child PR if needed (only for non-forks)

Benefits:
- Single workflow file, easier to maintain
- Check fails (blocks merge) while still creating helpful fix PR
- Fork detection in first job, second job skips for forks
- Hash-based tracking prevents duplicate PRs

* refactor: simplify license-check to auto-commit approach

Much simpler workflow:
1. Always try to auto-commit fix directly to PR branch
2. If push fails (fork without permissions), comment once with instructions
3. Don't create child PRs - just fix in place or give instructions
4. Only comment if not already commented (prevent spam)
5. Always fail check if licenses need updating

Benefits:
- Much simpler - single job
- No child PR management complexity
- Clear UX: either fixed or instructed
- Works for all PRs (internal/fork/dependabot)
- Use separate -m flags for multi-line git commit message
- Add proper indentation to template literal content to fix YAML parsing

Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
Bumps [github.com/google/jsonschema-go](https://github.com/google/jsonschema-go) from 0.3.0 to 0.4.2.
- [Release notes](https://github.com/google/jsonschema-go/releases)
- [Commits](google/jsonschema-go@v0.3.0...v0.4.2)

---
updated-dependencies:
- dependency-name: github.com/google/jsonschema-go
  dependency-version: 0.4.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Auto-generated by license-check workflow
Bumps [github.com/modelcontextprotocol/go-sdk](https://github.com/modelcontextprotocol/go-sdk) from 1.2.0-pre.1 to 1.2.0.
- [Release notes](https://github.com/modelcontextprotocol/go-sdk/releases)
- [Commits](modelcontextprotocol/go-sdk@v1.2.0-pre.1...v1.2.0)

---
updated-dependencies:
- dependency-name: github.com/modelcontextprotocol/go-sdk
  dependency-version: 1.2.0
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Auto-generated by license-check workflow
Replace string conversions with mcp.IconThemeLight and mcp.IconThemeDark
constants to match the SDK's typed IconTheme field. This fixes the CI build
errors where string literals were being used instead of the proper IconTheme type.

Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
Bumps [github.com/spf13/cobra](https://github.com/spf13/cobra) from 1.10.1 to 1.10.2.
- [Release notes](https://github.com/spf13/cobra/releases)
- [Commits](spf13/cobra@v1.10.1...v1.10.2)

---
updated-dependencies:
- dependency-name: github.com/spf13/cobra
  dependency-version: 1.10.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Auto-generated by license-check workflow
…st (github#1669)

* Fallback to default branch in get_file_contents when main doesn't exist

* Addressing review comments
The CodeQL workflow requires security-events write permission and access
to internal GitHub registries/packs that aren't available in forks.
Adding a condition to only run on the main repository prevents workflow
failures in forked repositories.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5.9.0 to 5.10.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](docker/metadata-action@318604b...c299e40)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-version: 5.10.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
… in repo (github#1682)

* change list workflow runs to allow empty resource id to list all runs in repo

* update docs
kaitlin-duolingo and others added 28 commits February 25, 2026 13:42
* Add support for get_check_runs

* Run generate-docs

* Address AI code review comment

* make descriptions less ambiguous for model

* lint and docs

* fix lint

---------

Co-authored-by: tommaso-moro <tommaso-moro@github.com>
Co-authored-by: Tommaso Moro <37270480+tommaso-moro@users.noreply.github.com>
* update docs

* add mention of new doc

* Update docs/insiders-features.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* reduce context usage for list_issues

* address copilot feedback, align pagination tags to camelCase
* Gracefully handle numeric parameters passed as strings
* Fix SHA validation in create_or_update_file

* Doc update

* Handle non-404 errors

* Handle directory paths

* Update instructions
1. Empty (0-byte) files caused an unhandled error because the GitHub API
   returns null content with base64 encoding for them; GetContent() fails
   with "malformed response: base64 encoding of null content". Return
   empty text/plain content directly, bypassing decoding entirely.

Co-authored-by: Ksenia Bobrova <almaleksia@github.com>
* Cline & Roo code installation guides

* Update docs/installation-guides/install-cline.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Apply suggestion from @Copilot

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
When using issue_write with method "update" and a state parameter (e.g.
"closed"), the MCP Apps UI form was incorrectly shown. The form only
handles title/body editing and would lose the state transition. Now when
a state change is requested, the UI form is skipped and the update
executes directly.

Fixes github#798

Co-authored-by: mattdholloway <918573+mattdholloway@users.noreply.github.com>
Check for the "state" key directly in the args map rather than using
OptionalParam and ignoring its error. This ensures that a wrongly-typed
state value bypasses the UI form (falling through to the normal
validation path) instead of silently showing the form.

Co-authored-by: mattdholloway <918573+mattdholloway@users.noreply.github.com>
Bumps [reproducible-containers/buildkit-cache-dance](https://github.com/reproducible-containers/buildkit-cache-dance) from 3.3.1 to 3.3.2.
- [Release notes](https://github.com/reproducible-containers/buildkit-cache-dance/releases)
- [Commits](reproducible-containers/buildkit-cache-dance@6f699a7...1b8ab18)

---
updated-dependencies:
- dependency-name: reproducible-containers/buildkit-cache-dance
  dependency-version: 3.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.18.0 to 6.19.2.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](docker/build-push-action@2634353...10e90e3)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 6.19.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps golang from 1.25.7-alpine to 1.25.8-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-version: 1.25.8-alpine
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [sigstore/cosign-installer](https://github.com/sigstore/cosign-installer) from 4.0.0 to 4.1.0.
- [Release notes](https://github.com/sigstore/cosign-installer/releases)
- [Commits](sigstore/cosign-installer@faadad0...ba7bc0a)

---
updated-dependencies:
- dependency-name: sigstore/cosign-installer
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Adds `resolve_thread` and `unresolve_thread` methods to the
`pull_request_review_write` tool, enabling users to resolve and
unresolve PR review threads via GraphQL mutations.

- Add ThreadID field to PullRequestReviewWriteParams struct
- Add threadId parameter and new methods to tool schema
- Implement ResolveReviewThread function using GraphQL mutations
- Add switch cases for resolve_thread and unresolve_thread methods
- Add unit tests covering success, error, empty and omitted threadId
- Document that owner/repo/pullNumber are unused for these methods
- Document idempotency (resolving already-resolved is a no-op)
- Update toolsnaps and generated docs

Fixes github#1768

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Allows users running multiple GitHub MCP Server instances (e.g., for
github.com and GitHub Enterprise Server) to override the server name and
title in the MCP initialization response.

- Add --server-name / GITHUB_SERVER_NAME flag+env to override name
- Add --server-title / GITHUB_SERVER_TITLE flag+env to override title
- Defaults remain "github-mcp-server" and "GitHub MCP Server"
- Applies to both stdio and HTTP server modes
- Add tests for default and custom name/title

Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
Instead of new CLI flags (--server-name, --server-title), reuse the
existing string override mechanism that already supports tool title/
description overrides throughout the codebase.

Users can now configure the server name and title via:
  - GITHUB_MCP_SERVER_NAME / GITHUB_MCP_SERVER_TITLE env vars
  - "SERVER_NAME" / "SERVER_TITLE" keys in github-mcp-server-config.json

This is consistent with how all other user-visible strings are
overridden (e.g. GITHUB_MCP_TOOL_GET_ME_USER_TITLE). No new struct
fields or CLI flags are needed.

Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com>
Add documentation for the server name and title customization feature
to the README i18n section and server-configuration.md quick reference.
This helps users running multiple GitHub MCP Server instances discover
how to configure unique identities via environment variables or the
config JSON file.

Co-authored-by: Anika Reiter <1503135+Anika-Sol@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 6.19.2 to 7.0.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](docker/build-push-action@10e90e3...d08e5c3)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 7.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5.10.0 to 6.0.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](docker/metadata-action@c299e40...030e881)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
…guards

Add support for GitHub App authentication with multi-org installation
routing, repository denylist middleware, write-private-only guards,
per-toolset read/write mode configuration, and owner extraction middleware.

Multi-Org GitHub App Authentication:
- MultiOrgClientFactory routes API calls to the correct GitHub App
  installation based on repository/org owner
- Owner extraction middleware resolves owner from tool call parameters
- Per-org lockdown cache with TTL-based expiration
- CLI flags and env vars for app-id, installation-id, private-key,
  and org-installations mapping

Repository Denylist:
- RepoDenylist data structure with org-wildcard and exact-repo matching
- MCP middleware blocks tool calls targeting denied repos/orgs
- Search query rewriting strips denied repos/orgs from search_code
  and search_repositories calls
- Completion filtering excludes denied repos from autocomplete
- Case-insensitive matching throughout

Write Guards:
- WritePrivateOnlyMiddleware restricts write operations to private repos
- Visibility check via GitHub API with caching
- Per-toolset read/write modes via CLI (e.g., repos:rw,issues:ro)
- ExcludeTools support for disabling individual tools by name

Server Bootstrap:
- Unified config validation for PAT vs App Auth modes
- Wired all middleware into server initialization with correct ordering
- Integration tests for full middleware chain behavior

All features are additive and backward-compatible with existing PAT auth.
@tlongwell-block tlongwell-block force-pushed the feature/block-custom-features branch from a2db2af to 55c1a68 Compare March 17, 2026 15:51
Auto-generated by license-check workflow
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.