diff --git a/pkg/github/__toolsnaps__/search_commits_typed.snap b/pkg/github/__toolsnaps__/search_commits_typed.snap new file mode 100644 index 0000000000..dba069c8fa --- /dev/null +++ b/pkg/github/__toolsnaps__/search_commits_typed.snap @@ -0,0 +1,406 @@ +{ + "annotations": { + "idempotentHint": false, + "readOnlyHint": true, + "title": "Search commits" + }, + "description": "Search for commits across GitHub repositories using GitHub's commit search syntax. Useful for finding specific changes, authors, or messages across one or many repositories. Searches the default branch only.", + "icons": [ + { + "mimeType": "image/png", + "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==", + "theme": "light" + }, + { + "mimeType": "image/png", + "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=", + "theme": "dark" + } + ], + "inputSchema": { + "properties": { + "order": { + "description": "Sort order", + "enum": [ + "asc", + "desc" + ], + "type": "string" + }, + "page": { + "description": "Page number for pagination (min 1)", + "minimum": 1, + "type": "number" + }, + "perPage": { + "description": "Results per page for pagination (min 1, max 100)", + "maximum": 100, + "minimum": 1, + "type": "number" + }, + "query": { + "description": "Commit search query (GitHub commit search REST). Searches commit messages on the default branch only. Scope the search with `repo:owner/repo`, `org:`, or `user:` (queries without a scope qualifier match across all of GitHub and are usually not what you want). Other qualifiers: `author:`, `committer:`, `author-name:`, `committer-name:`, `author-email:`, `committer-email:`, `author-date:`, `committer-date:` (supports `\u003e`, `\u003c`, `\u003e=`, `\u003c=`, and `YYYY-MM-DD..YYYY-MM-DD` ranges), `merge:true|false`, `hash:`, `tree:`, `parent:`, `is:public`. Examples: `repo:owner/repo fix panic`; `org:github author:defunkt committer-date:\u003e=2024-01-01`; `\"refactor cache\" repo:o/r`; `hash:abc1234 repo:o/r`.", + "type": "string" + }, + "sort": { + "description": "Sort by author or committer date (defaults to best match)", + "enum": [ + "author-date", + "committer-date" + ], + "type": "string" + } + }, + "required": [ + "query" + ], + "type": "object" + }, + "name": "search_commits", + "outputSchema": { + "additionalProperties": false, + "properties": { + "incomplete_results": { + "type": "boolean" + }, + "items": { + "items": { + "additionalProperties": false, + "properties": { + "author": { + "additionalProperties": false, + "properties": { + "avatar_url": { + "type": "string" + }, + "details": { + "additionalProperties": false, + "properties": { + "bio": { + "type": "string" + }, + "blog": { + "type": "string" + }, + "company": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "email": { + "type": "string" + }, + "followers": { + "type": "integer" + }, + "following": { + "type": "integer" + }, + "hireable": { + "type": "boolean" + }, + "location": { + "type": "string" + }, + "name": { + "type": "string" + }, + "owned_private_repos": { + "type": "integer" + }, + "private_gists": { + "type": "integer" + }, + "public_gists": { + "type": "integer" + }, + "public_repos": { + "type": "integer" + }, + "total_private_repos": { + "type": "integer" + }, + "twitter_username": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + }, + "required": [ + "public_repos", + "public_gists", + "followers", + "following", + "created_at", + "updated_at" + ], + "type": [ + "null", + "object" + ] + }, + "id": { + "type": "integer" + }, + "login": { + "type": "string" + }, + "profile_url": { + "type": "string" + } + }, + "required": [ + "login" + ], + "type": [ + "null", + "object" + ] + }, + "commit": { + "additionalProperties": false, + "properties": { + "author": { + "additionalProperties": false, + "properties": { + "date": { + "type": "string" + }, + "email": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "type": [ + "null", + "object" + ] + }, + "committer": { + "additionalProperties": false, + "properties": { + "date": { + "type": "string" + }, + "email": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "type": [ + "null", + "object" + ] + }, + "message": { + "type": "string" + } + }, + "required": [ + "message" + ], + "type": [ + "null", + "object" + ] + }, + "committer": { + "additionalProperties": false, + "properties": { + "avatar_url": { + "type": "string" + }, + "details": { + "additionalProperties": false, + "properties": { + "bio": { + "type": "string" + }, + "blog": { + "type": "string" + }, + "company": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "email": { + "type": "string" + }, + "followers": { + "type": "integer" + }, + "following": { + "type": "integer" + }, + "hireable": { + "type": "boolean" + }, + "location": { + "type": "string" + }, + "name": { + "type": "string" + }, + "owned_private_repos": { + "type": "integer" + }, + "private_gists": { + "type": "integer" + }, + "public_gists": { + "type": "integer" + }, + "public_repos": { + "type": "integer" + }, + "total_private_repos": { + "type": "integer" + }, + "twitter_username": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + }, + "required": [ + "public_repos", + "public_gists", + "followers", + "following", + "created_at", + "updated_at" + ], + "type": [ + "null", + "object" + ] + }, + "id": { + "type": "integer" + }, + "login": { + "type": "string" + }, + "profile_url": { + "type": "string" + } + }, + "required": [ + "login" + ], + "type": [ + "null", + "object" + ] + }, + "files": { + "items": { + "additionalProperties": false, + "properties": { + "additions": { + "type": "integer" + }, + "changes": { + "type": "integer" + }, + "deletions": { + "type": "integer" + }, + "filename": { + "type": "string" + }, + "patch": { + "type": "string" + }, + "status": { + "type": "string" + } + }, + "required": [ + "filename" + ], + "type": "object" + }, + "type": [ + "null", + "array" + ] + }, + "html_url": { + "type": "string" + }, + "repository": { + "additionalProperties": false, + "properties": { + "full_name": { + "type": "string" + }, + "html_url": { + "type": "string" + }, + "private": { + "type": "boolean" + } + }, + "required": [ + "full_name" + ], + "type": [ + "null", + "object" + ] + }, + "sha": { + "type": "string" + }, + "stats": { + "additionalProperties": false, + "properties": { + "additions": { + "type": "integer" + }, + "deletions": { + "type": "integer" + }, + "total": { + "type": "integer" + } + }, + "type": [ + "null", + "object" + ] + } + }, + "required": [ + "sha", + "html_url" + ], + "type": "object" + }, + "type": [ + "null", + "array" + ] + }, + "total_count": { + "type": "integer" + } + }, + "required": [ + "total_count", + "incomplete_results", + "items" + ], + "type": "object" + } +} \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/search_orgs_typed.snap b/pkg/github/__toolsnaps__/search_orgs_typed.snap new file mode 100644 index 0000000000..f7340687e9 --- /dev/null +++ b/pkg/github/__toolsnaps__/search_orgs_typed.snap @@ -0,0 +1,173 @@ +{ + "annotations": { + "idempotentHint": false, + "readOnlyHint": true, + "title": "Search organizations" + }, + "description": "Find GitHub organizations by name, location, or other organization metadata. Ideal for discovering companies, open source foundations, or teams.", + "icons": [ + { + "mimeType": "image/png", + "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABqElEQVRIidWUsW4TQRCGv9kYFzwCRCDEUuxJsCelyiMAXSSs1Cn8ChQg4QiQeANIpHQoQumg4Q1IGxvJviiLKOIWOgQx8g1FOFux1z58SoTyVzvz7843ezs6uGBJsbjj0oeKbgHXF6qg9NXkzS/dzx9jdm20T/UNwnfg7YItPhA1W8CNuQCEZUR2QvegtUh9m6S/UH02yzeLFKuiKMAmvmGdP7FJ+gjAOt+xzndiXplq0exAP8kVea6DfB9AVF7N8ioBQuj0gRdFfJQd7M7yKgGsvbesddZlwLsQOn2b+AZA6Lb3Jr0yQPyR62ZVVF5SN6sAKE9F5UnUm9C1lZWr0ZrWebVJ2irraOpckras83q69hvW+R+3k7v359+gomr5yXvgUNS8nguoOqZZln1D5ANwcwSNtnKZxvT//CrOU/E3+Cub+AbKrTNJ4WvotvfOBYCyCyxN5IbAPwPKPtESIpuh15bQawsim1PAEo1voPRB16zzP8sOWecfj8/pGvB7lDuNj6cAavKm5GYbYTzzMFTNsyKQPD9UkSGc2VOoyB2r0WZZk5dHfwA6M7v5DAVu0gAAAABJRU5ErkJggg==", + "theme": "light" + }, + { + "mimeType": "image/png", + "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABLklEQVRIic2VsUoDQRCGvzVWVj5AUHwM38BoFzBo6zMIFlokqOAb+AYp7LTwEbRPZyWYtGIlIiqfhSssYe8uJ2f0a46ZuZ1/Z2duD+aFuqVOrM9Y7RTlDYnAGHgCrmvubRNYDiGsVFWg2q+ZHLWvWhRfqJuwLlkBtae+qtvRHqmjXKyKxQL/DXAM3Eb7rCQ2G3PtgdpW99V2tHtqLxeroqjJ68BpfAIcAYcFsenNLWUzNnFE6p76nH54TY/pJXAHnJcK/HRMQwiPwBWw+u379THNCoQQJsBJYg+LYlX8zVXRJEU9AL4aCqxNue9DCBeNCABDoDXl+wBmFqg6ohYwCBFgkBEsJa1gAnTVl6pF6kFidoG3xNcFxrlFnfh/TXlXd5J3dqOvjAd1o06V/5tPH0lBqyqxKbkAAAAASUVORK5CYII=", + "theme": "dark" + } + ], + "inputSchema": { + "properties": { + "order": { + "description": "Sort order", + "enum": [ + "asc", + "desc" + ], + "type": "string" + }, + "page": { + "description": "Page number for pagination (min 1)", + "minimum": 1, + "type": "number" + }, + "perPage": { + "description": "Results per page for pagination (min 1, max 100)", + "maximum": 100, + "minimum": 1, + "type": "number" + }, + "query": { + "description": "Organization search query. Examples: 'microsoft', 'location:california', 'created:\u003e=2025-01-01'. Search is automatically scoped to type:org.", + "type": "string" + }, + "sort": { + "description": "Sort field by category", + "enum": [ + "followers", + "repositories", + "joined" + ], + "type": "string" + } + }, + "required": [ + "query" + ], + "type": "object" + }, + "name": "search_orgs", + "outputSchema": { + "additionalProperties": false, + "properties": { + "incomplete_results": { + "type": "boolean" + }, + "items": { + "items": { + "additionalProperties": false, + "properties": { + "avatar_url": { + "type": "string" + }, + "details": { + "additionalProperties": false, + "properties": { + "bio": { + "type": "string" + }, + "blog": { + "type": "string" + }, + "company": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "email": { + "type": "string" + }, + "followers": { + "type": "integer" + }, + "following": { + "type": "integer" + }, + "hireable": { + "type": "boolean" + }, + "location": { + "type": "string" + }, + "name": { + "type": "string" + }, + "owned_private_repos": { + "type": "integer" + }, + "private_gists": { + "type": "integer" + }, + "public_gists": { + "type": "integer" + }, + "public_repos": { + "type": "integer" + }, + "total_private_repos": { + "type": "integer" + }, + "twitter_username": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + }, + "required": [ + "public_repos", + "public_gists", + "followers", + "following", + "created_at", + "updated_at" + ], + "type": [ + "null", + "object" + ] + }, + "id": { + "type": "integer" + }, + "login": { + "type": "string" + }, + "profile_url": { + "type": "string" + } + }, + "required": [ + "login" + ], + "type": "object" + }, + "type": [ + "null", + "array" + ] + }, + "total_count": { + "type": "integer" + } + }, + "required": [ + "total_count", + "incomplete_results", + "items" + ], + "type": "object" + } +} \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/search_repositories.snap b/pkg/github/__toolsnaps__/search_repositories.snap index 23b1d5e839..9ba57e968a 100644 --- a/pkg/github/__toolsnaps__/search_repositories.snap +++ b/pkg/github/__toolsnaps__/search_repositories.snap @@ -51,5 +51,2642 @@ ], "type": "object" }, - "name": "search_repositories" + "name": "search_repositories", + "outputSchema": { + "$defs": { + "searchPropertyValue": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "items": { + "$ref": "#/$defs/searchPropertyValue" + }, + "type": "array" + }, + { + "additionalProperties": { + "$ref": "#/$defs/searchPropertyValue" + }, + "type": "object" + } + ] + }, + "searchRepository": { + "additionalProperties": false, + "properties": { + "allow_auto_merge": { + "type": [ + "null", + "boolean" + ] + }, + "allow_forking": { + "type": [ + "null", + "boolean" + ] + }, + "allow_merge_commit": { + "type": [ + "null", + "boolean" + ] + }, + "allow_rebase_merge": { + "type": [ + "null", + "boolean" + ] + }, + "allow_squash_merge": { + "type": [ + "null", + "boolean" + ] + }, + "allow_update_branch": { + "type": [ + "null", + "boolean" + ] + }, + "archive_url": { + "type": [ + "null", + "string" + ] + }, + "archived": { + "type": [ + "null", + "boolean" + ] + }, + "assignees_url": { + "type": [ + "null", + "string" + ] + }, + "auto_init": { + "type": [ + "null", + "boolean" + ] + }, + "blobs_url": { + "type": [ + "null", + "string" + ] + }, + "branches_url": { + "type": [ + "null", + "string" + ] + }, + "clone_url": { + "type": [ + "null", + "string" + ] + }, + "code_of_conduct": { + "additionalProperties": false, + "properties": { + "body": { + "type": [ + "null", + "string" + ] + }, + "key": { + "type": [ + "null", + "string" + ] + }, + "name": { + "type": [ + "null", + "string" + ] + }, + "url": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "collaborators_url": { + "type": [ + "null", + "string" + ] + }, + "comments_url": { + "type": [ + "null", + "string" + ] + }, + "commits_url": { + "type": [ + "null", + "string" + ] + }, + "compare_url": { + "type": [ + "null", + "string" + ] + }, + "contents_url": { + "type": [ + "null", + "string" + ] + }, + "contributors_url": { + "type": [ + "null", + "string" + ] + }, + "created_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "custom_properties": { + "additionalProperties": { + "$ref": "#/$defs/searchPropertyValue" + }, + "type": "object" + }, + "default_branch": { + "type": [ + "null", + "string" + ] + }, + "delete_branch_on_merge": { + "type": [ + "null", + "boolean" + ] + }, + "deployments_url": { + "type": [ + "null", + "string" + ] + }, + "description": { + "type": [ + "null", + "string" + ] + }, + "disabled": { + "type": [ + "null", + "boolean" + ] + }, + "downloads_url": { + "type": [ + "null", + "string" + ] + }, + "events_url": { + "type": [ + "null", + "string" + ] + }, + "fork": { + "type": [ + "null", + "boolean" + ] + }, + "forks_count": { + "type": [ + "null", + "integer" + ] + }, + "forks_url": { + "type": [ + "null", + "string" + ] + }, + "full_name": { + "type": [ + "null", + "string" + ] + }, + "git_commits_url": { + "type": [ + "null", + "string" + ] + }, + "git_refs_url": { + "type": [ + "null", + "string" + ] + }, + "git_tags_url": { + "type": [ + "null", + "string" + ] + }, + "git_url": { + "type": [ + "null", + "string" + ] + }, + "gitignore_template": { + "type": [ + "null", + "string" + ] + }, + "has_discussions": { + "type": [ + "null", + "boolean" + ] + }, + "has_downloads": { + "type": [ + "null", + "boolean" + ] + }, + "has_issues": { + "type": [ + "null", + "boolean" + ] + }, + "has_pages": { + "type": [ + "null", + "boolean" + ] + }, + "has_projects": { + "type": [ + "null", + "boolean" + ] + }, + "has_pull_requests": { + "type": [ + "null", + "boolean" + ] + }, + "has_wiki": { + "type": [ + "null", + "boolean" + ] + }, + "homepage": { + "type": [ + "null", + "string" + ] + }, + "hooks_url": { + "type": [ + "null", + "string" + ] + }, + "html_url": { + "type": [ + "null", + "string" + ] + }, + "id": { + "type": [ + "null", + "integer" + ] + }, + "is_template": { + "type": [ + "null", + "boolean" + ] + }, + "issue_comment_url": { + "type": [ + "null", + "string" + ] + }, + "issue_events_url": { + "type": [ + "null", + "string" + ] + }, + "issues_url": { + "type": [ + "null", + "string" + ] + }, + "keys_url": { + "type": [ + "null", + "string" + ] + }, + "labels_url": { + "type": [ + "null", + "string" + ] + }, + "language": { + "type": [ + "null", + "string" + ] + }, + "languages_url": { + "type": [ + "null", + "string" + ] + }, + "license": { + "additionalProperties": false, + "properties": { + "body": { + "type": [ + "null", + "string" + ] + }, + "conditions": { + "items": { + "type": "string" + }, + "type": [ + "null", + "array" + ] + }, + "description": { + "type": [ + "null", + "string" + ] + }, + "featured": { + "type": [ + "null", + "boolean" + ] + }, + "html_url": { + "type": [ + "null", + "string" + ] + }, + "implementation": { + "type": [ + "null", + "string" + ] + }, + "key": { + "type": [ + "null", + "string" + ] + }, + "limitations": { + "items": { + "type": "string" + }, + "type": [ + "null", + "array" + ] + }, + "name": { + "type": [ + "null", + "string" + ] + }, + "permissions": { + "items": { + "type": "string" + }, + "type": [ + "null", + "array" + ] + }, + "spdx_id": { + "type": [ + "null", + "string" + ] + }, + "url": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "license_template": { + "type": [ + "null", + "string" + ] + }, + "master_branch": { + "type": [ + "null", + "string" + ] + }, + "merge_commit_message": { + "type": [ + "null", + "string" + ] + }, + "merge_commit_title": { + "type": [ + "null", + "string" + ] + }, + "merges_url": { + "type": [ + "null", + "string" + ] + }, + "milestones_url": { + "type": [ + "null", + "string" + ] + }, + "mirror_url": { + "type": [ + "null", + "string" + ] + }, + "name": { + "type": [ + "null", + "string" + ] + }, + "network_count": { + "type": [ + "null", + "integer" + ] + }, + "node_id": { + "type": [ + "null", + "string" + ] + }, + "notifications_url": { + "type": [ + "null", + "string" + ] + }, + "open_issues": { + "type": [ + "null", + "integer" + ] + }, + "open_issues_count": { + "type": [ + "null", + "integer" + ] + }, + "organization": { + "additionalProperties": false, + "properties": { + "advanced_security_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "archived_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "avatar_url": { + "type": [ + "null", + "string" + ] + }, + "billing_email": { + "type": [ + "null", + "string" + ] + }, + "blog": { + "type": [ + "null", + "string" + ] + }, + "collaborators": { + "type": [ + "null", + "integer" + ] + }, + "company": { + "type": [ + "null", + "string" + ] + }, + "created_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "default_repository_branch": { + "type": [ + "null", + "string" + ] + }, + "default_repository_permission": { + "type": [ + "null", + "string" + ] + }, + "default_repository_settings": { + "type": [ + "null", + "string" + ] + }, + "dependabot_alerts_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "dependabot_security_updates_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "dependency_graph_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "deploy_keys_enabled_for_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "description": { + "type": [ + "null", + "string" + ] + }, + "disk_usage": { + "type": [ + "null", + "integer" + ] + }, + "display_commenter_full_name_setting_enabled": { + "type": [ + "null", + "boolean" + ] + }, + "email": { + "type": [ + "null", + "string" + ] + }, + "events_url": { + "type": [ + "null", + "string" + ] + }, + "followers": { + "type": [ + "null", + "integer" + ] + }, + "following": { + "type": [ + "null", + "integer" + ] + }, + "has_organization_projects": { + "type": [ + "null", + "boolean" + ] + }, + "has_repository_projects": { + "type": [ + "null", + "boolean" + ] + }, + "hooks_url": { + "type": [ + "null", + "string" + ] + }, + "html_url": { + "type": [ + "null", + "string" + ] + }, + "id": { + "type": [ + "null", + "integer" + ] + }, + "is_verified": { + "type": [ + "null", + "boolean" + ] + }, + "issues_url": { + "type": [ + "null", + "string" + ] + }, + "location": { + "type": [ + "null", + "string" + ] + }, + "login": { + "type": [ + "null", + "string" + ] + }, + "members_allowed_repository_creation_type": { + "type": [ + "null", + "string" + ] + }, + "members_can_change_repo_visibility": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_internal_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_pages": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_private_pages": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_private_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_public_pages": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_public_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_teams": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_delete_issues": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_delete_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_fork_private_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_invite_outside_collaborators": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_view_dependency_insights": { + "type": [ + "null", + "boolean" + ] + }, + "members_url": { + "type": [ + "null", + "string" + ] + }, + "name": { + "type": [ + "null", + "string" + ] + }, + "node_id": { + "type": [ + "null", + "string" + ] + }, + "owned_private_repos": { + "type": [ + "null", + "integer" + ] + }, + "plan": { + "additionalProperties": false, + "properties": { + "collaborators": { + "type": [ + "null", + "integer" + ] + }, + "filled_seats": { + "type": [ + "null", + "integer" + ] + }, + "name": { + "type": [ + "null", + "string" + ] + }, + "private_repos": { + "type": [ + "null", + "integer" + ] + }, + "seats": { + "type": [ + "null", + "integer" + ] + }, + "space": { + "type": [ + "null", + "integer" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "private_gists": { + "type": [ + "null", + "integer" + ] + }, + "public_gists": { + "type": [ + "null", + "integer" + ] + }, + "public_members_url": { + "type": [ + "null", + "string" + ] + }, + "public_repos": { + "type": [ + "null", + "integer" + ] + }, + "readers_can_create_discussions": { + "type": [ + "null", + "boolean" + ] + }, + "repos_url": { + "type": [ + "null", + "string" + ] + }, + "secret_scanning_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "secret_scanning_push_protection_custom_link": { + "type": [ + "null", + "string" + ] + }, + "secret_scanning_push_protection_custom_link_enabled": { + "type": [ + "null", + "boolean" + ] + }, + "secret_scanning_push_protection_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "secret_scanning_validity_checks_enabled": { + "type": [ + "null", + "boolean" + ] + }, + "total_private_repos": { + "type": [ + "null", + "integer" + ] + }, + "twitter_username": { + "type": [ + "null", + "string" + ] + }, + "two_factor_requirement_enabled": { + "type": [ + "null", + "boolean" + ] + }, + "type": { + "type": [ + "null", + "string" + ] + }, + "updated_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "url": { + "type": [ + "null", + "string" + ] + }, + "web_commit_signoff_required": { + "type": [ + "null", + "boolean" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "owner": { + "additionalProperties": false, + "properties": { + "assignment": { + "type": [ + "null", + "string" + ] + }, + "avatar_url": { + "type": [ + "null", + "string" + ] + }, + "bio": { + "type": [ + "null", + "string" + ] + }, + "blog": { + "type": [ + "null", + "string" + ] + }, + "business_plus": { + "type": [ + "null", + "boolean" + ] + }, + "collaborators": { + "type": [ + "null", + "integer" + ] + }, + "company": { + "type": [ + "null", + "string" + ] + }, + "created_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "disk_usage": { + "type": [ + "null", + "integer" + ] + }, + "email": { + "type": [ + "null", + "string" + ] + }, + "events_url": { + "type": [ + "null", + "string" + ] + }, + "followers": { + "type": [ + "null", + "integer" + ] + }, + "followers_url": { + "type": [ + "null", + "string" + ] + }, + "following": { + "type": [ + "null", + "integer" + ] + }, + "following_url": { + "type": [ + "null", + "string" + ] + }, + "gists_url": { + "type": [ + "null", + "string" + ] + }, + "gravatar_id": { + "type": [ + "null", + "string" + ] + }, + "hireable": { + "type": [ + "null", + "boolean" + ] + }, + "html_url": { + "type": [ + "null", + "string" + ] + }, + "id": { + "type": [ + "null", + "integer" + ] + }, + "inherited": { + "type": [ + "null", + "boolean" + ] + }, + "inherited_from": { + "items": { + "$ref": "#/$defs/team", + "type": [ + "null", + "object" + ] + }, + "type": [ + "null", + "array" + ] + }, + "ldap_dn": { + "type": [ + "null", + "string" + ] + }, + "location": { + "type": [ + "null", + "string" + ] + }, + "login": { + "type": [ + "null", + "string" + ] + }, + "name": { + "type": [ + "null", + "string" + ] + }, + "node_id": { + "type": [ + "null", + "string" + ] + }, + "notification_email": { + "type": [ + "null", + "string" + ] + }, + "organizations_url": { + "type": [ + "null", + "string" + ] + }, + "owned_private_repos": { + "type": [ + "null", + "integer" + ] + }, + "permissions": { + "additionalProperties": false, + "properties": { + "admin": { + "type": [ + "null", + "boolean" + ] + }, + "maintain": { + "type": [ + "null", + "boolean" + ] + }, + "pull": { + "type": [ + "null", + "boolean" + ] + }, + "push": { + "type": [ + "null", + "boolean" + ] + }, + "triage": { + "type": [ + "null", + "boolean" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "plan": { + "additionalProperties": false, + "properties": { + "collaborators": { + "type": [ + "null", + "integer" + ] + }, + "filled_seats": { + "type": [ + "null", + "integer" + ] + }, + "name": { + "type": [ + "null", + "string" + ] + }, + "private_repos": { + "type": [ + "null", + "integer" + ] + }, + "seats": { + "type": [ + "null", + "integer" + ] + }, + "space": { + "type": [ + "null", + "integer" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "private_gists": { + "type": [ + "null", + "integer" + ] + }, + "public_gists": { + "type": [ + "null", + "integer" + ] + }, + "public_repos": { + "type": [ + "null", + "integer" + ] + }, + "received_events_url": { + "type": [ + "null", + "string" + ] + }, + "repos_url": { + "type": [ + "null", + "string" + ] + }, + "role": { + "type": [ + "null", + "string" + ] + }, + "role_name": { + "type": [ + "null", + "string" + ] + }, + "site_admin": { + "type": [ + "null", + "boolean" + ] + }, + "starred_url": { + "type": [ + "null", + "string" + ] + }, + "subscriptions_url": { + "type": [ + "null", + "string" + ] + }, + "suspended_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "text_matches": { + "items": { + "additionalProperties": false, + "properties": { + "fragment": { + "type": [ + "null", + "string" + ] + }, + "matches": { + "items": { + "additionalProperties": false, + "properties": { + "indices": { + "items": { + "type": "integer" + }, + "type": [ + "null", + "array" + ] + }, + "text": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "type": [ + "null", + "array" + ] + }, + "object_type": { + "type": [ + "null", + "string" + ] + }, + "object_url": { + "type": [ + "null", + "string" + ] + }, + "property": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "type": [ + "null", + "array" + ] + }, + "total_private_repos": { + "type": [ + "null", + "integer" + ] + }, + "twitter_username": { + "type": [ + "null", + "string" + ] + }, + "two_factor_authentication": { + "type": [ + "null", + "boolean" + ] + }, + "type": { + "type": [ + "null", + "string" + ] + }, + "updated_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "url": { + "type": [ + "null", + "string" + ] + }, + "user_view_type": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "parent": { + "$ref": "#/$defs/searchRepository", + "type": [ + "null", + "object" + ] + }, + "permissions": { + "additionalProperties": false, + "properties": { + "admin": { + "type": [ + "null", + "boolean" + ] + }, + "maintain": { + "type": [ + "null", + "boolean" + ] + }, + "pull": { + "type": [ + "null", + "boolean" + ] + }, + "push": { + "type": [ + "null", + "boolean" + ] + }, + "triage": { + "type": [ + "null", + "boolean" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "private": { + "type": [ + "null", + "boolean" + ] + }, + "pull_request_creation_policy": { + "type": [ + "null", + "string" + ] + }, + "pulls_url": { + "type": [ + "null", + "string" + ] + }, + "pushed_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "releases_url": { + "type": [ + "null", + "string" + ] + }, + "role_name": { + "type": [ + "null", + "string" + ] + }, + "security_and_analysis": { + "additionalProperties": false, + "properties": { + "advanced_security": { + "additionalProperties": false, + "properties": { + "status": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "code_security": { + "additionalProperties": false, + "properties": { + "status": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "dependabot_security_updates": { + "additionalProperties": false, + "properties": { + "status": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "secret_scanning": { + "additionalProperties": false, + "properties": { + "status": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "secret_scanning_push_protection": { + "additionalProperties": false, + "properties": { + "status": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "secret_scanning_validity_checks": { + "additionalProperties": false, + "properties": { + "status": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "size": { + "type": [ + "null", + "integer" + ] + }, + "source": { + "$ref": "#/$defs/searchRepository", + "type": [ + "null", + "object" + ] + }, + "squash_merge_commit_message": { + "type": [ + "null", + "string" + ] + }, + "squash_merge_commit_title": { + "type": [ + "null", + "string" + ] + }, + "ssh_url": { + "type": [ + "null", + "string" + ] + }, + "stargazers_count": { + "type": [ + "null", + "integer" + ] + }, + "stargazers_url": { + "type": [ + "null", + "string" + ] + }, + "statuses_url": { + "type": [ + "null", + "string" + ] + }, + "subscribers_count": { + "type": [ + "null", + "integer" + ] + }, + "subscribers_url": { + "type": [ + "null", + "string" + ] + }, + "subscription_url": { + "type": [ + "null", + "string" + ] + }, + "svn_url": { + "type": [ + "null", + "string" + ] + }, + "tags_url": { + "type": [ + "null", + "string" + ] + }, + "team_id": { + "type": [ + "null", + "integer" + ] + }, + "teams_url": { + "type": [ + "null", + "string" + ] + }, + "template_repository": { + "$ref": "#/$defs/searchRepository", + "type": [ + "null", + "object" + ] + }, + "text_matches": { + "items": { + "additionalProperties": false, + "properties": { + "fragment": { + "type": [ + "null", + "string" + ] + }, + "matches": { + "items": { + "additionalProperties": false, + "properties": { + "indices": { + "items": { + "type": "integer" + }, + "type": [ + "null", + "array" + ] + }, + "text": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "type": [ + "null", + "array" + ] + }, + "object_type": { + "type": [ + "null", + "string" + ] + }, + "object_url": { + "type": [ + "null", + "string" + ] + }, + "property": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "type": [ + "null", + "array" + ] + }, + "topics": { + "items": { + "type": "string" + }, + "type": [ + "null", + "array" + ] + }, + "trees_url": { + "type": [ + "null", + "string" + ] + }, + "updated_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "url": { + "type": [ + "null", + "string" + ] + }, + "use_squash_pr_title_as_default": { + "type": [ + "null", + "boolean" + ] + }, + "visibility": { + "type": [ + "null", + "string" + ] + }, + "watchers": { + "type": [ + "null", + "integer" + ] + }, + "watchers_count": { + "type": [ + "null", + "integer" + ] + }, + "web_commit_signoff_required": { + "type": [ + "null", + "boolean" + ] + } + }, + "type": [ + "object", + "null" + ] + }, + "team": { + "additionalProperties": false, + "properties": { + "access_source": { + "type": [ + "null", + "string" + ] + }, + "assignment": { + "type": [ + "null", + "string" + ] + }, + "description": { + "type": [ + "null", + "string" + ] + }, + "html_url": { + "type": [ + "null", + "string" + ] + }, + "id": { + "type": [ + "null", + "integer" + ] + }, + "ldap_dn": { + "type": [ + "null", + "string" + ] + }, + "members_count": { + "type": [ + "null", + "integer" + ] + }, + "members_url": { + "type": [ + "null", + "string" + ] + }, + "name": { + "type": [ + "null", + "string" + ] + }, + "node_id": { + "type": [ + "null", + "string" + ] + }, + "notification_setting": { + "type": [ + "null", + "string" + ] + }, + "organization": { + "additionalProperties": false, + "properties": { + "advanced_security_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "archived_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "avatar_url": { + "type": [ + "null", + "string" + ] + }, + "billing_email": { + "type": [ + "null", + "string" + ] + }, + "blog": { + "type": [ + "null", + "string" + ] + }, + "collaborators": { + "type": [ + "null", + "integer" + ] + }, + "company": { + "type": [ + "null", + "string" + ] + }, + "created_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "default_repository_branch": { + "type": [ + "null", + "string" + ] + }, + "default_repository_permission": { + "type": [ + "null", + "string" + ] + }, + "default_repository_settings": { + "type": [ + "null", + "string" + ] + }, + "dependabot_alerts_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "dependabot_security_updates_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "dependency_graph_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "deploy_keys_enabled_for_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "description": { + "type": [ + "null", + "string" + ] + }, + "disk_usage": { + "type": [ + "null", + "integer" + ] + }, + "display_commenter_full_name_setting_enabled": { + "type": [ + "null", + "boolean" + ] + }, + "email": { + "type": [ + "null", + "string" + ] + }, + "events_url": { + "type": [ + "null", + "string" + ] + }, + "followers": { + "type": [ + "null", + "integer" + ] + }, + "following": { + "type": [ + "null", + "integer" + ] + }, + "has_organization_projects": { + "type": [ + "null", + "boolean" + ] + }, + "has_repository_projects": { + "type": [ + "null", + "boolean" + ] + }, + "hooks_url": { + "type": [ + "null", + "string" + ] + }, + "html_url": { + "type": [ + "null", + "string" + ] + }, + "id": { + "type": [ + "null", + "integer" + ] + }, + "is_verified": { + "type": [ + "null", + "boolean" + ] + }, + "issues_url": { + "type": [ + "null", + "string" + ] + }, + "location": { + "type": [ + "null", + "string" + ] + }, + "login": { + "type": [ + "null", + "string" + ] + }, + "members_allowed_repository_creation_type": { + "type": [ + "null", + "string" + ] + }, + "members_can_change_repo_visibility": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_internal_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_pages": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_private_pages": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_private_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_public_pages": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_public_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_teams": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_delete_issues": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_delete_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_fork_private_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_invite_outside_collaborators": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_view_dependency_insights": { + "type": [ + "null", + "boolean" + ] + }, + "members_url": { + "type": [ + "null", + "string" + ] + }, + "name": { + "type": [ + "null", + "string" + ] + }, + "node_id": { + "type": [ + "null", + "string" + ] + }, + "owned_private_repos": { + "type": [ + "null", + "integer" + ] + }, + "plan": { + "additionalProperties": false, + "properties": { + "collaborators": { + "type": [ + "null", + "integer" + ] + }, + "filled_seats": { + "type": [ + "null", + "integer" + ] + }, + "name": { + "type": [ + "null", + "string" + ] + }, + "private_repos": { + "type": [ + "null", + "integer" + ] + }, + "seats": { + "type": [ + "null", + "integer" + ] + }, + "space": { + "type": [ + "null", + "integer" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "private_gists": { + "type": [ + "null", + "integer" + ] + }, + "public_gists": { + "type": [ + "null", + "integer" + ] + }, + "public_members_url": { + "type": [ + "null", + "string" + ] + }, + "public_repos": { + "type": [ + "null", + "integer" + ] + }, + "readers_can_create_discussions": { + "type": [ + "null", + "boolean" + ] + }, + "repos_url": { + "type": [ + "null", + "string" + ] + }, + "secret_scanning_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "secret_scanning_push_protection_custom_link": { + "type": [ + "null", + "string" + ] + }, + "secret_scanning_push_protection_custom_link_enabled": { + "type": [ + "null", + "boolean" + ] + }, + "secret_scanning_push_protection_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "secret_scanning_validity_checks_enabled": { + "type": [ + "null", + "boolean" + ] + }, + "total_private_repos": { + "type": [ + "null", + "integer" + ] + }, + "twitter_username": { + "type": [ + "null", + "string" + ] + }, + "two_factor_requirement_enabled": { + "type": [ + "null", + "boolean" + ] + }, + "type": { + "type": [ + "null", + "string" + ] + }, + "updated_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "url": { + "type": [ + "null", + "string" + ] + }, + "web_commit_signoff_required": { + "type": [ + "null", + "boolean" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "parent": { + "$ref": "#/$defs/team", + "type": [ + "null", + "object" + ] + }, + "permission": { + "type": [ + "null", + "string" + ] + }, + "permissions": { + "additionalProperties": { + "type": "boolean" + }, + "type": "object" + }, + "privacy": { + "type": [ + "null", + "string" + ] + }, + "repos_count": { + "type": [ + "null", + "integer" + ] + }, + "repositories_url": { + "type": [ + "null", + "string" + ] + }, + "slug": { + "type": [ + "null", + "string" + ] + }, + "type": { + "type": [ + "null", + "string" + ] + }, + "url": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "object", + "null" + ] + } + }, + "anyOf": [ + { + "type": "null" + }, + { + "additionalProperties": false, + "properties": { + "incomplete_results": { + "type": "boolean" + }, + "items": { + "items": { + "additionalProperties": false, + "properties": { + "archived": { + "type": "boolean" + }, + "created_at": { + "type": "string" + }, + "default_branch": { + "type": "string" + }, + "description": { + "type": "string" + }, + "fork": { + "type": "boolean" + }, + "forks_count": { + "type": "integer" + }, + "full_name": { + "type": "string" + }, + "html_url": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "language": { + "type": "string" + }, + "name": { + "type": "string" + }, + "open_issues_count": { + "type": "integer" + }, + "private": { + "type": "boolean" + }, + "stargazers_count": { + "type": "integer" + }, + "topics": { + "items": { + "type": "string" + }, + "type": [ + "null", + "array" + ] + }, + "updated_at": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "full_name", + "html_url", + "stargazers_count", + "forks_count", + "open_issues_count", + "private", + "fork", + "archived" + ], + "type": "object" + }, + "type": [ + "null", + "array" + ] + }, + "total_count": { + "type": "integer" + } + }, + "required": [ + "total_count", + "incomplete_results", + "items" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "incomplete_results": { + "type": [ + "null", + "boolean" + ] + }, + "items": { + "items": { + "$ref": "#/$defs/searchRepository", + "type": [ + "null", + "object" + ] + }, + "type": [ + "null", + "array" + ] + }, + "total_count": { + "type": [ + "null", + "integer" + ] + } + }, + "type": "object" + } + ] + } } \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/search_repositories_typed.snap b/pkg/github/__toolsnaps__/search_repositories_typed.snap new file mode 100644 index 0000000000..547aba69dc --- /dev/null +++ b/pkg/github/__toolsnaps__/search_repositories_typed.snap @@ -0,0 +1,2704 @@ +{ + "annotations": { + "idempotentHint": false, + "readOnlyHint": true, + "title": "Search repositories" + }, + "description": "Find GitHub repositories by name, description, readme, topics, or other metadata. Perfect for discovering projects, finding examples, or locating specific repositories across GitHub.", + "icons": [ + { + "mimeType": "image/png", + "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABTklEQVRIie2UPUvDUBSGn5PW2qnYwUXExWhNpY2CUKT+BB10E3R0dnbrppuKLgouHbr5X5qlKRIFB+cuguBHj4O05KYfNsVJ+m7n3Hue54ZcrhDKQqGQTX1YVwg7QIbx0gh8b7VTJMMrqS+5RtgD7oDX2GhhC6UcbhkCVLZVuX1sesex4YCdX6uAGgIrsicjlrTGgQ9KVPDnmQgmgongPwgkXNiOq8ADMAtkx4UGvtflJvus20ANeIlN/vW5/kkt8L3D2HD6P9dRgSI8dQcc9w1IR3acBE3vbFSp8ZMVnoFSqJ/unZDe3pAYXyDIJarn9opbDZrewaAh2y7Oy5S1r6h5QG2Xxbw3piDw6xe244oKyxFmC5ihc+tS1qaqngKJyAEBGmZvSGzHVYT790T7aPozsaFoFZGboFGvDJsbOYuOuxuuc7n1uaV8sRSH8Q1DUVLnYLty3gAAAABJRU5ErkJggg==", + "theme": "light" + }, + { + "mimeType": "image/png", + "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAA+ElEQVRIie2WLU5DQRRGzwUEirQCQ9BsoSGtZQHgSFgIsg4JXQCGP8V2QCBIEATZkJDgOJi+MJnMo30TFHmfmzvzfeeKyZ2BROpQvVHfrddDmhkZ4BY4Ai6BD7prAowjIoq7i85nFcGNf6qa1tayM1vAvBZQUg74c/WAHtAD/gMgn6YCT8A2MKwOTaZpCfAF3AGvFdlLx7XqdUVw4186rgWeE8Nn4cU67QLNAS/ASG3qmwVPqdaqjWw9A86BK+CkzaTuAseFBse/AiLiQg1gLzs3Bwb8XIp94AxYL/Af2xordap6v/htHKhv6nTlgBUAh9l6Rx11yfgG8ne/zwh2OysAAAAASUVORK5CYII=", + "theme": "dark" + } + ], + "inputSchema": { + "properties": { + "minimal_output": { + "default": true, + "description": "Return minimal repository information (default: true). When false, returns full GitHub API repository objects.", + "type": "boolean" + }, + "order": { + "description": "Sort order", + "enum": [ + "asc", + "desc" + ], + "type": "string" + }, + "page": { + "description": "Page number for pagination (min 1)", + "minimum": 1, + "type": "number" + }, + "perPage": { + "description": "Results per page for pagination (min 1, max 100)", + "maximum": 100, + "minimum": 1, + "type": "number" + }, + "query": { + "description": "Repository search query. Examples: 'machine learning in:name stars:\u003e1000 language:python', 'topic:react', 'user:facebook'. Supports advanced search syntax for precise filtering.", + "type": "string" + }, + "sort": { + "description": "Sort repositories by field, defaults to best match", + "enum": [ + "stars", + "forks", + "help-wanted-issues", + "updated" + ], + "type": "string" + } + }, + "required": [ + "query" + ], + "type": "object" + }, + "name": "search_repositories", + "outputSchema": { + "$defs": { + "searchPropertyValue": { + "anyOf": [ + { + "type": "null" + }, + { + "type": "string" + }, + { + "type": "number" + }, + { + "type": "boolean" + }, + { + "items": { + "$ref": "#/$defs/searchPropertyValue" + }, + "type": "array" + }, + { + "additionalProperties": { + "$ref": "#/$defs/searchPropertyValue" + }, + "type": "object" + } + ] + }, + "searchRepository": { + "additionalProperties": false, + "properties": { + "allow_auto_merge": { + "type": [ + "null", + "boolean" + ] + }, + "allow_forking": { + "type": [ + "null", + "boolean" + ] + }, + "allow_merge_commit": { + "type": [ + "null", + "boolean" + ] + }, + "allow_rebase_merge": { + "type": [ + "null", + "boolean" + ] + }, + "allow_squash_merge": { + "type": [ + "null", + "boolean" + ] + }, + "allow_update_branch": { + "type": [ + "null", + "boolean" + ] + }, + "archive_url": { + "type": [ + "null", + "string" + ] + }, + "archived": { + "type": [ + "null", + "boolean" + ] + }, + "assignees_url": { + "type": [ + "null", + "string" + ] + }, + "auto_init": { + "type": [ + "null", + "boolean" + ] + }, + "blobs_url": { + "type": [ + "null", + "string" + ] + }, + "branches_url": { + "type": [ + "null", + "string" + ] + }, + "clone_url": { + "type": [ + "null", + "string" + ] + }, + "code_of_conduct": { + "additionalProperties": false, + "properties": { + "body": { + "type": [ + "null", + "string" + ] + }, + "key": { + "type": [ + "null", + "string" + ] + }, + "name": { + "type": [ + "null", + "string" + ] + }, + "url": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "collaborators_url": { + "type": [ + "null", + "string" + ] + }, + "comments_url": { + "type": [ + "null", + "string" + ] + }, + "commits_url": { + "type": [ + "null", + "string" + ] + }, + "compare_url": { + "type": [ + "null", + "string" + ] + }, + "contents_url": { + "type": [ + "null", + "string" + ] + }, + "contributors_url": { + "type": [ + "null", + "string" + ] + }, + "created_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "custom_properties": { + "additionalProperties": { + "$ref": "#/$defs/searchPropertyValue" + }, + "type": "object" + }, + "default_branch": { + "type": [ + "null", + "string" + ] + }, + "delete_branch_on_merge": { + "type": [ + "null", + "boolean" + ] + }, + "deployments_url": { + "type": [ + "null", + "string" + ] + }, + "description": { + "type": [ + "null", + "string" + ] + }, + "disabled": { + "type": [ + "null", + "boolean" + ] + }, + "downloads_url": { + "type": [ + "null", + "string" + ] + }, + "events_url": { + "type": [ + "null", + "string" + ] + }, + "fork": { + "type": [ + "null", + "boolean" + ] + }, + "forks_count": { + "type": [ + "null", + "integer" + ] + }, + "forks_url": { + "type": [ + "null", + "string" + ] + }, + "full_name": { + "type": [ + "null", + "string" + ] + }, + "git_commits_url": { + "type": [ + "null", + "string" + ] + }, + "git_refs_url": { + "type": [ + "null", + "string" + ] + }, + "git_tags_url": { + "type": [ + "null", + "string" + ] + }, + "git_url": { + "type": [ + "null", + "string" + ] + }, + "gitignore_template": { + "type": [ + "null", + "string" + ] + }, + "has_discussions": { + "type": [ + "null", + "boolean" + ] + }, + "has_downloads": { + "type": [ + "null", + "boolean" + ] + }, + "has_issues": { + "type": [ + "null", + "boolean" + ] + }, + "has_pages": { + "type": [ + "null", + "boolean" + ] + }, + "has_projects": { + "type": [ + "null", + "boolean" + ] + }, + "has_pull_requests": { + "type": [ + "null", + "boolean" + ] + }, + "has_wiki": { + "type": [ + "null", + "boolean" + ] + }, + "homepage": { + "type": [ + "null", + "string" + ] + }, + "hooks_url": { + "type": [ + "null", + "string" + ] + }, + "html_url": { + "type": [ + "null", + "string" + ] + }, + "id": { + "type": [ + "null", + "integer" + ] + }, + "is_template": { + "type": [ + "null", + "boolean" + ] + }, + "issue_comment_url": { + "type": [ + "null", + "string" + ] + }, + "issue_events_url": { + "type": [ + "null", + "string" + ] + }, + "issues_url": { + "type": [ + "null", + "string" + ] + }, + "keys_url": { + "type": [ + "null", + "string" + ] + }, + "labels_url": { + "type": [ + "null", + "string" + ] + }, + "language": { + "type": [ + "null", + "string" + ] + }, + "languages_url": { + "type": [ + "null", + "string" + ] + }, + "license": { + "additionalProperties": false, + "properties": { + "body": { + "type": [ + "null", + "string" + ] + }, + "conditions": { + "items": { + "type": "string" + }, + "type": [ + "null", + "array" + ] + }, + "description": { + "type": [ + "null", + "string" + ] + }, + "featured": { + "type": [ + "null", + "boolean" + ] + }, + "html_url": { + "type": [ + "null", + "string" + ] + }, + "implementation": { + "type": [ + "null", + "string" + ] + }, + "key": { + "type": [ + "null", + "string" + ] + }, + "limitations": { + "items": { + "type": "string" + }, + "type": [ + "null", + "array" + ] + }, + "name": { + "type": [ + "null", + "string" + ] + }, + "permissions": { + "items": { + "type": "string" + }, + "type": [ + "null", + "array" + ] + }, + "spdx_id": { + "type": [ + "null", + "string" + ] + }, + "url": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "license_template": { + "type": [ + "null", + "string" + ] + }, + "master_branch": { + "type": [ + "null", + "string" + ] + }, + "merge_commit_message": { + "type": [ + "null", + "string" + ] + }, + "merge_commit_title": { + "type": [ + "null", + "string" + ] + }, + "merges_url": { + "type": [ + "null", + "string" + ] + }, + "milestones_url": { + "type": [ + "null", + "string" + ] + }, + "mirror_url": { + "type": [ + "null", + "string" + ] + }, + "name": { + "type": [ + "null", + "string" + ] + }, + "network_count": { + "type": [ + "null", + "integer" + ] + }, + "node_id": { + "type": [ + "null", + "string" + ] + }, + "notifications_url": { + "type": [ + "null", + "string" + ] + }, + "open_issues": { + "type": [ + "null", + "integer" + ] + }, + "open_issues_count": { + "type": [ + "null", + "integer" + ] + }, + "organization": { + "additionalProperties": false, + "properties": { + "advanced_security_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "archived_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "avatar_url": { + "type": [ + "null", + "string" + ] + }, + "billing_email": { + "type": [ + "null", + "string" + ] + }, + "blog": { + "type": [ + "null", + "string" + ] + }, + "collaborators": { + "type": [ + "null", + "integer" + ] + }, + "company": { + "type": [ + "null", + "string" + ] + }, + "created_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "default_repository_branch": { + "type": [ + "null", + "string" + ] + }, + "default_repository_permission": { + "type": [ + "null", + "string" + ] + }, + "default_repository_settings": { + "type": [ + "null", + "string" + ] + }, + "dependabot_alerts_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "dependabot_security_updates_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "dependency_graph_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "deploy_keys_enabled_for_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "description": { + "type": [ + "null", + "string" + ] + }, + "disk_usage": { + "type": [ + "null", + "integer" + ] + }, + "display_commenter_full_name_setting_enabled": { + "type": [ + "null", + "boolean" + ] + }, + "email": { + "type": [ + "null", + "string" + ] + }, + "events_url": { + "type": [ + "null", + "string" + ] + }, + "followers": { + "type": [ + "null", + "integer" + ] + }, + "following": { + "type": [ + "null", + "integer" + ] + }, + "has_organization_projects": { + "type": [ + "null", + "boolean" + ] + }, + "has_repository_projects": { + "type": [ + "null", + "boolean" + ] + }, + "hooks_url": { + "type": [ + "null", + "string" + ] + }, + "html_url": { + "type": [ + "null", + "string" + ] + }, + "id": { + "type": [ + "null", + "integer" + ] + }, + "is_verified": { + "type": [ + "null", + "boolean" + ] + }, + "issues_url": { + "type": [ + "null", + "string" + ] + }, + "location": { + "type": [ + "null", + "string" + ] + }, + "login": { + "type": [ + "null", + "string" + ] + }, + "members_allowed_repository_creation_type": { + "type": [ + "null", + "string" + ] + }, + "members_can_change_repo_visibility": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_internal_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_pages": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_private_pages": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_private_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_public_pages": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_public_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_teams": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_delete_issues": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_delete_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_fork_private_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_invite_outside_collaborators": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_view_dependency_insights": { + "type": [ + "null", + "boolean" + ] + }, + "members_url": { + "type": [ + "null", + "string" + ] + }, + "name": { + "type": [ + "null", + "string" + ] + }, + "node_id": { + "type": [ + "null", + "string" + ] + }, + "owned_private_repos": { + "type": [ + "null", + "integer" + ] + }, + "plan": { + "additionalProperties": false, + "properties": { + "collaborators": { + "type": [ + "null", + "integer" + ] + }, + "filled_seats": { + "type": [ + "null", + "integer" + ] + }, + "name": { + "type": [ + "null", + "string" + ] + }, + "private_repos": { + "type": [ + "null", + "integer" + ] + }, + "seats": { + "type": [ + "null", + "integer" + ] + }, + "space": { + "type": [ + "null", + "integer" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "private_gists": { + "type": [ + "null", + "integer" + ] + }, + "public_gists": { + "type": [ + "null", + "integer" + ] + }, + "public_members_url": { + "type": [ + "null", + "string" + ] + }, + "public_repos": { + "type": [ + "null", + "integer" + ] + }, + "readers_can_create_discussions": { + "type": [ + "null", + "boolean" + ] + }, + "repos_url": { + "type": [ + "null", + "string" + ] + }, + "secret_scanning_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "secret_scanning_push_protection_custom_link": { + "type": [ + "null", + "string" + ] + }, + "secret_scanning_push_protection_custom_link_enabled": { + "type": [ + "null", + "boolean" + ] + }, + "secret_scanning_push_protection_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "secret_scanning_validity_checks_enabled": { + "type": [ + "null", + "boolean" + ] + }, + "total_private_repos": { + "type": [ + "null", + "integer" + ] + }, + "twitter_username": { + "type": [ + "null", + "string" + ] + }, + "two_factor_requirement_enabled": { + "type": [ + "null", + "boolean" + ] + }, + "type": { + "type": [ + "null", + "string" + ] + }, + "updated_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "url": { + "type": [ + "null", + "string" + ] + }, + "web_commit_signoff_required": { + "type": [ + "null", + "boolean" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "owner": { + "additionalProperties": false, + "properties": { + "assignment": { + "type": [ + "null", + "string" + ] + }, + "avatar_url": { + "type": [ + "null", + "string" + ] + }, + "bio": { + "type": [ + "null", + "string" + ] + }, + "blog": { + "type": [ + "null", + "string" + ] + }, + "business_plus": { + "type": [ + "null", + "boolean" + ] + }, + "collaborators": { + "type": [ + "null", + "integer" + ] + }, + "company": { + "type": [ + "null", + "string" + ] + }, + "created_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "disk_usage": { + "type": [ + "null", + "integer" + ] + }, + "email": { + "type": [ + "null", + "string" + ] + }, + "events_url": { + "type": [ + "null", + "string" + ] + }, + "followers": { + "type": [ + "null", + "integer" + ] + }, + "followers_url": { + "type": [ + "null", + "string" + ] + }, + "following": { + "type": [ + "null", + "integer" + ] + }, + "following_url": { + "type": [ + "null", + "string" + ] + }, + "gists_url": { + "type": [ + "null", + "string" + ] + }, + "gravatar_id": { + "type": [ + "null", + "string" + ] + }, + "hireable": { + "type": [ + "null", + "boolean" + ] + }, + "html_url": { + "type": [ + "null", + "string" + ] + }, + "id": { + "type": [ + "null", + "integer" + ] + }, + "inherited": { + "type": [ + "null", + "boolean" + ] + }, + "inherited_from": { + "items": { + "$ref": "#/$defs/team", + "type": [ + "null", + "object" + ] + }, + "type": [ + "null", + "array" + ] + }, + "ldap_dn": { + "type": [ + "null", + "string" + ] + }, + "location": { + "type": [ + "null", + "string" + ] + }, + "login": { + "type": [ + "null", + "string" + ] + }, + "name": { + "type": [ + "null", + "string" + ] + }, + "node_id": { + "type": [ + "null", + "string" + ] + }, + "notification_email": { + "type": [ + "null", + "string" + ] + }, + "organizations_url": { + "type": [ + "null", + "string" + ] + }, + "owned_private_repos": { + "type": [ + "null", + "integer" + ] + }, + "permissions": { + "additionalProperties": false, + "properties": { + "admin": { + "type": [ + "null", + "boolean" + ] + }, + "maintain": { + "type": [ + "null", + "boolean" + ] + }, + "pull": { + "type": [ + "null", + "boolean" + ] + }, + "push": { + "type": [ + "null", + "boolean" + ] + }, + "triage": { + "type": [ + "null", + "boolean" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "plan": { + "additionalProperties": false, + "properties": { + "collaborators": { + "type": [ + "null", + "integer" + ] + }, + "filled_seats": { + "type": [ + "null", + "integer" + ] + }, + "name": { + "type": [ + "null", + "string" + ] + }, + "private_repos": { + "type": [ + "null", + "integer" + ] + }, + "seats": { + "type": [ + "null", + "integer" + ] + }, + "space": { + "type": [ + "null", + "integer" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "private_gists": { + "type": [ + "null", + "integer" + ] + }, + "public_gists": { + "type": [ + "null", + "integer" + ] + }, + "public_repos": { + "type": [ + "null", + "integer" + ] + }, + "received_events_url": { + "type": [ + "null", + "string" + ] + }, + "repos_url": { + "type": [ + "null", + "string" + ] + }, + "role": { + "type": [ + "null", + "string" + ] + }, + "role_name": { + "type": [ + "null", + "string" + ] + }, + "site_admin": { + "type": [ + "null", + "boolean" + ] + }, + "starred_url": { + "type": [ + "null", + "string" + ] + }, + "subscriptions_url": { + "type": [ + "null", + "string" + ] + }, + "suspended_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "text_matches": { + "items": { + "additionalProperties": false, + "properties": { + "fragment": { + "type": [ + "null", + "string" + ] + }, + "matches": { + "items": { + "additionalProperties": false, + "properties": { + "indices": { + "items": { + "type": "integer" + }, + "type": [ + "null", + "array" + ] + }, + "text": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "type": [ + "null", + "array" + ] + }, + "object_type": { + "type": [ + "null", + "string" + ] + }, + "object_url": { + "type": [ + "null", + "string" + ] + }, + "property": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "type": [ + "null", + "array" + ] + }, + "total_private_repos": { + "type": [ + "null", + "integer" + ] + }, + "twitter_username": { + "type": [ + "null", + "string" + ] + }, + "two_factor_authentication": { + "type": [ + "null", + "boolean" + ] + }, + "type": { + "type": [ + "null", + "string" + ] + }, + "updated_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "url": { + "type": [ + "null", + "string" + ] + }, + "user_view_type": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "parent": { + "$ref": "#/$defs/searchRepository", + "type": [ + "null", + "object" + ] + }, + "permissions": { + "additionalProperties": false, + "properties": { + "admin": { + "type": [ + "null", + "boolean" + ] + }, + "maintain": { + "type": [ + "null", + "boolean" + ] + }, + "pull": { + "type": [ + "null", + "boolean" + ] + }, + "push": { + "type": [ + "null", + "boolean" + ] + }, + "triage": { + "type": [ + "null", + "boolean" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "private": { + "type": [ + "null", + "boolean" + ] + }, + "pull_request_creation_policy": { + "type": [ + "null", + "string" + ] + }, + "pulls_url": { + "type": [ + "null", + "string" + ] + }, + "pushed_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "releases_url": { + "type": [ + "null", + "string" + ] + }, + "role_name": { + "type": [ + "null", + "string" + ] + }, + "security_and_analysis": { + "additionalProperties": false, + "properties": { + "advanced_security": { + "additionalProperties": false, + "properties": { + "status": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "code_security": { + "additionalProperties": false, + "properties": { + "status": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "dependabot_security_updates": { + "additionalProperties": false, + "properties": { + "status": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "secret_scanning": { + "additionalProperties": false, + "properties": { + "status": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "secret_scanning_push_protection": { + "additionalProperties": false, + "properties": { + "status": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "secret_scanning_validity_checks": { + "additionalProperties": false, + "properties": { + "status": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "size": { + "type": [ + "null", + "integer" + ] + }, + "source": { + "$ref": "#/$defs/searchRepository", + "type": [ + "null", + "object" + ] + }, + "squash_merge_commit_message": { + "type": [ + "null", + "string" + ] + }, + "squash_merge_commit_title": { + "type": [ + "null", + "string" + ] + }, + "ssh_url": { + "type": [ + "null", + "string" + ] + }, + "stargazers_count": { + "type": [ + "null", + "integer" + ] + }, + "stargazers_url": { + "type": [ + "null", + "string" + ] + }, + "statuses_url": { + "type": [ + "null", + "string" + ] + }, + "subscribers_count": { + "type": [ + "null", + "integer" + ] + }, + "subscribers_url": { + "type": [ + "null", + "string" + ] + }, + "subscription_url": { + "type": [ + "null", + "string" + ] + }, + "svn_url": { + "type": [ + "null", + "string" + ] + }, + "tags_url": { + "type": [ + "null", + "string" + ] + }, + "team_id": { + "type": [ + "null", + "integer" + ] + }, + "teams_url": { + "type": [ + "null", + "string" + ] + }, + "template_repository": { + "$ref": "#/$defs/searchRepository", + "type": [ + "null", + "object" + ] + }, + "text_matches": { + "items": { + "additionalProperties": false, + "properties": { + "fragment": { + "type": [ + "null", + "string" + ] + }, + "matches": { + "items": { + "additionalProperties": false, + "properties": { + "indices": { + "items": { + "type": "integer" + }, + "type": [ + "null", + "array" + ] + }, + "text": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "type": [ + "null", + "array" + ] + }, + "object_type": { + "type": [ + "null", + "string" + ] + }, + "object_url": { + "type": [ + "null", + "string" + ] + }, + "property": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "type": [ + "null", + "array" + ] + }, + "topics": { + "items": { + "type": "string" + }, + "type": [ + "null", + "array" + ] + }, + "trees_url": { + "type": [ + "null", + "string" + ] + }, + "updated_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "url": { + "type": [ + "null", + "string" + ] + }, + "use_squash_pr_title_as_default": { + "type": [ + "null", + "boolean" + ] + }, + "visibility": { + "type": [ + "null", + "string" + ] + }, + "watchers": { + "type": [ + "null", + "integer" + ] + }, + "watchers_count": { + "type": [ + "null", + "integer" + ] + }, + "web_commit_signoff_required": { + "type": [ + "null", + "boolean" + ] + } + }, + "type": [ + "object", + "null" + ] + }, + "team": { + "additionalProperties": false, + "properties": { + "access_source": { + "type": [ + "null", + "string" + ] + }, + "assignment": { + "type": [ + "null", + "string" + ] + }, + "description": { + "type": [ + "null", + "string" + ] + }, + "html_url": { + "type": [ + "null", + "string" + ] + }, + "id": { + "type": [ + "null", + "integer" + ] + }, + "ldap_dn": { + "type": [ + "null", + "string" + ] + }, + "members_count": { + "type": [ + "null", + "integer" + ] + }, + "members_url": { + "type": [ + "null", + "string" + ] + }, + "name": { + "type": [ + "null", + "string" + ] + }, + "node_id": { + "type": [ + "null", + "string" + ] + }, + "notification_setting": { + "type": [ + "null", + "string" + ] + }, + "organization": { + "additionalProperties": false, + "properties": { + "advanced_security_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "archived_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "avatar_url": { + "type": [ + "null", + "string" + ] + }, + "billing_email": { + "type": [ + "null", + "string" + ] + }, + "blog": { + "type": [ + "null", + "string" + ] + }, + "collaborators": { + "type": [ + "null", + "integer" + ] + }, + "company": { + "type": [ + "null", + "string" + ] + }, + "created_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "default_repository_branch": { + "type": [ + "null", + "string" + ] + }, + "default_repository_permission": { + "type": [ + "null", + "string" + ] + }, + "default_repository_settings": { + "type": [ + "null", + "string" + ] + }, + "dependabot_alerts_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "dependabot_security_updates_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "dependency_graph_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "deploy_keys_enabled_for_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "description": { + "type": [ + "null", + "string" + ] + }, + "disk_usage": { + "type": [ + "null", + "integer" + ] + }, + "display_commenter_full_name_setting_enabled": { + "type": [ + "null", + "boolean" + ] + }, + "email": { + "type": [ + "null", + "string" + ] + }, + "events_url": { + "type": [ + "null", + "string" + ] + }, + "followers": { + "type": [ + "null", + "integer" + ] + }, + "following": { + "type": [ + "null", + "integer" + ] + }, + "has_organization_projects": { + "type": [ + "null", + "boolean" + ] + }, + "has_repository_projects": { + "type": [ + "null", + "boolean" + ] + }, + "hooks_url": { + "type": [ + "null", + "string" + ] + }, + "html_url": { + "type": [ + "null", + "string" + ] + }, + "id": { + "type": [ + "null", + "integer" + ] + }, + "is_verified": { + "type": [ + "null", + "boolean" + ] + }, + "issues_url": { + "type": [ + "null", + "string" + ] + }, + "location": { + "type": [ + "null", + "string" + ] + }, + "login": { + "type": [ + "null", + "string" + ] + }, + "members_allowed_repository_creation_type": { + "type": [ + "null", + "string" + ] + }, + "members_can_change_repo_visibility": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_internal_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_pages": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_private_pages": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_private_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_public_pages": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_public_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_create_teams": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_delete_issues": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_delete_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_fork_private_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_invite_outside_collaborators": { + "type": [ + "null", + "boolean" + ] + }, + "members_can_view_dependency_insights": { + "type": [ + "null", + "boolean" + ] + }, + "members_url": { + "type": [ + "null", + "string" + ] + }, + "name": { + "type": [ + "null", + "string" + ] + }, + "node_id": { + "type": [ + "null", + "string" + ] + }, + "owned_private_repos": { + "type": [ + "null", + "integer" + ] + }, + "plan": { + "additionalProperties": false, + "properties": { + "collaborators": { + "type": [ + "null", + "integer" + ] + }, + "filled_seats": { + "type": [ + "null", + "integer" + ] + }, + "name": { + "type": [ + "null", + "string" + ] + }, + "private_repos": { + "type": [ + "null", + "integer" + ] + }, + "seats": { + "type": [ + "null", + "integer" + ] + }, + "space": { + "type": [ + "null", + "integer" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "private_gists": { + "type": [ + "null", + "integer" + ] + }, + "public_gists": { + "type": [ + "null", + "integer" + ] + }, + "public_members_url": { + "type": [ + "null", + "string" + ] + }, + "public_repos": { + "type": [ + "null", + "integer" + ] + }, + "readers_can_create_discussions": { + "type": [ + "null", + "boolean" + ] + }, + "repos_url": { + "type": [ + "null", + "string" + ] + }, + "secret_scanning_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "secret_scanning_push_protection_custom_link": { + "type": [ + "null", + "string" + ] + }, + "secret_scanning_push_protection_custom_link_enabled": { + "type": [ + "null", + "boolean" + ] + }, + "secret_scanning_push_protection_enabled_for_new_repositories": { + "type": [ + "null", + "boolean" + ] + }, + "secret_scanning_validity_checks_enabled": { + "type": [ + "null", + "boolean" + ] + }, + "total_private_repos": { + "type": [ + "null", + "integer" + ] + }, + "twitter_username": { + "type": [ + "null", + "string" + ] + }, + "two_factor_requirement_enabled": { + "type": [ + "null", + "boolean" + ] + }, + "type": { + "type": [ + "null", + "string" + ] + }, + "updated_at": { + "format": "date-time", + "type": [ + "null", + "string" + ] + }, + "url": { + "type": [ + "null", + "string" + ] + }, + "web_commit_signoff_required": { + "type": [ + "null", + "boolean" + ] + } + }, + "type": [ + "null", + "object" + ] + }, + "parent": { + "$ref": "#/$defs/team", + "type": [ + "null", + "object" + ] + }, + "permission": { + "type": [ + "null", + "string" + ] + }, + "permissions": { + "additionalProperties": { + "type": "boolean" + }, + "type": "object" + }, + "privacy": { + "type": [ + "null", + "string" + ] + }, + "repos_count": { + "type": [ + "null", + "integer" + ] + }, + "repositories_url": { + "type": [ + "null", + "string" + ] + }, + "slug": { + "type": [ + "null", + "string" + ] + }, + "type": { + "type": [ + "null", + "string" + ] + }, + "url": { + "type": [ + "null", + "string" + ] + } + }, + "type": [ + "object", + "null" + ] + } + }, + "anyOf": [ + { + "type": "null" + }, + { + "additionalProperties": false, + "properties": { + "incomplete_results": { + "type": "boolean" + }, + "items": { + "items": { + "additionalProperties": false, + "properties": { + "archived": { + "type": "boolean" + }, + "created_at": { + "type": "string" + }, + "default_branch": { + "type": "string" + }, + "description": { + "type": "string" + }, + "fork": { + "type": "boolean" + }, + "forks_count": { + "type": "integer" + }, + "full_name": { + "type": "string" + }, + "html_url": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "language": { + "type": "string" + }, + "name": { + "type": "string" + }, + "open_issues_count": { + "type": "integer" + }, + "private": { + "type": "boolean" + }, + "stargazers_count": { + "type": "integer" + }, + "topics": { + "items": { + "type": "string" + }, + "type": [ + "null", + "array" + ] + }, + "updated_at": { + "type": "string" + } + }, + "required": [ + "id", + "name", + "full_name", + "html_url", + "stargazers_count", + "forks_count", + "open_issues_count", + "private", + "fork", + "archived" + ], + "type": "object" + }, + "type": [ + "null", + "array" + ] + }, + "total_count": { + "type": "integer" + } + }, + "required": [ + "total_count", + "incomplete_results", + "items" + ], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "incomplete_results": { + "type": [ + "null", + "boolean" + ] + }, + "items": { + "items": { + "$ref": "#/$defs/searchRepository", + "type": [ + "null", + "object" + ] + }, + "type": [ + "null", + "array" + ] + }, + "total_count": { + "type": [ + "null", + "integer" + ] + } + }, + "type": "object" + } + ] + } +} \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/search_users_typed.snap b/pkg/github/__toolsnaps__/search_users_typed.snap new file mode 100644 index 0000000000..cbbb8eedbc --- /dev/null +++ b/pkg/github/__toolsnaps__/search_users_typed.snap @@ -0,0 +1,173 @@ +{ + "annotations": { + "idempotentHint": false, + "readOnlyHint": true, + "title": "Search users" + }, + "description": "Find GitHub users by username, real name, or other profile information. Useful for locating developers, contributors, or team members.", + "icons": [ + { + "mimeType": "image/png", + "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAADWklEQVRIidWTT2hcVRTGf+e+mUn9Q3WMqbbWgJ1JnMw4meiAUkoxIRG1IKK4ELqoVkQXUsVuXbropoq4EWykuBDURV2I0r9GrC0VopnXTGaefYkhDahBOg4SnSTv3eMiTphMJ6Spq57dPd+933e/794DN3rJWkA+n49W54MB4AE1aoyawqVS1xn4PPzfAsnUg48iOgyaaIJ8I7r/5wn3u2sVMM2N7nTvbsQeB42J6nOOrW12bG2zIs8ohFblZHe6d/d1Oejv74/MzlU8lGibCfqKxeKVRrwzm43HAlMAatvviqdHRkaC9QScxkVk021DwAFRfalcGv+xeXN1bq52x5ats8Crf80vnL3yx29TiZ7e59s7tg7Ft3TEc5n09PT0tG08syoiEckBGF04seaVFtuOA1ixuf9OvQP6rqj5avb3SmlHKptdU0BhaT3L1gbLsVpRgMlSYdvNUb1VlKeAqBFzsjObjbcWMNYFCMxNg2sJmE3B4wCOmEK957ru/KVy4UtxnCeBO2OheaOONX9Tk+zJlRXsUsTunLl4sdIIplKp9oC2MYR//FJ3T6uZSKZzZ7Es+OXCIECkOQFVeUFET8UCU0im+w7WMydWeyKwHEboUCuDaw2cKGpFVx76qjmYLI+dRzkE3IvqZ0RrVaK1KsqnCNsROTTpjX3fijyReSip8LCo+aFlRN3d2ZR15COQnUAFOIVwedmbdCI6CMSBc47V/Z7neivkqWxexHwC2h4lmi2VRn9dJZC4Pzcghi9Qaoq8dfstztHR0dFVvyqfz0f/nA9fFNG3gRhqnvbLP30LkOzJfQP0Kzw7WSocW+Vg+ebmAjCjTrhncnz8cqsI6rUjk+80NvgauMex+ojnuV4ynTuMcgD4G/RNv+QO199ArGOGUWrXQg4wVRydCTF7gCVr5Agg/kThIE6YQjkH8mEi1bcPQLp6+h5T9AToy37JPbIeeWN1pXOvqPKBFR2amnBPA2QymdiidY4pMmhCEgbsXqDSZsKPN0IOoIvzR4GqsbK33isWi4vG8hogoaOvG0V2oXK6WCwublTA9/0FgTMIuxr7nuf+AowIDBjgboz6GyVfcSF4wLarAGEcuC8iyj4JuXC9Ak5o3g8j4fnmvpXIe44NWg7kjVX/Ap7dYx0LcmfJAAAAAElFTkSuQmCC", + "theme": "light" + }, + { + "mimeType": "image/png", + "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAB20lEQVRIidXUvUuVcRQH8PPc1JYUrKWUDCmwoKa2QsTKoKEwxWhr6G1tqDVqaAn8F6I5oS0ipBp6WZqiAi1bEgIrIpcorD4NHvFye+7Vrkv94Dc853zP93venl/E/36Keg60RsRgROyOiEpEPI+IB0VR/FyzKgYw48/zBv1rJe/HN7zDKNrzDmMqfc2JoAVvk3xjib8zfa/R0ozA4WzFaAPMWGKG8vskLuDIiqK4lMHtDTAdibmY3+9rZrSnGl+piV9YqcpY3jwREUVRdEXEhog4GhGtETGJznrZHchMhhtUcCIxh0p8u/ADV+sFV3KAU2VZYBNmE7OuDsdj3K+XYGAfvua2jGXPOzLz2VzT/Q3iH2GykUCByyU/2dK50iB2B77jWj3ATjxNos+4hfG8E2mDJ+irid2LaXzCljLyQcxjDmctvkW1mFacwwd8wUCV72GKH6+X+TxeYGvd/i3je/AqRfrSNo6F5DldDS6y5LnVkFfFbcPHHGqRtu24i184tQQcytLOrJa8SuR8xh6ssrXhTm5bd+BmDq+tCYH12aYbNfbe3KbrYfH9mPhb8iqy25gusd/Ds0pEbI6ImWYFImI6IrpK7C8jojcwgu5m2dGFYyX2How0y/vvnN8dpHfeBcHNQgAAAABJRU5ErkJggg==", + "theme": "dark" + } + ], + "inputSchema": { + "properties": { + "order": { + "description": "Sort order", + "enum": [ + "asc", + "desc" + ], + "type": "string" + }, + "page": { + "description": "Page number for pagination (min 1)", + "minimum": 1, + "type": "number" + }, + "perPage": { + "description": "Results per page for pagination (min 1, max 100)", + "maximum": 100, + "minimum": 1, + "type": "number" + }, + "query": { + "description": "User search query. Examples: 'john smith', 'location:seattle', 'followers:\u003e100'. Search is automatically scoped to type:user.", + "type": "string" + }, + "sort": { + "description": "Sort users by number of followers or repositories, or when the person joined GitHub.", + "enum": [ + "followers", + "repositories", + "joined" + ], + "type": "string" + } + }, + "required": [ + "query" + ], + "type": "object" + }, + "name": "search_users", + "outputSchema": { + "additionalProperties": false, + "properties": { + "incomplete_results": { + "type": "boolean" + }, + "items": { + "items": { + "additionalProperties": false, + "properties": { + "avatar_url": { + "type": "string" + }, + "details": { + "additionalProperties": false, + "properties": { + "bio": { + "type": "string" + }, + "blog": { + "type": "string" + }, + "company": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "email": { + "type": "string" + }, + "followers": { + "type": "integer" + }, + "following": { + "type": "integer" + }, + "hireable": { + "type": "boolean" + }, + "location": { + "type": "string" + }, + "name": { + "type": "string" + }, + "owned_private_repos": { + "type": "integer" + }, + "private_gists": { + "type": "integer" + }, + "public_gists": { + "type": "integer" + }, + "public_repos": { + "type": "integer" + }, + "total_private_repos": { + "type": "integer" + }, + "twitter_username": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + }, + "required": [ + "public_repos", + "public_gists", + "followers", + "following", + "created_at", + "updated_at" + ], + "type": [ + "null", + "object" + ] + }, + "id": { + "type": "integer" + }, + "login": { + "type": "string" + }, + "profile_url": { + "type": "string" + } + }, + "required": [ + "login" + ], + "type": "object" + }, + "type": [ + "null", + "array" + ] + }, + "total_count": { + "type": "integer" + } + }, + "required": [ + "total_count", + "incomplete_results", + "items" + ], + "type": "object" + } +} \ No newline at end of file diff --git a/pkg/github/search.go b/pkg/github/search.go index c731b2b701..08881d6088 100644 --- a/pkg/github/search.go +++ b/pkg/github/search.go @@ -48,7 +48,7 @@ func SearchRepositories(t translations.TranslationHelperFunc) inventory.ServerTo } WithPagination(schema) - return NewTool( + return NewTool[SearchRepositoriesInput, *SearchRepositoriesOutput]( ToolsetMetadataRepos, mcp.Tool{ Name: "search_repositories", @@ -57,33 +57,21 @@ func SearchRepositories(t translations.TranslationHelperFunc) inventory.ServerTo Title: t("TOOL_SEARCH_REPOSITORIES_USER_TITLE", "Search repositories"), ReadOnlyHint: true, }, - InputSchema: schema, + InputSchema: schema, + OutputSchema: searchRepositoriesOutputSchema(), }, scopes.PublicRead(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 { - return utils.NewToolResultError(err.Error()), nil, nil - } - sort, err := OptionalParam[string](args, "sort") - if err != nil { - return utils.NewToolResultError(err.Error()), nil, nil - } - order, err := OptionalParam[string](args, "order") - if err != nil { - return utils.NewToolResultError(err.Error()), nil, nil - } - pagination, err := OptionalPaginationParams(args) - if err != nil { - return utils.NewToolResultError(err.Error()), nil, nil - } - minimalOutput, err := OptionalBoolParamWithDefault(args, "minimal_output", true) - if err != nil { - return utils.NewToolResultError(err.Error()), nil, nil + func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, input SearchRepositoriesInput) (*mcp.CallToolResult, *SearchRepositoriesOutput, error) { + var output *SearchRepositoriesOutput + if input.Query == "" { + return utils.NewToolResultError("missing required parameter: query"), output, nil } + query := input.Query + pagination := repositoryPagination(input.Page, input.PerPage) + minimalOutput := input.MinimalOutput == nil || *input.MinimalOutput opts := &github.SearchOptions{ - Sort: sort, - Order: order, + Sort: input.Sort, + Order: input.Order, ListOptions: github.ListOptions{ Page: pagination.Page, PerPage: pagination.PerPage, @@ -92,7 +80,7 @@ func SearchRepositories(t translations.TranslationHelperFunc) inventory.ServerTo client, err := deps.GetClient(ctx) if err != nil { - return utils.NewToolResultErrorFromErr("failed to get GitHub client", err), nil, nil + return utils.NewToolResultErrorFromErr("failed to get GitHub client", err), output, nil } result, resp, err := client.Search.Repositories(ctx, query, opts) if err != nil { @@ -100,20 +88,21 @@ func SearchRepositories(t translations.TranslationHelperFunc) inventory.ServerTo fmt.Sprintf("failed to search repositories with query '%s'", query), resp, err, - ), nil, nil + ), output, nil } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != http.StatusOK { body, err := io.ReadAll(resp.Body) if err != nil { - return utils.NewToolResultErrorFromErr("failed to read response body", err), nil, nil + return utils.NewToolResultErrorFromErr("failed to read response body", err), output, nil } - return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to search repositories", resp, body), nil, nil + return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to search repositories", resp, body), output, nil } // Return either minimal or full response based on parameter var r []byte + output = new(SearchRepositoriesOutput) if minimalOutput { minimalRepos := make([]MinimalRepository, 0, len(result.Repositories)) for _, repo := range result.Repositories { @@ -156,17 +145,23 @@ func SearchRepositories(t translations.TranslationHelperFunc) inventory.ServerTo if err != nil { return utils.NewToolResultErrorFromErr("failed to marshal minimal response", err), nil, nil } + output.Minimal = minimalResult } else { r, err = json.Marshal(result) if err != nil { return utils.NewToolResultErrorFromErr("failed to marshal full response", err), nil, nil } + output.Full = new(FullSearchRepositoriesResult) + if err := json.Unmarshal(r, output.Full); err != nil { + return utils.NewToolResultErrorFromErr("failed to decode full response", err), nil, nil + } } callResult := utils.NewToolResultText(string(r)) attachSearchRepositoriesIFCLabel(ctx, deps, result.Repositories, callResult) - return callResult, nil, nil + return callResult, output, nil }, + normalizeSearchArguments(true), ) } @@ -398,27 +393,16 @@ func recordSearchCodeFieldsUsage(ctx context.Context, deps ToolDependencies, ful recordFieldsUsageFor(ctx, deps, "search_code", full, filtered, sentBytes) } -func userOrOrgHandler(ctx context.Context, accountType string, deps ToolDependencies, args map[string]any) (*mcp.CallToolResult, any, error) { - query, err := RequiredParam[string](args, "query") - if err != nil { - return utils.NewToolResultError(err.Error()), nil, nil - } - sort, err := OptionalParam[string](args, "sort") - if err != nil { - return utils.NewToolResultError(err.Error()), nil, nil - } - order, err := OptionalParam[string](args, "order") - if err != nil { - return utils.NewToolResultError(err.Error()), nil, nil - } - pagination, err := OptionalPaginationParams(args) - if err != nil { - return utils.NewToolResultError(err.Error()), nil, nil +func userOrOrgHandler(ctx context.Context, accountType string, deps ToolDependencies, input SearchAccountsInput) (*mcp.CallToolResult, *MinimalSearchUsersResult, error) { + if input.Query == "" { + return utils.NewToolResultError("missing required parameter: query"), nil, nil } + query := input.Query + pagination := repositoryPagination(input.Page, input.PerPage) opts := &github.SearchOptions{ - Sort: sort, - Order: order, + Sort: input.Sort, + Order: input.Order, ListOptions: github.ListOptions{ PerPage: pagination.PerPage, Page: pagination.Page, @@ -485,7 +469,7 @@ func userOrOrgHandler(ctx context.Context, accountType string, deps ToolDependen // User and organization search returns public profile information that is // authored by the account holders themselves, so it is public-untrusted. callResult = attachStaticIFCLabel(ctx, deps, callResult, ifc.PublicUntrusted()) - return callResult, nil, nil + return callResult, minimalResp, nil } // SearchUsers creates a tool to search for GitHub users. @@ -512,7 +496,7 @@ func SearchUsers(t translations.TranslationHelperFunc) inventory.ServerTool { } WithPagination(schema) - return NewTool( + return NewTool[SearchAccountsInput, *MinimalSearchUsersResult]( ToolsetMetadataUsers, mcp.Tool{ Name: "search_users", @@ -524,9 +508,10 @@ func SearchUsers(t translations.TranslationHelperFunc) inventory.ServerTool { InputSchema: schema, }, scopes.PublicRead(scopes.Repo), - func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { - return userOrOrgHandler(ctx, "user", deps, args) + func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, input SearchAccountsInput) (*mcp.CallToolResult, *MinimalSearchUsersResult, error) { + return userOrOrgHandler(ctx, "user", deps, input) }, + normalizeSearchArguments(false), ) } @@ -554,7 +539,7 @@ func SearchOrgs(t translations.TranslationHelperFunc) inventory.ServerTool { } WithPagination(schema) - return NewTool( + return NewTool[SearchAccountsInput, *MinimalSearchUsersResult]( ToolsetMetadataOrgs, mcp.Tool{ Name: "search_orgs", @@ -566,9 +551,10 @@ func SearchOrgs(t translations.TranslationHelperFunc) inventory.ServerTool { InputSchema: schema, }, scopes.RequireAll(scopes.ReadOrg), - func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { - return userOrOrgHandler(ctx, "org", deps, args) + func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, input SearchAccountsInput) (*mcp.CallToolResult, *MinimalSearchUsersResult, error) { + return userOrOrgHandler(ctx, "org", deps, input) }, + normalizeSearchArguments(false), ) } @@ -596,7 +582,7 @@ func SearchCommits(t translations.TranslationHelperFunc) inventory.ServerTool { } WithPagination(schema) - return NewTool( + return NewTool[SearchCommitsInput, *MinimalSearchCommitsResult]( ToolsetMetadataRepos, mcp.Tool{ Name: "search_commits", @@ -608,27 +594,16 @@ func SearchCommits(t translations.TranslationHelperFunc) inventory.ServerTool { InputSchema: schema, }, scopes.PublicRead(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 { - return utils.NewToolResultError(err.Error()), nil, nil - } - sort, err := OptionalParam[string](args, "sort") - if err != nil { - return utils.NewToolResultError(err.Error()), nil, nil - } - order, err := OptionalParam[string](args, "order") - if err != nil { - return utils.NewToolResultError(err.Error()), nil, nil - } - pagination, err := OptionalPaginationParams(args) - if err != nil { - return utils.NewToolResultError(err.Error()), nil, nil + func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, input SearchCommitsInput) (*mcp.CallToolResult, *MinimalSearchCommitsResult, error) { + if input.Query == "" { + return utils.NewToolResultError("missing required parameter: query"), nil, nil } + query := input.Query + pagination := repositoryPagination(input.Page, input.PerPage) opts := &github.SearchOptions{ - Sort: sort, - Order: order, + Sort: input.Sort, + Order: input.Order, ListOptions: github.ListOptions{ Page: pagination.Page, PerPage: pagination.PerPage, @@ -684,7 +659,8 @@ func SearchCommits(t translations.TranslationHelperFunc) inventory.ServerTool { } } callResult = attachJoinedIFCLabel(ctx, deps, callResult, visibilities, ifc.LabelSearchIssues) - return callResult, nil, nil + return callResult, minimalResult, nil }, + normalizeSearchArguments(false), ) } diff --git a/pkg/github/search_tool_types.go b/pkg/github/search_tool_types.go new file mode 100644 index 0000000000..99d5138011 --- /dev/null +++ b/pkg/github/search_tool_types.go @@ -0,0 +1,266 @@ +package github + +import ( + "bytes" + "encoding/json" + "io" + "reflect" + + "github.com/google/go-github/v92/github" + "github.com/google/jsonschema-go/jsonschema" +) + +type SearchAccountsInput struct { + Query string `json:"query"` + Sort string `json:"sort,omitempty"` + Order string `json:"order,omitempty"` + Page *int `json:"page,omitempty"` + PerPage *int `json:"perPage,omitempty"` +} + +type SearchRepositoriesInput struct { + SearchAccountsInput + MinimalOutput *bool `json:"minimal_output,omitempty"` +} + +type SearchCommitsInput = SearchAccountsInput + +type SearchRepositoriesOutput struct { + Minimal *MinimalSearchRepositoriesResult + Full *FullSearchRepositoriesResult +} + +func (out SearchRepositoriesOutput) MarshalJSON() ([]byte, error) { + if out.Minimal != nil { + return json.Marshal(out.Minimal) + } + return json.Marshal(out.Full) +} + +func searchRepositoriesOutputSchema() *jsonschema.Schema { + minimal := repositoryOutputSchema[MinimalSearchRepositoriesResult]() + options := &jsonschema.ForOptions{TypeSchemas: map[reflect.Type]*jsonschema.Schema{ + reflect.TypeFor[github.Timestamp](): {Type: "string", Format: "date-time"}, + reflect.TypeFor[github.Team](): {Type: "object", Ref: "#/$defs/team"}, + reflect.TypeFor[SearchRepository](): {Type: "object", Ref: "#/$defs/searchRepository"}, + reflect.TypeFor[SearchPropertyValue](): {Ref: "#/$defs/searchPropertyValue"}, + }} + full, err := jsonschema.For[FullSearchRepositoriesResult](options) + if err != nil { + panic(err) + } + type repositorySchema SearchRepository + repository, err := jsonschema.For[repositorySchema](options) + if err != nil { + panic(err) + } + repository.Type = "" + repository.Types = []string{"object", "null"} + full.Defs = map[string]*jsonschema.Schema{ + "searchRepository": repository, + "team": repositoryOutputSchema[github.Team]().Defs["team"], + "searchPropertyValue": {AnyOf: []*jsonschema.Schema{ + {Type: "null"}, {Type: "string"}, {Type: "number"}, {Type: "boolean"}, + {Type: "array", Items: &jsonschema.Schema{Ref: "#/$defs/searchPropertyValue"}}, + {Type: "object", AdditionalProperties: &jsonschema.Schema{Ref: "#/$defs/searchPropertyValue"}}, + }}, + } + union := repositoryUnionSchema(&jsonschema.Schema{Type: "null"}, minimal, full) + // Empty items can satisfy both variants. AnyOf preserves that overlap. + union.AnyOf, union.OneOf = union.OneOf, nil + return union +} + +type FullSearchRepositoriesResult struct { + Total *int `json:"total_count,omitempty"` + IncompleteResults *bool `json:"incomplete_results,omitempty"` + Repositories []*SearchRepository `json:"items,omitempty"` +} + +// SearchRepository mirrors the full search response without the upstream +// untyped custom_properties map. Parent/source/template repositories retain +// the same recursive wire contract. +type SearchRepository struct { + ID *int64 `json:"id,omitempty"` + NodeID *string `json:"node_id,omitempty"` + Owner *github.User `json:"owner,omitempty"` + Name *string `json:"name,omitempty"` + FullName *string `json:"full_name,omitempty"` + Description *string `json:"description,omitempty"` + Homepage *string `json:"homepage,omitempty"` + CodeOfConduct *github.CodeOfConduct `json:"code_of_conduct,omitempty"` + DefaultBranch *string `json:"default_branch,omitempty"` + MasterBranch *string `json:"master_branch,omitempty"` + CreatedAt *github.Timestamp `json:"created_at,omitempty"` + PushedAt *github.Timestamp `json:"pushed_at,omitempty"` + UpdatedAt *github.Timestamp `json:"updated_at,omitempty"` + HTMLURL *string `json:"html_url,omitempty"` + CloneURL *string `json:"clone_url,omitempty"` + GitURL *string `json:"git_url,omitempty"` + MirrorURL *string `json:"mirror_url,omitempty"` + SSHURL *string `json:"ssh_url,omitempty"` + SVNURL *string `json:"svn_url,omitempty"` + Language *string `json:"language,omitempty"` + Fork *bool `json:"fork,omitempty"` + ForksCount *int `json:"forks_count,omitempty"` + NetworkCount *int `json:"network_count,omitempty"` + OpenIssuesCount *int `json:"open_issues_count,omitempty"` + OpenIssues *int `json:"open_issues,omitempty"` + StargazersCount *int `json:"stargazers_count,omitempty"` + SubscribersCount *int `json:"subscribers_count,omitempty"` + WatchersCount *int `json:"watchers_count,omitempty"` + Watchers *int `json:"watchers,omitempty"` + Size *int `json:"size,omitempty"` + AutoInit *bool `json:"auto_init,omitempty"` + Parent *SearchRepository `json:"parent,omitempty"` + Source *SearchRepository `json:"source,omitempty"` + TemplateRepository *SearchRepository `json:"template_repository,omitempty"` + Organization *github.Organization `json:"organization,omitempty"` + Permissions *github.RepositoryPermissions `json:"permissions,omitempty"` + AllowRebaseMerge *bool `json:"allow_rebase_merge,omitempty"` + AllowUpdateBranch *bool `json:"allow_update_branch,omitempty"` + AllowSquashMerge *bool `json:"allow_squash_merge,omitempty"` + AllowMergeCommit *bool `json:"allow_merge_commit,omitempty"` + AllowAutoMerge *bool `json:"allow_auto_merge,omitempty"` + AllowForking *bool `json:"allow_forking,omitempty"` + WebCommitSignoffRequired *bool `json:"web_commit_signoff_required,omitempty"` + DeleteBranchOnMerge *bool `json:"delete_branch_on_merge,omitempty"` + UseSquashPRTitleAsDefault *bool `json:"use_squash_pr_title_as_default,omitempty"` + SquashMergeCommitTitle *string `json:"squash_merge_commit_title,omitempty"` + SquashMergeCommitMessage *string `json:"squash_merge_commit_message,omitempty"` + MergeCommitTitle *string `json:"merge_commit_title,omitempty"` + MergeCommitMessage *string `json:"merge_commit_message,omitempty"` + Topics []string `json:"topics,omitempty"` + CustomProperties map[string]SearchPropertyValue `json:"custom_properties,omitempty"` + Archived *bool `json:"archived,omitempty"` + Disabled *bool `json:"disabled,omitempty"` + License *github.License `json:"license,omitempty"` + Private *bool `json:"private,omitempty"` + HasIssues *bool `json:"has_issues,omitempty"` + HasWiki *bool `json:"has_wiki,omitempty"` + HasPages *bool `json:"has_pages,omitempty"` + HasProjects *bool `json:"has_projects,omitempty"` + HasDownloads *bool `json:"has_downloads,omitempty"` + HasDiscussions *bool `json:"has_discussions,omitempty"` + HasPullRequests *bool `json:"has_pull_requests,omitempty"` + PullRequestCreationPolicy *string `json:"pull_request_creation_policy,omitempty"` + IsTemplate *bool `json:"is_template,omitempty"` + LicenseTemplate *string `json:"license_template,omitempty"` + GitignoreTemplate *string `json:"gitignore_template,omitempty"` + SecurityAndAnalysis *github.SecurityAndAnalysis `json:"security_and_analysis,omitempty"` + TeamID *int64 `json:"team_id,omitempty"` + URL *string `json:"url,omitempty"` + ArchiveURL *string `json:"archive_url,omitempty"` + AssigneesURL *string `json:"assignees_url,omitempty"` + BlobsURL *string `json:"blobs_url,omitempty"` + BranchesURL *string `json:"branches_url,omitempty"` + CollaboratorsURL *string `json:"collaborators_url,omitempty"` + CommentsURL *string `json:"comments_url,omitempty"` + CommitsURL *string `json:"commits_url,omitempty"` + CompareURL *string `json:"compare_url,omitempty"` + ContentsURL *string `json:"contents_url,omitempty"` + ContributorsURL *string `json:"contributors_url,omitempty"` + DeploymentsURL *string `json:"deployments_url,omitempty"` + DownloadsURL *string `json:"downloads_url,omitempty"` + EventsURL *string `json:"events_url,omitempty"` + ForksURL *string `json:"forks_url,omitempty"` + GitCommitsURL *string `json:"git_commits_url,omitempty"` + GitRefsURL *string `json:"git_refs_url,omitempty"` + GitTagsURL *string `json:"git_tags_url,omitempty"` + HooksURL *string `json:"hooks_url,omitempty"` + IssueCommentURL *string `json:"issue_comment_url,omitempty"` + IssueEventsURL *string `json:"issue_events_url,omitempty"` + IssuesURL *string `json:"issues_url,omitempty"` + KeysURL *string `json:"keys_url,omitempty"` + LabelsURL *string `json:"labels_url,omitempty"` + LanguagesURL *string `json:"languages_url,omitempty"` + MergesURL *string `json:"merges_url,omitempty"` + MilestonesURL *string `json:"milestones_url,omitempty"` + NotificationsURL *string `json:"notifications_url,omitempty"` + PullsURL *string `json:"pulls_url,omitempty"` + ReleasesURL *string `json:"releases_url,omitempty"` + StargazersURL *string `json:"stargazers_url,omitempty"` + StatusesURL *string `json:"statuses_url,omitempty"` + SubscribersURL *string `json:"subscribers_url,omitempty"` + SubscriptionURL *string `json:"subscription_url,omitempty"` + TagsURL *string `json:"tags_url,omitempty"` + TreesURL *string `json:"trees_url,omitempty"` + TeamsURL *string `json:"teams_url,omitempty"` + TextMatches []*github.TextMatch `json:"text_matches,omitempty"` + Visibility *string `json:"visibility,omitempty"` + RoleName *string `json:"role_name,omitempty"` +} + +// Custom property values are JSON unions, not arbitrary Go values. +type SearchPropertyValue struct { + String *string + Number *json.Number + Bool *bool + Array []SearchPropertyValue + Object map[string]SearchPropertyValue +} + +func (value SearchPropertyValue) MarshalJSON() ([]byte, error) { + switch { + case value.String != nil: + return json.Marshal(value.String) + case value.Number != nil: + return json.Marshal(value.Number) + case value.Bool != nil: + return json.Marshal(value.Bool) + case value.Array != nil: + return json.Marshal(value.Array) + case value.Object != nil: + return json.Marshal(value.Object) + default: + return []byte("null"), nil + } +} + +func (value *SearchPropertyValue) UnmarshalJSON(raw []byte) error { + *value = SearchPropertyValue{} + raw = bytes.TrimSpace(raw) + if len(raw) == 0 { + return io.ErrUnexpectedEOF + } + switch raw[0] { + case '"': + return json.Unmarshal(raw, &value.String) + case '[': + return json.Unmarshal(raw, &value.Array) + case '{': + return json.Unmarshal(raw, &value.Object) + case 't', 'f': + return json.Unmarshal(raw, &value.Bool) + case 'n': + return json.Unmarshal(raw, new(*string)) + default: + return json.Unmarshal(raw, &value.Number) + } +} + +func normalizeSearchArguments(minimalOutput bool) func(json.RawMessage) (json.RawMessage, error) { + return func(raw json.RawMessage) (json.RawMessage, error) { + var args map[string]any + if err := json.Unmarshal(raw, &args); err != nil { + return nil, err + } + if _, err := RequiredParam[string](args, "query"); err != nil { + return nil, err + } + for _, field := range []string{"sort", "order"} { + if _, err := OptionalParam[string](args, field); err != nil { + return nil, err + } + } + if _, err := OptionalPaginationParams(args); err != nil { + return nil, err + } + if minimalOutput { + if _, err := OptionalBoolParamWithDefault(args, "minimal_output", true); err != nil { + return nil, err + } + } + return normalizeRepositoryArguments(raw) + } +} diff --git a/pkg/github/typed_account_repository_search_test.go b/pkg/github/typed_account_repository_search_test.go new file mode 100644 index 0000000000..3bcecad930 --- /dev/null +++ b/pkg/github/typed_account_repository_search_test.go @@ -0,0 +1,352 @@ +package github + +import ( + "context" + "encoding/json" + "errors" + "io" + "maps" + "net/http" + "reflect" + "strings" + "testing" + + "github.com/github/github-mcp-server/internal/toolsnaps" + "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/translations" + "github.com/google/go-github/v92/github" + "github.com/google/jsonschema-go/jsonschema" + "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func typedAccountRepositorySearchTools() []inventory.ServerTool { + return []inventory.ServerTool{ + SearchRepositories(translations.NullTranslationHelper), + SearchUsers(translations.NullTranslationHelper), + SearchOrgs(translations.NullTranslationHelper), + SearchCommits(translations.NullTranslationHelper), + } +} + +func connectTypedSearchClient(t *testing.T, tool inventory.ServerTool, deps ToolDependencies, protocol string) *mcp.ClientSession { + t.Helper() + server := mcp.NewServer(&mcp.Implementation{Name: "typed-search-test", Version: "v1"}, nil) + server.AddReceivingMiddleware(InjectDepsMiddleware(deps)) + tool.RegisterFunc(server, deps) + version := protocol + if protocol == "" { + version = inventory.ProtocolVersionMultiRoundTrip + server.AddReceivingMiddleware(func(next mcp.MethodHandler) mcp.MethodHandler { + return func(ctx context.Context, method string, req mcp.Request) (mcp.Result, error) { + switch request := req.(type) { + case *mcp.ListToolsRequest: + request.Params.Meta = mcp.Meta{mcp.MetaKeyProtocolVersion: ""} + case *mcp.CallToolRequest: + request.Params.Meta = mcp.Meta{mcp.MetaKeyProtocolVersion: ""} + } + return next(ctx, method, req) + } + }) + } + return connectCommentVisibilityClient(t, server, version) +} + +func TestTypedAccountRepositorySearchOutputs(t *testing.T) { + fixtures := map[string]struct { + path string + body string + text string + }{ + "search_repositories": { + GetSearchRepositories, + `{"total_count":1,"incomplete_results":false,"items":[{"id":7,"name":"repo","full_name":"owner/repo","description":"description","html_url":"repo-url","stargazers_count":0,"private":false,"fork":false,"archived":false,"created_at":"2026-01-02T03:04:05Z","topics":["go"],"owner":{"login":"owner"},"parent":{"id":8,"custom_properties":{"nested":{"flag":false}}},"custom_properties":{"text":"value","list":["one","two"],"empty":[],"nil":null,"number":12.5,"bool":true,"object":{}}}]}`, + `{"total_count":1,"incomplete_results":false,"items":[{"id":7,"name":"repo","full_name":"owner/repo","description":"\u003cb\u003edescription\u003c/b\u003e","html_url":"repo-url","stargazers_count":0,"forks_count":0,"open_issues_count":0,"created_at":"2026-01-02T03:04:05Z","topics":["go"],"private":false,"fork":false,"archived":false}]}`, + }, + "search_users": { + GetSearchUsers, + `{"total_count":3,"incomplete_results":true,"items":[{"id":7,"login":"octo","html_url":"profile","avatar_url":"avatar"},{"id":8},{"id":9,"login":""}]}`, + `{"total_count":3,"incomplete_results":true,"items":[{"login":"octo","id":7,"profile_url":"profile","avatar_url":"avatar"},{"login":"","id":9}]}`, + }, + "search_orgs": { + GetSearchUsers, + `{"total_count":3,"incomplete_results":true,"items":[{"id":7,"login":"octo","html_url":"profile","avatar_url":"avatar"},{"id":8},{"id":9,"login":""}]}`, + `{"total_count":3,"incomplete_results":true,"items":[{"login":"octo","id":7,"profile_url":"profile","avatar_url":"avatar"},{"login":"","id":9}]}`, + }, + "search_commits": { + GetSearchCommits, + `{"total_count":2,"incomplete_results":false,"items":[{"sha":"abc","html_url":"commit-url","commit":{"message":"fix","author":{"name":"Author","email":"a@example.com","date":"2026-01-02T03:04:05Z"}},"author":{"login":"octo","id":7,"html_url":"profile","avatar_url":"avatar"},"repository":{"full_name":"owner/repo","html_url":"repo-url","private":true}},{"sha":"def","html_url":""}]}`, + `{"total_count":2,"incomplete_results":false,"items":[{"sha":"abc","html_url":"commit-url","commit":{"message":"\u003cb\u003efix\u003c/b\u003e","author":{"name":"Author","email":"a@example.com","date":"2026-01-02T03:04:05Z"}},"author":{"login":"octo","id":7,"profile_url":"profile","avatar_url":"avatar"},"repository":{"full_name":"owner/repo","html_url":"repo-url","private":true}},{"sha":"def","html_url":""}]}`, + }, + } + for _, tool := range typedAccountRepositorySearchTools() { + fixture := fixtures[tool.Tool.Name] + var apiErrorText string + for _, protocol := range []string{"2025-11-25", inventory.ProtocolVersionMultiRoundTrip, ""} { + t.Run(tool.Tool.Name+"/"+protocol, func(t *testing.T) { + body := fixture.body + query := "repo:owner/repo fix" + page, perPage, sort, order := "1", "30", "", "" + calls := 0 + handler := func(w http.ResponseWriter, r *http.Request) { + calls++ + expectedQuery := query + if !hasTypeFilter(query) { + switch tool.Tool.Name { + case "search_users": + expectedQuery = "type:user " + query + case "search_orgs": + expectedQuery = "type:org " + query + } + } + assert.Equal(t, expectedQuery, r.URL.Query().Get("q")) + assert.Equal(t, page, r.URL.Query().Get("page")) + assert.Equal(t, perPage, r.URL.Query().Get("per_page")) + assert.Equal(t, sort, r.URL.Query().Get("sort")) + assert.Equal(t, order, r.URL.Query().Get("order")) + if body == "forbidden" { + w.WriteHeader(http.StatusForbidden) + _, _ = w.Write([]byte(`{"message":"Forbidden"}`)) + return + } + _, _ = w.Write([]byte(body)) + } + deps := BaseDeps{ + Client: mustNewGHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{fixture.path: handler})), + featureChecker: featureCheckerFor(FeatureFlagIFCLabels), + } + session := connectTypedSearchClient(t, tool, deps, protocol) + list, err := session.ListTools(context.Background(), nil) + require.NoError(t, err) + require.Len(t, list.Tools, 1) + listed := list.Tools[0] + assert.Equal(t, tool.Tool.Name, listed.Name) + var resolved *jsonschema.Resolved + if protocol == inventory.ProtocolVersionMultiRoundTrip { + require.NotNil(t, listed.OutputSchema) + require.NoError(t, toolsnaps.Test(tool.Tool.Name+"_typed", *listed)) + var schema jsonschema.Schema + require.NoError(t, json.Unmarshal([]byte(mustMarshalJSON(t, listed.OutputSchema)), &schema)) + resolved, err = schema.Resolve(nil) + require.NoError(t, err) + } else { + assert.Nil(t, listed.OutputSchema) + } + check := func(args map[string]any, expected string) { + t.Helper() + args = maps.Clone(args) + args["query"] = query + result, err := session.CallTool(context.Background(), &mcp.CallToolParams{Name: tool.Tool.Name, Arguments: args}) + require.NoError(t, err) + require.False(t, result.IsError, "%+v", result) + require.Len(t, result.Content, 1) + assert.Equal(t, expected, getTextResult(t, result).Text) + require.NotNil(t, result.Meta) + label := unmarshalIFC(t, result.Meta["ifc"]) + if tool.Tool.Name == "search_commits" && body == fixture.body { + assert.Equal(t, "trusted", label["integrity"]) + assert.Equal(t, "private", label["confidentiality"]) + } else { + assert.Equal(t, "untrusted", label["integrity"]) + assert.Equal(t, "public", label["confidentiality"]) + } + if resolved == nil { + assert.Nil(t, result.StructuredContent) + } else { + require.NotNil(t, result.StructuredContent) + encoded := mustMarshalJSON(t, result.StructuredContent) + assert.JSONEq(t, expected, encoded) + var output any + require.NoError(t, json.Unmarshal([]byte(encoded), &output)) + require.NoError(t, resolved.Validate(output)) + } + request := createMCPRequest(args) + direct, err := tool.Handler(deps)(ContextWithDeps(context.Background(), deps), &request) + require.NoError(t, err) + require.False(t, direct.IsError) + assert.Equal(t, expected, getTextResult(t, direct).Text) + } + for _, args := range []map[string]any{ + {}, {"page": 0, "perPage": 0}, {"page": "0", "perPage": "0"}, + {"page": "0e0", "perPage": "-0"}, {"fields": []string{"id"}}, + } { + check(args, fixture.text) + } + page, perPage, order = "2", "7", "asc" + switch tool.Tool.Name { + case "search_repositories": + sort = "stars" + case "search_commits": + sort = "author-date" + default: + sort = "followers" + } + check(map[string]any{"page": "2e0", "perPage": "7.0", "sort": sort, "order": order}, fixture.text) + page, perPage, sort, order = "1", "30", "", "" + query = "type:org (location:seattle OR location:california)" + check(map[string]any{}, fixture.text) + if tool.Tool.Name == "search_repositories" { + var full github.RepositoriesSearchResult + require.NoError(t, json.Unmarshal([]byte(body), &full)) + check(map[string]any{"minimal_output": false}, mustMarshalJSON(t, full)) + check(map[string]any{"minimal_output": true}, fixture.text) + } + for _, emptyBody := range []string{`{"total_count":0,"incomplete_results":false,"items":[]}`, `{}`} { + body = emptyBody + check(map[string]any{}, `{"total_count":0,"incomplete_results":false,"items":[]}`) + if tool.Tool.Name == "search_repositories" { + expected := `{"total_count":0,"incomplete_results":false}` + if emptyBody == "{}" { + expected = "{}" + } + check(map[string]any{"minimal_output": false}, expected) + } + } + before := calls + invalid := []struct { + args map[string]any + text string + }{ + {map[string]any{}, "missing required parameter: query"}, + {map[string]any{"query": ""}, "missing required parameter: query"}, + {map[string]any{"query": nil}, "parameter query is not of type string"}, + {map[string]any{"query": 1}, "parameter query is not of type string"}, + {map[string]any{"query": query, "sort": nil}, "parameter sort is not of type string"}, + {map[string]any{"query": query, "order": false}, "parameter order is not of type string"}, + {map[string]any{"query": query, "page": "1.5"}, "non-integer numeric value"}, + {map[string]any{"query": query, "page": 1.5}, "non-integer numeric value"}, + {map[string]any{"query": query, "perPage": false}, "expected number"}, + {map[string]any{"query": query, "perPage": nil}, "expected number"}, + {map[string]any{"query": query, "page": "NaN"}, "non-finite numeric value"}, + {map[string]any{"query": query, "page": "1e30"}, "numeric value out of int range"}, + {map[string]any{"query": query, "after": nil}, "parameter after is not of type string"}, + } + if tool.Tool.Name == "search_repositories" { + invalid = append(invalid, struct { + args map[string]any + text string + }{map[string]any{"query": query, "minimal_output": nil}, "parameter minimal_output is not of type bool"}) + } + for _, tc := range invalid { + result, err := session.CallTool(context.Background(), &mcp.CallToolParams{Name: tool.Tool.Name, Arguments: tc.args}) + require.NoError(t, err) + require.True(t, result.IsError) + assert.Contains(t, getErrorResult(t, result).Text, tc.text) + assert.Nil(t, result.StructuredContent) + request := createMCPRequest(tc.args) + direct, err := tool.Handler(deps)(ContextWithDeps(context.Background(), deps), &request) + require.NoError(t, err) + require.True(t, direct.IsError) + assert.Contains(t, getErrorResult(t, direct).Text, tc.text) + } + assert.Equal(t, before, calls, "invalid input must never reach GitHub") + body = "forbidden" + result, err := session.CallTool(context.Background(), &mcp.CallToolParams{ + Name: tool.Tool.Name, Arguments: map[string]any{"query": query}, + }) + require.NoError(t, err) + require.True(t, result.IsError) + assert.Contains(t, getErrorResult(t, result).Text, "failed to search") + assert.Contains(t, getErrorResult(t, result).Text, query) + assert.Nil(t, result.StructuredContent) + if apiErrorText == "" { + apiErrorText = getErrorResult(t, result).Text + } + assert.Equal(t, apiErrorText, getErrorResult(t, result).Text, "API errors are identical across protocols") + }) + } + } +} + +func TestSearchRepositoryOutputCoversUpstreamFields(t *testing.T) { + upstream := reflect.TypeFor[github.Repository]() + output := reflect.TypeFor[SearchRepository]() + require.Equal(t, upstream.NumField(), output.NumField()) + for field := range upstream.Fields() { + actual, ok := output.FieldByName(field.Name) + require.True(t, ok, field.Name) + assert.Equal(t, field.Tag, actual.Tag, field.Name) + switch field.Name { + case "CustomProperties", "Parent", "Source", "TemplateRepository": + default: + assert.Equal(t, field.Type, actual.Type, field.Name) + } + } +} + +func TestSearchPropertyValueJSONUnion(t *testing.T) { + schema, err := searchRepositoriesOutputSchema().Resolve(nil) + require.NoError(t, err) + for _, raw := range []string{`null`, `false`, `0`, `""`, `[]`, `{}`, `["a",null]`, `{"nested":[true,1,"a"]}`} { + var value SearchPropertyValue + require.NoError(t, json.Unmarshal([]byte(raw), &value)) + assert.JSONEq(t, raw, mustMarshalJSON(t, value)) + out := FullSearchRepositoriesResult{Repositories: []*SearchRepository{{CustomProperties: map[string]SearchPropertyValue{"value": value}}}} + var decoded any + require.NoError(t, json.Unmarshal([]byte(mustMarshalJSON(t, out)), &decoded)) + require.NoError(t, schema.Validate(decoded)) + } + for _, raw := range []string{``, `"`, `[`, `NaN`, `null false`} { + var value SearchPropertyValue + require.Error(t, json.Unmarshal([]byte(raw), &value)) + } + var value SearchPropertyValue + require.ErrorIs(t, value.UnmarshalJSON(nil), io.ErrUnexpectedEOF) + for _, tool := range typedAccountRepositorySearchTools() { + assert.True(t, tool.IsReadOnly()) + } +} + +type failingSearchClientDeps struct { + BaseDeps +} + +func (failingSearchClientDeps) GetClient(context.Context) (*github.Client, error) { + return nil, errors.New("client unavailable") +} + +func TestTypedAccountRepositorySearchClientErrors(t *testing.T) { + for _, tool := range typedAccountRepositorySearchTools() { + for _, protocol := range []string{"2025-11-25", inventory.ProtocolVersionMultiRoundTrip, ""} { + t.Run(tool.Tool.Name+"/"+protocol, func(t *testing.T) { + session := connectTypedSearchClient(t, tool, failingSearchClientDeps{}, protocol) + result, err := session.CallTool(context.Background(), &mcp.CallToolParams{ + Name: tool.Tool.Name, Arguments: map[string]any{"query": "query"}, + }) + require.NoError(t, err) + require.True(t, result.IsError) + assert.Equal(t, "failed to get GitHub client: client unavailable", getErrorResult(t, result).Text) + assert.Nil(t, result.StructuredContent) + }) + } + } +} + +func TestSearchOutputsHaveNoUntypedFields(t *testing.T) { + seen := make(map[reflect.Type]bool) + var check func(reflect.Type) + check = func(typ reflect.Type) { + if seen[typ] { + return + } + seen[typ] = true + switch typ.Kind() { + case reflect.Interface: + t.Errorf("untyped output field: %s", typ) + case reflect.Pointer, reflect.Slice, reflect.Array: + check(typ.Elem()) + case reflect.Map: + check(typ.Elem()) + case reflect.Struct: + for field := range typ.Fields() { + if field.IsExported() && !strings.HasPrefix(field.Tag.Get("json"), "-") { + check(field.Type) + } + } + } + } + check(reflect.TypeFor[SearchRepositoriesOutput]()) + check(reflect.TypeFor[MinimalSearchUsersResult]()) + check(reflect.TypeFor[MinimalSearchCommitsResult]()) +}