diff --git a/README.md b/README.md index 419f89297..25e6f77a6 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ The remote GitHub MCP Server is hosted by GitHub and provides the easiest method 1. A compatible MCP host with remote server support (VS Code 1.101+, Claude Desktop, Cursor, Windsurf, etc.) 2. Any applicable [policies enabled](https://github.com/github/github-mcp-server/blob/main/docs/policies-and-governance.md) +3. (Optional) [Tool-level policies](#tool-level-policy-enforcement) configured for rate limiting and access control ### Install in VS Code @@ -1575,3 +1576,50 @@ The exported Go API of this module should currently be considered unstable, and ## License This project is licensed under the terms of the MIT open source license. Please refer to [MIT](./LICENSE) for the full terms. + +## Tool-level policy enforcement + +The [policies and governance](docs/policies-and-governance.md) doc covers access control via PATs, OAuth, and SSO. For tool-level enforcement — rate limiting, daily caps, and blocking specific operations — you can wrap the server with [PolicyLayer Intercept](https://github.com/policylayer/intercept), an open-source MCP proxy. + +Three policy presets are included in [`/policies`](/policies): + +| Policy | Description | +|--------|-------------| +| `recommended.yaml` | Rate limits on writes, blocks `delete_file`, daily caps on merges and pushes | +| `strict.yaml` | Default deny — only read tools allowed unless explicitly opted in | +| `permissive.yaml` | Everything allowed, rate limits on destructive and high-risk operations | + +### Usage + +Local server: + +```sh +npx -y @policylayer/intercept \ + --policy policies/recommended.yaml \ + -- docker run -i --rm -e GITHUB_PERSONAL_ACCESS_TOKEN ghcr.io/github/github-mcp-server +``` + +Or in your MCP client config: + +```json +{ + "mcpServers": { + "github": { + "command": "npx", + "args": [ + "-y", "@policylayer/intercept", + "--policy", "policies/recommended.yaml", + "--", "docker", "run", "-i", "--rm", + "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", + "ghcr.io/github/github-mcp-server" + ], + "env": { + "GITHUB_PERSONAL_ACCESS_TOKEN": "" + } + } + } +} +``` + +Policies are YAML files you can customise. See the [Intercept docs](https://github.com/policylayer/intercept) for the full reference. + diff --git a/policies/permissive.yaml b/policies/permissive.yaml new file mode 100644 index 000000000..5fb85f9dd --- /dev/null +++ b/policies/permissive.yaml @@ -0,0 +1,42 @@ +version: "1" +description: "GitHub MCP — permissive policy. All tools allowed. Rate limits on destructive and high-risk operations." +default: allow + +tools: + # Destructive — blocked even in permissive mode + delete_file: + rules: + - action: deny + on_deny: "File deletion blocked by policy" + + # High-risk writes — rate limited + merge_pull_request: + rules: + - name: "rate-limit" + rate_limit: "20/hour" + on_deny: "Max 20 merges per hour" + + push_files: + rules: + - name: "rate-limit" + rate_limit: "30/hour" + on_deny: "Max 30 pushes per hour" + + actions_run_trigger: + rules: + - name: "rate-limit" + rate_limit: "10/hour" + on_deny: "Max 10 workflow triggers per hour" + + create_repository: + rules: + - name: "rate-limit" + rate_limit: "10/hour" + on_deny: "Max 10 repository creations per hour" + + # Global safety net + "*": + rules: + - name: "global-rate-limit" + rate_limit: "180/minute" + on_deny: "Global rate limit — max 180 tool calls per minute" diff --git a/policies/recommended.yaml b/policies/recommended.yaml new file mode 100644 index 000000000..1b971e097 --- /dev/null +++ b/policies/recommended.yaml @@ -0,0 +1,127 @@ +version: "1" +description: "GitHub MCP — recommended policy. Rate limits on writes, blocks destructive ops, reads allowed freely." +default: allow + +tools: + # Destructive — blocked entirely + delete_file: + hide: true + rules: + - action: deny + on_deny: "File deletion blocked by policy" + + # High-risk writes — tight limits + merge_pull_request: + rules: + - name: "burst-limit" + rate_limit: "2/minute" + on_deny: "Slow down — max 2 merges per minute" + - name: "hourly-cap" + rate_limit: "10/hour" + on_deny: "Hourly merge limit (10) reached" + + push_files: + rules: + - name: "burst-limit" + rate_limit: "3/minute" + on_deny: "Slow down — max 3 pushes per minute" + - name: "hourly-cap" + rate_limit: "20/hour" + on_deny: "Hourly push limit (20) reached" + + create_or_update_file: + rules: + - name: "rate-limit" + rate_limit: "30/hour" + on_deny: "Max 30 file writes per hour" + + # Execution — rate limited + actions_run_trigger: + rules: + - name: "rate-limit" + rate_limit: "5/hour" + on_deny: "Max 5 workflow triggers per hour" + + # Repository management — moderate limits + create_repository: + rules: + - name: "rate-limit" + rate_limit: "5/hour" + on_deny: "Max 5 repository creations per hour" + + fork_repository: + rules: + - name: "rate-limit" + rate_limit: "5/hour" + on_deny: "Max 5 forks per hour" + + # PR and issue writes — reasonable throughput + create_pull_request: + rules: + - name: "rate-limit" + rate_limit: "20/hour" + on_deny: "Max 20 PR creations per hour" + + update_pull_request: + rules: + - name: "rate-limit" + rate_limit: "30/hour" + on_deny: "Max 30 PR updates per hour" + + update_pull_request_branch: + rules: + - name: "rate-limit" + rate_limit: "20/hour" + on_deny: "Max 20 branch updates per hour" + + issue_write: + rules: + - name: "rate-limit" + rate_limit: "30/hour" + on_deny: "Max 30 issue writes per hour" + + add_issue_comment: + rules: + - name: "rate-limit" + rate_limit: "30/hour" + on_deny: "Max 30 issue comments per hour" + + # Branch creation + create_branch: + rules: + - name: "rate-limit" + rate_limit: "20/hour" + on_deny: "Max 20 branch creations per hour" + + # Copilot tools — limited + assign_copilot_to_issue: + rules: + - name: "rate-limit" + rate_limit: "10/hour" + on_deny: "Max 10 Copilot assignments per hour" + + request_copilot_review: + rules: + - name: "rate-limit" + rate_limit: "10/hour" + on_deny: "Max 10 Copilot review requests per hour" + + create_pull_request_with_copilot: + rules: + - name: "rate-limit" + rate_limit: "10/hour" + on_deny: "Max 10 Copilot PR creations per hour" + + # Gist writes + create_gist: + rules: + - name: "rate-limit" + rate_limit: "20/hour" + on_deny: "Max 20 gist creations per hour" + + # Global safety net + "*": + rules: + - name: "global-rate-limit" + rate_limit: "120/minute" + on_deny: "Global rate limit — max 120 tool calls per minute" diff --git a/policies/strict.yaml b/policies/strict.yaml new file mode 100644 index 000000000..2fab83947 --- /dev/null +++ b/policies/strict.yaml @@ -0,0 +1,269 @@ +version: "1" +description: "GitHub MCP — strict policy. Default deny. Only read tools are allowed." +default: deny + +tools: + # Read tools — explicitly allowed + get_file_contents: + rules: + - action: allow + rate_limit: "60/minute" + + get_commit: + rules: + - action: allow + rate_limit: "30/minute" + + get_latest_release: + rules: + - action: allow + rate_limit: "30/minute" + + get_release_by_tag: + rules: + - action: allow + rate_limit: "30/minute" + + get_tag: + rules: + - action: allow + rate_limit: "30/minute" + + get_repository_tree: + rules: + - action: allow + rate_limit: "30/minute" + + list_branches: + rules: + - action: allow + rate_limit: "30/minute" + + list_commits: + rules: + - action: allow + rate_limit: "30/minute" + + list_releases: + rules: + - action: allow + rate_limit: "30/minute" + + list_tags: + rules: + - action: allow + rate_limit: "30/minute" + + search_code: + rules: + - action: allow + rate_limit: "30/minute" + + search_repositories: + rules: + - action: allow + rate_limit: "30/minute" + + # Issues and PRs — read only + issue_read: + rules: + - action: allow + rate_limit: "30/minute" + + list_issues: + rules: + - action: allow + rate_limit: "30/minute" + + search_issues: + rules: + - action: allow + rate_limit: "30/minute" + + pull_request_read: + rules: + - action: allow + rate_limit: "30/minute" + + list_pull_requests: + rules: + - action: allow + rate_limit: "30/minute" + + search_pull_requests: + rules: + - action: allow + rate_limit: "30/minute" + + # Actions — read only + actions_get: + rules: + - action: allow + rate_limit: "30/minute" + + actions_list: + rules: + - action: allow + rate_limit: "30/minute" + + get_job_logs: + rules: + - action: allow + rate_limit: "30/minute" + + # Security — read only + get_code_scanning_alert: + rules: + - action: allow + rate_limit: "30/minute" + + list_code_scanning_alerts: + rules: + - action: allow + rate_limit: "30/minute" + + get_dependabot_alert: + rules: + - action: allow + rate_limit: "30/minute" + + list_dependabot_alerts: + rules: + - action: allow + rate_limit: "30/minute" + + get_secret_scanning_alert: + rules: + - action: allow + rate_limit: "30/minute" + + list_secret_scanning_alerts: + rules: + - action: allow + rate_limit: "30/minute" + + get_global_security_advisory: + rules: + - action: allow + rate_limit: "30/minute" + + list_global_security_advisories: + rules: + - action: allow + rate_limit: "30/minute" + + list_org_repository_security_advisories: + rules: + - action: allow + rate_limit: "30/minute" + + list_repository_security_advisories: + rules: + - action: allow + rate_limit: "30/minute" + + # Discussions — read only + get_discussion: + rules: + - action: allow + rate_limit: "30/minute" + + get_discussion_comments: + rules: + - action: allow + rate_limit: "30/minute" + + list_discussion_categories: + rules: + - action: allow + rate_limit: "30/minute" + + list_discussions: + rules: + - action: allow + rate_limit: "30/minute" + + # Context + get_me: + rules: + - action: allow + rate_limit: "10/minute" + + get_team_members: + rules: + - action: allow + rate_limit: "30/minute" + + get_teams: + rules: + - action: allow + rate_limit: "30/minute" + + search_orgs: + rules: + - action: allow + rate_limit: "30/minute" + + search_users: + rules: + - action: allow + rate_limit: "30/minute" + + # Notifications — read only + get_notification_details: + rules: + - action: allow + rate_limit: "30/minute" + + list_notifications: + rules: + - action: allow + rate_limit: "30/minute" + + # Gists — read only + get_gist: + rules: + - action: allow + rate_limit: "30/minute" + + list_gists: + rules: + - action: allow + rate_limit: "30/minute" + + # Labels — read only + get_label: + rules: + - action: allow + rate_limit: "30/minute" + + list_label: + rules: + - action: allow + rate_limit: "30/minute" + + # Projects — read only + projects_get: + rules: + - action: allow + rate_limit: "30/minute" + + projects_list: + rules: + - action: allow + rate_limit: "30/minute" + + # Starred repos — read only + list_starred_repositories: + rules: + - action: allow + rate_limit: "30/minute" + + # Docs + github_support_docs_search: + rules: + - action: allow + rate_limit: "30/minute" + + # Everything else blocked by default: deny + # To allow writes, copy rules from recommended.yaml