diff --git a/README.md b/README.md index 059256aaa..18b317d7e 100644 --- a/README.md +++ b/README.md @@ -491,6 +491,7 @@ The following sets of tools are available: workflow Actions - **actions_get** - Get details of GitHub Actions resources (workflows, workflow runs, jobs, and artifacts) + - **Required OAuth Scopes**: `repo` - `method`: The method to execute (string, required) - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) @@ -502,6 +503,7 @@ The following sets of tools are available: (string, required) - **actions_list** - List GitHub Actions workflows in a repository + - **Required OAuth Scopes**: `repo` - `method`: The action to perform (string, required) - `owner`: Repository owner (string, required) - `page`: Page number for pagination (default: 1) (number, optional) @@ -516,6 +518,7 @@ The following sets of tools are available: - `workflow_runs_filter`: Filters for workflow runs. **ONLY** used when method is 'list_workflow_runs' (object, optional) - **actions_run_trigger** - Trigger GitHub Actions workflow actions + - **Required OAuth Scopes**: `repo` - `inputs`: Inputs the workflow accepts. Only used for 'run_workflow' method. (object, optional) - `method`: The method to execute (string, required) - `owner`: Repository owner (string, required) @@ -525,21 +528,25 @@ The following sets of tools are available: - `workflow_id`: The workflow ID (numeric) or workflow file name (e.g., main.yml, ci.yaml). Required for 'run_workflow' method. (string, optional) - **cancel_workflow_run** - Cancel workflow run + - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `run_id`: The unique identifier of the workflow run (number, required) - **delete_workflow_run_logs** - Delete workflow logs + - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `run_id`: The unique identifier of the workflow run (number, required) - **download_workflow_run_artifact** - Download workflow artifact + - **Required OAuth Scopes**: `repo` - `artifact_id`: The unique identifier of the artifact (number, required) - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - **get_job_logs** - Get job logs + - **Required OAuth Scopes**: `repo` - `failed_only`: When true, gets logs for all failed jobs in run_id (boolean, optional) - `job_id`: The unique identifier of the workflow job (required for single job logs) (number, optional) - `owner`: Repository owner (string, required) @@ -549,6 +556,7 @@ The following sets of tools are available: - `tail_lines`: Number of lines to return from the end of the log (number, optional) - **get_job_logs** - Get GitHub Actions workflow job logs + - **Required OAuth Scopes**: `repo` - `failed_only`: When true, gets logs for all failed jobs in the workflow run specified by run_id. Requires run_id to be provided. (boolean, optional) - `job_id`: The unique identifier of the workflow job. Required when getting logs for a single job. (number, optional) - `owner`: Repository owner (string, required) @@ -558,21 +566,25 @@ The following sets of tools are available: - `tail_lines`: Number of lines to return from the end of the log (number, optional) - **get_workflow_run** - Get workflow run + - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `run_id`: The unique identifier of the workflow run (number, required) - **get_workflow_run_logs** - Get workflow run logs + - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `run_id`: The unique identifier of the workflow run (number, required) - **get_workflow_run_usage** - Get workflow usage + - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `run_id`: The unique identifier of the workflow run (number, required) - **list_workflow_jobs** - List workflow jobs + - **Required OAuth Scopes**: `repo` - `filter`: Filters jobs by their completed_at timestamp (string, optional) - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) @@ -581,6 +593,7 @@ The following sets of tools are available: - `run_id`: The unique identifier of the workflow run (number, required) - **list_workflow_run_artifacts** - List workflow artifacts + - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) @@ -588,6 +601,7 @@ The following sets of tools are available: - `run_id`: The unique identifier of the workflow run (number, required) - **list_workflow_runs** - List workflow runs + - **Required OAuth Scopes**: `repo` - `actor`: Returns someone's workflow runs. Use the login for the user who created the workflow run. (string, optional) - `branch`: Returns workflow runs associated with a branch. Use the name of the branch. (string, optional) - `event`: Returns workflow runs for a specific event type (string, optional) @@ -599,22 +613,26 @@ The following sets of tools are available: - `workflow_id`: The workflow ID or workflow file name (string, required) - **list_workflows** - List workflows + - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) - `repo`: Repository name (string, required) - **rerun_failed_jobs** - Rerun failed jobs + - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `run_id`: The unique identifier of the workflow run (number, required) - **rerun_workflow_run** - Rerun workflow run + - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `run_id`: The unique identifier of the workflow run (number, required) - **run_workflow** - Run workflow + - **Required OAuth Scopes**: `repo` - `inputs`: Inputs the workflow accepts (object, optional) - `owner`: Repository owner (string, required) - `ref`: The git reference for the workflow. The reference can be a branch or tag name. (string, required) @@ -628,11 +646,15 @@ The following sets of tools are available: codescan Code Security - **get_code_scanning_alert** - Get code scanning alert + - **Required OAuth Scopes**: `security_events` + - **Accepted OAuth Scopes**: `security_events`, `repo` - `alertNumber`: The number of the alert. (number, required) - `owner`: The owner of the repository. (string, required) - `repo`: The name of the repository. (string, required) - **list_code_scanning_alerts** - List code scanning alerts + - **Required OAuth Scopes**: `security_events` + - **Accepted OAuth Scopes**: `security_events`, `repo` - `owner`: The owner of the repository. (string, required) - `ref`: The Git reference for the results you want to list. (string, optional) - `repo`: The name of the repository. (string, required) @@ -650,10 +672,14 @@ The following sets of tools are available: - No parameters required - **get_team_members** - Get team members + - **Required OAuth Scopes**: `read:org` + - **Accepted OAuth Scopes**: `read:org`, `write:org`, `admin:org` - `org`: Organization login (owner) that contains the team. (string, required) - `team_slug`: Team slug (string, required) - **get_teams** - Get teams + - **Required OAuth Scopes**: `read:org` + - **Accepted OAuth Scopes**: `read:org`, `write:org`, `admin:org` - `user`: Username to get teams for. If not provided, uses the authenticated user. (string, optional) @@ -663,11 +689,15 @@ The following sets of tools are available: dependabot Dependabot - **get_dependabot_alert** - Get dependabot alert + - **Required OAuth Scopes**: `security_events` + - **Accepted OAuth Scopes**: `security_events`, `repo` - `alertNumber`: The number of the alert. (number, required) - `owner`: The owner of the repository. (string, required) - `repo`: The name of the repository. (string, required) - **list_dependabot_alerts** - List dependabot alerts + - **Required OAuth Scopes**: `security_events` + - **Accepted OAuth Scopes**: `security_events`, `repo` - `owner`: The owner of the repository. (string, required) - `repo`: The name of the repository. (string, required) - `severity`: Filter dependabot alerts by severity (string, optional) @@ -680,11 +710,13 @@ The following sets of tools are available: comment-discussion Discussions - **get_discussion** - Get discussion + - **Required OAuth Scopes**: `repo` - `discussionNumber`: Discussion Number (number, required) - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - **get_discussion_comments** - Get discussion comments + - **Required OAuth Scopes**: `repo` - `after`: Cursor for pagination. Use the endCursor from the previous page's PageInfo for GraphQL APIs. (string, optional) - `discussionNumber`: Discussion Number (number, required) - `owner`: Repository owner (string, required) @@ -692,10 +724,12 @@ The following sets of tools are available: - `repo`: Repository name (string, required) - **list_discussion_categories** - List discussion categories + - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name. If not provided, discussion categories will be queried at the organisation level. (string, optional) - **list_discussions** - List discussions + - **Required OAuth Scopes**: `repo` - `after`: Cursor for pagination. Use the endCursor from the previous page's PageInfo for GraphQL APIs. (string, optional) - `category`: Optional filter by discussion category ID. If provided, only discussions with this category are listed. (string, optional) - `direction`: Order direction. (string, optional) @@ -711,6 +745,7 @@ The following sets of tools are available: logo-gist Gists - **create_gist** - Create Gist + - **Required OAuth Scopes**: `gist` - `content`: Content for simple single-file gist creation (string, required) - `description`: Description of the gist (string, optional) - `filename`: Filename for simple single-file gist creation (string, required) @@ -726,6 +761,7 @@ The following sets of tools are available: - `username`: GitHub username (omit for authenticated user's gists) (string, optional) - **update_gist** - Update Gist + - **Required OAuth Scopes**: `gist` - `content`: Content for the file (string, required) - `description`: Updated description of the gist (string, optional) - `filename`: Filename to update or create (string, required) @@ -738,6 +774,7 @@ The following sets of tools are available: git-branch Git - **get_repository_tree** - Get repository tree + - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (username or organization) (string, required) - `path_filter`: Optional path prefix to filter the tree results (e.g., 'src/' to only show files in the src directory) (string, optional) - `recursive`: Setting this parameter to true returns the objects or subtrees referenced by the tree. Default is false (boolean, optional) @@ -751,22 +788,26 @@ The following sets of tools are available: issue-opened Issues - **add_issue_comment** - Add comment to issue + - **Required OAuth Scopes**: `repo` - `body`: Comment content (string, required) - `issue_number`: Issue number to comment on (number, required) - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - **assign_copilot_to_issue** - Assign Copilot to issue + - **Required OAuth Scopes**: `repo` - `issueNumber`: Issue number (number, required) - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - **get_label** - Get a specific label from a repository. + - **Required OAuth Scopes**: `repo` - `name`: Label name. (string, required) - `owner`: Repository owner (username or organization name) (string, required) - `repo`: Repository name (string, required) - **issue_read** - Get issue details + - **Required OAuth Scopes**: `repo` - `issue_number`: The number of the issue (number, required) - `method`: The read operation to perform on a single issue. Options are: @@ -781,6 +822,7 @@ The following sets of tools are available: - `repo`: The name of the repository (string, required) - **issue_write** - Create or update issue. + - **Required OAuth Scopes**: `repo` - `assignees`: Usernames to assign to this issue (string[], optional) - `body`: Issue body content (string, optional) - `duplicate_of`: Issue number that this issue is a duplicate of. Only used when state_reason is 'duplicate'. (number, optional) @@ -800,9 +842,12 @@ The following sets of tools are available: - `type`: Type of this issue. Only use if the repository has issue types configured. Use list_issue_types tool to get valid type values for the organization. If the repository doesn't support issue types, omit this parameter. (string, optional) - **list_issue_types** - List available issue types + - **Required OAuth Scopes**: `read:org` + - **Accepted OAuth Scopes**: `read:org`, `write:org`, `admin:org` - `owner`: The organization owner of the repository (string, required) - **list_issues** - List issues + - **Required OAuth Scopes**: `repo` - `after`: Cursor for pagination. Use the endCursor from the previous page's PageInfo for GraphQL APIs. (string, optional) - `direction`: Order direction. If provided, the 'orderBy' also needs to be provided. (string, optional) - `labels`: Filter by labels (string[], optional) @@ -814,6 +859,7 @@ The following sets of tools are available: - `state`: Filter by state, by default both open and closed issues are returned when not provided (string, optional) - **search_issues** - Search issues + - **Required OAuth Scopes**: `repo` - `order`: Sort order (string, optional) - `owner`: Optional repository owner. If provided with repo, only issues for this repository are listed. (string, optional) - `page`: Page number for pagination (min 1) (number, optional) @@ -823,6 +869,7 @@ The following sets of tools are available: - `sort`: Sort field by number of matches of categories, defaults to best match (string, optional) - **sub_issue_write** - Change sub-issue + - **Required OAuth Scopes**: `repo` - `after_id`: The ID of the sub-issue to be prioritized after (either after_id OR before_id should be specified) (number, optional) - `before_id`: The ID of the sub-issue to be prioritized before (either after_id OR before_id should be specified) (number, optional) - `issue_number`: The number of the parent issue (number, required) @@ -844,11 +891,13 @@ The following sets of tools are available: tag Labels - **get_label** - Get a specific label from a repository. + - **Required OAuth Scopes**: `repo` - `name`: Label name. (string, required) - `owner`: Repository owner (username or organization name) (string, required) - `repo`: Repository name (string, required) - **label_write** - Write operations on repository labels. + - **Required OAuth Scopes**: `repo` - `color`: Label color as 6-character hex code without '#' prefix (e.g., 'f29513'). Required for 'create', optional for 'update'. (string, optional) - `description`: Label description text. Optional for 'create' and 'update'. (string, optional) - `method`: Operation to perform: 'create', 'update', or 'delete' (string, required) @@ -858,6 +907,7 @@ The following sets of tools are available: - `repo`: Repository name (string, required) - **list_label** - List labels from a repository + - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (username or organization name) - required for all operations (string, required) - `repo`: Repository name - required for all operations (string, required) @@ -868,13 +918,16 @@ The following sets of tools are available: bell Notifications - **dismiss_notification** - Dismiss notification + - **Required OAuth Scopes**: `notifications` - `state`: The new state of the notification (read/done) (string, required) - `threadID`: The ID of the notification thread (string, required) - **get_notification_details** - Get notification details + - **Required OAuth Scopes**: `notifications` - `notificationID`: The ID of the notification (string, required) - **list_notifications** - List notifications + - **Required OAuth Scopes**: `notifications` - `before`: Only show notifications updated before the given time (ISO 8601 format) (string, optional) - `filter`: Filter notifications to, use default unless specified. Read notifications are ones that have already been acknowledged by the user. Participating notifications are those that the user is directly involved in, such as issues or pull requests they have commented on or created. (string, optional) - `owner`: Optional repository owner. If provided with repo, only notifications for this repository are listed. (string, optional) @@ -884,15 +937,18 @@ The following sets of tools are available: - `since`: Only show notifications updated after the given time (ISO 8601 format) (string, optional) - **manage_notification_subscription** - Manage notification subscription + - **Required OAuth Scopes**: `notifications` - `action`: Action to perform: ignore, watch, or delete the notification subscription. (string, required) - `notificationID`: The ID of the notification thread. (string, required) - **manage_repository_notification_subscription** - Manage repository notification subscription + - **Required OAuth Scopes**: `notifications` - `action`: Action to perform: ignore, watch, or delete the repository notification subscription. (string, required) - `owner`: The account owner of the repository. (string, required) - `repo`: The name of the repository. (string, required) - **mark_all_notifications_read** - Mark all notifications as read + - **Required OAuth Scopes**: `notifications` - `lastReadAt`: Describes the last point that notifications were checked (optional). Default: Now (string, optional) - `owner`: Optional repository owner. If provided with repo, only notifications for this repository are marked as read. (string, optional) - `repo`: Optional repository name. If provided with owner, only notifications for this repository are marked as read. (string, optional) @@ -904,6 +960,8 @@ The following sets of tools are available: organization Organizations - **search_orgs** - Search organizations + - **Required OAuth Scopes**: `read:org` + - **Accepted OAuth Scopes**: `read:org`, `write:org`, `admin:org` - `order`: Sort order (string, optional) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) @@ -917,6 +975,7 @@ The following sets of tools are available: project Projects - **add_project_item** - Add project item + - **Required OAuth Scopes**: `project` - `item_id`: The numeric ID of the issue or pull request to add to the project. (number, required) - `item_type`: The item's type, either issue or pull_request. (string, required) - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) @@ -924,23 +983,30 @@ The following sets of tools are available: - `project_number`: The project's number. (number, required) - **delete_project_item** - Delete project item + - **Required OAuth Scopes**: `project` - `item_id`: The internal project item ID to delete from the project (not the issue or pull request ID). (number, required) - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) - `owner_type`: Owner type (string, required) - `project_number`: The project's number. (number, required) - **get_project** - Get project + - **Required OAuth Scopes**: `read:project` + - **Accepted OAuth Scopes**: `read:project`, `project` - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) - `owner_type`: Owner type (string, required) - `project_number`: The project's number (number, required) - **get_project_field** - Get project field + - **Required OAuth Scopes**: `read:project` + - **Accepted OAuth Scopes**: `read:project`, `project` - `field_id`: The field's id. (number, required) - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) - `owner_type`: Owner type (string, required) - `project_number`: The project's number. (number, required) - **get_project_item** - Get project item + - **Required OAuth Scopes**: `read:project` + - **Accepted OAuth Scopes**: `read:project`, `project` - `fields`: Specific list of field IDs to include in the response (e.g. ["102589", "985201", "169875"]). If not provided, only the title field is included. (string[], optional) - `item_id`: The item's ID. (number, required) - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) @@ -948,6 +1014,8 @@ The following sets of tools are available: - `project_number`: The project's number. (number, required) - **list_project_fields** - List project fields + - **Required OAuth Scopes**: `read:project` + - **Accepted OAuth Scopes**: `read:project`, `project` - `after`: Forward pagination cursor from previous pageInfo.nextCursor. (string, optional) - `before`: Backward pagination cursor from previous pageInfo.prevCursor (rare). (string, optional) - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) @@ -956,6 +1024,8 @@ The following sets of tools are available: - `project_number`: The project's number. (number, required) - **list_project_items** - List project items + - **Required OAuth Scopes**: `read:project` + - **Accepted OAuth Scopes**: `read:project`, `project` - `after`: Forward pagination cursor from previous pageInfo.nextCursor. (string, optional) - `before`: Backward pagination cursor from previous pageInfo.prevCursor (rare). (string, optional) - `fields`: Field IDs to include (e.g. ["102589", "985201"]). CRITICAL: Always provide to get field values. Without this, only titles returned. (string[], optional) @@ -966,6 +1036,8 @@ The following sets of tools are available: - `query`: Query string for advanced filtering of project items using GitHub's project filtering syntax. (string, optional) - **list_projects** - List projects + - **Required OAuth Scopes**: `read:project` + - **Accepted OAuth Scopes**: `read:project`, `project` - `after`: Forward pagination cursor from previous pageInfo.nextCursor. (string, optional) - `before`: Backward pagination cursor from previous pageInfo.prevCursor (rare). (string, optional) - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) @@ -974,6 +1046,7 @@ The following sets of tools are available: - `query`: Filter projects by title text and open/closed state; permitted qualifiers: is:open, is:closed; examples: "roadmap is:open", "is:open feature planning". (string, optional) - **update_project_item** - Update project item + - **Required OAuth Scopes**: `project` - `item_id`: The unique identifier of the project item. This is not the issue or pull request ID. (number, required) - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) - `owner_type`: Owner type (string, required) @@ -987,6 +1060,7 @@ The following sets of tools are available: git-pull-request Pull Requests - **add_comment_to_pending_review** - Add review comment to the requester's latest pending pull request review + - **Required OAuth Scopes**: `repo` - `body`: The text of the review comment (string, required) - `line`: The line of the blob in the pull request diff that the comment applies to. For multi-line comments, the last line of the range (number, optional) - `owner`: Repository owner (string, required) @@ -999,6 +1073,7 @@ The following sets of tools are available: - `subjectType`: The level at which the comment is targeted (string, required) - **create_pull_request** - Open new pull request + - **Required OAuth Scopes**: `repo` - `base`: Branch to merge into (string, required) - `body`: PR description (string, optional) - `draft`: Create as draft PR (boolean, optional) @@ -1009,6 +1084,7 @@ The following sets of tools are available: - `title`: PR title (string, required) - **list_pull_requests** - List pull requests + - **Required OAuth Scopes**: `repo` - `base`: Filter by base branch (string, optional) - `direction`: Sort direction (string, optional) - `head`: Filter by head user/org and branch (string, optional) @@ -1020,6 +1096,7 @@ The following sets of tools are available: - `state`: Filter by state (string, optional) - **merge_pull_request** - Merge pull request + - **Required OAuth Scopes**: `repo` - `commit_message`: Extra detail for merge commit (string, optional) - `commit_title`: Title for merge commit (string, optional) - `merge_method`: Merge method (string, optional) @@ -1028,6 +1105,7 @@ The following sets of tools are available: - `repo`: Repository name (string, required) - **pull_request_read** - Get details for a single pull request + - **Required OAuth Scopes**: `repo` - `method`: Action to specify what pull request data needs to be retrieved from GitHub. Possible options: 1. get - Get details of a specific pull request. @@ -1045,6 +1123,7 @@ The following sets of tools are available: - `repo`: Repository name (string, required) - **pull_request_review_write** - Write operations (create, submit, delete) on pull request reviews. + - **Required OAuth Scopes**: `repo` - `body`: Review comment text (string, optional) - `commitID`: SHA of commit to review (string, optional) - `event`: Review action to perform. (string, optional) @@ -1054,11 +1133,13 @@ The following sets of tools are available: - `repo`: Repository name (string, required) - **request_copilot_review** - Request Copilot review + - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `pullNumber`: Pull request number (number, required) - `repo`: Repository name (string, required) - **search_pull_requests** - Search pull requests + - **Required OAuth Scopes**: `repo` - `order`: Sort order (string, optional) - `owner`: Optional repository owner. If provided with repo, only pull requests for this repository are listed. (string, optional) - `page`: Page number for pagination (min 1) (number, optional) @@ -1068,6 +1149,7 @@ The following sets of tools are available: - `sort`: Sort field by number of matches of categories, defaults to best match (string, optional) - **update_pull_request** - Edit pull request + - **Required OAuth Scopes**: `repo` - `base`: New base branch name (string, optional) - `body`: New description (string, optional) - `draft`: Mark pull request as draft (true) or ready for review (false) (boolean, optional) @@ -1080,6 +1162,7 @@ The following sets of tools are available: - `title`: New title (string, optional) - **update_pull_request_branch** - Update pull request branch + - **Required OAuth Scopes**: `repo` - `expectedHeadSha`: The expected SHA of the pull request's HEAD ref (string, optional) - `owner`: Repository owner (string, required) - `pullNumber`: Pull request number (number, required) @@ -1092,12 +1175,14 @@ The following sets of tools are available: repo Repositories - **create_branch** - Create branch + - **Required OAuth Scopes**: `repo` - `branch`: Name for new branch (string, required) - `from_branch`: Source branch (defaults to repo default) (string, optional) - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - **create_or_update_file** - Create or update file + - **Required OAuth Scopes**: `repo` - `branch`: Branch to create/update the file in (string, required) - `content`: Content of the file (string, required) - `message`: Commit message (string, required) @@ -1107,6 +1192,7 @@ The following sets of tools are available: - `sha`: The blob SHA of the file being replaced. (string, optional) - **create_repository** - Create repository + - **Required OAuth Scopes**: `repo` - `autoInit`: Initialize with README (boolean, optional) - `description`: Repository description (string, optional) - `name`: Repository name (string, required) @@ -1114,6 +1200,7 @@ The following sets of tools are available: - `private`: Whether repo should be private (boolean, optional) - **delete_file** - Delete file + - **Required OAuth Scopes**: `repo` - `branch`: Branch to delete the file from (string, required) - `message`: Commit message (string, required) - `owner`: Repository owner (username or organization) (string, required) @@ -1121,11 +1208,13 @@ The following sets of tools are available: - `repo`: Repository name (string, required) - **fork_repository** - Fork repository + - **Required OAuth Scopes**: `repo` - `organization`: Organization to fork to (string, optional) - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - **get_commit** - Get commit details + - **Required OAuth Scopes**: `repo` - `include_diff`: Whether to include file diffs and stats in the response. Default is true. (boolean, optional) - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) @@ -1134,6 +1223,7 @@ The following sets of tools are available: - `sha`: Commit SHA, branch name, or tag name (string, required) - **get_file_contents** - Get file or directory contents + - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (username or organization) (string, required) - `path`: Path to file/directory (string, optional) - `ref`: Accepts optional git refs such as `refs/tags/{tag}`, `refs/heads/{branch}` or `refs/pull/{pr_number}/head` (string, optional) @@ -1141,26 +1231,31 @@ The following sets of tools are available: - `sha`: Accepts optional commit SHA. If specified, it will be used instead of ref (string, optional) - **get_latest_release** - Get latest release + - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - **get_release_by_tag** - Get a release by tag name + - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `tag`: Tag name (e.g., 'v1.0.0') (string, required) - **get_tag** - Get tag details + - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `tag`: Tag name (string, required) - **list_branches** - List branches + - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) - `repo`: Repository name (string, required) - **list_commits** - List commits + - **Required OAuth Scopes**: `repo` - `author`: Author username or email address to filter commits by (string, optional) - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) @@ -1169,18 +1264,21 @@ The following sets of tools are available: - `sha`: Commit SHA, branch or tag name to list commits of. If not provided, uses the default branch of the repository. If a commit SHA is provided, will list commits up to that SHA. (string, optional) - **list_releases** - List releases + - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) - `repo`: Repository name (string, required) - **list_tags** - List tags + - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) - `repo`: Repository name (string, required) - **push_files** - Push files to repository + - **Required OAuth Scopes**: `repo` - `branch`: Branch to push to (string, required) - `files`: Array of file objects to push, each object with path (string) and content (string) (object[], required) - `message`: Commit message (string, required) @@ -1188,6 +1286,7 @@ The following sets of tools are available: - `repo`: Repository name (string, required) - **search_code** - Search code + - **Required OAuth Scopes**: `repo` - `order`: Sort order for results (string, optional) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) @@ -1195,6 +1294,7 @@ The following sets of tools are available: - `sort`: Sort field ('indexed' only) (string, optional) - **search_repositories** - Search repositories + - **Required OAuth Scopes**: `repo` - `minimal_output`: Return minimal repository information (default: true). When false, returns full GitHub API repository objects. (boolean, optional) - `order`: Sort order (string, optional) - `page`: Page number for pagination (min 1) (number, optional) @@ -1209,11 +1309,15 @@ The following sets of tools are available: shield-lock Secret Protection - **get_secret_scanning_alert** - Get secret scanning alert + - **Required OAuth Scopes**: `security_events` + - **Accepted OAuth Scopes**: `security_events`, `repo` - `alertNumber`: The number of the alert. (number, required) - `owner`: The owner of the repository. (string, required) - `repo`: The name of the repository. (string, required) - **list_secret_scanning_alerts** - List secret scanning alerts + - **Required OAuth Scopes**: `security_events` + - **Accepted OAuth Scopes**: `security_events`, `repo` - `owner`: The owner of the repository. (string, required) - `repo`: The name of the repository. (string, required) - `resolution`: Filter by resolution (string, optional) @@ -1227,9 +1331,13 @@ The following sets of tools are available: shield Security Advisories - **get_global_security_advisory** - Get a global security advisory + - **Required OAuth Scopes**: `security_events` + - **Accepted OAuth Scopes**: `security_events`, `repo` - `ghsaId`: GitHub Security Advisory ID (format: GHSA-xxxx-xxxx-xxxx). (string, required) - **list_global_security_advisories** - List global security advisories + - **Required OAuth Scopes**: `security_events` + - **Accepted OAuth Scopes**: `security_events`, `repo` - `affects`: Filter advisories by affected package or version (e.g. "package1,package2@1.0.0"). (string, optional) - `cveId`: Filter by CVE ID. (string, optional) - `cwes`: Filter by Common Weakness Enumeration IDs (e.g. ["79", "284", "22"]). (string[], optional) @@ -1243,12 +1351,16 @@ The following sets of tools are available: - `updated`: Filter by update date or date range (ISO 8601 date or range). (string, optional) - **list_org_repository_security_advisories** - List org repository security advisories + - **Required OAuth Scopes**: `security_events` + - **Accepted OAuth Scopes**: `security_events`, `repo` - `direction`: Sort direction. (string, optional) - `org`: The organization login. (string, required) - `sort`: Sort field. (string, optional) - `state`: Filter by advisory state. (string, optional) - **list_repository_security_advisories** - List repository security advisories + - **Required OAuth Scopes**: `security_events` + - **Accepted OAuth Scopes**: `security_events`, `repo` - `direction`: Sort direction. (string, optional) - `owner`: The owner of the repository. (string, required) - `repo`: The name of the repository. (string, required) @@ -1262,6 +1374,7 @@ The following sets of tools are available: star Stargazers - **list_starred_repositories** - List starred repositories + - **Required OAuth Scopes**: `repo` - `direction`: The direction to sort the results by. (string, optional) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) @@ -1269,10 +1382,14 @@ The following sets of tools are available: - `username`: Username to list starred repositories for. Defaults to the authenticated user. (string, optional) - **star_repository** - Star repository + - **Required OAuth Scopes**: `public_repo` + - **Accepted OAuth Scopes**: `public_repo`, `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - **unstar_repository** - Unstar repository + - **Required OAuth Scopes**: `public_repo` + - **Accepted OAuth Scopes**: `public_repo`, `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) @@ -1283,6 +1400,7 @@ The following sets of tools are available: people Users - **search_users** - Search users + - **Required OAuth Scopes**: `repo` - `order`: Sort order (string, optional) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) diff --git a/cmd/github-mcp-server/generate_docs.go b/cmd/github-mcp-server/generate_docs.go index b40e3e2f4..c70fcefa0 100644 --- a/cmd/github-mcp-server/generate_docs.go +++ b/cmd/github-mcp-server/generate_docs.go @@ -11,7 +11,6 @@ import ( "github.com/github/github-mcp-server/pkg/inventory" "github.com/github/github-mcp-server/pkg/translations" "github.com/google/jsonschema-go/jsonschema" - "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/spf13/cobra" ) @@ -190,7 +189,7 @@ func generateToolsDoc(r *inventory.Inventory) string { currentToolsetID = tool.Toolset.ID currentToolsetIcon = tool.Toolset.Icon } - writeToolDoc(&toolBuf, tool.Tool) + writeToolDoc(&toolBuf, tool) toolBuf.WriteString("\n\n") } @@ -224,16 +223,26 @@ func formatToolsetName(name string) string { } } -func writeToolDoc(buf *strings.Builder, tool mcp.Tool) { +func writeToolDoc(buf *strings.Builder, tool inventory.ServerTool) { // Tool name (no icon - section header already has the toolset icon) - fmt.Fprintf(buf, "- **%s** - %s\n", tool.Name, tool.Annotations.Title) + fmt.Fprintf(buf, "- **%s** - %s\n", tool.Tool.Name, tool.Tool.Annotations.Title) + + // OAuth scopes if present + if len(tool.RequiredScopes) > 0 { + fmt.Fprintf(buf, " - **Required OAuth Scopes**: `%s`\n", strings.Join(tool.RequiredScopes, "`, `")) + + // Only show accepted scopes if they differ from required scopes + if len(tool.AcceptedScopes) > 0 && !scopesEqual(tool.RequiredScopes, tool.AcceptedScopes) { + fmt.Fprintf(buf, " - **Accepted OAuth Scopes**: `%s`\n", strings.Join(tool.AcceptedScopes, "`, `")) + } + } // Parameters - if tool.InputSchema == nil { + if tool.Tool.InputSchema == nil { buf.WriteString(" - No parameters required") return } - schema, ok := tool.InputSchema.(*jsonschema.Schema) + schema, ok := tool.Tool.InputSchema.(*jsonschema.Schema) if !ok || schema == nil { buf.WriteString(" - No parameters required") return @@ -282,6 +291,28 @@ func writeToolDoc(buf *strings.Builder, tool mcp.Tool) { } } +// scopesEqual checks if two scope slices contain the same elements (order-independent) +func scopesEqual(a, b []string) bool { + if len(a) != len(b) { + return false + } + + // Create a map for quick lookup + aMap := make(map[string]bool, len(a)) + for _, scope := range a { + aMap[scope] = true + } + + // Check if all elements in b are in a + for _, scope := range b { + if !aMap[scope] { + return false + } + } + + return true +} + func contains(slice []string, item string) bool { for _, s := range slice { if s == item { diff --git a/pkg/github/actions.go b/pkg/github/actions.go index 6c7cdc367..8e3382acf 100644 --- a/pkg/github/actions.go +++ b/pkg/github/actions.go @@ -13,6 +13,7 @@ import ( buffer "github.com/github/github-mcp-server/pkg/buffer" ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" @@ -74,6 +75,8 @@ func ListWorkflows(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -200,6 +203,8 @@ func ListWorkflowRuns(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo", "workflow_id"}, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -311,6 +316,8 @@ func RunWorkflow(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "workflow_id", "ref"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -415,6 +422,8 @@ func GetWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "run_id"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -483,6 +492,8 @@ func GetWorkflowRunLogs(t translations.TranslationHelperFunc) inventory.ServerTo Required: []string{"owner", "repo", "run_id"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -566,6 +577,8 @@ func ListWorkflowJobs(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo", "run_id"}, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -678,6 +691,8 @@ func GetJobLogs(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -926,6 +941,8 @@ func RerunWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo", "run_id"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -1001,6 +1018,8 @@ func RerunFailedJobs(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo", "run_id"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -1076,6 +1095,8 @@ func CancelWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerToo Required: []string{"owner", "repo", "run_id"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -1153,6 +1174,8 @@ func ListWorkflowRunArtifacts(t translations.TranslationHelperFunc) inventory.Se Required: []string{"owner", "repo", "run_id"}, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -1233,6 +1256,8 @@ func DownloadWorkflowRunArtifact(t translations.TranslationHelperFunc) inventory Required: []string{"owner", "repo", "artifact_id"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -1311,6 +1336,8 @@ func DeleteWorkflowRunLogs(t translations.TranslationHelperFunc) inventory.Serve Required: []string{"owner", "repo", "run_id"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -1386,6 +1413,8 @@ func GetWorkflowRunUsage(t translations.TranslationHelperFunc) inventory.ServerT Required: []string{"owner", "repo", "run_id"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -1550,6 +1579,8 @@ Use this tool to list workflows in a repository, or list workflow runs, jobs, an Required: []string{"method", "owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1668,6 +1699,8 @@ Use this tool to get details about individual workflows, workflow runs, jobs, an Required: []string{"method", "owner", "repo", "resource_id"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1781,6 +1814,8 @@ func ActionsRunTrigger(t translations.TranslationHelperFunc) inventory.ServerToo Required: []string{"method", "owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1895,6 +1930,8 @@ For single job logs, provide job_id. For all failed jobs in a run, provide run_i Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/code_scanning.go b/pkg/github/code_scanning.go index 5e25d0501..99ab8ad4f 100644 --- a/pkg/github/code_scanning.go +++ b/pkg/github/code_scanning.go @@ -8,6 +8,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" @@ -44,6 +45,8 @@ func GetCodeScanningAlert(t translations.TranslationHelperFunc) inventory.Server Required: []string{"owner", "repo", "alertNumber"}, }, }, + scopes.ToStringSlice(scopes.SecurityEvents), + scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -135,6 +138,8 @@ func ListCodeScanningAlerts(t translations.TranslationHelperFunc) inventory.Serv Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.SecurityEvents), + scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/context_tools.go b/pkg/github/context_tools.go index e0df82c88..ad3c8daa4 100644 --- a/pkg/github/context_tools.go +++ b/pkg/github/context_tools.go @@ -7,6 +7,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/jsonschema-go/jsonschema" @@ -51,6 +52,8 @@ func GetMe(t translations.TranslationHelperFunc) inventory.ServerTool { // OpenAI strict mode requires the properties field to be present. InputSchema: json.RawMessage(`{"type":"object","properties":{}}`), }, + nil, // no required scopes + nil, // no accepted scopes func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, _ map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -129,6 +132,8 @@ func GetTeams(t translations.TranslationHelperFunc) inventory.ServerTool { }, }, }, + scopes.ToStringSlice(scopes.ReadOrg), + scopes.ToStringSlice(scopes.ReadOrg, scopes.WriteOrg, scopes.AdminOrg), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { user, err := OptionalParam[string](args, "user") if err != nil { @@ -231,6 +236,8 @@ func GetTeamMembers(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"org", "team_slug"}, }, }, + scopes.ToStringSlice(scopes.ReadOrg), + scopes.ToStringSlice(scopes.ReadOrg, scopes.WriteOrg, scopes.AdminOrg), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { org, err := RequiredParam[string](args, "org") if err != nil { diff --git a/pkg/github/dependabot.go b/pkg/github/dependabot.go index db6352dab..5c86d9709 100644 --- a/pkg/github/dependabot.go +++ b/pkg/github/dependabot.go @@ -9,6 +9,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" @@ -45,6 +46,8 @@ func GetDependabotAlert(t translations.TranslationHelperFunc) inventory.ServerTo Required: []string{"owner", "repo", "alertNumber"}, }, }, + scopes.ToStringSlice(scopes.SecurityEvents), + scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -128,6 +131,8 @@ func ListDependabotAlerts(t translations.TranslationHelperFunc) inventory.Server Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.SecurityEvents), + scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/dependencies.go b/pkg/github/dependencies.go index d23e993c3..d5a5e30aa 100644 --- a/pkg/github/dependencies.go +++ b/pkg/github/dependencies.go @@ -148,11 +148,22 @@ func (d BaseDeps) GetContentWindowSize() int { return d.ContentWindowSize } // // The handler function receives deps extracted from context via MustDepsFromContext. // Ensure ContextWithDeps is called to inject deps before any tool handlers are invoked. -func NewTool[In, Out any](toolset inventory.ToolsetMetadata, tool mcp.Tool, handler func(ctx context.Context, deps ToolDependencies, req *mcp.CallToolRequest, args In) (*mcp.CallToolResult, Out, error)) inventory.ServerTool { - return inventory.NewServerToolWithContextHandler(tool, toolset, func(ctx context.Context, req *mcp.CallToolRequest, args In) (*mcp.CallToolResult, Out, error) { +// +// All tools must explicitly specify their OAuth scope requirements, even if empty (nil). +func NewTool[In, Out any]( + toolset inventory.ToolsetMetadata, + tool mcp.Tool, + requiredScopes []string, + acceptedScopes []string, + handler func(ctx context.Context, deps ToolDependencies, req *mcp.CallToolRequest, args In) (*mcp.CallToolResult, Out, error), +) inventory.ServerTool { + st := inventory.NewServerToolWithContextHandler(tool, toolset, func(ctx context.Context, req *mcp.CallToolRequest, args In) (*mcp.CallToolResult, Out, error) { deps := MustDepsFromContext(ctx) return handler(ctx, deps, req, args) }) + st.RequiredScopes = requiredScopes + st.AcceptedScopes = acceptedScopes + return st } // NewToolFromHandler creates a ServerTool that retrieves ToolDependencies from context at call time. @@ -160,9 +171,20 @@ func NewTool[In, Out any](toolset inventory.ToolsetMetadata, tool mcp.Tool, hand // // The handler function receives deps extracted from context via MustDepsFromContext. // Ensure ContextWithDeps is called to inject deps before any tool handlers are invoked. -func NewToolFromHandler(toolset inventory.ToolsetMetadata, tool mcp.Tool, handler func(ctx context.Context, deps ToolDependencies, req *mcp.CallToolRequest) (*mcp.CallToolResult, error)) inventory.ServerTool { - return inventory.NewServerToolWithRawContextHandler(tool, toolset, func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { +// +// All tools must explicitly specify their OAuth scope requirements, even if empty (nil). +func NewToolFromHandler( + toolset inventory.ToolsetMetadata, + tool mcp.Tool, + requiredScopes []string, + acceptedScopes []string, + handler func(ctx context.Context, deps ToolDependencies, req *mcp.CallToolRequest) (*mcp.CallToolResult, error), +) inventory.ServerTool { + st := inventory.NewServerToolWithRawContextHandler(tool, toolset, func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { deps := MustDepsFromContext(ctx) return handler(ctx, deps, req) }) + st.RequiredScopes = requiredScopes + st.AcceptedScopes = acceptedScopes + return st } diff --git a/pkg/github/discussions.go b/pkg/github/discussions.go index c891ba294..270228c73 100644 --- a/pkg/github/discussions.go +++ b/pkg/github/discussions.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/go-viper/mapstructure/v2" @@ -161,6 +162,8 @@ func ListDiscussions(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner"}, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -303,6 +306,8 @@ func GetDiscussion(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "discussionNumber"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { // Decode params var params struct { @@ -406,6 +411,8 @@ func GetDiscussionComments(t translations.TranslationHelperFunc) inventory.Serve Required: []string{"owner", "repo", "discussionNumber"}, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { // Decode params var params struct { @@ -528,6 +535,8 @@ func ListDiscussionCategories(t translations.TranslationHelperFunc) inventory.Se Required: []string{"owner"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/gists.go b/pkg/github/gists.go index 4d741b88d..0509220e3 100644 --- a/pkg/github/gists.go +++ b/pkg/github/gists.go @@ -9,6 +9,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" @@ -41,6 +42,8 @@ func ListGists(t translations.TranslationHelperFunc) inventory.ServerTool { }, }), }, + nil, // no required scopes + nil, // no accepted scopes func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { username, err := OptionalParam[string](args, "username") if err != nil { @@ -124,6 +127,8 @@ func GetGist(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"gist_id"}, }, }, + nil, // no required scopes + nil, // no accepted scopes func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { gistID, err := RequiredParam[string](args, "gist_id") if err != nil { @@ -194,6 +199,8 @@ func CreateGist(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"filename", "content"}, }, }, + scopes.ToStringSlice(scopes.Gist), + scopes.ToStringSlice(scopes.Gist), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { description, err := OptionalParam[string](args, "description") if err != nil { @@ -295,6 +302,8 @@ func UpdateGist(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"gist_id", "filename", "content"}, }, }, + scopes.ToStringSlice(scopes.Gist), + scopes.ToStringSlice(scopes.Gist), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { gistID, err := RequiredParam[string](args, "gist_id") if err != nil { diff --git a/pkg/github/git.go b/pkg/github/git.go index 7b93c3675..8e632c1af 100644 --- a/pkg/github/git.go +++ b/pkg/github/git.go @@ -8,6 +8,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" @@ -76,6 +77,8 @@ func GetRepositoryTree(t translations.TranslationHelperFunc) inventory.ServerToo Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/issues.go b/pkg/github/issues.go index f06dc2d9d..aec4783d8 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -14,6 +14,7 @@ import ( "github.com/github/github-mcp-server/pkg/lockdown" "github.com/github/github-mcp-server/pkg/octicons" "github.com/github/github-mcp-server/pkg/sanitize" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/go-viper/mapstructure/v2" @@ -274,6 +275,8 @@ Options are: }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { method, err := RequiredParam[string](args, "method") if err != nil { @@ -565,6 +568,8 @@ func ListIssueTypes(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner"}, }, }, + scopes.ToStringSlice(scopes.ReadOrg), + scopes.ToStringSlice(scopes.ReadOrg, scopes.WriteOrg, scopes.AdminOrg), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -632,6 +637,8 @@ func AddIssueComment(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo", "issue_number", "body"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -736,6 +743,8 @@ Options are: Required: []string{"method", "owner", "repo", "issue_number", "sub_issue_id"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { method, err := RequiredParam[string](args, "method") if err != nil { @@ -963,6 +972,8 @@ func SearchIssues(t translations.TranslationHelperFunc) inventory.ServerTool { }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { result, err := searchHandler(ctx, deps.GetClient, args, "issue", "failed to search issues") return result, nil, err @@ -1052,6 +1063,8 @@ Options are: Required: []string{"method", "owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { method, err := RequiredParam[string](args, "method") if err != nil { @@ -1381,6 +1394,8 @@ func ListIssues(t translations.TranslationHelperFunc) inventory.ServerTool { }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1634,6 +1649,8 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server Required: []string{"owner", "repo", "issueNumber"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { var params struct { Owner string diff --git a/pkg/github/labels.go b/pkg/github/labels.go index 2811cf66e..e85dee450 100644 --- a/pkg/github/labels.go +++ b/pkg/github/labels.go @@ -8,6 +8,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/jsonschema-go/jsonschema" @@ -45,6 +46,8 @@ func GetLabel(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "name"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -142,6 +145,8 @@ func ListLabels(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -253,6 +258,8 @@ func LabelWrite(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"method", "owner", "repo", "name"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { // Get and validate required parameters method, err := RequiredParam[string](args, "method") diff --git a/pkg/github/notifications.go b/pkg/github/notifications.go index 1e2011fa3..87026e2cb 100644 --- a/pkg/github/notifications.go +++ b/pkg/github/notifications.go @@ -11,6 +11,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" @@ -62,6 +63,8 @@ func ListNotifications(t translations.TranslationHelperFunc) inventory.ServerToo }, }), }, + scopes.ToStringSlice(scopes.Notifications), + scopes.ToStringSlice(scopes.Notifications), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -187,6 +190,8 @@ func DismissNotification(t translations.TranslationHelperFunc) inventory.ServerT Required: []string{"threadID", "state"}, }, }, + scopes.ToStringSlice(scopes.Notifications), + scopes.ToStringSlice(scopes.Notifications), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -270,6 +275,8 @@ func MarkAllNotificationsRead(t translations.TranslationHelperFunc) inventory.Se }, }, }, + scopes.ToStringSlice(scopes.Notifications), + scopes.ToStringSlice(scopes.Notifications), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -354,6 +361,8 @@ func GetNotificationDetails(t translations.TranslationHelperFunc) inventory.Serv Required: []string{"notificationID"}, }, }, + scopes.ToStringSlice(scopes.Notifications), + scopes.ToStringSlice(scopes.Notifications), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -427,6 +436,8 @@ func ManageNotificationSubscription(t translations.TranslationHelperFunc) invent Required: []string{"notificationID", "action"}, }, }, + scopes.ToStringSlice(scopes.Notifications), + scopes.ToStringSlice(scopes.Notifications), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -526,6 +537,8 @@ func ManageRepositoryNotificationSubscription(t translations.TranslationHelperFu Required: []string{"owner", "repo", "action"}, }, }, + scopes.ToStringSlice(scopes.Notifications), + scopes.ToStringSlice(scopes.Notifications), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { diff --git a/pkg/github/projects.go b/pkg/github/projects.go index 0536bed99..3204243f7 100644 --- a/pkg/github/projects.go +++ b/pkg/github/projects.go @@ -10,6 +10,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" @@ -67,6 +68,8 @@ func ListProjects(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner_type", "owner"}, }, }, + scopes.ToStringSlice(scopes.ReadProject), + scopes.ToStringSlice(scopes.ReadProject, scopes.Project), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") @@ -172,6 +175,8 @@ func GetProject(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"project_number", "owner_type", "owner"}, }, }, + scopes.ToStringSlice(scopes.ReadProject), + scopes.ToStringSlice(scopes.ReadProject, scopes.Project), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { projectNumber, err := RequiredInt(args, "project_number") @@ -272,6 +277,8 @@ func ListProjectFields(t translations.TranslationHelperFunc) inventory.ServerToo Required: []string{"owner_type", "owner", "project_number"}, }, }, + scopes.ToStringSlice(scopes.ReadProject), + scopes.ToStringSlice(scopes.ReadProject, scopes.Project), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") @@ -370,6 +377,8 @@ func GetProjectField(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner_type", "owner", "project_number", "field_id"}, }, }, + scopes.ToStringSlice(scopes.ReadProject), + scopes.ToStringSlice(scopes.ReadProject, scopes.Project), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") @@ -481,6 +490,8 @@ func ListProjectItems(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner_type", "owner", "project_number"}, }, }, + scopes.ToStringSlice(scopes.ReadProject), + scopes.ToStringSlice(scopes.ReadProject, scopes.Project), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") @@ -605,6 +616,8 @@ func GetProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner_type", "owner", "project_number", "item_id"}, }, }, + scopes.ToStringSlice(scopes.ReadProject), + scopes.ToStringSlice(scopes.ReadProject, scopes.Project), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") @@ -709,6 +722,8 @@ func AddProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner_type", "owner", "project_number", "item_type", "item_id"}, }, }, + scopes.ToStringSlice(scopes.Project), + scopes.ToStringSlice(scopes.Project), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") @@ -819,6 +834,8 @@ func UpdateProjectItem(t translations.TranslationHelperFunc) inventory.ServerToo Required: []string{"owner_type", "owner", "project_number", "item_id", "updated_field"}, }, }, + scopes.ToStringSlice(scopes.Project), + scopes.ToStringSlice(scopes.Project), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") @@ -928,6 +945,8 @@ func DeleteProjectItem(t translations.TranslationHelperFunc) inventory.ServerToo Required: []string{"owner_type", "owner", "project_number", "item_id"}, }, }, + scopes.ToStringSlice(scopes.Project), + scopes.ToStringSlice(scopes.Project), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") diff --git a/pkg/github/pullrequests.go b/pkg/github/pullrequests.go index d51c14fa4..91145f61d 100644 --- a/pkg/github/pullrequests.go +++ b/pkg/github/pullrequests.go @@ -18,6 +18,7 @@ import ( "github.com/github/github-mcp-server/pkg/lockdown" "github.com/github/github-mcp-server/pkg/octicons" "github.com/github/github-mcp-server/pkg/sanitize" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" ) @@ -69,6 +70,8 @@ Possible options: }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { method, err := RequiredParam[string](args, "method") if err != nil { @@ -518,6 +521,8 @@ func CreatePullRequest(t translations.TranslationHelperFunc) inventory.ServerToo }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -669,6 +674,8 @@ func UpdatePullRequest(t translations.TranslationHelperFunc) inventory.ServerToo }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -950,6 +957,8 @@ func ListPullRequests(t translations.TranslationHelperFunc) inventory.ServerTool }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1086,6 +1095,8 @@ func MergePullRequest(t translations.TranslationHelperFunc) inventory.ServerTool }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1203,6 +1214,8 @@ func SearchPullRequests(t translations.TranslationHelperFunc) inventory.ServerTo }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { result, err := searchHandler(ctx, deps.GetClient, args, "pr", "failed to search pull requests") return result, nil, err @@ -1245,6 +1258,8 @@ func UpdatePullRequestBranch(t translations.TranslationHelperFunc) inventory.Ser }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1371,6 +1386,8 @@ Available methods: }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { var params PullRequestReviewWriteParams if err := mapstructure.Decode(args, ¶ms); err != nil { @@ -1694,6 +1711,8 @@ func AddCommentToPendingReview(t translations.TranslationHelperFunc) inventory.S }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { var params struct { Owner string @@ -1846,6 +1865,8 @@ func RequestCopilotReview(t translations.TranslationHelperFunc) inventory.Server }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index c31bb7df2..b4316afa8 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -13,6 +13,7 @@ import ( "github.com/github/github-mcp-server/pkg/inventory" "github.com/github/github-mcp-server/pkg/octicons" "github.com/github/github-mcp-server/pkg/raw" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" @@ -54,6 +55,8 @@ func GetCommit(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "sha"}, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -150,6 +153,8 @@ func ListCommits(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -249,6 +254,8 @@ func ListBranches(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -362,6 +369,8 @@ If the SHA is not provided, the tool will attempt to acquire it by fetching the Required: []string{"owner", "repo", "path", "content", "message", "branch"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -545,6 +554,8 @@ func CreateRepository(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"name"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { name, err := RequiredParam[string](args, "name") if err != nil { @@ -651,6 +662,8 @@ func GetFileContents(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -834,6 +847,8 @@ func ForkRepository(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -940,6 +955,8 @@ func DeleteFile(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "path", "message", "branch"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1119,6 +1136,8 @@ func CreateBranch(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "branch"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1249,6 +1268,8 @@ func PushFiles(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "branch", "files", "message"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1406,6 +1427,8 @@ func ListTags(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1488,6 +1511,8 @@ func GetTag(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "tag"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1581,6 +1606,8 @@ func ListReleases(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1655,6 +1682,8 @@ func GetLatestRelease(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1723,6 +1752,8 @@ func GetReleaseByTag(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo", "tag"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -2039,6 +2070,8 @@ func ListStarredRepositories(t translations.TranslationHelperFunc) inventory.Ser }, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { username, err := OptionalParam[string](args, "username") if err != nil { @@ -2166,6 +2199,8 @@ func StarRepository(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.PublicRepo), + scopes.ToStringSlice(scopes.PublicRepo, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -2230,6 +2265,8 @@ func UnstarRepository(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.PublicRepo), + scopes.ToStringSlice(scopes.PublicRepo, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/search.go b/pkg/github/search.go index 9a8b971e2..b662cecdd 100644 --- a/pkg/github/search.go +++ b/pkg/github/search.go @@ -9,6 +9,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" @@ -56,6 +57,8 @@ func SearchRepositories(t translations.TranslationHelperFunc) inventory.ServerTo }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { query, err := RequiredParam[string](args, "query") if err != nil { @@ -198,6 +201,8 @@ func SearchCode(t translations.TranslationHelperFunc) inventory.ServerTool { }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { query, err := RequiredParam[string](args, "query") if err != nil { @@ -379,6 +384,8 @@ func SearchUsers(t translations.TranslationHelperFunc) inventory.ServerTool { }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { return userOrOrgHandler(ctx, "user", deps, args) }, @@ -420,6 +427,8 @@ func SearchOrgs(t translations.TranslationHelperFunc) inventory.ServerTool { }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.ReadOrg), + scopes.ToStringSlice(scopes.ReadOrg, scopes.WriteOrg, scopes.AdminOrg), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { return userOrOrgHandler(ctx, "org", deps, args) }, diff --git a/pkg/github/secret_scanning.go b/pkg/github/secret_scanning.go index 0de5166ba..2f3c00877 100644 --- a/pkg/github/secret_scanning.go +++ b/pkg/github/secret_scanning.go @@ -9,6 +9,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" @@ -45,6 +46,8 @@ func GetSecretScanningAlert(t translations.TranslationHelperFunc) inventory.Serv Required: []string{"owner", "repo", "alertNumber"}, }, }, + scopes.ToStringSlice(scopes.SecurityEvents), + scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -131,6 +134,8 @@ func ListSecretScanningAlerts(t translations.TranslationHelperFunc) inventory.Se Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.SecurityEvents), + scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/security_advisories.go b/pkg/github/security_advisories.go index f898de61d..3bbb0d1f7 100644 --- a/pkg/github/security_advisories.go +++ b/pkg/github/security_advisories.go @@ -9,6 +9,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" @@ -83,6 +84,8 @@ func ListGlobalSecurityAdvisories(t translations.TranslationHelperFunc) inventor }, }, }, + scopes.ToStringSlice(scopes.SecurityEvents), + scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -246,6 +249,8 @@ func ListRepositorySecurityAdvisories(t translations.TranslationHelperFunc) inve Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.SecurityEvents), + scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -330,6 +335,8 @@ func GetGlobalSecurityAdvisory(t translations.TranslationHelperFunc) inventory.S Required: []string{"ghsaId"}, }, }, + scopes.ToStringSlice(scopes.SecurityEvents), + scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -401,6 +408,8 @@ func ListOrgRepositorySecurityAdvisories(t translations.TranslationHelperFunc) i Required: []string{"org"}, }, }, + scopes.ToStringSlice(scopes.SecurityEvents), + scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { org, err := RequiredParam[string](args, "org") if err != nil { diff --git a/pkg/inventory/server_tool.go b/pkg/inventory/server_tool.go index 362ee2643..095bedf2b 100644 --- a/pkg/inventory/server_tool.go +++ b/pkg/inventory/server_tool.go @@ -70,6 +70,15 @@ type ServerTool struct { // The context carries request-scoped information for the consumer to use. // Returns (enabled, error). On error, the tool should be treated as disabled. Enabled func(ctx context.Context) (bool, error) + + // RequiredScopes specifies the minimum OAuth scopes required for this tool. + // These are the scopes that must be present for the tool to function. + RequiredScopes []string + + // AcceptedScopes specifies all OAuth scopes that can be used with this tool. + // This includes the required scopes plus any higher-level scopes that provide + // the necessary permissions due to scope hierarchy. + AcceptedScopes []string } // IsReadOnly returns true if this tool is marked as read-only via annotations. diff --git a/pkg/scopes/scopes.go b/pkg/scopes/scopes.go new file mode 100644 index 000000000..268a24d99 --- /dev/null +++ b/pkg/scopes/scopes.go @@ -0,0 +1,77 @@ +package scopes + +// Scope represents a GitHub OAuth scope. +// These constants define all OAuth scopes used by the GitHub MCP server tools. +// See https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps +type Scope string + +const ( + // Repo grants full control of private repositories + Repo Scope = "repo" + + // PublicRepo grants access to public repositories + PublicRepo Scope = "public_repo" + + // ReadOrg grants read-only access to organization membership, teams, and projects + ReadOrg Scope = "read:org" + + // WriteOrg grants write access to organization membership and teams + WriteOrg Scope = "write:org" + + // AdminOrg grants full control of organizations and teams + AdminOrg Scope = "admin:org" + + // Gist grants write access to gists + Gist Scope = "gist" + + // Notifications grants access to notifications + Notifications Scope = "notifications" + + // ReadProject grants read-only access to projects + ReadProject Scope = "read:project" + + // Project grants full control of projects + Project Scope = "project" + + // SecurityEvents grants read and write access to security events + SecurityEvents Scope = "security_events" +) + +// ScopeSet represents a set of OAuth scopes. +type ScopeSet map[Scope]bool + +// NewScopeSet creates a new ScopeSet from the given scopes. +func NewScopeSet(scopes ...Scope) ScopeSet { + set := make(ScopeSet) + for _, scope := range scopes { + set[scope] = true + } + return set +} + +// ToSlice converts a ScopeSet to a slice of Scope values. +func (s ScopeSet) ToSlice() []Scope { + scopes := make([]Scope, 0, len(s)) + for scope := range s { + scopes = append(scopes, scope) + } + return scopes +} + +// ToStringSlice converts a ScopeSet to a slice of string values. +func (s ScopeSet) ToStringSlice() []string { + scopes := make([]string, 0, len(s)) + for scope := range s { + scopes = append(scopes, string(scope)) + } + return scopes +} + +// ToStringSlice converts a slice of Scopes to a slice of strings. +func ToStringSlice(scopes ...Scope) []string { + result := make([]string, len(scopes)) + for i, scope := range scopes { + result[i] = string(scope) + } + return result +}