diff --git a/.github/workflows/ai-issue-assessment.yml b/.github/workflows/ai-issue-assessment.yml
index 7481ce6db..189ae959f 100644
--- a/.github/workflows/ai-issue-assessment.yml
+++ b/.github/workflows/ai-issue-assessment.yml
@@ -6,9 +6,6 @@ on:
jobs:
ai-issue-assessment:
- if: >
- (github.event.action == 'opened' && github.event.issue.labels[0] == null) ||
- (github.event.action == 'labeled' && github.event.label.name == 'bug')
runs-on: ubuntu-latest
permissions:
issues: write
@@ -23,8 +20,8 @@ jobs:
uses: github/ai-assessment-comment-labeler@e3bedc38cfffa9179fe4cee8f7ecc93bffb3fee7 # v1.0.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
- ai_review_label: 'bug, enhancement'
+ ai_review_label: "request ai review"
issue_number: ${{ github.event.issue.number }}
issue_body: ${{ github.event.issue.body }}
- prompts_directory: '.github/prompts'
- labels_to_prompts_mapping: 'bug,bug-report-review.prompt.yml|default,default-issue-review.prompt.yml'
+ prompts_directory: ".github/prompts"
+ labels_to_prompts_mapping: "bug,bug-report-review.prompt.yml|default,default-issue-review.prompt.yml"
diff --git a/.github/workflows/issue-labeler.yml b/.github/workflows/issue-labeler.yml
new file mode 100644
index 000000000..278bb8705
--- /dev/null
+++ b/.github/workflows/issue-labeler.yml
@@ -0,0 +1,19 @@
+name: Label issues for AI review
+on:
+ issues:
+ types:
+ - reopened
+ - opened
+jobs:
+ label_issues:
+ runs-on: ubuntu-latest
+ permissions:
+ issues: write
+ steps:
+ - name: Add AI review label to issue
+ run: gh issue edit "$NUMBER" --add-label "$LABELS"
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ GH_REPO: ${{ github.repository }}
+ NUMBER: ${{ github.event.issue.number }}
+ LABELS: "request ai review"
diff --git a/.gitignore b/.gitignore
index 5684108b0..eedf65165 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,6 +18,8 @@ bin/
# binary
github-mcp-server
+mcpcurl
+e2e.test
.history
conformance-report/
diff --git a/README.md b/README.md
index 6da46c00e..6f1cda8fa 100644
--- a/README.md
+++ b/README.md
@@ -80,6 +80,7 @@ Alternatively, to manually configure VS Code, choose the appropriate JSON block
### Install in other MCP hosts
+
- **[GitHub Copilot in other IDEs](/docs/installation-guides/install-other-copilot-ides.md)** - Installation for JetBrains, Visual Studio, Eclipse, and Xcode with GitHub Copilot
- **[Claude Applications](/docs/installation-guides/install-claude.md)** - Installation guide for Claude Desktop and Claude Code CLI
- **[Codex](/docs/installation-guides/install-codex.md)** - Installation guide for Open AI Codex
@@ -104,6 +105,7 @@ When no toolsets are specified, [default toolsets](#default-toolset) are used.
GitHub Enterprise Cloud can also make use of the remote server.
Example for `https://octocorp.ghe.com` with GitHub PAT token:
+
```
{
...
@@ -140,24 +142,30 @@ The MCP server can use many of the GitHub APIs, so enable the permissions that y
Handling PATs Securely
### Environment Variables (Recommended)
+
To keep your GitHub PAT secure and reusable across different MCP hosts:
1. **Store your PAT in environment variables**
+
```bash
export GITHUB_PAT=your_token_here
```
+
Or create a `.env` file:
+
```env
GITHUB_PAT=your_token_here
```
2. **Protect your `.env` file**
+
```bash
# Add to .gitignore to prevent accidental commits
echo ".env" >> .gitignore
```
3. **Reference the token in configurations**
+
```bash
# CLI usage
claude mcp update github -e GITHUB_PERSONAL_ACCESS_TOKEN=$GITHUB_PAT
@@ -180,6 +188,7 @@ To keep your GitHub PAT secure and reusable across different MCP hosts:
- **Regular rotation**: Update tokens periodically
- **Never commit**: Keep tokens out of version control
- **File permissions**: Restrict access to config files containing tokens
+
```bash
chmod 600 ~/.your-app/config.json
```
@@ -193,6 +202,7 @@ the hostname for GitHub Enterprise Server or GitHub Enterprise Cloud with data r
- For GitHub Enterprise Server, prefix the hostname with the `https://` URI scheme, as it otherwise defaults to `http://`, which GitHub Enterprise Server does not support.
- For GitHub Enterprise Cloud with data residency, use `https://YOURSUBDOMAIN.ghe.com` as the hostname.
+
``` json
"github": {
"command": "docker",
@@ -328,6 +338,18 @@ If you don't have Docker, you can use `go build` to build the binary in the
}
```
+### CLI utilities
+
+The `github-mcp-server` binary includes a few CLI subcommands that are helpful for debugging and exploring the server.
+
+- `github-mcp-server tool-search ""` searches tools by name, description, and input parameter names. Use `--max-results` to return more matches.
+Example:
+
+```bash
+docker run -i --rm ghcr.io/github/github-mcp-server tool-search "issue" --max-results 5
+github-mcp-server tool-search "issue" --max-results 5
+```
+
## Tool Configuration
The GitHub MCP Server supports enabling or disabling specific groups of functionalities via the `--toolsets` flag. This allows you to control which GitHub API capabilities are available to your AI tools. Enabling only the toolsets that you need can help the LLM with tool choice and reduce the context size.
@@ -349,6 +371,7 @@ To specify toolsets you want available to the LLM, you can pass an allow-list in
```
2. **Using Environment Variable**:
+
```bash
GITHUB_TOOLSETS="repos,issues,pull_requests,actions,code_security" ./github-mcp-server
```
@@ -366,23 +389,29 @@ You can also configure specific tools using the `--tools` flag. Tools can be use
```
2. **Using Environment Variable**:
+
```bash
GITHUB_TOOLS="get_file_contents,issue_read,create_pull_request" ./github-mcp-server
```
3. **Combining with Toolsets** (additive):
+
```bash
github-mcp-server --toolsets repos,issues --tools get_gist
```
+
This registers all tools from `repos` and `issues` toolsets, plus `get_gist`.
4. **Combining with Dynamic Toolsets** (additive):
+
```bash
github-mcp-server --tools get_file_contents --dynamic-toolsets
```
+
This registers `get_file_contents` plus the dynamic toolset tools (`enable_toolset`, `list_available_toolsets`, `get_toolset_tools`).
**Important Notes:**
+
- Tools, toolsets, and dynamic toolsets can all be used together
- Read-only mode takes priority: write tools are skipped if `--read-only` is set, even if explicitly requested via `--tools`
- Tool names must match exactly (e.g., `get_file_contents`, not `getFileContents`). Invalid tool names will cause the server to fail at startup with an error message
@@ -435,9 +464,11 @@ GITHUB_TOOLSETS="all" ./github-mcp-server
```
#### "default" toolset
+
The default toolset `default` is the configuration that gets passed to the server if no toolsets are specified.
The default configuration is:
+
- context
- repos
- issues
@@ -752,6 +783,7 @@ The following sets of tools are available:
- **assign_copilot_to_issue** - Assign Copilot to issue
- **Required OAuth Scopes**: `repo`
- `base_ref`: Git reference (e.g., branch) that the agent will start its work from. If not specified, defaults to the repository's default branch (string, optional)
+ - `custom_instructions`: Optional custom instructions to guide the agent beyond the issue body. Use this to provide additional context, constraints, or guidance that is not captured in the issue description (string, optional)
- `issue_number`: Issue number (number, required)
- `owner`: Repository owner (string, required)
- `repo`: Repository name (string, required)
@@ -1370,12 +1402,12 @@ The following sets of tools are available:
Copilot
-- **create_pull_request_with_copilot** - Perform task with GitHub Copilot coding agent
- - `owner`: Repository owner. You can guess the owner, but confirm it with the user before proceeding. (string, required)
- - `repo`: Repository name. You can guess the repository name, but confirm it with the user before proceeding. (string, required)
- - `problem_statement`: Detailed description of the task to be performed (e.g., 'Implement a feature that does X', 'Fix bug Y', etc.) (string, required)
- - `title`: Title for the pull request that will be created (string, required)
- - `base_ref`: Git reference (e.g., branch) that the agent will start its work from. If not specified, defaults to the repository's default branch (string, optional)
+- **create_pull_request_with_copilot** - Perform task with GitHub Copilot coding agent
+ - `owner`: Repository owner. You can guess the owner, but confirm it with the user before proceeding. (string, required)
+ - `repo`: Repository name. You can guess the repository name, but confirm it with the user before proceeding. (string, required)
+ - `problem_statement`: Detailed description of the task to be performed (e.g., 'Implement a feature that does X', 'Fix bug Y', etc.) (string, required)
+ - `title`: Title for the pull request that will be created (string, required)
+ - `base_ref`: Git reference (e.g., branch) that the agent will start its work from. If not specified, defaults to the repository's default branch (string, optional)
@@ -1383,19 +1415,21 @@ The following sets of tools are available:
Copilot Spaces
-- **get_copilot_space** - Get Copilot Space
- - `owner`: The owner of the space. (string, required)
- - `name`: The name of the space. (string, required)
+- **get_copilot_space** - Get Copilot Space
+ - `owner`: The owner of the space. (string, required)
+ - `name`: The name of the space. (string, required)
+
+- **list_copilot_spaces** - List Copilot Spaces
-- **list_copilot_spaces** - List Copilot Spaces
GitHub Support Docs Search
-- **github_support_docs_search** - Retrieve documentation relevant to answer GitHub product and support questions. Support topics include: GitHub Actions Workflows, Authentication, GitHub Support Inquiries, Pull Request Practices, Repository Maintenance, GitHub Pages, GitHub Packages, GitHub Discussions, Copilot Spaces
- - `query`: Input from the user about the question they need answered. This is the latest raw unedited user message. You should ALWAYS leave the user message as it is, you should never modify it. (string, required)
+- **github_support_docs_search** - Retrieve documentation relevant to answer GitHub product and support questions. Support topics include: GitHub Actions Workflows, Authentication, GitHub Support Inquiries, Pull Request Practices, Repository Maintenance, GitHub Pages, GitHub Packages, GitHub Discussions, Copilot Spaces
+ - `query`: Input from the user about the question they need answered. This is the latest raw unedited user message. You should ALWAYS leave the user message as it is, you should never modify it. (string, required)
+
## Dynamic Tool Discovery
diff --git a/cmd/github-mcp-server/generate_docs.go b/cmd/github-mcp-server/generate_docs.go
index a458c04b6..78fd6c40a 100644
--- a/cmd/github-mcp-server/generate_docs.go
+++ b/cmd/github-mcp-server/generate_docs.go
@@ -51,7 +51,8 @@ func generateReadmeDocs(readmePath string) error {
t, _ := translations.TranslationHelper()
// (not available to regular users) while including tools with FeatureFlagDisable.
- r := github.NewInventory(t).WithToolsets([]string{"all"}).Build()
+ // Build() can only fail if WithTools specifies invalid tools - not used here
+ r, _ := github.NewInventory(t).WithToolsets([]string{"all"}).Build()
// Generate toolsets documentation
toolsetsDoc := generateToolsetsDoc(r)
@@ -341,7 +342,8 @@ func generateRemoteToolsetsDoc() string {
t, _ := translations.TranslationHelper()
// Build inventory - stateless
- r := github.NewInventory(t).Build()
+ // Build() can only fail if WithTools specifies invalid tools - not used here
+ r, _ := github.NewInventory(t).Build()
// Generate table header (icon is combined with Name column)
buf.WriteString("| Name | Description | API URL | 1-Click Install (VS Code) | Read-only Link | 1-Click Read-only Install (VS Code) |\n")
diff --git a/cmd/github-mcp-server/list_scopes.go b/cmd/github-mcp-server/list_scopes.go
index 2d1817500..d8b8bf392 100644
--- a/cmd/github-mcp-server/list_scopes.go
+++ b/cmd/github-mcp-server/list_scopes.go
@@ -121,7 +121,10 @@ func runListScopes() error {
inventoryBuilder = inventoryBuilder.WithTools(enabledTools)
}
- inv := inventoryBuilder.Build()
+ inv, err := inventoryBuilder.Build()
+ if err != nil {
+ return fmt.Errorf("failed to build inventory: %w", err)
+ }
// Collect all tools and their scopes
output := collectToolScopes(inv, readOnly)
diff --git a/cmd/github-mcp-server/main.go b/cmd/github-mcp-server/main.go
index cfb68be4e..27bffaff8 100644
--- a/cmd/github-mcp-server/main.go
+++ b/cmd/github-mcp-server/main.go
@@ -83,6 +83,7 @@ var (
LogFilePath: viper.GetString("log-file"),
ContentWindowSize: viper.GetInt("content-window-size"),
LockdownMode: viper.GetBool("lockdown-mode"),
+ InsiderMode: viper.GetBool("insider-mode"),
RepoAccessCacheTTL: &ttl,
}
return ghmcp.RunStdioServer(stdioServerConfig)
@@ -108,6 +109,7 @@ func init() {
rootCmd.PersistentFlags().String("gh-host", "", "Specify the GitHub hostname (for GitHub Enterprise etc.)")
rootCmd.PersistentFlags().Int("content-window-size", 5000, "Specify the content window size")
rootCmd.PersistentFlags().Bool("lockdown-mode", false, "Enable lockdown mode")
+ rootCmd.PersistentFlags().Bool("insider-mode", false, "Enable insider features")
rootCmd.PersistentFlags().Duration("repo-access-cache-ttl", 5*time.Minute, "Override the repo access cache TTL (e.g. 1m, 0s to disable)")
// Bind flag to viper
@@ -122,6 +124,7 @@ func init() {
_ = viper.BindPFlag("host", rootCmd.PersistentFlags().Lookup("gh-host"))
_ = viper.BindPFlag("content-window-size", rootCmd.PersistentFlags().Lookup("content-window-size"))
_ = viper.BindPFlag("lockdown-mode", rootCmd.PersistentFlags().Lookup("lockdown-mode"))
+ _ = viper.BindPFlag("insider-mode", rootCmd.PersistentFlags().Lookup("insider-mode"))
_ = viper.BindPFlag("repo-access-cache-ttl", rootCmd.PersistentFlags().Lookup("repo-access-cache-ttl"))
// Add subcommands
@@ -133,7 +136,6 @@ func initConfig() {
viper.SetEnvPrefix("github")
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
viper.AutomaticEnv()
-
}
func main() {
diff --git a/cmd/mcpcurl/mcpcurl b/cmd/mcpcurl/mcpcurl
deleted file mode 100755
index 6ea4eeda6..000000000
Binary files a/cmd/mcpcurl/mcpcurl and /dev/null differ
diff --git a/e2e.test b/e2e.test
deleted file mode 100755
index 58505b3a2..000000000
Binary files a/e2e.test and /dev/null differ
diff --git a/go.mod b/go.mod
index 5322b47ec..e50f52c72 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@ module github.com/github/github-mcp-server
go 1.24.0
require (
+ github.com/fatih/color v1.18.0
github.com/google/go-github/v79 v79.0.0
github.com/google/jsonschema-go v0.4.2
github.com/josephburnett/jd v1.9.2
@@ -20,6 +21,8 @@ require (
github.com/gorilla/css v1.0.1 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
+ github.com/mattn/go-colorable v0.1.13 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
@@ -31,9 +34,10 @@ require (
require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fsnotify/fsnotify v1.9.0 // indirect
- github.com/go-viper/mapstructure/v2 v2.4.0
+ github.com/go-viper/mapstructure/v2 v2.5.0
github.com/google/go-querystring v1.1.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
+ github.com/lithammer/fuzzysearch v1.1.8
github.com/modelcontextprotocol/go-sdk v1.2.0
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
diff --git a/go.sum b/go.sum
index 25cbf7fa9..89c8b1dda 100644
--- a/go.sum
+++ b/go.sum
@@ -6,6 +6,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
+github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
@@ -15,8 +17,8 @@ github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU=
github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
-github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
-github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
+github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
+github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -44,11 +46,18 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
+github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/modelcontextprotocol/go-sdk v1.2.0 h1:Y23co09300CEk8iZ/tMxIX1dVmKZkzoSBZOpJwUnc/s=
@@ -96,20 +105,53 @@ github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zI
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go
index 250f6b4cc..9e7dafcae 100644
--- a/internal/ghmcp/server.go
+++ b/internal/ghmcp/server.go
@@ -64,6 +64,9 @@ type MCPServerConfig struct {
// LockdownMode indicates if we should enable lockdown mode
LockdownMode bool
+ // Insider indicates if we should enable experimental features
+ InsiderMode bool
+
// Logger is used for logging within the server
Logger *slog.Logger
// RepoAccessTTL overrides the default TTL for repository access cache entries.
@@ -96,8 +99,10 @@ func createGitHubClients(cfg MCPServerConfig, apiHost apiHost) (*githubClients,
// We use NewEnterpriseClient unconditionally since we already parsed the API host
gqlHTTPClient := &http.Client{
Transport: &bearerAuthTransport{
- transport: http.DefaultTransport,
- token: cfg.Token,
+ transport: &github.GraphQLFeaturesTransport{
+ Transport: http.DefaultTransport,
+ },
+ token: cfg.Token,
},
}
gqlClient := githubv4.NewEnterpriseClient(apiHost.graphqlURL.String(), gqlHTTPClient)
@@ -198,6 +203,9 @@ func NewMCPServer(cfg MCPServerConfig) (*mcp.Server, error) {
ghServer.AddReceivingMiddleware(addGitHubAPIErrorToContext)
ghServer.AddReceivingMiddleware(addUserAgentsMiddleware(cfg, clients.rest, clients.gqlHTTP))
+ // Create feature checker
+ featureChecker := createFeatureChecker(cfg.EnabledFeatures)
+
// Create dependencies for tool handlers
deps := github.NewBaseDeps(
clients.rest,
@@ -205,8 +213,12 @@ func NewMCPServer(cfg MCPServerConfig) (*mcp.Server, error) {
clients.raw,
clients.repoAccess,
cfg.Translator,
- github.FeatureFlags{LockdownMode: cfg.LockdownMode},
+ github.FeatureFlags{
+ LockdownMode: cfg.LockdownMode,
+ InsiderMode: cfg.InsiderMode,
+ },
cfg.ContentWindowSize,
+ featureChecker,
)
// Inject dependencies into context for all tool handlers
@@ -221,15 +233,18 @@ func NewMCPServer(cfg MCPServerConfig) (*mcp.Server, error) {
WithDeprecatedAliases(github.DeprecatedToolAliases).
WithReadOnly(cfg.ReadOnly).
WithToolsets(enabledToolsets).
- WithTools(github.CleanTools(cfg.EnabledTools)).
- WithFeatureChecker(createFeatureChecker(cfg.EnabledFeatures))
+ WithTools(cfg.EnabledTools).
+ WithFeatureChecker(featureChecker)
// Apply token scope filtering if scopes are known (for PAT filtering)
if cfg.TokenScopes != nil {
inventoryBuilder = inventoryBuilder.WithFilter(github.CreateToolScopeFilter(cfg.TokenScopes))
}
- inventory := inventoryBuilder.Build()
+ inventory, err := inventoryBuilder.Build()
+ if err != nil {
+ return nil, fmt.Errorf("failed to build inventory: %w", err)
+ }
if unrecognized := inventory.UnrecognizedToolsets(); len(unrecognized) > 0 {
fmt.Fprintf(os.Stderr, "Warning: unrecognized toolsets ignored: %s\n", strings.Join(unrecognized, ", "))
@@ -322,6 +337,9 @@ type StdioServerConfig struct {
// LockdownMode indicates if we should enable lockdown mode
LockdownMode bool
+ // InsiderMode indicates if we should enable experimental features
+ InsiderMode bool
+
// RepoAccessCacheTTL overrides the default TTL for repository access cache entries.
RepoAccessCacheTTL *time.Duration
}
@@ -378,6 +396,7 @@ func RunStdioServer(cfg StdioServerConfig) error {
Translator: t,
ContentWindowSize: cfg.ContentWindowSize,
LockdownMode: cfg.LockdownMode,
+ InsiderMode: cfg.InsiderMode,
Logger: logger,
RepoAccessTTL: cfg.RepoAccessCacheTTL,
TokenScopes: tokenScopes,
@@ -622,12 +641,6 @@ type bearerAuthTransport struct {
func (t *bearerAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) {
req = req.Clone(req.Context())
req.Header.Set("Authorization", "Bearer "+t.token)
-
- // Check for GraphQL-Features in context and add header if present
- if features := github.GetGraphQLFeatures(req.Context()); len(features) > 0 {
- req.Header.Set("GraphQL-Features", strings.Join(features, ", "))
- }
-
return t.transport.RoundTrip(req)
}
diff --git a/internal/ghmcp/server_test.go b/internal/ghmcp/server_test.go
index 04c0989d4..7854cb77f 100644
--- a/internal/ghmcp/server_test.go
+++ b/internal/ghmcp/server_test.go
@@ -23,6 +23,7 @@ func TestNewMCPServer_CreatesSuccessfully(t *testing.T) {
Translator: translations.NullTranslationHelper,
ContentWindowSize: 5000,
LockdownMode: false,
+ InsiderMode: false,
}
// Create the server
diff --git a/internal/toolsnaps/toolsnaps.go b/internal/toolsnaps/toolsnaps.go
index 89d02e1ee..4b25904ed 100644
--- a/internal/toolsnaps/toolsnaps.go
+++ b/internal/toolsnaps/toolsnaps.go
@@ -67,15 +67,35 @@ func Test(toolName string, tool any) error {
}
func writeSnap(snapPath string, contents []byte) error {
+ // Sort the JSON keys recursively to ensure consistent output.
+ // We do this by unmarshaling and remarshaling, which ensures Go's JSON encoder
+ // sorts all map keys alphabetically at every level.
+ sortedJSON, err := sortJSONKeys(contents)
+ if err != nil {
+ return fmt.Errorf("failed to sort JSON keys: %w", err)
+ }
+
// Ensure the directory exists
if err := os.MkdirAll(filepath.Dir(snapPath), 0700); err != nil {
return fmt.Errorf("failed to create snapshot directory: %w", err)
}
// Write the snapshot file
- if err := os.WriteFile(snapPath, contents, 0600); err != nil {
+ if err := os.WriteFile(snapPath, sortedJSON, 0600); err != nil {
return fmt.Errorf("failed to write snapshot file: %w", err)
}
return nil
}
+
+// sortJSONKeys recursively sorts all object keys in a JSON byte array by
+// unmarshaling to map[string]any and remarshaling. Go's JSON encoder
+// automatically sorts map keys alphabetically.
+func sortJSONKeys(jsonData []byte) ([]byte, error) {
+ var data any
+ if err := json.Unmarshal(jsonData, &data); err != nil {
+ return nil, err
+ }
+
+ return json.MarshalIndent(data, "", " ")
+}
diff --git a/internal/toolsnaps/toolsnaps_test.go b/internal/toolsnaps/toolsnaps_test.go
index be9cadf7f..c7d7301bc 100644
--- a/internal/toolsnaps/toolsnaps_test.go
+++ b/internal/toolsnaps/toolsnaps_test.go
@@ -131,3 +131,184 @@ func TestMalformedSnapshotJSON(t *testing.T) {
require.Error(t, err)
assert.Contains(t, err.Error(), "failed to parse snapshot JSON for dummy", "expected error about malformed snapshot JSON")
}
+
+func TestSortJSONKeys(t *testing.T) {
+ tests := []struct {
+ name string
+ input string
+ expected string
+ }{
+ {
+ name: "simple object",
+ input: `{"z": 1, "a": 2, "m": 3}`,
+ expected: "{\n \"a\": 2,\n \"m\": 3,\n \"z\": 1\n}",
+ },
+ {
+ name: "nested object",
+ input: `{"z": {"y": 1, "x": 2}, "a": 3}`,
+ expected: "{\n \"a\": 3,\n \"z\": {\n \"x\": 2,\n \"y\": 1\n }\n}",
+ },
+ {
+ name: "array with objects",
+ input: `{"items": [{"z": 1, "a": 2}, {"y": 3, "b": 4}]}`,
+ expected: "{\n \"items\": [\n {\n \"a\": 2,\n \"z\": 1\n },\n {\n \"b\": 4,\n \"y\": 3\n }\n ]\n}",
+ },
+ {
+ name: "deeply nested",
+ input: `{"z": {"y": {"x": 1, "a": 2}, "b": 3}, "m": 4}`,
+ expected: "{\n \"m\": 4,\n \"z\": {\n \"b\": 3,\n \"y\": {\n \"a\": 2,\n \"x\": 1\n }\n }\n}",
+ },
+ {
+ name: "properties field like in toolsnaps",
+ input: `{"name": "test", "properties": {"repo": {"type": "string"}, "owner": {"type": "string"}, "page": {"type": "number"}}}`,
+ expected: "{\n \"name\": \"test\",\n \"properties\": {\n \"owner\": {\n \"type\": \"string\"\n },\n \"page\": {\n \"type\": \"number\"\n },\n \"repo\": {\n \"type\": \"string\"\n }\n }\n}",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result, err := sortJSONKeys([]byte(tt.input))
+ require.NoError(t, err)
+ assert.Equal(t, tt.expected, string(result))
+ })
+ }
+}
+
+func TestSortJSONKeysIdempotent(t *testing.T) {
+ // Given a JSON string that's already sorted
+ input := `{"a": 1, "b": {"x": 2, "y": 3}, "c": [{"m": 4, "n": 5}]}`
+
+ // When we sort it once
+ sorted1, err := sortJSONKeys([]byte(input))
+ require.NoError(t, err)
+
+ // And sort it again
+ sorted2, err := sortJSONKeys(sorted1)
+ require.NoError(t, err)
+
+ // Then the results should be identical
+ assert.Equal(t, string(sorted1), string(sorted2))
+}
+
+func TestToolSnapKeysSorted(t *testing.T) {
+ withIsolatedWorkingDir(t)
+
+ // Given a tool with fields that could be in any order
+ type complexTool struct {
+ Name string `json:"name"`
+ Description string `json:"description"`
+ Properties map[string]interface{} `json:"properties"`
+ Annotations map[string]interface{} `json:"annotations"`
+ }
+
+ tool := complexTool{
+ Name: "test_tool",
+ Description: "A test tool",
+ Properties: map[string]interface{}{
+ "zzz": "last",
+ "aaa": "first",
+ "mmm": "middle",
+ "owner": map[string]interface{}{"type": "string", "description": "Owner"},
+ "repo": map[string]interface{}{"type": "string", "description": "Repo"},
+ },
+ Annotations: map[string]interface{}{
+ "readOnly": true,
+ "title": "Test",
+ },
+ }
+
+ // When we write the snapshot
+ t.Setenv("UPDATE_TOOLSNAPS", "true")
+ err := Test("complex", tool)
+ require.NoError(t, err)
+
+ // Then the snapshot file should have sorted keys
+ snapJSON, err := os.ReadFile("__toolsnaps__/complex.snap")
+ require.NoError(t, err)
+
+ // Verify that the JSON is properly sorted by checking key order
+ var parsed map[string]interface{}
+ err = json.Unmarshal(snapJSON, &parsed)
+ require.NoError(t, err)
+
+ // Check that properties are sorted
+ propsJSON, _ := json.MarshalIndent(parsed["properties"], "", " ")
+ propsStr := string(propsJSON)
+ // The properties should have "aaa" before "mmm" before "zzz"
+ aaaIndex := -1
+ mmmIndex := -1
+ zzzIndex := -1
+ for i, line := range propsStr {
+ if line == 'a' && i+2 < len(propsStr) && propsStr[i:i+3] == "aaa" {
+ aaaIndex = i
+ }
+ if line == 'm' && i+2 < len(propsStr) && propsStr[i:i+3] == "mmm" {
+ mmmIndex = i
+ }
+ if line == 'z' && i+2 < len(propsStr) && propsStr[i:i+3] == "zzz" {
+ zzzIndex = i
+ }
+ }
+ assert.Greater(t, mmmIndex, aaaIndex, "mmm should come after aaa")
+ assert.Greater(t, zzzIndex, mmmIndex, "zzz should come after mmm")
+}
+
+func TestStructFieldOrderingSortedAlphabetically(t *testing.T) {
+ withIsolatedWorkingDir(t)
+
+ // Given a struct with fields defined in non-alphabetical order
+ // This test ensures that struct field order doesn't affect the JSON output
+ type toolWithNonAlphabeticalFields struct {
+ ZField string `json:"zField"` // Should appear last in JSON
+ AField string `json:"aField"` // Should appear first in JSON
+ MField string `json:"mField"` // Should appear in the middle
+ }
+
+ tool := toolWithNonAlphabeticalFields{
+ ZField: "z value",
+ AField: "a value",
+ MField: "m value",
+ }
+
+ // When we write the snapshot
+ t.Setenv("UPDATE_TOOLSNAPS", "true")
+ err := Test("struct_field_order", tool)
+ require.NoError(t, err)
+
+ // Then the snapshot file should have alphabetically sorted keys despite struct field order
+ snapJSON, err := os.ReadFile("__toolsnaps__/struct_field_order.snap")
+ require.NoError(t, err)
+
+ snapStr := string(snapJSON)
+
+ // Find the positions of each field in the JSON string
+ aFieldIndex := -1
+ mFieldIndex := -1
+ zFieldIndex := -1
+ for i := 0; i < len(snapStr)-7; i++ {
+ switch snapStr[i : i+6] {
+ case "aField":
+ aFieldIndex = i
+ case "mField":
+ mFieldIndex = i
+ case "zField":
+ zFieldIndex = i
+ }
+ }
+
+ // Verify alphabetical ordering in the JSON output
+ require.NotEqual(t, -1, aFieldIndex, "aField should be present")
+ require.NotEqual(t, -1, mFieldIndex, "mField should be present")
+ require.NotEqual(t, -1, zFieldIndex, "zField should be present")
+ assert.Less(t, aFieldIndex, mFieldIndex, "aField should appear before mField")
+ assert.Less(t, mFieldIndex, zFieldIndex, "mField should appear before zField")
+
+ // Also verify idempotency - running the test again should produce identical output
+ err = Test("struct_field_order", tool)
+ require.NoError(t, err)
+
+ snapJSON2, err := os.ReadFile("__toolsnaps__/struct_field_order.snap")
+ require.NoError(t, err)
+
+ assert.Equal(t, string(snapJSON), string(snapJSON2), "Multiple runs should produce identical output")
+}
diff --git a/pkg/github/__toolsnaps__/actions_get.snap b/pkg/github/__toolsnaps__/actions_get.snap
index b5f3b85bd..ba128875e 100644
--- a/pkg/github/__toolsnaps__/actions_get.snap
+++ b/pkg/github/__toolsnaps__/actions_get.snap
@@ -5,16 +5,8 @@
},
"description": "Get details about specific GitHub Actions resources.\nUse this tool to get details about individual workflows, workflow runs, jobs, and artifacts by their unique IDs.\n",
"inputSchema": {
- "type": "object",
- "required": [
- "method",
- "owner",
- "repo",
- "resource_id"
- ],
"properties": {
"method": {
- "type": "string",
"description": "The method to execute",
"enum": [
"get_workflow",
@@ -23,21 +15,29 @@
"download_workflow_run_artifact",
"get_workflow_run_usage",
"get_workflow_run_logs_url"
- ]
+ ],
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"resource_id": {
- "type": "string",
- "description": "The unique identifier of the resource. This will vary based on the \"method\" provided, so ensure you provide the correct ID:\n- Provide a workflow ID or workflow file name (e.g. ci.yaml) for 'get_workflow' method.\n- Provide a workflow run ID for 'get_workflow_run', 'get_workflow_run_usage', and 'get_workflow_run_logs_url' methods.\n- Provide an artifact ID for 'download_workflow_run_artifact' method.\n- Provide a job ID for 'get_workflow_job' method.\n"
+ "description": "The unique identifier of the resource. This will vary based on the \"method\" provided, so ensure you provide the correct ID:\n- Provide a workflow ID or workflow file name (e.g. ci.yaml) for 'get_workflow' method.\n- Provide a workflow run ID for 'get_workflow_run', 'get_workflow_run_usage', and 'get_workflow_run_logs_url' methods.\n- Provide an artifact ID for 'download_workflow_run_artifact' method.\n- Provide a job ID for 'get_workflow_job' method.\n",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "method",
+ "owner",
+ "repo",
+ "resource_id"
+ ],
+ "type": "object"
},
"name": "actions_get"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/actions_list.snap b/pkg/github/__toolsnaps__/actions_list.snap
index 4bd029388..a7e9ec56b 100644
--- a/pkg/github/__toolsnaps__/actions_list.snap
+++ b/pkg/github/__toolsnaps__/actions_list.snap
@@ -5,68 +5,66 @@
},
"description": "Tools for listing GitHub Actions resources.\nUse this tool to list workflows in a repository, or list workflow runs, jobs, and artifacts for a specific workflow or workflow run.\n",
"inputSchema": {
- "type": "object",
"properties": {
"method": {
- "type": "string",
"description": "The action to perform",
"enum": [
"list_workflows",
"list_workflow_runs",
"list_workflow_jobs",
"list_workflow_run_artifacts"
- ]
+ ],
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"page": {
- "type": "number",
"description": "Page number for pagination (default: 1)",
- "minimum": 1
+ "minimum": 1,
+ "type": "number"
},
"per_page": {
- "type": "number",
"description": "Results per page for pagination (default: 30, max: 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"resource_id": {
- "type": "string",
- "description": "The unique identifier of the resource. This will vary based on the \"method\" provided, so ensure you provide the correct ID:\n- Do not provide any resource ID for 'list_workflows' method.\n- Provide a workflow ID or workflow file name (e.g. ci.yaml) for 'list_workflow_runs' method, or omit to list all workflow runs in the repository.\n- Provide a workflow run ID for 'list_workflow_jobs' and 'list_workflow_run_artifacts' methods.\n"
+ "description": "The unique identifier of the resource. This will vary based on the \"method\" provided, so ensure you provide the correct ID:\n- Do not provide any resource ID for 'list_workflows' method.\n- Provide a workflow ID or workflow file name (e.g. ci.yaml) for 'list_workflow_runs' method, or omit to list all workflow runs in the repository.\n- Provide a workflow run ID for 'list_workflow_jobs' and 'list_workflow_run_artifacts' methods.\n",
+ "type": "string"
},
"workflow_jobs_filter": {
- "type": "object",
+ "description": "Filters for workflow jobs. **ONLY** used when method is 'list_workflow_jobs'",
"properties": {
"filter": {
- "type": "string",
"description": "Filters jobs by their completed_at timestamp",
"enum": [
"latest",
"all"
- ]
+ ],
+ "type": "string"
}
},
- "description": "Filters for workflow jobs. **ONLY** used when method is 'list_workflow_jobs'"
+ "type": "object"
},
"workflow_runs_filter": {
- "type": "object",
+ "description": "Filters for workflow runs. **ONLY** used when method is 'list_workflow_runs'",
"properties": {
"actor": {
- "type": "string",
- "description": "Filter to a specific GitHub user's workflow runs."
+ "description": "Filter to a specific GitHub user's workflow runs.",
+ "type": "string"
},
"branch": {
- "type": "string",
- "description": "Filter workflow runs to a specific Git branch. Use the name of the branch."
+ "description": "Filter workflow runs to a specific Git branch. Use the name of the branch.",
+ "type": "string"
},
"event": {
- "type": "string",
"description": "Filter workflow runs to a specific event type",
"enum": [
"branch_protection_rule",
@@ -101,10 +99,10 @@
"workflow_call",
"workflow_dispatch",
"workflow_run"
- ]
+ ],
+ "type": "string"
},
"status": {
- "type": "string",
"description": "Filter workflow runs to only runs with a specific status",
"enum": [
"queued",
@@ -112,17 +110,19 @@
"completed",
"requested",
"waiting"
- ]
+ ],
+ "type": "string"
}
},
- "description": "Filters for workflow runs. **ONLY** used when method is 'list_workflow_runs'"
+ "type": "object"
}
},
"required": [
"method",
"owner",
"repo"
- ]
+ ],
+ "type": "object"
},
"name": "actions_list"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/actions_run_trigger.snap b/pkg/github/__toolsnaps__/actions_run_trigger.snap
index 4e16f8958..c51501c17 100644
--- a/pkg/github/__toolsnaps__/actions_run_trigger.snap
+++ b/pkg/github/__toolsnaps__/actions_run_trigger.snap
@@ -5,19 +5,12 @@
},
"description": "Trigger GitHub Actions workflow operations, including running, re-running, cancelling workflow runs, and deleting workflow run logs.",
"inputSchema": {
- "type": "object",
- "required": [
- "method",
- "owner",
- "repo"
- ],
"properties": {
"inputs": {
- "type": "object",
- "description": "Inputs the workflow accepts. Only used for 'run_workflow' method."
+ "description": "Inputs the workflow accepts. Only used for 'run_workflow' method.",
+ "type": "object"
},
"method": {
- "type": "string",
"description": "The method to execute",
"enum": [
"run_workflow",
@@ -25,29 +18,36 @@
"rerun_failed_jobs",
"cancel_workflow_run",
"delete_workflow_run_logs"
- ]
+ ],
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"ref": {
- "type": "string",
- "description": "The git reference for the workflow. The reference can be a branch or tag name. Required for 'run_workflow' method."
+ "description": "The git reference for the workflow. The reference can be a branch or tag name. Required for 'run_workflow' method.",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"run_id": {
- "type": "number",
- "description": "The ID of the workflow run. Required for all methods except 'run_workflow'."
+ "description": "The ID of the workflow run. Required for all methods except 'run_workflow'.",
+ "type": "number"
},
"workflow_id": {
- "type": "string",
- "description": "The workflow ID (numeric) or workflow file name (e.g., main.yml, ci.yaml). Required for 'run_workflow' method."
+ "description": "The workflow ID (numeric) or workflow file name (e.g., main.yml, ci.yaml). Required for 'run_workflow' method.",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "method",
+ "owner",
+ "repo"
+ ],
+ "type": "object"
},
"name": "actions_run_trigger"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/add_comment_to_pending_review.snap b/pkg/github/__toolsnaps__/add_comment_to_pending_review.snap
index 78795c096..af4c41f52 100644
--- a/pkg/github/__toolsnaps__/add_comment_to_pending_review.snap
+++ b/pkg/github/__toolsnaps__/add_comment_to_pending_review.snap
@@ -4,69 +4,69 @@
},
"description": "Add review comment to the requester's latest pending pull request review. A pending review needs to already exist to call this (check with the user if not sure).",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "pullNumber",
- "path",
- "body",
- "subjectType"
- ],
"properties": {
"body": {
- "type": "string",
- "description": "The text of the review comment"
+ "description": "The text of the review comment",
+ "type": "string"
},
"line": {
- "type": "number",
- "description": "The line of the blob in the pull request diff that the comment applies to. For multi-line comments, the last line of the range"
+ "description": "The line of the blob in the pull request diff that the comment applies to. For multi-line comments, the last line of the range",
+ "type": "number"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"path": {
- "type": "string",
- "description": "The relative path to the file that necessitates a comment"
+ "description": "The relative path to the file that necessitates a comment",
+ "type": "string"
},
"pullNumber": {
- "type": "number",
- "description": "Pull request number"
+ "description": "Pull request number",
+ "type": "number"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"side": {
- "type": "string",
"description": "The side of the diff to comment on. LEFT indicates the previous state, RIGHT indicates the new state",
"enum": [
"LEFT",
"RIGHT"
- ]
+ ],
+ "type": "string"
},
"startLine": {
- "type": "number",
- "description": "For multi-line comments, the first line of the range that the comment applies to"
+ "description": "For multi-line comments, the first line of the range that the comment applies to",
+ "type": "number"
},
"startSide": {
- "type": "string",
"description": "For multi-line comments, the starting side of the diff that the comment applies to. LEFT indicates the previous state, RIGHT indicates the new state",
"enum": [
"LEFT",
"RIGHT"
- ]
+ ],
+ "type": "string"
},
"subjectType": {
- "type": "string",
"description": "The level at which the comment is targeted",
"enum": [
"FILE",
"LINE"
- ]
+ ],
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "pullNumber",
+ "path",
+ "body",
+ "subjectType"
+ ],
+ "type": "object"
},
"name": "add_comment_to_pending_review"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/add_issue_comment.snap b/pkg/github/__toolsnaps__/add_issue_comment.snap
index fb2a9e7b3..d273a582d 100644
--- a/pkg/github/__toolsnaps__/add_issue_comment.snap
+++ b/pkg/github/__toolsnaps__/add_issue_comment.snap
@@ -4,31 +4,31 @@
},
"description": "Add a comment to a specific issue in a GitHub repository. Use this tool to add comments to pull requests as well (in this case pass pull request number as issue_number), but only if user is not asking specifically to add review comments.",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "issue_number",
- "body"
- ],
"properties": {
"body": {
- "type": "string",
- "description": "Comment content"
+ "description": "Comment content",
+ "type": "string"
},
"issue_number": {
- "type": "number",
- "description": "Issue number to comment on"
+ "description": "Issue number to comment on",
+ "type": "number"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "issue_number",
+ "body"
+ ],
+ "type": "object"
},
"name": "add_issue_comment"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/add_project_item.snap b/pkg/github/__toolsnaps__/add_project_item.snap
index 08f495370..e6a5cc3c4 100644
--- a/pkg/github/__toolsnaps__/add_project_item.snap
+++ b/pkg/github/__toolsnaps__/add_project_item.snap
@@ -4,44 +4,44 @@
},
"description": "Add a specific Project item for a user or org",
"inputSchema": {
- "type": "object",
- "required": [
- "owner_type",
- "owner",
- "project_number",
- "item_type",
- "item_id"
- ],
"properties": {
"item_id": {
- "type": "number",
- "description": "The numeric ID of the issue or pull request to add to the project."
+ "description": "The numeric ID of the issue or pull request to add to the project.",
+ "type": "number"
},
"item_type": {
- "type": "string",
"description": "The item's type, either issue or pull_request.",
"enum": [
"issue",
"pull_request"
- ]
+ ],
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive."
+ "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive.",
+ "type": "string"
},
"owner_type": {
- "type": "string",
"description": "Owner type",
"enum": [
"user",
"org"
- ]
+ ],
+ "type": "string"
},
"project_number": {
- "type": "number",
- "description": "The project's number."
+ "description": "The project's number.",
+ "type": "number"
}
- }
+ },
+ "required": [
+ "owner_type",
+ "owner",
+ "project_number",
+ "item_type",
+ "item_id"
+ ],
+ "type": "object"
},
"name": "add_project_item"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/assign_copilot_to_issue.snap b/pkg/github/__toolsnaps__/assign_copilot_to_issue.snap
index 7ad1922a0..76a122f16 100644
--- a/pkg/github/__toolsnaps__/assign_copilot_to_issue.snap
+++ b/pkg/github/__toolsnaps__/assign_copilot_to_issue.snap
@@ -4,43 +4,47 @@
"title": "Assign Copilot to issue"
},
"description": "Assign Copilot to a specific issue in a GitHub repository.\n\nThis tool can help with the following outcomes:\n- a Pull Request created with source code changes to resolve the issue\n\n\nMore information can be found at:\n- https://docs.github.com/en/copilot/using-github-copilot/using-copilot-coding-agent-to-work-on-tasks/about-assigning-tasks-to-copilot\n",
+ "icons": [
+ {
+ "mimeType": "image/png",
+ "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC20lEQVRIidWUS4wMURSGv3O7kWmPEMRrSMzcbl1dpqtmGuOxsCKECCKxEBusSJhIWEhsWLFAbC1sWFiISBARCyQ2kzSZGaMxHokgXvGIiMH0PRZjpJqqHpb+TeX+59z//H/q5sD/DqlX9H1/zFeX2qzIKoFWYDKgwBtUymL0UkNaT3V3d3/+5wG2EGxB9TDIxGFMvhVhb9/drpN/NaDJC7MGdwJk6TDCv0Gvq0lve9R762GUNdFDLleaZNBrICGq+4yhvf9TJtP/KZNB2PrLlbBliBfRhajuAwnFVa/n8/nkxFkv3GO9oJrzgwVxdesV71ov6I2r5fxggfWCatYL9yYmUJgLPH7Q29WZ4OED6Me4wuAdeQK6MMqna9t0GuibBHFAmgZ9JMG9BhkXZWoSCDSATIq7aguBD0wBplq/tZBgYDIwKnZAs99mFRYD9vd/YK0dpcqhobM6d9haWyOULRTbAauwuNlvsxHTYP3iBnVyXGAa8BIYC3oVeAKioCtAPEE7FCOgR0ErIJdBBZgNskzh40+NF6K6s+9e91lp9osrxMnFoTSmSmPVsF+E5cB0YEDgtoMjjypd5wCy+WC9GnajhEAa4bkqV9LOHKwa9/yneYeyUqwX3AdyQ5EeVrrqro/hYL0g+ggemKh4HGbPmVu0+fB8U76lpR6XgJwZpoGUpNYiusZg1tXjkmCAav0OMTXfJC4eVYPqwbot6l4BCPqyLhd7lwMAWC/cYb3gi/UCzRaKOxsbFzVEM1iv2Ebt5v2Dm14qZbJecZf1Ah3UCrcTbbB+awHnjgHLgHeinHYqZ8aPSXWWy+XvcQZLpdKI9/0D7UbZiLIJmABckVSqo+/OrUrNgF+D8q1LEdcBrAJGAJ8ROlGeicorABWdAswE5gOjge8CF8Ad66v03IjqJb75WS0tE0YOmNWqLBGReaAzgIkMLrt3oM9UpSzCzW9pd+FpT8/7JK3/Gz8Ao5X6wtwP7N4AAAAASUVORK5CYII=",
+ "theme": "light"
+ },
+ {
+ "mimeType": "image/png",
+ "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACCElEQVRIid2UPWsUYRSFn3dxWWJUkESiBgslFokfhehGiGClBBQx4h9IGlEh2ijYxh+gxEL/hIWwhYpF8KNZsFRJYdJEiUbjCkqisj4W+y6Mk5nd1U4PDMOce+45L3fmDvzXUDeo59WK+kb9rn5TF9R76jm1+2/NJ9QPtseSOv4nxrvVmQ6M05hRB9qZ98ZR1NRralntitdEwmw8wQ9HbS329rQKuKLW1XJO/aX6IqdWjr1Xk/y6lG4vMBdCqOacoZZ3uBBCVZ0HDrcK2AYs5ZkAuwBb1N8Dm5JEISXoAnqzOtU9QB+wVR3KCdgClDIr6kCc4c/0O1BLNnahiYpaSmmGY62e/JpCLJ4FpmmMaBHYCDwC5mmMZBQYBC7HnhvAK+B+fN4JHAM+R4+3wGQI4S7qaExtol+9o86pq+oX9Yk6ljjtGfVprK2qr9Xb6vaET109jjqb3Jac2XaM1PLNpok1Aep+G/+dfa24nADTX1EWTgOngLE2XCYKQL0DTfKex2WhXgCutxG9i/fFNlwWpgBQL6orcWyTaldToRbUA2pow61XL0WPFfXCb1HqkPowCj6q0+qIWsw7nlpUj6i31OXY+0AdbGpCRtNRGgt1AigCX4EqsJAYTR+wAzgEdAM/gApwM4TwOOm3JiARtBk4CYwAB4F+oIfGZi/HwOfAM6ASQviU5/Vv4xcBzmW2eT1nrQAAAABJRU5ErkJggg==",
+ "theme": "dark"
+ }
+ ],
"inputSchema": {
- "type": "object",
"properties": {
"base_ref": {
+ "description": "Git reference (e.g., branch) that the agent will start its work from. If not specified, defaults to the repository's default branch",
+ "type": "string"
+ },
+ "custom_instructions": {
"type": "string",
- "description": "Git reference (e.g., branch) that the agent will start its work from. If not specified, defaults to the repository's default branch"
+ "description": "Optional custom instructions to guide the agent beyond the issue body. Use this to provide additional context, constraints, or guidance that is not captured in the issue description"
},
"issue_number": {
- "type": "number",
- "description": "Issue number"
+ "description": "Issue number",
+ "type": "number"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
}
},
"required": [
"owner",
"repo",
"issue_number"
- ]
+ ],
+ "type": "object"
},
- "name": "assign_copilot_to_issue",
- "icons": [
- {
- "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC20lEQVRIidWUS4wMURSGv3O7kWmPEMRrSMzcbl1dpqtmGuOxsCKECCKxEBusSJhIWEhsWLFAbC1sWFiISBARCyQ2kzSZGaMxHokgXvGIiMH0PRZjpJqqHpb+TeX+59z//H/q5sD/DqlX9H1/zFeX2qzIKoFWYDKgwBtUymL0UkNaT3V3d3/+5wG2EGxB9TDIxGFMvhVhb9/drpN/NaDJC7MGdwJk6TDCv0Gvq0lve9R762GUNdFDLleaZNBrICGq+4yhvf9TJtP/KZNB2PrLlbBliBfRhajuAwnFVa/n8/nkxFkv3GO9oJrzgwVxdesV71ov6I2r5fxggfWCatYL9yYmUJgLPH7Q29WZ4OED6Me4wuAdeQK6MMqna9t0GuibBHFAmgZ9JMG9BhkXZWoSCDSATIq7aguBD0wBplq/tZBgYDIwKnZAs99mFRYD9vd/YK0dpcqhobM6d9haWyOULRTbAauwuNlvsxHTYP3iBnVyXGAa8BIYC3oVeAKioCtAPEE7FCOgR0ErIJdBBZgNskzh40+NF6K6s+9e91lp9osrxMnFoTSmSmPVsF+E5cB0YEDgtoMjjypd5wCy+WC9GnajhEAa4bkqV9LOHKwa9/yneYeyUqwX3AdyQ5EeVrrqro/hYL0g+ggemKh4HGbPmVu0+fB8U76lpR6XgJwZpoGUpNYiusZg1tXjkmCAav0OMTXfJC4eVYPqwbot6l4BCPqyLhd7lwMAWC/cYb3gi/UCzRaKOxsbFzVEM1iv2Ebt5v2Dm14qZbJecZf1Ah3UCrcTbbB+awHnjgHLgHeinHYqZ8aPSXWWy+XvcQZLpdKI9/0D7UbZiLIJmABckVSqo+/OrUrNgF+D8q1LEdcBrAJGAJ8ROlGeicorABWdAswE5gOjge8CF8Ad66v03IjqJb75WS0tE0YOmNWqLBGReaAzgIkMLrt3oM9UpSzCzW9pd+FpT8/7JK3/Gz8Ao5X6wtwP7N4AAAAASUVORK5CYII=",
- "mimeType": "image/png",
- "theme": "light"
- },
- {
- "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACCElEQVRIid2UPWsUYRSFn3dxWWJUkESiBgslFokfhehGiGClBBQx4h9IGlEh2ijYxh+gxEL/hIWwhYpF8KNZsFRJYdJEiUbjCkqisj4W+y6Mk5nd1U4PDMOce+45L3fmDvzXUDeo59WK+kb9rn5TF9R76jm1+2/NJ9QPtseSOv4nxrvVmQ6M05hRB9qZ98ZR1NRralntitdEwmw8wQ9HbS329rQKuKLW1XJO/aX6IqdWjr1Xk/y6lG4vMBdCqOacoZZ3uBBCVZ0HDrcK2AYs5ZkAuwBb1N8Dm5JEISXoAnqzOtU9QB+wVR3KCdgClDIr6kCc4c/0O1BLNnahiYpaSmmGY62e/JpCLJ4FpmmMaBHYCDwC5mmMZBQYBC7HnhvAK+B+fN4JHAM+R4+3wGQI4S7qaExtol+9o86pq+oX9Yk6ljjtGfVprK2qr9Xb6vaET109jjqb3Jac2XaM1PLNpok1Aep+G/+dfa24nADTX1EWTgOngLE2XCYKQL0DTfKex2WhXgCutxG9i/fFNlwWpgBQL6orcWyTaldToRbUA2pow61XL0WPFfXCb1HqkPowCj6q0+qIWsw7nlpUj6i31OXY+0AdbGpCRtNRGgt1AigCX4EqsJAYTR+wAzgEdAM/gApwM4TwOOm3JiARtBk4CYwAB4F+oIfGZi/HwOfAM6ASQviU5/Vv4xcBzmW2eT1nrQAAAABJRU5ErkJggg==",
- "mimeType": "image/png",
- "theme": "dark"
- }
- ]
+ "name": "assign_copilot_to_issue"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/cancel_workflow_run.snap b/pkg/github/__toolsnaps__/cancel_workflow_run.snap
index 83eb31a7f..40bcae740 100644
--- a/pkg/github/__toolsnaps__/cancel_workflow_run.snap
+++ b/pkg/github/__toolsnaps__/cancel_workflow_run.snap
@@ -4,26 +4,26 @@
},
"description": "Cancel a workflow run",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "run_id"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"run_id": {
- "type": "number",
- "description": "The unique identifier of the workflow run"
+ "description": "The unique identifier of the workflow run",
+ "type": "number"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "run_id"
+ ],
+ "type": "object"
},
"name": "cancel_workflow_run"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/create_branch.snap b/pkg/github/__toolsnaps__/create_branch.snap
index 675a2de9c..a561247ef 100644
--- a/pkg/github/__toolsnaps__/create_branch.snap
+++ b/pkg/github/__toolsnaps__/create_branch.snap
@@ -4,30 +4,30 @@
},
"description": "Create a new branch in a GitHub repository",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "branch"
- ],
"properties": {
"branch": {
- "type": "string",
- "description": "Name for new branch"
+ "description": "Name for new branch",
+ "type": "string"
},
"from_branch": {
- "type": "string",
- "description": "Source branch (defaults to repo default)"
+ "description": "Source branch (defaults to repo default)",
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "branch"
+ ],
+ "type": "object"
},
"name": "create_branch"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/create_gist.snap b/pkg/github/__toolsnaps__/create_gist.snap
index 465206ab4..0ef05aa4a 100644
--- a/pkg/github/__toolsnaps__/create_gist.snap
+++ b/pkg/github/__toolsnaps__/create_gist.snap
@@ -4,30 +4,30 @@
},
"description": "Create a new gist",
"inputSchema": {
- "type": "object",
- "required": [
- "filename",
- "content"
- ],
"properties": {
"content": {
- "type": "string",
- "description": "Content for simple single-file gist creation"
+ "description": "Content for simple single-file gist creation",
+ "type": "string"
},
"description": {
- "type": "string",
- "description": "Description of the gist"
+ "description": "Description of the gist",
+ "type": "string"
},
"filename": {
- "type": "string",
- "description": "Filename for simple single-file gist creation"
+ "description": "Filename for simple single-file gist creation",
+ "type": "string"
},
"public": {
- "type": "boolean",
+ "default": false,
"description": "Whether the gist is public",
- "default": false
+ "type": "boolean"
}
- }
+ },
+ "required": [
+ "filename",
+ "content"
+ ],
+ "type": "object"
},
"name": "create_gist"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/create_or_update_file.snap b/pkg/github/__toolsnaps__/create_or_update_file.snap
index 2d9ae1144..9d28c8085 100644
--- a/pkg/github/__toolsnaps__/create_or_update_file.snap
+++ b/pkg/github/__toolsnaps__/create_or_update_file.snap
@@ -4,45 +4,45 @@
},
"description": "Create or update a single file in a GitHub repository. \nIf updating, you should provide the SHA of the file you want to update. Use this tool to create or update a file in a GitHub repository remotely; do not use it for local file operations.\n\nIn order to obtain the SHA of original file version before updating, use the following git command:\ngit ls-tree HEAD \u003cpath to file\u003e\n\nIf the SHA is not provided, the tool will attempt to acquire it by fetching the current file contents from the repository, which may lead to rewriting latest committed changes if the file has changed since last retrieval.\n",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "path",
- "content",
- "message",
- "branch"
- ],
"properties": {
"branch": {
- "type": "string",
- "description": "Branch to create/update the file in"
+ "description": "Branch to create/update the file in",
+ "type": "string"
},
"content": {
- "type": "string",
- "description": "Content of the file"
+ "description": "Content of the file",
+ "type": "string"
},
"message": {
- "type": "string",
- "description": "Commit message"
+ "description": "Commit message",
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Repository owner (username or organization)"
+ "description": "Repository owner (username or organization)",
+ "type": "string"
},
"path": {
- "type": "string",
- "description": "Path where to create/update the file"
+ "description": "Path where to create/update the file",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"sha": {
- "type": "string",
- "description": "The blob SHA of the file being replaced."
+ "description": "The blob SHA of the file being replaced.",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "path",
+ "content",
+ "message",
+ "branch"
+ ],
+ "type": "object"
},
"name": "create_or_update_file"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/create_pull_request.snap b/pkg/github/__toolsnaps__/create_pull_request.snap
index 80f0b9863..cc22897fa 100644
--- a/pkg/github/__toolsnaps__/create_pull_request.snap
+++ b/pkg/github/__toolsnaps__/create_pull_request.snap
@@ -4,48 +4,48 @@
},
"description": "Create a new pull request in a GitHub repository.",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "title",
- "head",
- "base"
- ],
"properties": {
"base": {
- "type": "string",
- "description": "Branch to merge into"
+ "description": "Branch to merge into",
+ "type": "string"
},
"body": {
- "type": "string",
- "description": "PR description"
+ "description": "PR description",
+ "type": "string"
},
"draft": {
- "type": "boolean",
- "description": "Create as draft PR"
+ "description": "Create as draft PR",
+ "type": "boolean"
},
"head": {
- "type": "string",
- "description": "Branch containing changes"
+ "description": "Branch containing changes",
+ "type": "string"
},
"maintainer_can_modify": {
- "type": "boolean",
- "description": "Allow maintainer edits"
+ "description": "Allow maintainer edits",
+ "type": "boolean"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"title": {
- "type": "string",
- "description": "PR title"
+ "description": "PR title",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "title",
+ "head",
+ "base"
+ ],
+ "type": "object"
},
"name": "create_pull_request"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/create_repository.snap b/pkg/github/__toolsnaps__/create_repository.snap
index 290767c66..2cc4227b2 100644
--- a/pkg/github/__toolsnaps__/create_repository.snap
+++ b/pkg/github/__toolsnaps__/create_repository.snap
@@ -4,32 +4,32 @@
},
"description": "Create a new GitHub repository in your account or specified organization",
"inputSchema": {
- "type": "object",
- "required": [
- "name"
- ],
"properties": {
"autoInit": {
- "type": "boolean",
- "description": "Initialize with README"
+ "description": "Initialize with README",
+ "type": "boolean"
},
"description": {
- "type": "string",
- "description": "Repository description"
+ "description": "Repository description",
+ "type": "string"
},
"name": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"organization": {
- "type": "string",
- "description": "Organization to create the repository in (omit to create in your personal account)"
+ "description": "Organization to create the repository in (omit to create in your personal account)",
+ "type": "string"
},
"private": {
- "type": "boolean",
- "description": "Whether repo should be private"
+ "description": "Whether repo should be private",
+ "type": "boolean"
}
- }
+ },
+ "required": [
+ "name"
+ ],
+ "type": "object"
},
"name": "create_repository"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/delete_file.snap b/pkg/github/__toolsnaps__/delete_file.snap
index b985154e8..ff110ff78 100644
--- a/pkg/github/__toolsnaps__/delete_file.snap
+++ b/pkg/github/__toolsnaps__/delete_file.snap
@@ -5,36 +5,36 @@
},
"description": "Delete a file from a GitHub repository",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "path",
- "message",
- "branch"
- ],
"properties": {
"branch": {
- "type": "string",
- "description": "Branch to delete the file from"
+ "description": "Branch to delete the file from",
+ "type": "string"
},
"message": {
- "type": "string",
- "description": "Commit message"
+ "description": "Commit message",
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Repository owner (username or organization)"
+ "description": "Repository owner (username or organization)",
+ "type": "string"
},
"path": {
- "type": "string",
- "description": "Path to the file to delete"
+ "description": "Path to the file to delete",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "path",
+ "message",
+ "branch"
+ ],
+ "type": "object"
},
"name": "delete_file"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/delete_project_item.snap b/pkg/github/__toolsnaps__/delete_project_item.snap
index 430c83cc8..819fb8474 100644
--- a/pkg/github/__toolsnaps__/delete_project_item.snap
+++ b/pkg/github/__toolsnaps__/delete_project_item.snap
@@ -5,35 +5,35 @@
},
"description": "Delete a specific Project item for a user or org",
"inputSchema": {
- "type": "object",
- "required": [
- "owner_type",
- "owner",
- "project_number",
- "item_id"
- ],
"properties": {
"item_id": {
- "type": "number",
- "description": "The internal project item ID to delete from the project (not the issue or pull request ID)."
+ "description": "The internal project item ID to delete from the project (not the issue or pull request ID).",
+ "type": "number"
},
"owner": {
- "type": "string",
- "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive."
+ "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive.",
+ "type": "string"
},
"owner_type": {
- "type": "string",
"description": "Owner type",
"enum": [
"user",
"org"
- ]
+ ],
+ "type": "string"
},
"project_number": {
- "type": "number",
- "description": "The project's number."
+ "description": "The project's number.",
+ "type": "number"
}
- }
+ },
+ "required": [
+ "owner_type",
+ "owner",
+ "project_number",
+ "item_id"
+ ],
+ "type": "object"
},
"name": "delete_project_item"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/delete_workflow_run_logs.snap b/pkg/github/__toolsnaps__/delete_workflow_run_logs.snap
index fc9a5cd46..2e2de7331 100644
--- a/pkg/github/__toolsnaps__/delete_workflow_run_logs.snap
+++ b/pkg/github/__toolsnaps__/delete_workflow_run_logs.snap
@@ -5,26 +5,26 @@
},
"description": "Delete logs for a workflow run",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "run_id"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"run_id": {
- "type": "number",
- "description": "The unique identifier of the workflow run"
+ "description": "The unique identifier of the workflow run",
+ "type": "number"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "run_id"
+ ],
+ "type": "object"
},
"name": "delete_workflow_run_logs"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/dismiss_notification.snap b/pkg/github/__toolsnaps__/dismiss_notification.snap
index b0125ba53..6c65d9ce0 100644
--- a/pkg/github/__toolsnaps__/dismiss_notification.snap
+++ b/pkg/github/__toolsnaps__/dismiss_notification.snap
@@ -4,25 +4,25 @@
},
"description": "Dismiss a notification by marking it as read or done",
"inputSchema": {
- "type": "object",
- "required": [
- "threadID",
- "state"
- ],
"properties": {
"state": {
- "type": "string",
"description": "The new state of the notification (read/done)",
"enum": [
"read",
"done"
- ]
+ ],
+ "type": "string"
},
"threadID": {
- "type": "string",
- "description": "The ID of the notification thread"
+ "description": "The ID of the notification thread",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "threadID",
+ "state"
+ ],
+ "type": "object"
},
"name": "dismiss_notification"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/download_workflow_run_artifact.snap b/pkg/github/__toolsnaps__/download_workflow_run_artifact.snap
index c4d89872c..e831b21d5 100644
--- a/pkg/github/__toolsnaps__/download_workflow_run_artifact.snap
+++ b/pkg/github/__toolsnaps__/download_workflow_run_artifact.snap
@@ -5,26 +5,26 @@
},
"description": "Get download URL for a workflow run artifact",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "artifact_id"
- ],
"properties": {
"artifact_id": {
- "type": "number",
- "description": "The unique identifier of the artifact"
+ "description": "The unique identifier of the artifact",
+ "type": "number"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "artifact_id"
+ ],
+ "type": "object"
},
"name": "download_workflow_run_artifact"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/fork_repository.snap b/pkg/github/__toolsnaps__/fork_repository.snap
index 18525a4f7..d635734a9 100644
--- a/pkg/github/__toolsnaps__/fork_repository.snap
+++ b/pkg/github/__toolsnaps__/fork_repository.snap
@@ -3,38 +3,38 @@
"title": "Fork repository"
},
"description": "Fork a GitHub repository to your account or specified organization",
- "inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo"
- ],
- "properties": {
- "organization": {
- "type": "string",
- "description": "Organization to fork to"
- },
- "owner": {
- "type": "string",
- "description": "Repository owner"
- },
- "repo": {
- "type": "string",
- "description": "Repository name"
- }
- }
- },
- "name": "fork_repository",
"icons": [
{
- "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACuElEQVRIibWTTUhUYRiFn/fOdYyoydQxk4LEGzN3RudaLYL+qRaBQYsIItoHCW37ISNbRwUFLWoRZBEt+4EIooKoTdZQ6TWaNIgouzJkuGhG731b6JTojDNBntX3ne+c97zfH8wzZCbREm9bZ4hsQvkeDvl3+/r6xuYqEIvFFgdSvRuDqCrPMu6bVyUDrITTjdI1jR8KBbrj/fs3Q8WLp5p9Qx4BzVOUInIm058+XdAY0ztH6RLhSpAza1RlI2jENzhfqntfjAugEdTYMFEtS0GvonrKslNrZwWIhDYDMh6Wo4ODvaMfB9LPFaMHZGvJ8xHdAlzPDLx+8Smd/pE39SggAptnB2gwDBD6ReJvhSCpMFyq/uSa/NFX5UMJgGCaxywMwiH/bi4wh0SCOy1x5waiCUF2gnSW3AByEfSSZTsPVXFF9CDC4ALx7xU0ocLA87x8tG7ZHRUShsheVMKInMy46culArIj317WRpd7KB2GsAl4bKoccN2330t5ALBsJ7ASTvecoun6hNNt2U5QbM0oRip8E6Wt0gCUFPC12FKoGFnX0BgBDtVGG3/W1qzqz2a/5IrpLGt9pLahvhPhCKrnsiPDT2dqZv1kgGQyGc4FZg+wr8I93F6y0DzY29s7XlHAnw7j7dswgg2oRCYZPTBluzk51VEwXmQG0k8qbGRuWHbqiWWn/qlY0Uv+n5j3gKKvaCaSyeSimrqms4hsB4kurW9c0bSs/pnneflyXrOcACCn5jWEPSr0AAgczvlVTVT+ykojFlvTZNmOWvHU8QJnJVInLNtR2163vJy/7B0EpjYAqBhugVMVF8A3goZy/rJHFGa8P4fpCXosHm9PqwbiwzHAqyLvlvPP+dEKWG23dyh6C1g0RY0Jsv+Dm77/XwIAWlpbVzJh7gLAnHjw8d27z5V65xW/AVGM6Ekx9nZCAAAAAElFTkSuQmCC",
"mimeType": "image/png",
+ "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACuElEQVRIibWTTUhUYRiFn/fOdYyoydQxk4LEGzN3RudaLYL+qRaBQYsIItoHCW37ISNbRwUFLWoRZBEt+4EIooKoTdZQ6TWaNIgouzJkuGhG731b6JTojDNBntX3ne+c97zfH8wzZCbREm9bZ4hsQvkeDvl3+/r6xuYqEIvFFgdSvRuDqCrPMu6bVyUDrITTjdI1jR8KBbrj/fs3Q8WLp5p9Qx4BzVOUInIm058+XdAY0ztH6RLhSpAza1RlI2jENzhfqntfjAugEdTYMFEtS0GvonrKslNrZwWIhDYDMh6Wo4ODvaMfB9LPFaMHZGvJ8xHdAlzPDLx+8Smd/pE39SggAptnB2gwDBD6ReJvhSCpMFyq/uSa/NFX5UMJgGCaxywMwiH/bi4wh0SCOy1x5waiCUF2gnSW3AByEfSSZTsPVXFF9CDC4ALx7xU0ocLA87x8tG7ZHRUShsheVMKInMy46culArIj317WRpd7KB2GsAl4bKoccN2330t5ALBsJ7ASTvecoun6hNNt2U5QbM0oRip8E6Wt0gCUFPC12FKoGFnX0BgBDtVGG3/W1qzqz2a/5IrpLGt9pLahvhPhCKrnsiPDT2dqZv1kgGQyGc4FZg+wr8I93F6y0DzY29s7XlHAnw7j7dswgg2oRCYZPTBluzk51VEwXmQG0k8qbGRuWHbqiWWn/qlY0Uv+n5j3gKKvaCaSyeSimrqms4hsB4kurW9c0bSs/pnneflyXrOcACCn5jWEPSr0AAgczvlVTVT+ykojFlvTZNmOWvHU8QJnJVInLNtR2163vJy/7B0EpjYAqBhugVMVF8A3goZy/rJHFGa8P4fpCXosHm9PqwbiwzHAqyLvlvPP+dEKWG23dyh6C1g0RY0Jsv+Dm77/XwIAWlpbVzJh7gLAnHjw8d27z5V65xW/AVGM6Ekx9nZCAAAAAElFTkSuQmCC",
"theme": "light"
},
{
- "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABoUlEQVRIibWUPS+EQRSFz0hsxaIgIZHYllDYiiiUEn6Bn0Dho6Nhf4GPjkYn8ZEgGqFRSNBoVTQKdhEROsk+mrt2svvu7Gutk7yZzJlz77nzztyR/hmulADSkkYk5SQdO+c+QwmAZkkTktolXTjnbkLiDJCniHsgFdCnTFNAHliuJE6bYANoAYaBF+AwYHBkmiGgFdi0HINR4lmrotXjVoG3gMEbsOLN2yzHTIFr8PRZG3s9rs/jo5At0fd6fFk1TfY/X4A14MyqmQrsYNo0pxbzCtwBTZUCUsAh8GHCKaDspnl6ZyZ3FnMA9AR2/BOYBzJVhUV9BshHrTVEkZKeJPXHNZA0IOkxttrrhzkgGdAlgXnTLv3GIAHsEh87QGNUrooHaEajkoYlFXYxaeO2je+SLp1z57Grr2J4DvwqWaVDrhv+3SAWrMvXgWcgZ10b3a01GuwDX8CWfV/AXr2Sd9lVXPC4ReM6q8XHOYMOG2897rZkrXZY0+WAK6DHHsRr4xJ/NjCTcXstC/gAxuPEBju5xKRb0phNT5xzD7UUW3d8A4p92DZKdSwEAAAAAElFTkSuQmCC",
"mimeType": "image/png",
+ "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABoUlEQVRIibWUPS+EQRSFz0hsxaIgIZHYllDYiiiUEn6Bn0Dho6Nhf4GPjkYn8ZEgGqFRSNBoVTQKdhEROsk+mrt2svvu7Gutk7yZzJlz77nzztyR/hmulADSkkYk5SQdO+c+QwmAZkkTktolXTjnbkLiDJCniHsgFdCnTFNAHliuJE6bYANoAYaBF+AwYHBkmiGgFdi0HINR4lmrotXjVoG3gMEbsOLN2yzHTIFr8PRZG3s9rs/jo5At0fd6fFk1TfY/X4A14MyqmQrsYNo0pxbzCtwBTZUCUsAh8GHCKaDspnl6ZyZ3FnMA9AR2/BOYBzJVhUV9BshHrTVEkZKeJPXHNZA0IOkxttrrhzkgGdAlgXnTLv3GIAHsEh87QGNUrooHaEajkoYlFXYxaeO2je+SLp1z57Grr2J4DvwqWaVDrhv+3SAWrMvXgWcgZ10b3a01GuwDX8CWfV/AXr2Sd9lVXPC4ReM6q8XHOYMOG2897rZkrXZY0+WAK6DHHsRr4xJ/NjCTcXstC/gAxuPEBju5xKRb0phNT5xzD7UUW3d8A4p92DZKdSwEAAAAAElFTkSuQmCC",
"theme": "dark"
}
- ]
+ ],
+ "inputSchema": {
+ "properties": {
+ "organization": {
+ "description": "Organization to fork to",
+ "type": "string"
+ },
+ "owner": {
+ "description": "Repository owner",
+ "type": "string"
+ },
+ "repo": {
+ "description": "Repository name",
+ "type": "string"
+ }
+ },
+ "required": [
+ "owner",
+ "repo"
+ ],
+ "type": "object"
+ },
+ "name": "fork_repository"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_code_scanning_alert.snap b/pkg/github/__toolsnaps__/get_code_scanning_alert.snap
index 9e46b960a..2a65aefa6 100644
--- a/pkg/github/__toolsnaps__/get_code_scanning_alert.snap
+++ b/pkg/github/__toolsnaps__/get_code_scanning_alert.snap
@@ -5,26 +5,26 @@
},
"description": "Get details of a specific code scanning alert in a GitHub repository.",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "alertNumber"
- ],
"properties": {
"alertNumber": {
- "type": "number",
- "description": "The number of the alert."
+ "description": "The number of the alert.",
+ "type": "number"
},
"owner": {
- "type": "string",
- "description": "The owner of the repository."
+ "description": "The owner of the repository.",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "The name of the repository."
+ "description": "The name of the repository.",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "alertNumber"
+ ],
+ "type": "object"
},
"name": "get_code_scanning_alert"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_commit.snap b/pkg/github/__toolsnaps__/get_commit.snap
index c6b96d5ed..9e2346b59 100644
--- a/pkg/github/__toolsnaps__/get_commit.snap
+++ b/pkg/github/__toolsnaps__/get_commit.snap
@@ -5,42 +5,42 @@
},
"description": "Get details for a commit from a GitHub repository",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "sha"
- ],
"properties": {
"include_diff": {
- "type": "boolean",
+ "default": true,
"description": "Whether to include file diffs and stats in the response. Default is true.",
- "default": true
+ "type": "boolean"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"page": {
- "type": "number",
"description": "Page number for pagination (min 1)",
- "minimum": 1
+ "minimum": 1,
+ "type": "number"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"sha": {
- "type": "string",
- "description": "Commit SHA, branch name, or tag name"
+ "description": "Commit SHA, branch name, or tag name",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "sha"
+ ],
+ "type": "object"
},
"name": "get_commit"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_dependabot_alert.snap b/pkg/github/__toolsnaps__/get_dependabot_alert.snap
index a517809e2..78ff827d2 100644
--- a/pkg/github/__toolsnaps__/get_dependabot_alert.snap
+++ b/pkg/github/__toolsnaps__/get_dependabot_alert.snap
@@ -5,26 +5,26 @@
},
"description": "Get details of a specific dependabot alert in a GitHub repository.",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "alertNumber"
- ],
"properties": {
"alertNumber": {
- "type": "number",
- "description": "The number of the alert."
+ "description": "The number of the alert.",
+ "type": "number"
},
"owner": {
- "type": "string",
- "description": "The owner of the repository."
+ "description": "The owner of the repository.",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "The name of the repository."
+ "description": "The name of the repository.",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "alertNumber"
+ ],
+ "type": "object"
},
"name": "get_dependabot_alert"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_discussion.snap b/pkg/github/__toolsnaps__/get_discussion.snap
index feef0f057..b931afe79 100644
--- a/pkg/github/__toolsnaps__/get_discussion.snap
+++ b/pkg/github/__toolsnaps__/get_discussion.snap
@@ -5,26 +5,26 @@
},
"description": "Get a specific discussion by ID",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "discussionNumber"
- ],
"properties": {
"discussionNumber": {
- "type": "number",
- "description": "Discussion Number"
+ "description": "Discussion Number",
+ "type": "number"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "discussionNumber"
+ ],
+ "type": "object"
},
"name": "get_discussion"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_discussion_comments.snap b/pkg/github/__toolsnaps__/get_discussion_comments.snap
index 3af5edc8c..f9e609565 100644
--- a/pkg/github/__toolsnaps__/get_discussion_comments.snap
+++ b/pkg/github/__toolsnaps__/get_discussion_comments.snap
@@ -5,36 +5,36 @@
},
"description": "Get comments from a discussion",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "discussionNumber"
- ],
"properties": {
"after": {
- "type": "string",
- "description": "Cursor for pagination. Use the endCursor from the previous page's PageInfo for GraphQL APIs."
+ "description": "Cursor for pagination. Use the endCursor from the previous page's PageInfo for GraphQL APIs.",
+ "type": "string"
},
"discussionNumber": {
- "type": "number",
- "description": "Discussion Number"
+ "description": "Discussion Number",
+ "type": "number"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "discussionNumber"
+ ],
+ "type": "object"
},
"name": "get_discussion_comments"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_file_contents.snap b/pkg/github/__toolsnaps__/get_file_contents.snap
index 638452fe7..94b7aeeda 100644
--- a/pkg/github/__toolsnaps__/get_file_contents.snap
+++ b/pkg/github/__toolsnaps__/get_file_contents.snap
@@ -5,34 +5,34 @@
},
"description": "Get the contents of a file or directory from a GitHub repository",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "Repository owner (username or organization)"
+ "description": "Repository owner (username or organization)",
+ "type": "string"
},
"path": {
- "type": "string",
+ "default": "/",
"description": "Path to file/directory",
- "default": "/"
+ "type": "string"
},
"ref": {
- "type": "string",
- "description": "Accepts optional git refs such as `refs/tags/{tag}`, `refs/heads/{branch}` or `refs/pull/{pr_number}/head`"
+ "description": "Accepts optional git refs such as `refs/tags/{tag}`, `refs/heads/{branch}` or `refs/pull/{pr_number}/head`",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"sha": {
- "type": "string",
- "description": "Accepts optional commit SHA. If specified, it will be used instead of ref"
+ "description": "Accepts optional commit SHA. If specified, it will be used instead of ref",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo"
+ ],
+ "type": "object"
},
"name": "get_file_contents"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_gist.snap b/pkg/github/__toolsnaps__/get_gist.snap
index 4d2661822..ef316937f 100644
--- a/pkg/github/__toolsnaps__/get_gist.snap
+++ b/pkg/github/__toolsnaps__/get_gist.snap
@@ -5,16 +5,16 @@
},
"description": "Get gist content of a particular gist, by gist ID",
"inputSchema": {
- "type": "object",
- "required": [
- "gist_id"
- ],
"properties": {
"gist_id": {
- "type": "string",
- "description": "The ID of the gist"
+ "description": "The ID of the gist",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "gist_id"
+ ],
+ "type": "object"
},
"name": "get_gist"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_global_security_advisory.snap b/pkg/github/__toolsnaps__/get_global_security_advisory.snap
index 18c30425a..97b81d17d 100644
--- a/pkg/github/__toolsnaps__/get_global_security_advisory.snap
+++ b/pkg/github/__toolsnaps__/get_global_security_advisory.snap
@@ -5,16 +5,16 @@
},
"description": "Get a global security advisory",
"inputSchema": {
- "type": "object",
- "required": [
- "ghsaId"
- ],
"properties": {
"ghsaId": {
- "type": "string",
- "description": "GitHub Security Advisory ID (format: GHSA-xxxx-xxxx-xxxx)."
+ "description": "GitHub Security Advisory ID (format: GHSA-xxxx-xxxx-xxxx).",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "ghsaId"
+ ],
+ "type": "object"
},
"name": "get_global_security_advisory"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_job_logs.snap b/pkg/github/__toolsnaps__/get_job_logs.snap
index 8b2319527..575182c0b 100644
--- a/pkg/github/__toolsnaps__/get_job_logs.snap
+++ b/pkg/github/__toolsnaps__/get_job_logs.snap
@@ -5,42 +5,42 @@
},
"description": "Download logs for a specific workflow job or efficiently get all failed job logs for a workflow run",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo"
- ],
"properties": {
"failed_only": {
- "type": "boolean",
- "description": "When true, gets logs for all failed jobs in run_id"
+ "description": "When true, gets logs for all failed jobs in run_id",
+ "type": "boolean"
},
"job_id": {
- "type": "number",
- "description": "The unique identifier of the workflow job (required for single job logs)"
+ "description": "The unique identifier of the workflow job (required for single job logs)",
+ "type": "number"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"return_content": {
- "type": "boolean",
- "description": "Returns actual log content instead of URLs"
+ "description": "Returns actual log content instead of URLs",
+ "type": "boolean"
},
"run_id": {
- "type": "number",
- "description": "Workflow run ID (required when using failed_only)"
+ "description": "Workflow run ID (required when using failed_only)",
+ "type": "number"
},
"tail_lines": {
- "type": "number",
+ "default": 500,
"description": "Number of lines to return from the end of the log",
- "default": 500
+ "type": "number"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo"
+ ],
+ "type": "object"
},
"name": "get_job_logs"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_label.snap b/pkg/github/__toolsnaps__/get_label.snap
index 8541044d0..854f048c2 100644
--- a/pkg/github/__toolsnaps__/get_label.snap
+++ b/pkg/github/__toolsnaps__/get_label.snap
@@ -5,26 +5,26 @@
},
"description": "Get a specific label from a repository.",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "name"
- ],
"properties": {
"name": {
- "type": "string",
- "description": "Label name."
+ "description": "Label name.",
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Repository owner (username or organization name)"
+ "description": "Repository owner (username or organization name)",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "name"
+ ],
+ "type": "object"
},
"name": "get_label"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_latest_release.snap b/pkg/github/__toolsnaps__/get_latest_release.snap
index 23b551a0f..760d8f812 100644
--- a/pkg/github/__toolsnaps__/get_latest_release.snap
+++ b/pkg/github/__toolsnaps__/get_latest_release.snap
@@ -5,21 +5,21 @@
},
"description": "Get the latest release in a GitHub repository",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo"
+ ],
+ "type": "object"
},
"name": "get_latest_release"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_me.snap b/pkg/github/__toolsnaps__/get_me.snap
index e6d02929f..4d7d2573b 100644
--- a/pkg/github/__toolsnaps__/get_me.snap
+++ b/pkg/github/__toolsnaps__/get_me.snap
@@ -5,8 +5,8 @@
},
"description": "Get details of the authenticated GitHub user. Use this when a request is about the user's own profile for GitHub. Or when information is missing to build other tool calls.",
"inputSchema": {
- "type": "object",
- "properties": {}
+ "properties": {},
+ "type": "object"
},
"name": "get_me"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_notification_details.snap b/pkg/github/__toolsnaps__/get_notification_details.snap
index de197f2b1..48842229f 100644
--- a/pkg/github/__toolsnaps__/get_notification_details.snap
+++ b/pkg/github/__toolsnaps__/get_notification_details.snap
@@ -5,16 +5,16 @@
},
"description": "Get detailed information for a specific GitHub notification, always call this tool when the user asks for details about a specific notification, if you don't know the ID list notifications first.",
"inputSchema": {
- "type": "object",
- "required": [
- "notificationID"
- ],
"properties": {
"notificationID": {
- "type": "string",
- "description": "The ID of the notification"
+ "description": "The ID of the notification",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "notificationID"
+ ],
+ "type": "object"
},
"name": "get_notification_details"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_project.snap b/pkg/github/__toolsnaps__/get_project.snap
index 8194b7358..6ff320fe8 100644
--- a/pkg/github/__toolsnaps__/get_project.snap
+++ b/pkg/github/__toolsnaps__/get_project.snap
@@ -5,30 +5,30 @@
},
"description": "Get Project for a user or org",
"inputSchema": {
- "type": "object",
- "required": [
- "project_number",
- "owner_type",
- "owner"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive."
+ "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive.",
+ "type": "string"
},
"owner_type": {
- "type": "string",
"description": "Owner type",
"enum": [
"user",
"org"
- ]
+ ],
+ "type": "string"
},
"project_number": {
- "type": "number",
- "description": "The project's number"
+ "description": "The project's number",
+ "type": "number"
}
- }
+ },
+ "required": [
+ "project_number",
+ "owner_type",
+ "owner"
+ ],
+ "type": "object"
},
"name": "get_project"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_project_field.snap b/pkg/github/__toolsnaps__/get_project_field.snap
index 0df557a03..9d884a20f 100644
--- a/pkg/github/__toolsnaps__/get_project_field.snap
+++ b/pkg/github/__toolsnaps__/get_project_field.snap
@@ -5,35 +5,35 @@
},
"description": "Get Project field for a user or org",
"inputSchema": {
- "type": "object",
- "required": [
- "owner_type",
- "owner",
- "project_number",
- "field_id"
- ],
"properties": {
"field_id": {
- "type": "number",
- "description": "The field's id."
+ "description": "The field's id.",
+ "type": "number"
},
"owner": {
- "type": "string",
- "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive."
+ "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive.",
+ "type": "string"
},
"owner_type": {
- "type": "string",
"description": "Owner type",
"enum": [
"user",
"org"
- ]
+ ],
+ "type": "string"
},
"project_number": {
- "type": "number",
- "description": "The project's number."
+ "description": "The project's number.",
+ "type": "number"
}
- }
+ },
+ "required": [
+ "owner_type",
+ "owner",
+ "project_number",
+ "field_id"
+ ],
+ "type": "object"
},
"name": "get_project_field"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_project_item.snap b/pkg/github/__toolsnaps__/get_project_item.snap
index d77c49c1e..202bcc53e 100644
--- a/pkg/github/__toolsnaps__/get_project_item.snap
+++ b/pkg/github/__toolsnaps__/get_project_item.snap
@@ -5,42 +5,42 @@
},
"description": "Get a specific Project item for a user or org",
"inputSchema": {
- "type": "object",
- "required": [
- "owner_type",
- "owner",
- "project_number",
- "item_id"
- ],
"properties": {
"fields": {
- "type": "array",
"description": "Specific list of field IDs to include in the response (e.g. [\"102589\", \"985201\", \"169875\"]). If not provided, only the title field is included.",
"items": {
"type": "string"
- }
+ },
+ "type": "array"
},
"item_id": {
- "type": "number",
- "description": "The item's ID."
+ "description": "The item's ID.",
+ "type": "number"
},
"owner": {
- "type": "string",
- "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive."
+ "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive.",
+ "type": "string"
},
"owner_type": {
- "type": "string",
"description": "Owner type",
"enum": [
"user",
"org"
- ]
+ ],
+ "type": "string"
},
"project_number": {
- "type": "number",
- "description": "The project's number."
+ "description": "The project's number.",
+ "type": "number"
}
- }
+ },
+ "required": [
+ "owner_type",
+ "owner",
+ "project_number",
+ "item_id"
+ ],
+ "type": "object"
},
"name": "get_project_item"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_release_by_tag.snap b/pkg/github/__toolsnaps__/get_release_by_tag.snap
index 77f19488c..6e6d30e98 100644
--- a/pkg/github/__toolsnaps__/get_release_by_tag.snap
+++ b/pkg/github/__toolsnaps__/get_release_by_tag.snap
@@ -5,26 +5,26 @@
},
"description": "Get a specific release by its tag name in a GitHub repository",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "tag"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"tag": {
- "type": "string",
- "description": "Tag name (e.g., 'v1.0.0')"
+ "description": "Tag name (e.g., 'v1.0.0')",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "tag"
+ ],
+ "type": "object"
},
"name": "get_release_by_tag"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_repository_tree.snap b/pkg/github/__toolsnaps__/get_repository_tree.snap
index 882462883..c810d1e20 100644
--- a/pkg/github/__toolsnaps__/get_repository_tree.snap
+++ b/pkg/github/__toolsnaps__/get_repository_tree.snap
@@ -5,34 +5,34 @@
},
"description": "Get the tree structure (files and directories) of a GitHub repository at a specific ref or SHA",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "Repository owner (username or organization)"
+ "description": "Repository owner (username or organization)",
+ "type": "string"
},
"path_filter": {
- "type": "string",
- "description": "Optional path prefix to filter the tree results (e.g., 'src/' to only show files in the src directory)"
+ "description": "Optional path prefix to filter the tree results (e.g., 'src/' to only show files in the src directory)",
+ "type": "string"
},
"recursive": {
- "type": "boolean",
+ "default": false,
"description": "Setting this parameter to true returns the objects or subtrees referenced by the tree. Default is false",
- "default": false
+ "type": "boolean"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"tree_sha": {
- "type": "string",
- "description": "The SHA1 value or ref (branch or tag) name of the tree. Defaults to the repository's default branch"
+ "description": "The SHA1 value or ref (branch or tag) name of the tree. Defaults to the repository's default branch",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo"
+ ],
+ "type": "object"
},
"name": "get_repository_tree"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_secret_scanning_alert.snap b/pkg/github/__toolsnaps__/get_secret_scanning_alert.snap
index 4d55011da..2789cfbab 100644
--- a/pkg/github/__toolsnaps__/get_secret_scanning_alert.snap
+++ b/pkg/github/__toolsnaps__/get_secret_scanning_alert.snap
@@ -5,26 +5,26 @@
},
"description": "Get details of a specific secret scanning alert in a GitHub repository.",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "alertNumber"
- ],
"properties": {
"alertNumber": {
- "type": "number",
- "description": "The number of the alert."
+ "description": "The number of the alert.",
+ "type": "number"
},
"owner": {
- "type": "string",
- "description": "The owner of the repository."
+ "description": "The owner of the repository.",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "The name of the repository."
+ "description": "The name of the repository.",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "alertNumber"
+ ],
+ "type": "object"
},
"name": "get_secret_scanning_alert"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_tag.snap b/pkg/github/__toolsnaps__/get_tag.snap
index e33f5c2e4..126e8a999 100644
--- a/pkg/github/__toolsnaps__/get_tag.snap
+++ b/pkg/github/__toolsnaps__/get_tag.snap
@@ -5,26 +5,26 @@
},
"description": "Get details about a specific git tag in a GitHub repository",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "tag"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"tag": {
- "type": "string",
- "description": "Tag name"
+ "description": "Tag name",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "tag"
+ ],
+ "type": "object"
},
"name": "get_tag"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_team_members.snap b/pkg/github/__toolsnaps__/get_team_members.snap
index 5b7f090fe..4cde7237c 100644
--- a/pkg/github/__toolsnaps__/get_team_members.snap
+++ b/pkg/github/__toolsnaps__/get_team_members.snap
@@ -5,21 +5,21 @@
},
"description": "Get member usernames of a specific team in an organization. Limited to organizations accessible with current credentials",
"inputSchema": {
- "type": "object",
- "required": [
- "org",
- "team_slug"
- ],
"properties": {
"org": {
- "type": "string",
- "description": "Organization login (owner) that contains the team."
+ "description": "Organization login (owner) that contains the team.",
+ "type": "string"
},
"team_slug": {
- "type": "string",
- "description": "Team slug"
+ "description": "Team slug",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "org",
+ "team_slug"
+ ],
+ "type": "object"
},
"name": "get_team_members"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_teams.snap b/pkg/github/__toolsnaps__/get_teams.snap
index 595dd262d..946364bad 100644
--- a/pkg/github/__toolsnaps__/get_teams.snap
+++ b/pkg/github/__toolsnaps__/get_teams.snap
@@ -5,13 +5,13 @@
},
"description": "Get details of the teams the user is a member of. Limited to organizations accessible with current credentials",
"inputSchema": {
- "type": "object",
"properties": {
"user": {
- "type": "string",
- "description": "Username to get teams for. If not provided, uses the authenticated user."
+ "description": "Username to get teams for. If not provided, uses the authenticated user.",
+ "type": "string"
}
- }
+ },
+ "type": "object"
},
"name": "get_teams"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_workflow_run.snap b/pkg/github/__toolsnaps__/get_workflow_run.snap
index 37921ffad..e58ea0ba2 100644
--- a/pkg/github/__toolsnaps__/get_workflow_run.snap
+++ b/pkg/github/__toolsnaps__/get_workflow_run.snap
@@ -5,26 +5,26 @@
},
"description": "Get details of a specific workflow run",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "run_id"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"run_id": {
- "type": "number",
- "description": "The unique identifier of the workflow run"
+ "description": "The unique identifier of the workflow run",
+ "type": "number"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "run_id"
+ ],
+ "type": "object"
},
"name": "get_workflow_run"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_workflow_run_logs.snap b/pkg/github/__toolsnaps__/get_workflow_run_logs.snap
index 77fb619b7..8e76fbfc3 100644
--- a/pkg/github/__toolsnaps__/get_workflow_run_logs.snap
+++ b/pkg/github/__toolsnaps__/get_workflow_run_logs.snap
@@ -5,26 +5,26 @@
},
"description": "Download logs for a specific workflow run (EXPENSIVE: downloads ALL logs as ZIP. Consider using get_job_logs with failed_only=true for debugging failed jobs)",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "run_id"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"run_id": {
- "type": "number",
- "description": "The unique identifier of the workflow run"
+ "description": "The unique identifier of the workflow run",
+ "type": "number"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "run_id"
+ ],
+ "type": "object"
},
"name": "get_workflow_run_logs"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/get_workflow_run_usage.snap b/pkg/github/__toolsnaps__/get_workflow_run_usage.snap
index c9fe49f96..40069b836 100644
--- a/pkg/github/__toolsnaps__/get_workflow_run_usage.snap
+++ b/pkg/github/__toolsnaps__/get_workflow_run_usage.snap
@@ -5,26 +5,26 @@
},
"description": "Get usage metrics for a workflow run",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "run_id"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"run_id": {
- "type": "number",
- "description": "The unique identifier of the workflow run"
+ "description": "The unique identifier of the workflow run",
+ "type": "number"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "run_id"
+ ],
+ "type": "object"
},
"name": "get_workflow_run_usage"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/issue_read.snap b/pkg/github/__toolsnaps__/issue_read.snap
index c6a9e7306..21aa361f5 100644
--- a/pkg/github/__toolsnaps__/issue_read.snap
+++ b/pkg/github/__toolsnaps__/issue_read.snap
@@ -5,48 +5,48 @@
},
"description": "Get information about a specific issue in a GitHub repository.",
"inputSchema": {
- "type": "object",
- "required": [
- "method",
- "owner",
- "repo",
- "issue_number"
- ],
"properties": {
"issue_number": {
- "type": "number",
- "description": "The number of the issue"
+ "description": "The number of the issue",
+ "type": "number"
},
"method": {
- "type": "string",
"description": "The read operation to perform on a single issue.\nOptions are:\n1. get - Get details of a specific issue.\n2. get_comments - Get issue comments.\n3. get_sub_issues - Get sub-issues of the issue.\n4. get_labels - Get labels assigned to the issue.\n",
"enum": [
"get",
"get_comments",
"get_sub_issues",
"get_labels"
- ]
+ ],
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "The owner of the repository"
+ "description": "The owner of the repository",
+ "type": "string"
},
"page": {
- "type": "number",
"description": "Page number for pagination (min 1)",
- "minimum": 1
+ "minimum": 1,
+ "type": "number"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"repo": {
- "type": "string",
- "description": "The name of the repository"
+ "description": "The name of the repository",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "method",
+ "owner",
+ "repo",
+ "issue_number"
+ ],
+ "type": "object"
},
"name": "issue_read"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/issue_write.snap b/pkg/github/__toolsnaps__/issue_write.snap
index 8c6634a02..4512eb614 100644
--- a/pkg/github/__toolsnaps__/issue_write.snap
+++ b/pkg/github/__toolsnaps__/issue_write.snap
@@ -4,85 +4,85 @@
},
"description": "Create a new or update an existing issue in a GitHub repository.",
"inputSchema": {
- "type": "object",
- "required": [
- "method",
- "owner",
- "repo"
- ],
"properties": {
"assignees": {
- "type": "array",
"description": "Usernames to assign to this issue",
"items": {
"type": "string"
- }
+ },
+ "type": "array"
},
"body": {
- "type": "string",
- "description": "Issue body content"
+ "description": "Issue body content",
+ "type": "string"
},
"duplicate_of": {
- "type": "number",
- "description": "Issue number that this issue is a duplicate of. Only used when state_reason is 'duplicate'."
+ "description": "Issue number that this issue is a duplicate of. Only used when state_reason is 'duplicate'.",
+ "type": "number"
},
"issue_number": {
- "type": "number",
- "description": "Issue number to update"
+ "description": "Issue number to update",
+ "type": "number"
},
"labels": {
- "type": "array",
"description": "Labels to apply to this issue",
"items": {
"type": "string"
- }
+ },
+ "type": "array"
},
"method": {
- "type": "string",
"description": "Write operation to perform on a single issue.\nOptions are:\n- 'create' - creates a new issue.\n- 'update' - updates an existing issue.\n",
"enum": [
"create",
"update"
- ]
+ ],
+ "type": "string"
},
"milestone": {
- "type": "number",
- "description": "Milestone number"
+ "description": "Milestone number",
+ "type": "number"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"state": {
- "type": "string",
"description": "New state",
"enum": [
"open",
"closed"
- ]
+ ],
+ "type": "string"
},
"state_reason": {
- "type": "string",
"description": "Reason for the state change. Ignored unless state is changed.",
"enum": [
"completed",
"not_planned",
"duplicate"
- ]
+ ],
+ "type": "string"
},
"title": {
- "type": "string",
- "description": "Issue title"
+ "description": "Issue title",
+ "type": "string"
},
"type": {
- "type": "string",
- "description": "Type of this issue. Only use if the repository has issue types configured. Use list_issue_types tool to get valid type values for the organization. If the repository doesn't support issue types, omit this parameter."
+ "description": "Type of this issue. Only use if the repository has issue types configured. Use list_issue_types tool to get valid type values for the organization. If the repository doesn't support issue types, omit this parameter.",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "method",
+ "owner",
+ "repo"
+ ],
+ "type": "object"
},
"name": "issue_write"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/label_write.snap b/pkg/github/__toolsnaps__/label_write.snap
index 879817442..f0aca8cc9 100644
--- a/pkg/github/__toolsnaps__/label_write.snap
+++ b/pkg/github/__toolsnaps__/label_write.snap
@@ -4,48 +4,48 @@
},
"description": "Perform write operations on repository labels. To set labels on issues, use the 'update_issue' tool.",
"inputSchema": {
- "type": "object",
- "required": [
- "method",
- "owner",
- "repo",
- "name"
- ],
"properties": {
"color": {
- "type": "string",
- "description": "Label color as 6-character hex code without '#' prefix (e.g., 'f29513'). Required for 'create', optional for 'update'."
+ "description": "Label color as 6-character hex code without '#' prefix (e.g., 'f29513'). Required for 'create', optional for 'update'.",
+ "type": "string"
},
"description": {
- "type": "string",
- "description": "Label description text. Optional for 'create' and 'update'."
+ "description": "Label description text. Optional for 'create' and 'update'.",
+ "type": "string"
},
"method": {
- "type": "string",
"description": "Operation to perform: 'create', 'update', or 'delete'",
"enum": [
"create",
"update",
"delete"
- ]
+ ],
+ "type": "string"
},
"name": {
- "type": "string",
- "description": "Label name - required for all operations"
+ "description": "Label name - required for all operations",
+ "type": "string"
},
"new_name": {
- "type": "string",
- "description": "New name for the label (used only with 'update' method to rename)"
+ "description": "New name for the label (used only with 'update' method to rename)",
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Repository owner (username or organization name)"
+ "description": "Repository owner (username or organization name)",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "method",
+ "owner",
+ "repo",
+ "name"
+ ],
+ "type": "object"
},
"name": "label_write"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_branches.snap b/pkg/github/__toolsnaps__/list_branches.snap
index b589c9b7e..883a6fffc 100644
--- a/pkg/github/__toolsnaps__/list_branches.snap
+++ b/pkg/github/__toolsnaps__/list_branches.snap
@@ -5,32 +5,32 @@
},
"description": "List branches in a GitHub repository",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"page": {
- "type": "number",
"description": "Page number for pagination (min 1)",
- "minimum": 1
+ "minimum": 1,
+ "type": "number"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo"
+ ],
+ "type": "object"
},
"name": "list_branches"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_code_scanning_alerts.snap b/pkg/github/__toolsnaps__/list_code_scanning_alerts.snap
index 6f2a4e342..5b7d79ef4 100644
--- a/pkg/github/__toolsnaps__/list_code_scanning_alerts.snap
+++ b/pkg/github/__toolsnaps__/list_code_scanning_alerts.snap
@@ -5,26 +5,20 @@
},
"description": "List code scanning alerts in a GitHub repository.",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "The owner of the repository."
+ "description": "The owner of the repository.",
+ "type": "string"
},
"ref": {
- "type": "string",
- "description": "The Git reference for the results you want to list."
+ "description": "The Git reference for the results you want to list.",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "The name of the repository."
+ "description": "The name of the repository.",
+ "type": "string"
},
"severity": {
- "type": "string",
"description": "Filter code scanning alerts by severity",
"enum": [
"critical",
@@ -34,24 +28,30 @@
"warning",
"note",
"error"
- ]
+ ],
+ "type": "string"
},
"state": {
- "type": "string",
- "description": "Filter code scanning alerts by state. Defaults to open",
"default": "open",
+ "description": "Filter code scanning alerts by state. Defaults to open",
"enum": [
"open",
"closed",
"dismissed",
"fixed"
- ]
+ ],
+ "type": "string"
},
"tool_name": {
- "type": "string",
- "description": "The name of the tool used for code scanning."
+ "description": "The name of the tool used for code scanning.",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo"
+ ],
+ "type": "object"
},
"name": "list_code_scanning_alerts"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_commits.snap b/pkg/github/__toolsnaps__/list_commits.snap
index bd67602ed..38b63736f 100644
--- a/pkg/github/__toolsnaps__/list_commits.snap
+++ b/pkg/github/__toolsnaps__/list_commits.snap
@@ -5,40 +5,40 @@
},
"description": "Get list of commits of a branch in a GitHub repository. Returns at least 30 results per page by default, but can return more if specified using the perPage parameter (up to 100).",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo"
- ],
"properties": {
"author": {
- "type": "string",
- "description": "Author username or email address to filter commits by"
+ "description": "Author username or email address to filter commits by",
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"page": {
- "type": "number",
"description": "Page number for pagination (min 1)",
- "minimum": 1
+ "minimum": 1,
+ "type": "number"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"sha": {
- "type": "string",
- "description": "Commit SHA, branch or tag name to list commits of. If not provided, uses the default branch of the repository. If a commit SHA is provided, will list commits up to that SHA."
+ "description": "Commit SHA, branch or tag name to list commits of. If not provided, uses the default branch of the repository. If a commit SHA is provided, will list commits up to that SHA.",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo"
+ ],
+ "type": "object"
},
"name": "list_commits"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_dependabot_alerts.snap b/pkg/github/__toolsnaps__/list_dependabot_alerts.snap
index d96d3972c..83f725987 100644
--- a/pkg/github/__toolsnaps__/list_dependabot_alerts.snap
+++ b/pkg/github/__toolsnaps__/list_dependabot_alerts.snap
@@ -5,42 +5,42 @@
},
"description": "List dependabot alerts in a GitHub repository.",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "The owner of the repository."
+ "description": "The owner of the repository.",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "The name of the repository."
+ "description": "The name of the repository.",
+ "type": "string"
},
"severity": {
- "type": "string",
"description": "Filter dependabot alerts by severity",
"enum": [
"low",
"medium",
"high",
"critical"
- ]
+ ],
+ "type": "string"
},
"state": {
- "type": "string",
- "description": "Filter dependabot alerts by state. Defaults to open",
"default": "open",
+ "description": "Filter dependabot alerts by state. Defaults to open",
"enum": [
"open",
"fixed",
"dismissed",
"auto_dismissed"
- ]
+ ],
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo"
+ ],
+ "type": "object"
},
"name": "list_dependabot_alerts"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_discussion_categories.snap b/pkg/github/__toolsnaps__/list_discussion_categories.snap
index 888ebbdca..c46b75f84 100644
--- a/pkg/github/__toolsnaps__/list_discussion_categories.snap
+++ b/pkg/github/__toolsnaps__/list_discussion_categories.snap
@@ -5,20 +5,20 @@
},
"description": "List discussion categories with their id and name, for a repository or organisation.",
"inputSchema": {
- "type": "object",
- "required": [
- "owner"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name. If not provided, discussion categories will be queried at the organisation level."
+ "description": "Repository name. If not provided, discussion categories will be queried at the organisation level.",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner"
+ ],
+ "type": "object"
},
"name": "list_discussion_categories"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_discussions.snap b/pkg/github/__toolsnaps__/list_discussions.snap
index 95a8bebf5..42be76933 100644
--- a/pkg/github/__toolsnaps__/list_discussions.snap
+++ b/pkg/github/__toolsnaps__/list_discussions.snap
@@ -5,50 +5,50 @@
},
"description": "List discussions for a repository or organisation.",
"inputSchema": {
- "type": "object",
- "required": [
- "owner"
- ],
"properties": {
"after": {
- "type": "string",
- "description": "Cursor for pagination. Use the endCursor from the previous page's PageInfo for GraphQL APIs."
+ "description": "Cursor for pagination. Use the endCursor from the previous page's PageInfo for GraphQL APIs.",
+ "type": "string"
},
"category": {
- "type": "string",
- "description": "Optional filter by discussion category ID. If provided, only discussions with this category are listed."
+ "description": "Optional filter by discussion category ID. If provided, only discussions with this category are listed.",
+ "type": "string"
},
"direction": {
- "type": "string",
"description": "Order direction.",
"enum": [
"ASC",
"DESC"
- ]
+ ],
+ "type": "string"
},
"orderBy": {
- "type": "string",
"description": "Order discussions by field. If provided, the 'direction' also needs to be provided.",
"enum": [
"CREATED_AT",
"UPDATED_AT"
- ]
+ ],
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"repo": {
- "type": "string",
- "description": "Repository name. If not provided, discussions will be queried at the organisation level."
+ "description": "Repository name. If not provided, discussions will be queried at the organisation level.",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner"
+ ],
+ "type": "object"
},
"name": "list_discussions"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_gists.snap b/pkg/github/__toolsnaps__/list_gists.snap
index 834b45205..397417303 100644
--- a/pkg/github/__toolsnaps__/list_gists.snap
+++ b/pkg/github/__toolsnaps__/list_gists.snap
@@ -5,28 +5,28 @@
},
"description": "List gists for a user",
"inputSchema": {
- "type": "object",
"properties": {
"page": {
- "type": "number",
"description": "Page number for pagination (min 1)",
- "minimum": 1
+ "minimum": 1,
+ "type": "number"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"since": {
- "type": "string",
- "description": "Only gists updated after this time (ISO 8601 timestamp)"
+ "description": "Only gists updated after this time (ISO 8601 timestamp)",
+ "type": "string"
},
"username": {
- "type": "string",
- "description": "GitHub username (omit for authenticated user's gists)"
+ "description": "GitHub username (omit for authenticated user's gists)",
+ "type": "string"
}
- }
+ },
+ "type": "object"
},
"name": "list_gists"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_global_security_advisories.snap b/pkg/github/__toolsnaps__/list_global_security_advisories.snap
index fd9fa78c5..f714f4782 100644
--- a/pkg/github/__toolsnaps__/list_global_security_advisories.snap
+++ b/pkg/github/__toolsnaps__/list_global_security_advisories.snap
@@ -5,25 +5,23 @@
},
"description": "List global security advisories from GitHub.",
"inputSchema": {
- "type": "object",
"properties": {
"affects": {
- "type": "string",
- "description": "Filter advisories by affected package or version (e.g. \"package1,package2@1.0.0\")."
+ "description": "Filter advisories by affected package or version (e.g. \"package1,package2@1.0.0\").",
+ "type": "string"
},
"cveId": {
- "type": "string",
- "description": "Filter by CVE ID."
+ "description": "Filter by CVE ID.",
+ "type": "string"
},
"cwes": {
- "type": "array",
"description": "Filter by Common Weakness Enumeration IDs (e.g. [\"79\", \"284\", \"22\"]).",
"items": {
"type": "string"
- }
+ },
+ "type": "array"
},
"ecosystem": {
- "type": "string",
"description": "Filter by package ecosystem.",
"enum": [
"actions",
@@ -38,26 +36,26 @@
"pub",
"rubygems",
"rust"
- ]
+ ],
+ "type": "string"
},
"ghsaId": {
- "type": "string",
- "description": "Filter by GitHub Security Advisory ID (format: GHSA-xxxx-xxxx-xxxx)."
+ "description": "Filter by GitHub Security Advisory ID (format: GHSA-xxxx-xxxx-xxxx).",
+ "type": "string"
},
"isWithdrawn": {
- "type": "boolean",
- "description": "Whether to only return withdrawn advisories."
+ "description": "Whether to only return withdrawn advisories.",
+ "type": "boolean"
},
"modified": {
- "type": "string",
- "description": "Filter by publish or update date or date range (ISO 8601 date or range)."
+ "description": "Filter by publish or update date or date range (ISO 8601 date or range).",
+ "type": "string"
},
"published": {
- "type": "string",
- "description": "Filter by publish date or date range (ISO 8601 date or range)."
+ "description": "Filter by publish date or date range (ISO 8601 date or range).",
+ "type": "string"
},
"severity": {
- "type": "string",
"description": "Filter by severity.",
"enum": [
"unknown",
@@ -65,23 +63,25 @@
"medium",
"high",
"critical"
- ]
+ ],
+ "type": "string"
},
"type": {
- "type": "string",
- "description": "Advisory type.",
"default": "reviewed",
+ "description": "Advisory type.",
"enum": [
"reviewed",
"malware",
"unreviewed"
- ]
+ ],
+ "type": "string"
},
"updated": {
- "type": "string",
- "description": "Filter by update date or date range (ISO 8601 date or range)."
+ "description": "Filter by update date or date range (ISO 8601 date or range).",
+ "type": "string"
}
- }
+ },
+ "type": "object"
},
"name": "list_global_security_advisories"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_issue_types.snap b/pkg/github/__toolsnaps__/list_issue_types.snap
index b17dcc54f..f1f1377a8 100644
--- a/pkg/github/__toolsnaps__/list_issue_types.snap
+++ b/pkg/github/__toolsnaps__/list_issue_types.snap
@@ -5,16 +5,16 @@
},
"description": "List supported issue types for repository owner (organization).",
"inputSchema": {
- "type": "object",
- "required": [
- "owner"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "The organization owner of the repository"
+ "description": "The organization owner of the repository",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner"
+ ],
+ "type": "object"
},
"name": "list_issue_types"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_issues.snap b/pkg/github/__toolsnaps__/list_issues.snap
index 9d6b55586..a4be59bb0 100644
--- a/pkg/github/__toolsnaps__/list_issues.snap
+++ b/pkg/github/__toolsnaps__/list_issues.snap
@@ -5,67 +5,67 @@
},
"description": "List issues in a GitHub repository. For pagination, use the 'endCursor' from the previous response's 'pageInfo' in the 'after' parameter.",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo"
- ],
"properties": {
"after": {
- "type": "string",
- "description": "Cursor for pagination. Use the endCursor from the previous page's PageInfo for GraphQL APIs."
+ "description": "Cursor for pagination. Use the endCursor from the previous page's PageInfo for GraphQL APIs.",
+ "type": "string"
},
"direction": {
- "type": "string",
"description": "Order direction. If provided, the 'orderBy' also needs to be provided.",
"enum": [
"ASC",
"DESC"
- ]
+ ],
+ "type": "string"
},
"labels": {
- "type": "array",
"description": "Filter by labels",
"items": {
"type": "string"
- }
+ },
+ "type": "array"
},
"orderBy": {
- "type": "string",
"description": "Order issues by field. If provided, the 'direction' also needs to be provided.",
"enum": [
"CREATED_AT",
"UPDATED_AT",
"COMMENTS"
- ]
+ ],
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"since": {
- "type": "string",
- "description": "Filter by date (ISO 8601 timestamp)"
+ "description": "Filter by date (ISO 8601 timestamp)",
+ "type": "string"
},
"state": {
- "type": "string",
"description": "Filter by state, by default both open and closed issues are returned when not provided",
"enum": [
"OPEN",
"CLOSED"
- ]
+ ],
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo"
+ ],
+ "type": "object"
},
"name": "list_issues"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_label.snap b/pkg/github/__toolsnaps__/list_label.snap
index 0b4f3b20c..debc2d44e 100644
--- a/pkg/github/__toolsnaps__/list_label.snap
+++ b/pkg/github/__toolsnaps__/list_label.snap
@@ -5,21 +5,21 @@
},
"description": "List labels from a repository",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "Repository owner (username or organization name) - required for all operations"
+ "description": "Repository owner (username or organization name) - required for all operations",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name - required for all operations"
+ "description": "Repository name - required for all operations",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo"
+ ],
+ "type": "object"
},
"name": "list_label"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_notifications.snap b/pkg/github/__toolsnaps__/list_notifications.snap
index ae43e0f25..bf25c4fe0 100644
--- a/pkg/github/__toolsnaps__/list_notifications.snap
+++ b/pkg/github/__toolsnaps__/list_notifications.snap
@@ -5,45 +5,45 @@
},
"description": "Lists all GitHub notifications for the authenticated user, including unread notifications, mentions, review requests, assignments, and updates on issues or pull requests. Use this tool whenever the user asks what to work on next, requests a summary of their GitHub activity, wants to see pending reviews, or needs to check for new updates or tasks. This tool is the primary way to discover actionable items, reminders, and outstanding work on GitHub. Always call this tool when asked what to work on next, what is pending, or what needs attention in GitHub.",
"inputSchema": {
- "type": "object",
"properties": {
"before": {
- "type": "string",
- "description": "Only show notifications updated before the given time (ISO 8601 format)"
+ "description": "Only show notifications updated before the given time (ISO 8601 format)",
+ "type": "string"
},
"filter": {
- "type": "string",
"description": "Filter notifications to, use default unless specified. Read notifications are ones that have already been acknowledged by the user. Participating notifications are those that the user is directly involved in, such as issues or pull requests they have commented on or created.",
"enum": [
"default",
"include_read_notifications",
"only_participating"
- ]
+ ],
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Optional repository owner. If provided with repo, only notifications for this repository are listed."
+ "description": "Optional repository owner. If provided with repo, only notifications for this repository are listed.",
+ "type": "string"
},
"page": {
- "type": "number",
"description": "Page number for pagination (min 1)",
- "minimum": 1
+ "minimum": 1,
+ "type": "number"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"repo": {
- "type": "string",
- "description": "Optional repository name. If provided with owner, only notifications for this repository are listed."
+ "description": "Optional repository name. If provided with owner, only notifications for this repository are listed.",
+ "type": "string"
},
"since": {
- "type": "string",
- "description": "Only show notifications updated after the given time (ISO 8601 format)"
+ "description": "Only show notifications updated after the given time (ISO 8601 format)",
+ "type": "string"
}
- }
+ },
+ "type": "object"
},
"name": "list_notifications"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_org_repository_security_advisories.snap b/pkg/github/__toolsnaps__/list_org_repository_security_advisories.snap
index 5f8823659..563da98c3 100644
--- a/pkg/github/__toolsnaps__/list_org_repository_security_advisories.snap
+++ b/pkg/github/__toolsnaps__/list_org_repository_security_advisories.snap
@@ -5,43 +5,43 @@
},
"description": "List repository security advisories for a GitHub organization.",
"inputSchema": {
- "type": "object",
- "required": [
- "org"
- ],
"properties": {
"direction": {
- "type": "string",
"description": "Sort direction.",
"enum": [
"asc",
"desc"
- ]
+ ],
+ "type": "string"
},
"org": {
- "type": "string",
- "description": "The organization login."
+ "description": "The organization login.",
+ "type": "string"
},
"sort": {
- "type": "string",
"description": "Sort field.",
"enum": [
"created",
"updated",
"published"
- ]
+ ],
+ "type": "string"
},
"state": {
- "type": "string",
"description": "Filter by advisory state.",
"enum": [
"triage",
"draft",
"published",
"closed"
- ]
+ ],
+ "type": "string"
}
- }
+ },
+ "required": [
+ "org"
+ ],
+ "type": "object"
},
"name": "list_org_repository_security_advisories"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_project_fields.snap b/pkg/github/__toolsnaps__/list_project_fields.snap
index 6bef18507..5456388b2 100644
--- a/pkg/github/__toolsnaps__/list_project_fields.snap
+++ b/pkg/github/__toolsnaps__/list_project_fields.snap
@@ -5,42 +5,42 @@
},
"description": "List Project fields for a user or org",
"inputSchema": {
- "type": "object",
- "required": [
- "owner_type",
- "owner",
- "project_number"
- ],
"properties": {
"after": {
- "type": "string",
- "description": "Forward pagination cursor from previous pageInfo.nextCursor."
+ "description": "Forward pagination cursor from previous pageInfo.nextCursor.",
+ "type": "string"
},
"before": {
- "type": "string",
- "description": "Backward pagination cursor from previous pageInfo.prevCursor (rare)."
+ "description": "Backward pagination cursor from previous pageInfo.prevCursor (rare).",
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive."
+ "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive.",
+ "type": "string"
},
"owner_type": {
- "type": "string",
"description": "Owner type",
"enum": [
"user",
"org"
- ]
+ ],
+ "type": "string"
},
"per_page": {
- "type": "number",
- "description": "Results per page (max 50)"
+ "description": "Results per page (max 50)",
+ "type": "number"
},
"project_number": {
- "type": "number",
- "description": "The project's number."
+ "description": "The project's number.",
+ "type": "number"
}
- }
+ },
+ "required": [
+ "owner_type",
+ "owner",
+ "project_number"
+ ],
+ "type": "object"
},
"name": "list_project_fields"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_project_items.snap b/pkg/github/__toolsnaps__/list_project_items.snap
index bceb5d9eb..5089f4306 100644
--- a/pkg/github/__toolsnaps__/list_project_items.snap
+++ b/pkg/github/__toolsnaps__/list_project_items.snap
@@ -5,53 +5,53 @@
},
"description": "Search project items with advanced filtering",
"inputSchema": {
- "type": "object",
- "required": [
- "owner_type",
- "owner",
- "project_number"
- ],
"properties": {
"after": {
- "type": "string",
- "description": "Forward pagination cursor from previous pageInfo.nextCursor."
+ "description": "Forward pagination cursor from previous pageInfo.nextCursor.",
+ "type": "string"
},
"before": {
- "type": "string",
- "description": "Backward pagination cursor from previous pageInfo.prevCursor (rare)."
+ "description": "Backward pagination cursor from previous pageInfo.prevCursor (rare).",
+ "type": "string"
},
"fields": {
- "type": "array",
"description": "Field IDs to include (e.g. [\"102589\", \"985201\"]). CRITICAL: Always provide to get field values. Without this, only titles returned.",
"items": {
"type": "string"
- }
+ },
+ "type": "array"
},
"owner": {
- "type": "string",
- "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive."
+ "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive.",
+ "type": "string"
},
"owner_type": {
- "type": "string",
"description": "Owner type",
"enum": [
"user",
"org"
- ]
+ ],
+ "type": "string"
},
"per_page": {
- "type": "number",
- "description": "Results per page (max 50)"
+ "description": "Results per page (max 50)",
+ "type": "number"
},
"project_number": {
- "type": "number",
- "description": "The project's number."
+ "description": "The project's number.",
+ "type": "number"
},
"query": {
- "type": "string",
- "description": "Query string for advanced filtering of project items using GitHub's project filtering syntax."
+ "description": "Query string for advanced filtering of project items using GitHub's project filtering syntax.",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner_type",
+ "owner",
+ "project_number"
+ ],
+ "type": "object"
},
"name": "list_project_items"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_projects.snap b/pkg/github/__toolsnaps__/list_projects.snap
index f48e26217..be5a6713e 100644
--- a/pkg/github/__toolsnaps__/list_projects.snap
+++ b/pkg/github/__toolsnaps__/list_projects.snap
@@ -5,41 +5,41 @@
},
"description": "List Projects for a user or organization",
"inputSchema": {
- "type": "object",
- "required": [
- "owner_type",
- "owner"
- ],
"properties": {
"after": {
- "type": "string",
- "description": "Forward pagination cursor from previous pageInfo.nextCursor."
+ "description": "Forward pagination cursor from previous pageInfo.nextCursor.",
+ "type": "string"
},
"before": {
- "type": "string",
- "description": "Backward pagination cursor from previous pageInfo.prevCursor (rare)."
+ "description": "Backward pagination cursor from previous pageInfo.prevCursor (rare).",
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive."
+ "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive.",
+ "type": "string"
},
"owner_type": {
- "type": "string",
"description": "Owner type",
"enum": [
"user",
"org"
- ]
+ ],
+ "type": "string"
},
"per_page": {
- "type": "number",
- "description": "Results per page (max 50)"
+ "description": "Results per page (max 50)",
+ "type": "number"
},
"query": {
- "type": "string",
- "description": "Filter projects by title text and open/closed state; permitted qualifiers: is:open, is:closed; examples: \"roadmap is:open\", \"is:open feature planning\"."
+ "description": "Filter projects by title text and open/closed state; permitted qualifiers: is:open, is:closed; examples: \"roadmap is:open\", \"is:open feature planning\".",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner_type",
+ "owner"
+ ],
+ "type": "object"
},
"name": "list_projects"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_pull_requests.snap b/pkg/github/__toolsnaps__/list_pull_requests.snap
index ae90c3fe0..25f1268c6 100644
--- a/pkg/github/__toolsnaps__/list_pull_requests.snap
+++ b/pkg/github/__toolsnaps__/list_pull_requests.snap
@@ -5,67 +5,67 @@
},
"description": "List pull requests in a GitHub repository. If the user specifies an author, then DO NOT use this tool and use the search_pull_requests tool instead.",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo"
- ],
"properties": {
"base": {
- "type": "string",
- "description": "Filter by base branch"
+ "description": "Filter by base branch",
+ "type": "string"
},
"direction": {
- "type": "string",
"description": "Sort direction",
"enum": [
"asc",
"desc"
- ]
+ ],
+ "type": "string"
},
"head": {
- "type": "string",
- "description": "Filter by head user/org and branch"
+ "description": "Filter by head user/org and branch",
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"page": {
- "type": "number",
"description": "Page number for pagination (min 1)",
- "minimum": 1
+ "minimum": 1,
+ "type": "number"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"sort": {
- "type": "string",
"description": "Sort by",
"enum": [
"created",
"updated",
"popularity",
"long-running"
- ]
+ ],
+ "type": "string"
},
"state": {
- "type": "string",
"description": "Filter by state",
"enum": [
"open",
"closed",
"all"
- ]
+ ],
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo"
+ ],
+ "type": "object"
},
"name": "list_pull_requests"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_releases.snap b/pkg/github/__toolsnaps__/list_releases.snap
index 98d4ce66f..57502c3c8 100644
--- a/pkg/github/__toolsnaps__/list_releases.snap
+++ b/pkg/github/__toolsnaps__/list_releases.snap
@@ -5,32 +5,32 @@
},
"description": "List releases in a GitHub repository",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"page": {
- "type": "number",
"description": "Page number for pagination (min 1)",
- "minimum": 1
+ "minimum": 1,
+ "type": "number"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo"
+ ],
+ "type": "object"
},
"name": "list_releases"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_repository_security_advisories.snap b/pkg/github/__toolsnaps__/list_repository_security_advisories.snap
index 465fd881e..c86508f92 100644
--- a/pkg/github/__toolsnaps__/list_repository_security_advisories.snap
+++ b/pkg/github/__toolsnaps__/list_repository_security_advisories.snap
@@ -5,48 +5,48 @@
},
"description": "List repository security advisories for a GitHub repository.",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo"
- ],
"properties": {
"direction": {
- "type": "string",
"description": "Sort direction.",
"enum": [
"asc",
"desc"
- ]
+ ],
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "The owner of the repository."
+ "description": "The owner of the repository.",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "The name of the repository."
+ "description": "The name of the repository.",
+ "type": "string"
},
"sort": {
- "type": "string",
"description": "Sort field.",
"enum": [
"created",
"updated",
"published"
- ]
+ ],
+ "type": "string"
},
"state": {
- "type": "string",
"description": "Filter by advisory state.",
"enum": [
"triage",
"draft",
"published",
"closed"
- ]
+ ],
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo"
+ ],
+ "type": "object"
},
"name": "list_repository_security_advisories"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_secret_scanning_alerts.snap b/pkg/github/__toolsnaps__/list_secret_scanning_alerts.snap
index e7896c55f..f2f7cb125 100644
--- a/pkg/github/__toolsnaps__/list_secret_scanning_alerts.snap
+++ b/pkg/github/__toolsnaps__/list_secret_scanning_alerts.snap
@@ -5,22 +5,16 @@
},
"description": "List secret scanning alerts in a GitHub repository.",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "The owner of the repository."
+ "description": "The owner of the repository.",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "The name of the repository."
+ "description": "The name of the repository.",
+ "type": "string"
},
"resolution": {
- "type": "string",
"description": "Filter by resolution",
"enum": [
"false_positive",
@@ -29,21 +23,27 @@
"pattern_edited",
"pattern_deleted",
"used_in_tests"
- ]
+ ],
+ "type": "string"
},
"secret_type": {
- "type": "string",
- "description": "A comma-separated list of secret types to return. All default secret patterns are returned. To return generic patterns, pass the token name(s) in the parameter."
+ "description": "A comma-separated list of secret types to return. All default secret patterns are returned. To return generic patterns, pass the token name(s) in the parameter.",
+ "type": "string"
},
"state": {
- "type": "string",
"description": "Filter by state",
"enum": [
"open",
"resolved"
- ]
+ ],
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo"
+ ],
+ "type": "object"
},
"name": "list_secret_scanning_alerts"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_starred_repositories.snap b/pkg/github/__toolsnaps__/list_starred_repositories.snap
index a383b39d1..e631719fd 100644
--- a/pkg/github/__toolsnaps__/list_starred_repositories.snap
+++ b/pkg/github/__toolsnaps__/list_starred_repositories.snap
@@ -5,40 +5,40 @@
},
"description": "List starred repositories",
"inputSchema": {
- "type": "object",
"properties": {
"direction": {
- "type": "string",
"description": "The direction to sort the results by.",
"enum": [
"asc",
"desc"
- ]
+ ],
+ "type": "string"
},
"page": {
- "type": "number",
"description": "Page number for pagination (min 1)",
- "minimum": 1
+ "minimum": 1,
+ "type": "number"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"sort": {
- "type": "string",
"description": "How to sort the results. Can be either 'created' (when the repository was starred) or 'updated' (when the repository was last pushed to).",
"enum": [
"created",
"updated"
- ]
+ ],
+ "type": "string"
},
"username": {
- "type": "string",
- "description": "Username to list starred repositories for. Defaults to the authenticated user."
+ "description": "Username to list starred repositories for. Defaults to the authenticated user.",
+ "type": "string"
}
- }
+ },
+ "type": "object"
},
"name": "list_starred_repositories"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_tags.snap b/pkg/github/__toolsnaps__/list_tags.snap
index 5b667d19c..1e66d2c1f 100644
--- a/pkg/github/__toolsnaps__/list_tags.snap
+++ b/pkg/github/__toolsnaps__/list_tags.snap
@@ -5,32 +5,32 @@
},
"description": "List git tags in a GitHub repository",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"page": {
- "type": "number",
"description": "Page number for pagination (min 1)",
- "minimum": 1
+ "minimum": 1,
+ "type": "number"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo"
+ ],
+ "type": "object"
},
"name": "list_tags"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_workflow_jobs.snap b/pkg/github/__toolsnaps__/list_workflow_jobs.snap
index 59ff75afc..d8fed1965 100644
--- a/pkg/github/__toolsnaps__/list_workflow_jobs.snap
+++ b/pkg/github/__toolsnaps__/list_workflow_jobs.snap
@@ -5,45 +5,45 @@
},
"description": "List jobs for a specific workflow run",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "run_id"
- ],
"properties": {
"filter": {
- "type": "string",
"description": "Filters jobs by their completed_at timestamp",
"enum": [
"latest",
"all"
- ]
+ ],
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"page": {
- "type": "number",
"description": "Page number for pagination (min 1)",
- "minimum": 1
+ "minimum": 1,
+ "type": "number"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"run_id": {
- "type": "number",
- "description": "The unique identifier of the workflow run"
+ "description": "The unique identifier of the workflow run",
+ "type": "number"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "run_id"
+ ],
+ "type": "object"
},
"name": "list_workflow_jobs"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_workflow_run_artifacts.snap b/pkg/github/__toolsnaps__/list_workflow_run_artifacts.snap
index 6d6332d74..664722901 100644
--- a/pkg/github/__toolsnaps__/list_workflow_run_artifacts.snap
+++ b/pkg/github/__toolsnaps__/list_workflow_run_artifacts.snap
@@ -5,37 +5,37 @@
},
"description": "List artifacts for a workflow run",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "run_id"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"page": {
- "type": "number",
"description": "Page number for pagination (min 1)",
- "minimum": 1
+ "minimum": 1,
+ "type": "number"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"run_id": {
- "type": "number",
- "description": "The unique identifier of the workflow run"
+ "description": "The unique identifier of the workflow run",
+ "type": "number"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "run_id"
+ ],
+ "type": "object"
},
"name": "list_workflow_run_artifacts"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_workflow_runs.snap b/pkg/github/__toolsnaps__/list_workflow_runs.snap
index e5353f490..a9a9916c3 100644
--- a/pkg/github/__toolsnaps__/list_workflow_runs.snap
+++ b/pkg/github/__toolsnaps__/list_workflow_runs.snap
@@ -5,23 +5,16 @@
},
"description": "List workflow runs for a specific workflow",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "workflow_id"
- ],
"properties": {
"actor": {
- "type": "string",
- "description": "Returns someone's workflow runs. Use the login for the user who created the workflow run."
+ "description": "Returns someone's workflow runs. Use the login for the user who created the workflow run.",
+ "type": "string"
},
"branch": {
- "type": "string",
- "description": "Returns workflow runs associated with a branch. Use the name of the branch."
+ "description": "Returns workflow runs associated with a branch. Use the name of the branch.",
+ "type": "string"
},
"event": {
- "type": "string",
"description": "Returns workflow runs for a specific event type",
"enum": [
"branch_protection_rule",
@@ -56,29 +49,29 @@
"workflow_call",
"workflow_dispatch",
"workflow_run"
- ]
+ ],
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"page": {
- "type": "number",
"description": "Page number for pagination (min 1)",
- "minimum": 1
+ "minimum": 1,
+ "type": "number"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"status": {
- "type": "string",
"description": "Returns workflow runs with the check run status",
"enum": [
"queued",
@@ -86,13 +79,20 @@
"completed",
"requested",
"waiting"
- ]
+ ],
+ "type": "string"
},
"workflow_id": {
- "type": "string",
- "description": "The workflow ID or workflow file name"
+ "description": "The workflow ID or workflow file name",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "workflow_id"
+ ],
+ "type": "object"
},
"name": "list_workflow_runs"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/list_workflows.snap b/pkg/github/__toolsnaps__/list_workflows.snap
index f3f52f042..b0e51e03a 100644
--- a/pkg/github/__toolsnaps__/list_workflows.snap
+++ b/pkg/github/__toolsnaps__/list_workflows.snap
@@ -5,32 +5,32 @@
},
"description": "List workflows in a repository",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"page": {
- "type": "number",
"description": "Page number for pagination (min 1)",
- "minimum": 1
+ "minimum": 1,
+ "type": "number"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo"
+ ],
+ "type": "object"
},
"name": "list_workflows"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/manage_notification_subscription.snap b/pkg/github/__toolsnaps__/manage_notification_subscription.snap
index 4f0d466a0..e04acd11e 100644
--- a/pkg/github/__toolsnaps__/manage_notification_subscription.snap
+++ b/pkg/github/__toolsnaps__/manage_notification_subscription.snap
@@ -4,26 +4,26 @@
},
"description": "Manage a notification subscription: ignore, watch, or delete a notification thread subscription.",
"inputSchema": {
- "type": "object",
- "required": [
- "notificationID",
- "action"
- ],
"properties": {
"action": {
- "type": "string",
"description": "Action to perform: ignore, watch, or delete the notification subscription.",
"enum": [
"ignore",
"watch",
"delete"
- ]
+ ],
+ "type": "string"
},
"notificationID": {
- "type": "string",
- "description": "The ID of the notification thread."
+ "description": "The ID of the notification thread.",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "notificationID",
+ "action"
+ ],
+ "type": "object"
},
"name": "manage_notification_subscription"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/manage_repository_notification_subscription.snap b/pkg/github/__toolsnaps__/manage_repository_notification_subscription.snap
index 82ee40a89..0a4567b71 100644
--- a/pkg/github/__toolsnaps__/manage_repository_notification_subscription.snap
+++ b/pkg/github/__toolsnaps__/manage_repository_notification_subscription.snap
@@ -4,31 +4,31 @@
},
"description": "Manage a repository notification subscription: ignore, watch, or delete repository notifications subscription for the provided repository.",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "action"
- ],
"properties": {
"action": {
- "type": "string",
"description": "Action to perform: ignore, watch, or delete the repository notification subscription.",
"enum": [
"ignore",
"watch",
"delete"
- ]
+ ],
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "The account owner of the repository."
+ "description": "The account owner of the repository.",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "The name of the repository."
+ "description": "The name of the repository.",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "action"
+ ],
+ "type": "object"
},
"name": "manage_repository_notification_subscription"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/mark_all_notifications_read.snap b/pkg/github/__toolsnaps__/mark_all_notifications_read.snap
index 2d45ed78d..1f5a32284 100644
--- a/pkg/github/__toolsnaps__/mark_all_notifications_read.snap
+++ b/pkg/github/__toolsnaps__/mark_all_notifications_read.snap
@@ -4,21 +4,21 @@
},
"description": "Mark all notifications as read",
"inputSchema": {
- "type": "object",
"properties": {
"lastReadAt": {
- "type": "string",
- "description": "Describes the last point that notifications were checked (optional). Default: Now"
+ "description": "Describes the last point that notifications were checked (optional). Default: Now",
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Optional repository owner. If provided with repo, only notifications for this repository are marked as read."
+ "description": "Optional repository owner. If provided with repo, only notifications for this repository are marked as read.",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Optional repository name. If provided with owner, only notifications for this repository are marked as read."
+ "description": "Optional repository name. If provided with owner, only notifications for this repository are marked as read.",
+ "type": "string"
}
- }
+ },
+ "type": "object"
},
"name": "mark_all_notifications_read"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/merge_pull_request.snap b/pkg/github/__toolsnaps__/merge_pull_request.snap
index 179805b3a..d0cdb2b1a 100644
--- a/pkg/github/__toolsnaps__/merge_pull_request.snap
+++ b/pkg/github/__toolsnaps__/merge_pull_request.snap
@@ -3,56 +3,56 @@
"title": "Merge pull request"
},
"description": "Merge a pull request in a GitHub repository.",
+ "icons": [
+ {
+ "mimeType": "image/png",
+ "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACeElEQVRIibWVTUhUYRSGn/e74+iiQih1F9Vcmj9sptylUVBYkO4jcNeuJBdFKxe1CYQokGrRKjCEdtmqwEVmtqomQWeiUdc2EBUtUufe0yLHn1KLGXtX5zvn4zz3vd8f/Gfp90Qs0drmpA6MT1EveDo1NfV92wB+KnMdo39Nfs4L7eSHD5Nz1QJcJYglWtsw+iUehAuRRjO1g+0KHLerbb4OIHnHAC1FdW129s3XmUJuwnBDoOPbA7BwHsD7QWq1HKYN5msBRCpB1AueLoSROSkciSUyj5ClhE6BLtYC8CpBqVRabNrdMmIiJdQjuUbQ1WI+d78WwIbykxnzU9np7ejlNq2YxQ4ebNtTKyCyWcEgYl55EDj/a7ihFEtkLkr0As2YxjwL+9aem00dCEYNzvnJzLDvH27aaM5y80HEnKGHKGwPnEbT6fSOvzpAmrDQnkncpC7siiUzz2QqIPu25iOuGBorTufO/AJmH0v2ajHwuoHhrQHATOH9rQPJ7IjDLgs6kZ0F6it1AzArVcZLdUE+WnYgmv/uYFmz+dxH4NJGNT+RfYLCE7F4tn0pGkxHy94AmBm8/GfAVvIs7AukUTkbj5YdYIbZ9WJh8m1lzrrbNB4/tD+QuyPsdCibF26gmM/dY/NdRDqd3rEYeN04mswYL+ZXm68DxOPxnWXXMClsp+GGhCWBTtClYj53t1qXK78oVH2XYB/mHZ0pvHsN4Cczzw3rBaoGrJ6D5ZUvN1i+kjI0LWiptjmscbC88hZZCAf2trZeq1v0UsJ6wF7UAlhxUMxPvkW6AboQLbvPcjaO+BIx11cL4I9H308eOiLRQUhpOx79/66fNKzrOCYNDm0AAAAASUVORK5CYII=",
+ "theme": "light"
+ },
+ {
+ "mimeType": "image/png",
+ "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABjElEQVRIibWVPS/DURTGnysSC0HiZdWVrZ28JDaLT8BHaBsMdjqZJDXiAzC2LF5mX6GtATGiIsGARH+Gnj9X8a/kf3uWe3Py3Oc559xz75E6bK7VAWQkzUi6lXTonHsOpgYUgAZfdgmkQpFnjHwb6AemgDpQCiWwYlEPeL4i8JCEt8vb39g67vkmPH8yA3qt5nVgCzi1jLJBBEwkBZSAdxPKAj86LYQQQCU4cYvAKzDUSYF3YC+uRIAD8sA58ACU//VuTODE1n1g+A9c3jBH1tJ1a5TeCPNrdACSCpKeJG1IepN0LKkm6dGDrkqqOOdm7dyUpDNJi865PUnqjsvEObcJHEhaljQnaV5STwvszttXbR2J441KtB4LauLKVpZpYBDYte8mHUogZTWPrAGstTtQBl6AayDX7qHZD7AALMVGDvQBV5ZyETi2qHLtMvmXWRQAk57vBKgl4fV/0+jmq56vImk0icCnAWm7pB3riGngnlADx0TW+T4yL4CxJJy/Df20mkP/TqGHfifsA7INs3X5i3+yAAAAAElFTkSuQmCC",
+ "theme": "dark"
+ }
+ ],
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "pullNumber"
- ],
"properties": {
"commit_message": {
- "type": "string",
- "description": "Extra detail for merge commit"
+ "description": "Extra detail for merge commit",
+ "type": "string"
},
"commit_title": {
- "type": "string",
- "description": "Title for merge commit"
+ "description": "Title for merge commit",
+ "type": "string"
},
"merge_method": {
- "type": "string",
"description": "Merge method",
"enum": [
"merge",
"squash",
"rebase"
- ]
+ ],
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"pullNumber": {
- "type": "number",
- "description": "Pull request number"
+ "description": "Pull request number",
+ "type": "number"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
}
- }
- },
- "name": "merge_pull_request",
- "icons": [
- {
- "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACeElEQVRIibWVTUhUYRSGn/e74+iiQih1F9Vcmj9sptylUVBYkO4jcNeuJBdFKxe1CYQokGrRKjCEdtmqwEVmtqomQWeiUdc2EBUtUufe0yLHn1KLGXtX5zvn4zz3vd8f/Gfp90Qs0drmpA6MT1EveDo1NfV92wB+KnMdo39Nfs4L7eSHD5Nz1QJcJYglWtsw+iUehAuRRjO1g+0KHLerbb4OIHnHAC1FdW129s3XmUJuwnBDoOPbA7BwHsD7QWq1HKYN5msBRCpB1AueLoSROSkciSUyj5ClhE6BLtYC8CpBqVRabNrdMmIiJdQjuUbQ1WI+d78WwIbykxnzU9np7ejlNq2YxQ4ebNtTKyCyWcEgYl55EDj/a7ihFEtkLkr0As2YxjwL+9aem00dCEYNzvnJzLDvH27aaM5y80HEnKGHKGwPnEbT6fSOvzpAmrDQnkncpC7siiUzz2QqIPu25iOuGBorTufO/AJmH0v2ajHwuoHhrQHATOH9rQPJ7IjDLgs6kZ0F6it1AzArVcZLdUE+WnYgmv/uYFmz+dxH4NJGNT+RfYLCE7F4tn0pGkxHy94AmBm8/GfAVvIs7AukUTkbj5YdYIbZ9WJh8m1lzrrbNB4/tD+QuyPsdCibF26gmM/dY/NdRDqd3rEYeN04mswYL+ZXm68DxOPxnWXXMClsp+GGhCWBTtClYj53t1qXK78oVH2XYB/mHZ0pvHsN4Cczzw3rBaoGrJ6D5ZUvN1i+kjI0LWiptjmscbC88hZZCAf2trZeq1v0UsJ6wF7UAlhxUMxPvkW6AboQLbvPcjaO+BIx11cL4I9H308eOiLRQUhpOx79/66fNKzrOCYNDm0AAAAASUVORK5CYII=",
- "mimeType": "image/png",
- "theme": "light"
},
- {
- "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABjElEQVRIibWVPS/DURTGnysSC0HiZdWVrZ28JDaLT8BHaBsMdjqZJDXiAzC2LF5mX6GtATGiIsGARH+Gnj9X8a/kf3uWe3Py3Oc559xz75E6bK7VAWQkzUi6lXTonHsOpgYUgAZfdgmkQpFnjHwb6AemgDpQCiWwYlEPeL4i8JCEt8vb39g67vkmPH8yA3qt5nVgCzi1jLJBBEwkBZSAdxPKAj86LYQQQCU4cYvAKzDUSYF3YC+uRIAD8sA58ACU//VuTODE1n1g+A9c3jBH1tJ1a5TeCPNrdACSCpKeJG1IepN0LKkm6dGDrkqqOOdm7dyUpDNJi865PUnqjsvEObcJHEhaljQnaV5STwvszttXbR2J441KtB4LauLKVpZpYBDYte8mHUogZTWPrAGstTtQBl6AayDX7qHZD7AALMVGDvQBV5ZyETi2qHLtMvmXWRQAk57vBKgl4fV/0+jmq56vImk0icCnAWm7pB3riGngnlADx0TW+T4yL4CxJJy/Df20mkP/TqGHfifsA7INs3X5i3+yAAAAAElFTkSuQmCC",
- "mimeType": "image/png",
- "theme": "dark"
- }
- ]
+ "required": [
+ "owner",
+ "repo",
+ "pullNumber"
+ ],
+ "type": "object"
+ },
+ "name": "merge_pull_request"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/projects_get.snap b/pkg/github/__toolsnaps__/projects_get.snap
index 9758de0f2..9ccb1e75f 100644
--- a/pkg/github/__toolsnaps__/projects_get.snap
+++ b/pkg/github/__toolsnaps__/projects_get.snap
@@ -5,55 +5,55 @@
},
"description": "Get details about specific GitHub Projects resources.\nUse this tool to get details about individual projects, project fields, and project items by their unique IDs.\n",
"inputSchema": {
- "type": "object",
- "required": [
- "method",
- "owner_type",
- "owner",
- "project_number"
- ],
"properties": {
"field_id": {
- "type": "number",
- "description": "The field's ID. Required for 'get_project_field' method."
+ "description": "The field's ID. Required for 'get_project_field' method.",
+ "type": "number"
},
"fields": {
- "type": "array",
"description": "Specific list of field IDs to include in the response when getting a project item (e.g. [\"102589\", \"985201\", \"169875\"]). If not provided, only the title field is included. Only used for 'get_project_item' method.",
"items": {
"type": "string"
- }
+ },
+ "type": "array"
},
"item_id": {
- "type": "number",
- "description": "The item's ID. Required for 'get_project_item' method."
+ "description": "The item's ID. Required for 'get_project_item' method.",
+ "type": "number"
},
"method": {
- "type": "string",
"description": "The method to execute",
"enum": [
"get_project",
"get_project_field",
"get_project_item"
- ]
+ ],
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive."
+ "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive.",
+ "type": "string"
},
"owner_type": {
- "type": "string",
"description": "Owner type",
"enum": [
"user",
"org"
- ]
+ ],
+ "type": "string"
},
"project_number": {
- "type": "number",
- "description": "The project's number."
+ "description": "The project's number.",
+ "type": "number"
}
- }
+ },
+ "required": [
+ "method",
+ "owner_type",
+ "owner",
+ "project_number"
+ ],
+ "type": "object"
},
"name": "projects_get"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/projects_list.snap b/pkg/github/__toolsnaps__/projects_list.snap
index 7cc2e2df7..84e964a1d 100644
--- a/pkg/github/__toolsnaps__/projects_list.snap
+++ b/pkg/github/__toolsnaps__/projects_list.snap
@@ -5,62 +5,62 @@
},
"description": "Tools for listing GitHub Projects resources.\nUse this tool to list projects for a user or organization, or list project fields and items for a specific project.\n",
"inputSchema": {
- "type": "object",
- "required": [
- "method",
- "owner_type",
- "owner"
- ],
"properties": {
"after": {
- "type": "string",
- "description": "Forward pagination cursor from previous pageInfo.nextCursor."
+ "description": "Forward pagination cursor from previous pageInfo.nextCursor.",
+ "type": "string"
},
"before": {
- "type": "string",
- "description": "Backward pagination cursor from previous pageInfo.prevCursor (rare)."
+ "description": "Backward pagination cursor from previous pageInfo.prevCursor (rare).",
+ "type": "string"
},
"fields": {
- "type": "array",
"description": "Field IDs to include when listing project items (e.g. [\"102589\", \"985201\"]). CRITICAL: Always provide to get field values. Without this, only titles returned. Only used for 'list_project_items' method.",
"items": {
"type": "string"
- }
+ },
+ "type": "array"
},
"method": {
- "type": "string",
"description": "The action to perform",
"enum": [
"list_projects",
"list_project_fields",
"list_project_items"
- ]
+ ],
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive."
+ "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive.",
+ "type": "string"
},
"owner_type": {
- "type": "string",
"description": "Owner type",
"enum": [
"user",
"org"
- ]
+ ],
+ "type": "string"
},
"per_page": {
- "type": "number",
- "description": "Results per page (max 50)"
+ "description": "Results per page (max 50)",
+ "type": "number"
},
"project_number": {
- "type": "number",
- "description": "The project's number. Required for 'list_project_fields' and 'list_project_items' methods."
+ "description": "The project's number. Required for 'list_project_fields' and 'list_project_items' methods.",
+ "type": "number"
},
"query": {
- "type": "string",
- "description": "Filter/query string. For list_projects: filter by title text and state (e.g. \"roadmap is:open\"). For list_project_items: advanced filtering using GitHub's project filtering syntax."
+ "description": "Filter/query string. For list_projects: filter by title text and state (e.g. \"roadmap is:open\"). For list_project_items: advanced filtering using GitHub's project filtering syntax.",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "method",
+ "owner_type",
+ "owner"
+ ],
+ "type": "object"
},
"name": "projects_list"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/projects_write.snap b/pkg/github/__toolsnaps__/projects_write.snap
index 2224590c5..759f79579 100644
--- a/pkg/github/__toolsnaps__/projects_write.snap
+++ b/pkg/github/__toolsnaps__/projects_write.snap
@@ -5,56 +5,56 @@
},
"description": "Add, update, or delete project items in a GitHub Project.",
"inputSchema": {
- "type": "object",
- "required": [
- "method",
- "owner_type",
- "owner",
- "project_number"
- ],
"properties": {
"item_id": {
- "type": "number",
- "description": "The project item ID. Required for 'update_project_item' and 'delete_project_item' methods. For add_project_item, this is the numeric ID of the issue or pull request to add."
+ "description": "The project item ID. Required for 'update_project_item' and 'delete_project_item' methods. For add_project_item, this is the numeric ID of the issue or pull request to add.",
+ "type": "number"
},
"item_type": {
- "type": "string",
"description": "The item's type, either issue or pull_request. Required for 'add_project_item' method.",
"enum": [
"issue",
"pull_request"
- ]
+ ],
+ "type": "string"
},
"method": {
- "type": "string",
"description": "The method to execute",
"enum": [
"add_project_item",
"update_project_item",
"delete_project_item"
- ]
+ ],
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive."
+ "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive.",
+ "type": "string"
},
"owner_type": {
- "type": "string",
"description": "Owner type",
"enum": [
"user",
"org"
- ]
+ ],
+ "type": "string"
},
"project_number": {
- "type": "number",
- "description": "The project's number."
+ "description": "The project's number.",
+ "type": "number"
},
"updated_field": {
- "type": "object",
- "description": "Object consisting of the ID of the project field to update and the new value for the field. To clear the field, set value to null. Example: {\"id\": 123456, \"value\": \"New Value\"}. Required for 'update_project_item' method."
+ "description": "Object consisting of the ID of the project field to update and the new value for the field. To clear the field, set value to null. Example: {\"id\": 123456, \"value\": \"New Value\"}. Required for 'update_project_item' method.",
+ "type": "object"
}
- }
+ },
+ "required": [
+ "method",
+ "owner_type",
+ "owner",
+ "project_number"
+ ],
+ "type": "object"
},
"name": "projects_write"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/pull_request_read.snap b/pkg/github/__toolsnaps__/pull_request_read.snap
index 69b1bd901..a8591fc5c 100644
--- a/pkg/github/__toolsnaps__/pull_request_read.snap
+++ b/pkg/github/__toolsnaps__/pull_request_read.snap
@@ -5,16 +5,8 @@
},
"description": "Get information on a specific pull request in GitHub repository.",
"inputSchema": {
- "type": "object",
- "required": [
- "method",
- "owner",
- "repo",
- "pullNumber"
- ],
"properties": {
"method": {
- "type": "string",
"description": "Action to specify what pull request data needs to be retrieved from GitHub. \nPossible options: \n 1. get - Get details of a specific pull request.\n 2. get_diff - Get the diff of a pull request.\n 3. get_status - Get status of a head commit in a pull request. This reflects status of builds and checks.\n 4. get_files - Get the list of files changed in a pull request. Use with pagination parameters to control the number of results returned.\n 5. get_review_comments - Get review threads on a pull request. Each thread contains logically grouped review comments made on the same code location during pull request reviews. Returns threads with metadata (isResolved, isOutdated, isCollapsed) and their associated comments. Use cursor-based pagination (perPage, after) to control results.\n 6. get_reviews - Get the reviews on a pull request. When asked for review comments, use get_review_comments method.\n 7. get_comments - Get comments on a pull request. Use this if user doesn't specifically want review comments. Use with pagination parameters to control the number of results returned.\n",
"enum": [
"get",
@@ -24,32 +16,40 @@
"get_review_comments",
"get_reviews",
"get_comments"
- ]
+ ],
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"page": {
- "type": "number",
"description": "Page number for pagination (min 1)",
- "minimum": 1
+ "minimum": 1,
+ "type": "number"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"pullNumber": {
- "type": "number",
- "description": "Pull request number"
+ "description": "Pull request number",
+ "type": "number"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "method",
+ "owner",
+ "repo",
+ "pullNumber"
+ ],
+ "type": "object"
},
"name": "pull_request_read"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/pull_request_review_write.snap b/pkg/github/__toolsnaps__/pull_request_review_write.snap
index 92cc19924..7b533f472 100644
--- a/pkg/github/__toolsnaps__/pull_request_review_write.snap
+++ b/pkg/github/__toolsnaps__/pull_request_review_write.snap
@@ -4,53 +4,53 @@
},
"description": "Create and/or submit, delete review of a pull request.\n\nAvailable methods:\n- create: Create a new review of a pull request. If \"event\" parameter is provided, the review is submitted. If \"event\" is omitted, a pending review is created.\n- submit_pending: Submit an existing pending review of a pull request. This requires that a pending review exists for the current user on the specified pull request. The \"body\" and \"event\" parameters are used when submitting the review.\n- delete_pending: Delete an existing pending review of a pull request. This requires that a pending review exists for the current user on the specified pull request.\n",
"inputSchema": {
- "type": "object",
- "required": [
- "method",
- "owner",
- "repo",
- "pullNumber"
- ],
"properties": {
"body": {
- "type": "string",
- "description": "Review comment text"
+ "description": "Review comment text",
+ "type": "string"
},
"commitID": {
- "type": "string",
- "description": "SHA of commit to review"
+ "description": "SHA of commit to review",
+ "type": "string"
},
"event": {
- "type": "string",
"description": "Review action to perform.",
"enum": [
"APPROVE",
"REQUEST_CHANGES",
"COMMENT"
- ]
+ ],
+ "type": "string"
},
"method": {
- "type": "string",
"description": "The write operation to perform on pull request review.",
"enum": [
"create",
"submit_pending",
"delete_pending"
- ]
+ ],
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"pullNumber": {
- "type": "number",
- "description": "Pull request number"
+ "description": "Pull request number",
+ "type": "number"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "method",
+ "owner",
+ "repo",
+ "pullNumber"
+ ],
+ "type": "object"
},
"name": "pull_request_review_write"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/push_files.snap b/pkg/github/__toolsnaps__/push_files.snap
index 4db764cc9..c36c236f9 100644
--- a/pkg/github/__toolsnaps__/push_files.snap
+++ b/pkg/github/__toolsnaps__/push_files.snap
@@ -4,53 +4,53 @@
},
"description": "Push multiple files to a GitHub repository in a single commit",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "branch",
- "files",
- "message"
- ],
"properties": {
"branch": {
- "type": "string",
- "description": "Branch to push to"
+ "description": "Branch to push to",
+ "type": "string"
},
"files": {
- "type": "array",
"description": "Array of file objects to push, each object with path (string) and content (string)",
"items": {
- "type": "object",
- "required": [
- "path",
- "content"
- ],
"properties": {
"content": {
- "type": "string",
- "description": "file content"
+ "description": "file content",
+ "type": "string"
},
"path": {
- "type": "string",
- "description": "path to the file"
+ "description": "path to the file",
+ "type": "string"
}
- }
- }
+ },
+ "required": [
+ "path",
+ "content"
+ ],
+ "type": "object"
+ },
+ "type": "array"
},
"message": {
- "type": "string",
- "description": "Commit message"
+ "description": "Commit message",
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "branch",
+ "files",
+ "message"
+ ],
+ "type": "object"
},
"name": "push_files"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/request_copilot_review.snap b/pkg/github/__toolsnaps__/request_copilot_review.snap
index 0bf419d98..cd00f73fd 100644
--- a/pkg/github/__toolsnaps__/request_copilot_review.snap
+++ b/pkg/github/__toolsnaps__/request_copilot_review.snap
@@ -3,39 +3,39 @@
"title": "Request Copilot review"
},
"description": "Request a GitHub Copilot code review for a pull request. Use this for automated feedback on pull requests, usually before requesting a human reviewer.",
- "inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "pullNumber"
- ],
- "properties": {
- "owner": {
- "type": "string",
- "description": "Repository owner"
- },
- "pullNumber": {
- "type": "number",
- "description": "Pull request number"
- },
- "repo": {
- "type": "string",
- "description": "Repository name"
- }
- }
- },
- "name": "request_copilot_review",
"icons": [
{
- "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC20lEQVRIidWUS4wMURSGv3O7kWmPEMRrSMzcbl1dpqtmGuOxsCKECCKxEBusSJhIWEhsWLFAbC1sWFiISBARCyQ2kzSZGaMxHokgXvGIiMH0PRZjpJqqHpb+TeX+59z//H/q5sD/DqlX9H1/zFeX2qzIKoFWYDKgwBtUymL0UkNaT3V3d3/+5wG2EGxB9TDIxGFMvhVhb9/drpN/NaDJC7MGdwJk6TDCv0Gvq0lve9R762GUNdFDLleaZNBrICGq+4yhvf9TJtP/KZNB2PrLlbBliBfRhajuAwnFVa/n8/nkxFkv3GO9oJrzgwVxdesV71ov6I2r5fxggfWCatYL9yYmUJgLPH7Q29WZ4OED6Me4wuAdeQK6MMqna9t0GuibBHFAmgZ9JMG9BhkXZWoSCDSATIq7aguBD0wBplq/tZBgYDIwKnZAs99mFRYD9vd/YK0dpcqhobM6d9haWyOULRTbAauwuNlvsxHTYP3iBnVyXGAa8BIYC3oVeAKioCtAPEE7FCOgR0ErIJdBBZgNskzh40+NF6K6s+9e91lp9osrxMnFoTSmSmPVsF+E5cB0YEDgtoMjjypd5wCy+WC9GnajhEAa4bkqV9LOHKwa9/yneYeyUqwX3AdyQ5EeVrrqro/hYL0g+ggemKh4HGbPmVu0+fB8U76lpR6XgJwZpoGUpNYiusZg1tXjkmCAav0OMTXfJC4eVYPqwbot6l4BCPqyLhd7lwMAWC/cYb3gi/UCzRaKOxsbFzVEM1iv2Ebt5v2Dm14qZbJecZf1Ah3UCrcTbbB+awHnjgHLgHeinHYqZ8aPSXWWy+XvcQZLpdKI9/0D7UbZiLIJmABckVSqo+/OrUrNgF+D8q1LEdcBrAJGAJ8ROlGeicorABWdAswE5gOjge8CF8Ad66v03IjqJb75WS0tE0YOmNWqLBGReaAzgIkMLrt3oM9UpSzCzW9pd+FpT8/7JK3/Gz8Ao5X6wtwP7N4AAAAASUVORK5CYII=",
"mimeType": "image/png",
+ "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAAC20lEQVRIidWUS4wMURSGv3O7kWmPEMRrSMzcbl1dpqtmGuOxsCKECCKxEBusSJhIWEhsWLFAbC1sWFiISBARCyQ2kzSZGaMxHokgXvGIiMH0PRZjpJqqHpb+TeX+59z//H/q5sD/DqlX9H1/zFeX2qzIKoFWYDKgwBtUymL0UkNaT3V3d3/+5wG2EGxB9TDIxGFMvhVhb9/drpN/NaDJC7MGdwJk6TDCv0Gvq0lve9R762GUNdFDLleaZNBrICGq+4yhvf9TJtP/KZNB2PrLlbBliBfRhajuAwnFVa/n8/nkxFkv3GO9oJrzgwVxdesV71ov6I2r5fxggfWCatYL9yYmUJgLPH7Q29WZ4OED6Me4wuAdeQK6MMqna9t0GuibBHFAmgZ9JMG9BhkXZWoSCDSATIq7aguBD0wBplq/tZBgYDIwKnZAs99mFRYD9vd/YK0dpcqhobM6d9haWyOULRTbAauwuNlvsxHTYP3iBnVyXGAa8BIYC3oVeAKioCtAPEE7FCOgR0ErIJdBBZgNskzh40+NF6K6s+9e91lp9osrxMnFoTSmSmPVsF+E5cB0YEDgtoMjjypd5wCy+WC9GnajhEAa4bkqV9LOHKwa9/yneYeyUqwX3AdyQ5EeVrrqro/hYL0g+ggemKh4HGbPmVu0+fB8U76lpR6XgJwZpoGUpNYiusZg1tXjkmCAav0OMTXfJC4eVYPqwbot6l4BCPqyLhd7lwMAWC/cYb3gi/UCzRaKOxsbFzVEM1iv2Ebt5v2Dm14qZbJecZf1Ah3UCrcTbbB+awHnjgHLgHeinHYqZ8aPSXWWy+XvcQZLpdKI9/0D7UbZiLIJmABckVSqo+/OrUrNgF+D8q1LEdcBrAJGAJ8ROlGeicorABWdAswE5gOjge8CF8Ad66v03IjqJb75WS0tE0YOmNWqLBGReaAzgIkMLrt3oM9UpSzCzW9pd+FpT8/7JK3/Gz8Ao5X6wtwP7N4AAAAASUVORK5CYII=",
"theme": "light"
},
{
- "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACCElEQVRIid2UPWsUYRSFn3dxWWJUkESiBgslFokfhehGiGClBBQx4h9IGlEh2ijYxh+gxEL/hIWwhYpF8KNZsFRJYdJEiUbjCkqisj4W+y6Mk5nd1U4PDMOce+45L3fmDvzXUDeo59WK+kb9rn5TF9R76jm1+2/NJ9QPtseSOv4nxrvVmQ6M05hRB9qZ98ZR1NRralntitdEwmw8wQ9HbS329rQKuKLW1XJO/aX6IqdWjr1Xk/y6lG4vMBdCqOacoZZ3uBBCVZ0HDrcK2AYs5ZkAuwBb1N8Dm5JEISXoAnqzOtU9QB+wVR3KCdgClDIr6kCc4c/0O1BLNnahiYpaSmmGY62e/JpCLJ4FpmmMaBHYCDwC5mmMZBQYBC7HnhvAK+B+fN4JHAM+R4+3wGQI4S7qaExtol+9o86pq+oX9Yk6ljjtGfVprK2qr9Xb6vaET109jjqb3Jac2XaM1PLNpok1Aep+G/+dfa24nADTX1EWTgOngLE2XCYKQL0DTfKex2WhXgCutxG9i/fFNlwWpgBQL6orcWyTaldToRbUA2pow61XL0WPFfXCb1HqkPowCj6q0+qIWsw7nlpUj6i31OXY+0AdbGpCRtNRGgt1AigCX4EqsJAYTR+wAzgEdAM/gApwM4TwOOm3JiARtBk4CYwAB4F+oIfGZi/HwOfAM6ASQviU5/Vv4xcBzmW2eT1nrQAAAABJRU5ErkJggg==",
"mimeType": "image/png",
+ "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACCElEQVRIid2UPWsUYRSFn3dxWWJUkESiBgslFokfhehGiGClBBQx4h9IGlEh2ijYxh+gxEL/hIWwhYpF8KNZsFRJYdJEiUbjCkqisj4W+y6Mk5nd1U4PDMOce+45L3fmDvzXUDeo59WK+kb9rn5TF9R76jm1+2/NJ9QPtseSOv4nxrvVmQ6M05hRB9qZ98ZR1NRralntitdEwmw8wQ9HbS329rQKuKLW1XJO/aX6IqdWjr1Xk/y6lG4vMBdCqOacoZZ3uBBCVZ0HDrcK2AYs5ZkAuwBb1N8Dm5JEISXoAnqzOtU9QB+wVR3KCdgClDIr6kCc4c/0O1BLNnahiYpaSmmGY62e/JpCLJ4FpmmMaBHYCDwC5mmMZBQYBC7HnhvAK+B+fN4JHAM+R4+3wGQI4S7qaExtol+9o86pq+oX9Yk6ljjtGfVprK2qr9Xb6vaET109jjqb3Jac2XaM1PLNpok1Aep+G/+dfa24nADTX1EWTgOngLE2XCYKQL0DTfKex2WhXgCutxG9i/fFNlwWpgBQL6orcWyTaldToRbUA2pow61XL0WPFfXCb1HqkPowCj6q0+qIWsw7nlpUj6i31OXY+0AdbGpCRtNRGgt1AigCX4EqsJAYTR+wAzgEdAM/gApwM4TwOOm3JiARtBk4CYwAB4F+oIfGZi/HwOfAM6ASQviU5/Vv4xcBzmW2eT1nrQAAAABJRU5ErkJggg==",
"theme": "dark"
}
- ]
+ ],
+ "inputSchema": {
+ "properties": {
+ "owner": {
+ "description": "Repository owner",
+ "type": "string"
+ },
+ "pullNumber": {
+ "description": "Pull request number",
+ "type": "number"
+ },
+ "repo": {
+ "description": "Repository name",
+ "type": "string"
+ }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "pullNumber"
+ ],
+ "type": "object"
+ },
+ "name": "request_copilot_review"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/rerun_failed_jobs.snap b/pkg/github/__toolsnaps__/rerun_failed_jobs.snap
index 2c627637c..099c89153 100644
--- a/pkg/github/__toolsnaps__/rerun_failed_jobs.snap
+++ b/pkg/github/__toolsnaps__/rerun_failed_jobs.snap
@@ -4,26 +4,26 @@
},
"description": "Re-run only the failed jobs in a workflow run",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "run_id"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"run_id": {
- "type": "number",
- "description": "The unique identifier of the workflow run"
+ "description": "The unique identifier of the workflow run",
+ "type": "number"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "run_id"
+ ],
+ "type": "object"
},
"name": "rerun_failed_jobs"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/rerun_workflow_run.snap b/pkg/github/__toolsnaps__/rerun_workflow_run.snap
index 00514ee79..946bd72f3 100644
--- a/pkg/github/__toolsnaps__/rerun_workflow_run.snap
+++ b/pkg/github/__toolsnaps__/rerun_workflow_run.snap
@@ -4,26 +4,26 @@
},
"description": "Re-run an entire workflow run",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "run_id"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"run_id": {
- "type": "number",
- "description": "The unique identifier of the workflow run"
+ "description": "The unique identifier of the workflow run",
+ "type": "number"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "run_id"
+ ],
+ "type": "object"
},
"name": "rerun_workflow_run"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/run_workflow.snap b/pkg/github/__toolsnaps__/run_workflow.snap
index bb35e8213..1b6c8993e 100644
--- a/pkg/github/__toolsnaps__/run_workflow.snap
+++ b/pkg/github/__toolsnaps__/run_workflow.snap
@@ -4,35 +4,35 @@
},
"description": "Run an Actions workflow by workflow ID or filename",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "workflow_id",
- "ref"
- ],
"properties": {
"inputs": {
- "type": "object",
- "description": "Inputs the workflow accepts"
+ "description": "Inputs the workflow accepts",
+ "type": "object"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"ref": {
- "type": "string",
- "description": "The git reference for the workflow. The reference can be a branch or tag name."
+ "description": "The git reference for the workflow. The reference can be a branch or tag name.",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"workflow_id": {
- "type": "string",
- "description": "The workflow ID (numeric) or workflow file name (e.g., main.yml, ci.yaml)"
+ "description": "The workflow ID (numeric) or workflow file name (e.g., main.yml, ci.yaml)",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "workflow_id",
+ "ref"
+ ],
+ "type": "object"
},
"name": "run_workflow"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/search_code.snap b/pkg/github/__toolsnaps__/search_code.snap
index aebd432bf..8b5510aa6 100644
--- a/pkg/github/__toolsnaps__/search_code.snap
+++ b/pkg/github/__toolsnaps__/search_code.snap
@@ -5,39 +5,39 @@
},
"description": "Fast and precise code search across ALL GitHub repositories using GitHub's native search engine. Best for finding exact symbols, functions, classes, or specific code patterns.",
"inputSchema": {
- "type": "object",
- "required": [
- "query"
- ],
"properties": {
"order": {
- "type": "string",
"description": "Sort order for results",
"enum": [
"asc",
"desc"
- ]
+ ],
+ "type": "string"
},
"page": {
- "type": "number",
"description": "Page number for pagination (min 1)",
- "minimum": 1
+ "minimum": 1,
+ "type": "number"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"query": {
- "type": "string",
- "description": "Search query using GitHub's powerful code search syntax. Examples: 'content:Skill language:Java org:github', 'NOT is:archived language:Python OR language:go', 'repo:github/github-mcp-server'. Supports exact matching, language filters, path filters, and more."
+ "description": "Search query using GitHub's powerful code search syntax. Examples: 'content:Skill language:Java org:github', 'NOT is:archived language:Python OR language:go', 'repo:github/github-mcp-server'. Supports exact matching, language filters, path filters, and more.",
+ "type": "string"
},
"sort": {
- "type": "string",
- "description": "Sort field ('indexed' only)"
+ "description": "Sort field ('indexed' only)",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "query"
+ ],
+ "type": "object"
},
"name": "search_code"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/search_issues.snap b/pkg/github/__toolsnaps__/search_issues.snap
index f76a715fb..beaa5b737 100644
--- a/pkg/github/__toolsnaps__/search_issues.snap
+++ b/pkg/github/__toolsnaps__/search_issues.snap
@@ -5,44 +5,39 @@
},
"description": "Search for issues in GitHub repositories using issues search syntax already scoped to is:issue",
"inputSchema": {
- "type": "object",
- "required": [
- "query"
- ],
"properties": {
"order": {
- "type": "string",
"description": "Sort order",
"enum": [
"asc",
"desc"
- ]
+ ],
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Optional repository owner. If provided with repo, only issues for this repository are listed."
+ "description": "Optional repository owner. If provided with repo, only issues for this repository are listed.",
+ "type": "string"
},
"page": {
- "type": "number",
"description": "Page number for pagination (min 1)",
- "minimum": 1
+ "minimum": 1,
+ "type": "number"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"query": {
- "type": "string",
- "description": "Search query using GitHub issues search syntax"
+ "description": "Search query using GitHub issues search syntax",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Optional repository name. If provided with owner, only issues for this repository are listed."
+ "description": "Optional repository name. If provided with owner, only issues for this repository are listed.",
+ "type": "string"
},
"sort": {
- "type": "string",
"description": "Sort field by number of matches of categories, defaults to best match",
"enum": [
"comments",
@@ -56,9 +51,14 @@
"interactions",
"created",
"updated"
- ]
+ ],
+ "type": "string"
}
- }
+ },
+ "required": [
+ "query"
+ ],
+ "type": "object"
},
"name": "search_issues"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/search_orgs.snap b/pkg/github/__toolsnaps__/search_orgs.snap
index 36eb948ae..9670a4be8 100644
--- a/pkg/github/__toolsnaps__/search_orgs.snap
+++ b/pkg/github/__toolsnaps__/search_orgs.snap
@@ -5,44 +5,44 @@
},
"description": "Find GitHub organizations by name, location, or other organization metadata. Ideal for discovering companies, open source foundations, or teams.",
"inputSchema": {
- "type": "object",
- "required": [
- "query"
- ],
"properties": {
"order": {
- "type": "string",
"description": "Sort order",
"enum": [
"asc",
"desc"
- ]
+ ],
+ "type": "string"
},
"page": {
- "type": "number",
"description": "Page number for pagination (min 1)",
- "minimum": 1
+ "minimum": 1,
+ "type": "number"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"query": {
- "type": "string",
- "description": "Organization search query. Examples: 'microsoft', 'location:california', 'created:\u003e=2025-01-01'. Search is automatically scoped to type:org."
+ "description": "Organization search query. Examples: 'microsoft', 'location:california', 'created:\u003e=2025-01-01'. Search is automatically scoped to type:org.",
+ "type": "string"
},
"sort": {
- "type": "string",
"description": "Sort field by category",
"enum": [
"followers",
"repositories",
"joined"
- ]
+ ],
+ "type": "string"
}
- }
+ },
+ "required": [
+ "query"
+ ],
+ "type": "object"
},
"name": "search_orgs"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/search_pull_requests.snap b/pkg/github/__toolsnaps__/search_pull_requests.snap
index 2013f5c08..05376c006 100644
--- a/pkg/github/__toolsnaps__/search_pull_requests.snap
+++ b/pkg/github/__toolsnaps__/search_pull_requests.snap
@@ -5,44 +5,39 @@
},
"description": "Search for pull requests in GitHub repositories using issues search syntax already scoped to is:pr",
"inputSchema": {
- "type": "object",
- "required": [
- "query"
- ],
"properties": {
"order": {
- "type": "string",
"description": "Sort order",
"enum": [
"asc",
"desc"
- ]
+ ],
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Optional repository owner. If provided with repo, only pull requests for this repository are listed."
+ "description": "Optional repository owner. If provided with repo, only pull requests for this repository are listed.",
+ "type": "string"
},
"page": {
- "type": "number",
"description": "Page number for pagination (min 1)",
- "minimum": 1
+ "minimum": 1,
+ "type": "number"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"query": {
- "type": "string",
- "description": "Search query using GitHub pull request search syntax"
+ "description": "Search query using GitHub pull request search syntax",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Optional repository name. If provided with owner, only pull requests for this repository are listed."
+ "description": "Optional repository name. If provided with owner, only pull requests for this repository are listed.",
+ "type": "string"
},
"sort": {
- "type": "string",
"description": "Sort field by number of matches of categories, defaults to best match",
"enum": [
"comments",
@@ -56,9 +51,14 @@
"interactions",
"created",
"updated"
- ]
+ ],
+ "type": "string"
}
- }
+ },
+ "required": [
+ "query"
+ ],
+ "type": "object"
},
"name": "search_pull_requests"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/search_repositories.snap b/pkg/github/__toolsnaps__/search_repositories.snap
index 881bc3816..8e1cb3171 100644
--- a/pkg/github/__toolsnaps__/search_repositories.snap
+++ b/pkg/github/__toolsnaps__/search_repositories.snap
@@ -5,50 +5,50 @@
},
"description": "Find GitHub repositories by name, description, readme, topics, or other metadata. Perfect for discovering projects, finding examples, or locating specific repositories across GitHub.",
"inputSchema": {
- "type": "object",
- "required": [
- "query"
- ],
"properties": {
"minimal_output": {
- "type": "boolean",
+ "default": true,
"description": "Return minimal repository information (default: true). When false, returns full GitHub API repository objects.",
- "default": true
+ "type": "boolean"
},
"order": {
- "type": "string",
"description": "Sort order",
"enum": [
"asc",
"desc"
- ]
+ ],
+ "type": "string"
},
"page": {
- "type": "number",
"description": "Page number for pagination (min 1)",
- "minimum": 1
+ "minimum": 1,
+ "type": "number"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"query": {
- "type": "string",
- "description": "Repository search query. Examples: 'machine learning in:name stars:\u003e1000 language:python', 'topic:react', 'user:facebook'. Supports advanced search syntax for precise filtering."
+ "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": {
- "type": "string",
"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"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/search_users.snap b/pkg/github/__toolsnaps__/search_users.snap
index 293107696..bed86e8c6 100644
--- a/pkg/github/__toolsnaps__/search_users.snap
+++ b/pkg/github/__toolsnaps__/search_users.snap
@@ -5,44 +5,44 @@
},
"description": "Find GitHub users by username, real name, or other profile information. Useful for locating developers, contributors, or team members.",
"inputSchema": {
- "type": "object",
- "required": [
- "query"
- ],
"properties": {
"order": {
- "type": "string",
"description": "Sort order",
"enum": [
"asc",
"desc"
- ]
+ ],
+ "type": "string"
},
"page": {
- "type": "number",
"description": "Page number for pagination (min 1)",
- "minimum": 1
+ "minimum": 1,
+ "type": "number"
},
"perPage": {
- "type": "number",
"description": "Results per page for pagination (min 1, max 100)",
+ "maximum": 100,
"minimum": 1,
- "maximum": 100
+ "type": "number"
},
"query": {
- "type": "string",
- "description": "User search query. Examples: 'john smith', 'location:seattle', 'followers:\u003e100'. Search is automatically scoped to type:user."
+ "description": "User search query. Examples: 'john smith', 'location:seattle', 'followers:\u003e100'. Search is automatically scoped to type:user.",
+ "type": "string"
},
"sort": {
- "type": "string",
"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"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/star_repository.snap b/pkg/github/__toolsnaps__/star_repository.snap
index ab1514b3d..3d7088939 100644
--- a/pkg/github/__toolsnaps__/star_repository.snap
+++ b/pkg/github/__toolsnaps__/star_repository.snap
@@ -3,34 +3,34 @@
"title": "Star repository"
},
"description": "Star a GitHub repository",
- "inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo"
- ],
- "properties": {
- "owner": {
- "type": "string",
- "description": "Repository owner"
- },
- "repo": {
- "type": "string",
- "description": "Repository name"
- }
- }
- },
- "name": "star_repository",
"icons": [
{
- "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACG0lEQVRIidWVMWgTYRiGn+/+a21EClGrERRiTWLShrbiUETErDq7u3QRF0WhoKN06uYgKEVx1lGQLjo4OTUJ2FzSpBrEQiCkGYPm7nMwlZBe2rvaxXf6eb//fd//u/+7O0MIJDJz905MnJpvNRufg2oksHli5iwjUgXExUp9La3Vg+isoAGMyiJwBBi11XsQVBaog0zm8plfdGtApEd1LJdEpVL4sZ82UAc/cRf7zAHGPKMPg2j37eB8NnvauGYTODpQ6hjPulAur23tpTd7FePx+JhtIkvAVZ+yraJj48ciH9rtdneYhwCk03NxV5hWNAWSVLykIEngHPs/Rg/4ruiGYG2AbghSMcoXx8l/k3R6Lt4V3STEyAaE2iqTluPk66Arh2wO6Irj5OsGoNVsvIuejEVFmD8Ua+V5zSneAfTvJW83G6vHJ2LjwJV/tH9Wc4p3AYWBKWo1G6vRiZgRuH4ga3S5Vire7+d2jel2s/HxICEKT2ql4qNB3ncEbU9fhTEHGFF56cf7BrhCNmyAi/pqhr1EoQN0iGZIgEyHDUDw1dghNneB1731bR9tsA5yuZwNZPooBd4YT7PVUmGhWios2CpJEV7w5zu0g0xPO3DWAUymZ1OWUO6V3yP6uLpeWPM7XWJq9hIqS6A3ADzl4qZTqPTv2ZUYMd2tjms/NZa+rawXPvkZ76AXfDM1NXPN9eRWxHT3/Df8n/gNrfGxihYBZk0AAAAASUVORK5CYII=",
"mimeType": "image/png",
+ "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAACG0lEQVRIidWVMWgTYRiGn+/+a21EClGrERRiTWLShrbiUETErDq7u3QRF0WhoKN06uYgKEVx1lGQLjo4OTUJ2FzSpBrEQiCkGYPm7nMwlZBe2rvaxXf6eb//fd//u/+7O0MIJDJz905MnJpvNRufg2oksHli5iwjUgXExUp9La3Vg+isoAGMyiJwBBi11XsQVBaog0zm8plfdGtApEd1LJdEpVL4sZ82UAc/cRf7zAHGPKMPg2j37eB8NnvauGYTODpQ6hjPulAur23tpTd7FePx+JhtIkvAVZ+yraJj48ciH9rtdneYhwCk03NxV5hWNAWSVLykIEngHPs/Rg/4ruiGYG2AbghSMcoXx8l/k3R6Lt4V3STEyAaE2iqTluPk66Arh2wO6Irj5OsGoNVsvIuejEVFmD8Ua+V5zSneAfTvJW83G6vHJ2LjwJV/tH9Wc4p3AYWBKWo1G6vRiZgRuH4ga3S5Vire7+d2jel2s/HxICEKT2ql4qNB3ncEbU9fhTEHGFF56cf7BrhCNmyAi/pqhr1EoQN0iGZIgEyHDUDw1dghNneB1731bR9tsA5yuZwNZPooBd4YT7PVUmGhWios2CpJEV7w5zu0g0xPO3DWAUymZ1OWUO6V3yP6uLpeWPM7XWJq9hIqS6A3ADzl4qZTqPTv2ZUYMd2tjms/NZa+rawXPvkZ76AXfDM1NXPN9eRWxHT3/Df8n/gNrfGxihYBZk0AAAAASUVORK5CYII=",
"theme": "light"
},
{
- "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABLElEQVRIidWVTUrDQBiG3ylddNG9+IdRMC7qpgfoWTyCnqFLF/62UMGNWw/gTdwoiMUzaCtoHxeZ4BQn6SQdEV8IZPG9zzMzCYlUIcARcFilUwW+AUyBd2DrNwSXfOciNnwVeHMEE2A9puCMnzmNBV8BXj2CCbC2LLwFDDzwPAOgVcYwFpRI6khKJe0616akxoJ1zCS9SHp0rgdJ98aYZ2PhT7ksYpC005A0lnQdGS7LHGcqMMB5yVlXzQiYP1orOYkAHwLFxw30l4AfBx1eTUk/+OkA2zUEiY9V9I7vB69mQefPBJ0aAm+nWWH4Q9KNvT/wdMN2DTTJ/lx5ZsAtsOfMJMAV8OnMTYGiBc8JUqd0B3RLZrt2Jk8aImiTfTZ6QVvOOj3baYd2/k++AC+3Yx0GcXS0AAAAAElFTkSuQmCC",
"mimeType": "image/png",
+ "src": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABLElEQVRIidWVTUrDQBiG3ylddNG9+IdRMC7qpgfoWTyCnqFLF/62UMGNWw/gTdwoiMUzaCtoHxeZ4BQn6SQdEV8IZPG9zzMzCYlUIcARcFilUwW+AUyBd2DrNwSXfOciNnwVeHMEE2A9puCMnzmNBV8BXj2CCbC2LLwFDDzwPAOgVcYwFpRI6khKJe0616akxoJ1zCS9SHp0rgdJ98aYZ2PhT7ksYpC005A0lnQdGS7LHGcqMMB5yVlXzQiYP1orOYkAHwLFxw30l4AfBx1eTUk/+OkA2zUEiY9V9I7vB69mQefPBJ0aAm+nWWH4Q9KNvT/wdMN2DTTJ/lx5ZsAtsOfMJMAV8OnMTYGiBc8JUqd0B3RLZrt2Jk8aImiTfTZ6QVvOOj3baYd2/k++AC+3Yx0GcXS0AAAAAElFTkSuQmCC",
"theme": "dark"
}
- ]
+ ],
+ "inputSchema": {
+ "properties": {
+ "owner": {
+ "description": "Repository owner",
+ "type": "string"
+ },
+ "repo": {
+ "description": "Repository name",
+ "type": "string"
+ }
+ },
+ "required": [
+ "owner",
+ "repo"
+ ],
+ "type": "object"
+ },
+ "name": "star_repository"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/sub_issue_write.snap b/pkg/github/__toolsnaps__/sub_issue_write.snap
index 1c721a2bb..1e4fcceab 100644
--- a/pkg/github/__toolsnaps__/sub_issue_write.snap
+++ b/pkg/github/__toolsnaps__/sub_issue_write.snap
@@ -4,48 +4,48 @@
},
"description": "Add a sub-issue to a parent issue in a GitHub repository.",
"inputSchema": {
- "type": "object",
- "required": [
- "method",
- "owner",
- "repo",
- "issue_number",
- "sub_issue_id"
- ],
"properties": {
"after_id": {
- "type": "number",
- "description": "The ID of the sub-issue to be prioritized after (either after_id OR before_id should be specified)"
+ "description": "The ID of the sub-issue to be prioritized after (either after_id OR before_id should be specified)",
+ "type": "number"
},
"before_id": {
- "type": "number",
- "description": "The ID of the sub-issue to be prioritized before (either after_id OR before_id should be specified)"
+ "description": "The ID of the sub-issue to be prioritized before (either after_id OR before_id should be specified)",
+ "type": "number"
},
"issue_number": {
- "type": "number",
- "description": "The number of the parent issue"
+ "description": "The number of the parent issue",
+ "type": "number"
},
"method": {
- "type": "string",
- "description": "The action to perform on a single sub-issue\nOptions are:\n- 'add' - add a sub-issue to a parent issue in a GitHub repository.\n- 'remove' - remove a sub-issue from a parent issue in a GitHub repository.\n- 'reprioritize' - change the order of sub-issues within a parent issue in a GitHub repository. Use either 'after_id' or 'before_id' to specify the new position.\n\t\t\t\t"
+ "description": "The action to perform on a single sub-issue\nOptions are:\n- 'add' - add a sub-issue to a parent issue in a GitHub repository.\n- 'remove' - remove a sub-issue from a parent issue in a GitHub repository.\n- 'reprioritize' - change the order of sub-issues within a parent issue in a GitHub repository. Use either 'after_id' or 'before_id' to specify the new position.\n\t\t\t\t",
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"replace_parent": {
- "type": "boolean",
- "description": "When true, replaces the sub-issue's current parent issue. Use with 'add' method only."
+ "description": "When true, replaces the sub-issue's current parent issue. Use with 'add' method only.",
+ "type": "boolean"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"sub_issue_id": {
- "type": "number",
- "description": "The ID of the sub-issue to add. ID is not the same as issue number"
+ "description": "The ID of the sub-issue to add. ID is not the same as issue number",
+ "type": "number"
}
- }
+ },
+ "required": [
+ "method",
+ "owner",
+ "repo",
+ "issue_number",
+ "sub_issue_id"
+ ],
+ "type": "object"
},
"name": "sub_issue_write"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/unstar_repository.snap b/pkg/github/__toolsnaps__/unstar_repository.snap
index 709453650..2bb5d6825 100644
--- a/pkg/github/__toolsnaps__/unstar_repository.snap
+++ b/pkg/github/__toolsnaps__/unstar_repository.snap
@@ -4,21 +4,21 @@
},
"description": "Unstar a GitHub repository",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo"
- ],
"properties": {
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo"
+ ],
+ "type": "object"
},
"name": "unstar_repository"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/update_gist.snap b/pkg/github/__toolsnaps__/update_gist.snap
index a3907a88c..6d5ed100e 100644
--- a/pkg/github/__toolsnaps__/update_gist.snap
+++ b/pkg/github/__toolsnaps__/update_gist.snap
@@ -4,30 +4,30 @@
},
"description": "Update an existing gist",
"inputSchema": {
- "type": "object",
- "required": [
- "gist_id",
- "filename",
- "content"
- ],
"properties": {
"content": {
- "type": "string",
- "description": "Content for the file"
+ "description": "Content for the file",
+ "type": "string"
},
"description": {
- "type": "string",
- "description": "Updated description of the gist"
+ "description": "Updated description of the gist",
+ "type": "string"
},
"filename": {
- "type": "string",
- "description": "Filename to update or create"
+ "description": "Filename to update or create",
+ "type": "string"
},
"gist_id": {
- "type": "string",
- "description": "ID of the gist to update"
+ "description": "ID of the gist to update",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "gist_id",
+ "filename",
+ "content"
+ ],
+ "type": "object"
},
"name": "update_gist"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/update_project_item.snap b/pkg/github/__toolsnaps__/update_project_item.snap
index 8f5afaa58..987590741 100644
--- a/pkg/github/__toolsnaps__/update_project_item.snap
+++ b/pkg/github/__toolsnaps__/update_project_item.snap
@@ -4,40 +4,40 @@
},
"description": "Update a specific Project item for a user or org",
"inputSchema": {
- "type": "object",
- "required": [
- "owner_type",
- "owner",
- "project_number",
- "item_id",
- "updated_field"
- ],
"properties": {
"item_id": {
- "type": "number",
- "description": "The unique identifier of the project item. This is not the issue or pull request ID."
+ "description": "The unique identifier of the project item. This is not the issue or pull request ID.",
+ "type": "number"
},
"owner": {
- "type": "string",
- "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive."
+ "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive.",
+ "type": "string"
},
"owner_type": {
- "type": "string",
"description": "Owner type",
"enum": [
"user",
"org"
- ]
+ ],
+ "type": "string"
},
"project_number": {
- "type": "number",
- "description": "The project's number."
+ "description": "The project's number.",
+ "type": "number"
},
"updated_field": {
- "type": "object",
- "description": "Object consisting of the ID of the project field to update and the new value for the field. To clear the field, set value to null. Example: {\"id\": 123456, \"value\": \"New Value\"}"
+ "description": "Object consisting of the ID of the project field to update and the new value for the field. To clear the field, set value to null. Example: {\"id\": 123456, \"value\": \"New Value\"}",
+ "type": "object"
}
- }
+ },
+ "required": [
+ "owner_type",
+ "owner",
+ "project_number",
+ "item_id",
+ "updated_field"
+ ],
+ "type": "object"
},
"name": "update_project_item"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/update_pull_request.snap b/pkg/github/__toolsnaps__/update_pull_request.snap
index 6dec2c01f..ef330188f 100644
--- a/pkg/github/__toolsnaps__/update_pull_request.snap
+++ b/pkg/github/__toolsnaps__/update_pull_request.snap
@@ -4,61 +4,61 @@
},
"description": "Update an existing pull request in a GitHub repository.",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "pullNumber"
- ],
"properties": {
"base": {
- "type": "string",
- "description": "New base branch name"
+ "description": "New base branch name",
+ "type": "string"
},
"body": {
- "type": "string",
- "description": "New description"
+ "description": "New description",
+ "type": "string"
},
"draft": {
- "type": "boolean",
- "description": "Mark pull request as draft (true) or ready for review (false)"
+ "description": "Mark pull request as draft (true) or ready for review (false)",
+ "type": "boolean"
},
"maintainer_can_modify": {
- "type": "boolean",
- "description": "Allow maintainer edits"
+ "description": "Allow maintainer edits",
+ "type": "boolean"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"pullNumber": {
- "type": "number",
- "description": "Pull request number to update"
+ "description": "Pull request number to update",
+ "type": "number"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
},
"reviewers": {
- "type": "array",
"description": "GitHub usernames to request reviews from",
"items": {
"type": "string"
- }
+ },
+ "type": "array"
},
"state": {
- "type": "string",
"description": "New state",
"enum": [
"open",
"closed"
- ]
+ ],
+ "type": "string"
},
"title": {
- "type": "string",
- "description": "New title"
+ "description": "New title",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "pullNumber"
+ ],
+ "type": "object"
},
"name": "update_pull_request"
}
\ No newline at end of file
diff --git a/pkg/github/__toolsnaps__/update_pull_request_branch.snap b/pkg/github/__toolsnaps__/update_pull_request_branch.snap
index 9be1cb002..a84ac414d 100644
--- a/pkg/github/__toolsnaps__/update_pull_request_branch.snap
+++ b/pkg/github/__toolsnaps__/update_pull_request_branch.snap
@@ -4,30 +4,30 @@
},
"description": "Update the branch of a pull request with the latest changes from the base branch.",
"inputSchema": {
- "type": "object",
- "required": [
- "owner",
- "repo",
- "pullNumber"
- ],
"properties": {
"expectedHeadSha": {
- "type": "string",
- "description": "The expected SHA of the pull request's HEAD ref"
+ "description": "The expected SHA of the pull request's HEAD ref",
+ "type": "string"
},
"owner": {
- "type": "string",
- "description": "Repository owner"
+ "description": "Repository owner",
+ "type": "string"
},
"pullNumber": {
- "type": "number",
- "description": "Pull request number"
+ "description": "Pull request number",
+ "type": "number"
},
"repo": {
- "type": "string",
- "description": "Repository name"
+ "description": "Repository name",
+ "type": "string"
}
- }
+ },
+ "required": [
+ "owner",
+ "repo",
+ "pullNumber"
+ ],
+ "type": "object"
},
"name": "update_pull_request_branch"
}
\ No newline at end of file
diff --git a/pkg/github/dependencies.go b/pkg/github/dependencies.go
index b41bf0b87..15d807a24 100644
--- a/pkg/github/dependencies.go
+++ b/pkg/github/dependencies.go
@@ -3,6 +3,8 @@ package github
import (
"context"
"errors"
+ "fmt"
+ "os"
"github.com/github/github-mcp-server/pkg/inventory"
"github.com/github/github-mcp-server/pkg/lockdown"
@@ -77,6 +79,9 @@ type ToolDependencies interface {
// GetContentWindowSize returns the content window size for log truncation
GetContentWindowSize() int
+
+ // IsFeatureEnabled checks if a feature flag is enabled.
+ IsFeatureEnabled(ctx context.Context, flagName string) bool
}
// BaseDeps is the standard implementation of ToolDependencies for the local server.
@@ -93,8 +98,14 @@ type BaseDeps struct {
T translations.TranslationHelperFunc
Flags FeatureFlags
ContentWindowSize int
+
+ // Feature flag checker for runtime checks
+ featureChecker inventory.FeatureFlagChecker
}
+// Compile-time assertion to verify that BaseDeps implements the ToolDependencies interface.
+var _ ToolDependencies = (*BaseDeps)(nil)
+
// NewBaseDeps creates a BaseDeps with the provided clients and configuration.
func NewBaseDeps(
client *gogithub.Client,
@@ -104,6 +115,7 @@ func NewBaseDeps(
t translations.TranslationHelperFunc,
flags FeatureFlags,
contentWindowSize int,
+ featureChecker inventory.FeatureFlagChecker,
) *BaseDeps {
return &BaseDeps{
Client: client,
@@ -113,6 +125,7 @@ func NewBaseDeps(
T: t,
Flags: flags,
ContentWindowSize: contentWindowSize,
+ featureChecker: featureChecker,
}
}
@@ -143,6 +156,24 @@ func (d BaseDeps) GetFlags() FeatureFlags { return d.Flags }
// GetContentWindowSize implements ToolDependencies.
func (d BaseDeps) GetContentWindowSize() int { return d.ContentWindowSize }
+// IsFeatureEnabled checks if a feature flag is enabled.
+// Returns false if the feature checker is nil, flag name is empty, or an error occurs.
+// This allows tools to conditionally change behavior based on feature flags.
+func (d BaseDeps) IsFeatureEnabled(ctx context.Context, flagName string) bool {
+ if d.featureChecker == nil || flagName == "" {
+ return false
+ }
+
+ enabled, err := d.featureChecker(ctx, flagName)
+ if err != nil {
+ // Log error but don't fail the tool - treat as disabled
+ fmt.Fprintf(os.Stderr, "Feature flag check error for %q: %v\n", flagName, err)
+ return false
+ }
+
+ return enabled
+}
+
// NewTool creates a ServerTool that retrieves ToolDependencies from context at call time.
// This avoids creating closures at registration time, which is important for performance
// in servers that create a new server instance per request (like the remote server).
diff --git a/pkg/github/dependencies_test.go b/pkg/github/dependencies_test.go
new file mode 100644
index 000000000..d13160d4c
--- /dev/null
+++ b/pkg/github/dependencies_test.go
@@ -0,0 +1,108 @@
+package github_test
+
+import (
+ "context"
+ "errors"
+ "testing"
+
+ "github.com/github/github-mcp-server/pkg/github"
+ "github.com/github/github-mcp-server/pkg/translations"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestIsFeatureEnabled_WithEnabledFlag(t *testing.T) {
+ t.Parallel()
+
+ // Create a feature checker that returns true for "test_flag"
+ checker := func(_ context.Context, flagName string) (bool, error) {
+ return flagName == "test_flag", nil
+ }
+
+ // Create deps with the checker using NewBaseDeps
+ deps := github.NewBaseDeps(
+ nil, // client
+ nil, // gqlClient
+ nil, // rawClient
+ nil, // repoAccessCache
+ translations.NullTranslationHelper,
+ github.FeatureFlags{},
+ 0, // contentWindowSize
+ checker, // featureChecker
+ )
+
+ // Test enabled flag
+ result := deps.IsFeatureEnabled(context.Background(), "test_flag")
+ assert.True(t, result, "Expected test_flag to be enabled")
+
+ // Test disabled flag
+ result = deps.IsFeatureEnabled(context.Background(), "other_flag")
+ assert.False(t, result, "Expected other_flag to be disabled")
+}
+
+func TestIsFeatureEnabled_WithoutChecker(t *testing.T) {
+ t.Parallel()
+
+ // Create deps without feature checker (nil)
+ deps := github.NewBaseDeps(
+ nil, // client
+ nil, // gqlClient
+ nil, // rawClient
+ nil, // repoAccessCache
+ translations.NullTranslationHelper,
+ github.FeatureFlags{},
+ 0, // contentWindowSize
+ nil, // featureChecker (nil)
+ )
+
+ // Should return false when checker is nil
+ result := deps.IsFeatureEnabled(context.Background(), "any_flag")
+ assert.False(t, result, "Expected false when checker is nil")
+}
+
+func TestIsFeatureEnabled_EmptyFlagName(t *testing.T) {
+ t.Parallel()
+
+ // Create a feature checker
+ checker := func(_ context.Context, _ string) (bool, error) {
+ return true, nil
+ }
+
+ deps := github.NewBaseDeps(
+ nil, // client
+ nil, // gqlClient
+ nil, // rawClient
+ nil, // repoAccessCache
+ translations.NullTranslationHelper,
+ github.FeatureFlags{},
+ 0, // contentWindowSize
+ checker, // featureChecker
+ )
+
+ // Should return false for empty flag name
+ result := deps.IsFeatureEnabled(context.Background(), "")
+ assert.False(t, result, "Expected false for empty flag name")
+}
+
+func TestIsFeatureEnabled_CheckerError(t *testing.T) {
+ t.Parallel()
+
+ // Create a feature checker that returns an error
+ checker := func(_ context.Context, _ string) (bool, error) {
+ return false, errors.New("checker error")
+ }
+
+ deps := github.NewBaseDeps(
+ nil, // client
+ nil, // gqlClient
+ nil, // rawClient
+ nil, // repoAccessCache
+ translations.NullTranslationHelper,
+ github.FeatureFlags{},
+ 0, // contentWindowSize
+ checker, // featureChecker
+ )
+
+ // Should return false and log error (not crash)
+ result := deps.IsFeatureEnabled(context.Background(), "error_flag")
+ assert.False(t, result, "Expected false when checker returns error")
+}
diff --git a/pkg/github/dynamic_tools_test.go b/pkg/github/dynamic_tools_test.go
index 8d12b78c2..3e63c5d7b 100644
--- a/pkg/github/dynamic_tools_test.go
+++ b/pkg/github/dynamic_tools_test.go
@@ -25,9 +25,10 @@ func createDynamicRequest(args map[string]any) *mcp.CallToolRequest {
func TestDynamicTools_ListAvailableToolsets(t *testing.T) {
// Build a registry with no toolsets enabled (dynamic mode)
- reg := NewInventory(translations.NullTranslationHelper).
+ reg, err := NewInventory(translations.NullTranslationHelper).
WithToolsets([]string{}).
Build()
+ require.NoError(t, err)
// Create a mock server
server := mcp.NewServer(&mcp.Implementation{Name: "test"}, nil)
@@ -73,9 +74,10 @@ func TestDynamicTools_ListAvailableToolsets(t *testing.T) {
func TestDynamicTools_GetToolsetTools(t *testing.T) {
// Build a registry with no toolsets enabled (dynamic mode)
- reg := NewInventory(translations.NullTranslationHelper).
+ reg, err := NewInventory(translations.NullTranslationHelper).
WithToolsets([]string{}).
Build()
+ require.NoError(t, err)
// Create a mock server
server := mcp.NewServer(&mcp.Implementation{Name: "test"}, nil)
@@ -122,9 +124,10 @@ func TestDynamicTools_GetToolsetTools(t *testing.T) {
func TestDynamicTools_EnableToolset(t *testing.T) {
// Build a registry with no toolsets enabled (dynamic mode)
- reg := NewInventory(translations.NullTranslationHelper).
+ reg, err := NewInventory(translations.NullTranslationHelper).
WithToolsets([]string{}).
Build()
+ require.NoError(t, err)
// Create a mock server
server := mcp.NewServer(&mcp.Implementation{Name: "test"}, nil)
@@ -133,7 +136,7 @@ func TestDynamicTools_EnableToolset(t *testing.T) {
deps := DynamicToolDependencies{
Server: server,
Inventory: reg,
- ToolDeps: NewBaseDeps(nil, nil, nil, nil, translations.NullTranslationHelper, FeatureFlags{}, 0),
+ ToolDeps: NewBaseDeps(nil, nil, nil, nil, translations.NullTranslationHelper, FeatureFlags{}, 0, nil),
T: translations.NullTranslationHelper,
}
@@ -170,9 +173,10 @@ func TestDynamicTools_EnableToolset(t *testing.T) {
func TestDynamicTools_EnableToolset_InvalidToolset(t *testing.T) {
// Build a registry with no toolsets enabled (dynamic mode)
- reg := NewInventory(translations.NullTranslationHelper).
+ reg, err := NewInventory(translations.NullTranslationHelper).
WithToolsets([]string{}).
Build()
+ require.NoError(t, err)
// Create a mock server
server := mcp.NewServer(&mcp.Implementation{Name: "test"}, nil)
@@ -203,7 +207,8 @@ func TestDynamicTools_EnableToolset_InvalidToolset(t *testing.T) {
func TestDynamicTools_ToolsetsEnum(t *testing.T) {
// Build a registry
- reg := NewInventory(translations.NullTranslationHelper).Build()
+ reg, err := NewInventory(translations.NullTranslationHelper).Build()
+ require.NoError(t, err)
// Get tools to verify they have proper enum values
tools := DynamicTools(reg)
diff --git a/pkg/github/feature_flags.go b/pkg/github/feature_flags.go
index 047042e44..dbaa8cec2 100644
--- a/pkg/github/feature_flags.go
+++ b/pkg/github/feature_flags.go
@@ -3,4 +3,5 @@ package github
// FeatureFlags defines runtime feature toggles that adjust tool behavior.
type FeatureFlags struct {
LockdownMode bool
+ InsiderMode bool
}
diff --git a/pkg/github/feature_flags_test.go b/pkg/github/feature_flags_test.go
new file mode 100644
index 000000000..fb50448af
--- /dev/null
+++ b/pkg/github/feature_flags_test.go
@@ -0,0 +1,198 @@
+package github
+
+import (
+ "context"
+ "encoding/json"
+ "testing"
+
+ "github.com/github/github-mcp-server/pkg/translations"
+ "github.com/modelcontextprotocol/go-sdk/mcp"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ "github.com/github/github-mcp-server/pkg/inventory"
+ "github.com/github/github-mcp-server/pkg/scopes"
+ "github.com/github/github-mcp-server/pkg/utils"
+)
+
+// RemoteMCPEnthusiasticGreeting is a dummy test feature flag .
+const RemoteMCPEnthusiasticGreeting = "remote_mcp_enthusiastic_greeting"
+
+// FeatureChecker is an interface for checking if a feature flag is enabled.
+type FeatureChecker interface {
+ // IsFeatureEnabled checks if a feature flag is enabled.
+ IsFeatureEnabled(ctx context.Context, flagName string) bool
+}
+
+// HelloWorld returns a simple greeting tool that demonstrates feature flag conditional behavior.
+// This tool is for testing and demonstration purposes only.
+func HelloWorldTool(t translations.TranslationHelperFunc) inventory.ServerTool {
+ return NewTool(
+ ToolsetMetadataContext, // Use existing "context" toolset
+ mcp.Tool{
+ Name: "hello_world",
+ Description: t("TOOL_HELLO_WORLD_DESCRIPTION", "A simple greeting tool that demonstrates feature flag conditional behavior"),
+ Annotations: &mcp.ToolAnnotations{
+ Title: t("TOOL_HELLO_WORLD_TITLE", "Hello World"),
+ ReadOnlyHint: true,
+ },
+ },
+ []scopes.Scope{},
+ func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, _ map[string]any) (*mcp.CallToolResult, any, error) {
+
+ // Check feature flag to determine greeting style
+ greeting := "Hello, world!"
+ if deps.IsFeatureEnabled(ctx, RemoteMCPEnthusiasticGreeting) {
+ greeting += " Welcome to the future of MCP! 🎉"
+ }
+ if deps.GetFlags().InsiderMode {
+ greeting += " Experimental features are enabled! 🚀"
+ }
+
+ // Build response
+ response := map[string]any{
+ "greeting": greeting,
+ }
+
+ jsonBytes, err := json.Marshal(response)
+ if err != nil {
+ return utils.NewToolResultError("failed to marshal response"), nil, nil
+ }
+
+ return utils.NewToolResultText(string(jsonBytes)), nil, nil
+ },
+ )
+}
+
+func TestHelloWorld_ConditionalBehavior_Featureflag(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ featureFlagEnabled bool
+ inputName string
+ expectedGreeting string
+ }{
+ {
+ name: "Feature flag disabled - default greeting",
+ featureFlagEnabled: false,
+ expectedGreeting: "Hello, world!",
+ },
+ {
+ name: "Feature flag enabled - enthusiastic greeting",
+ featureFlagEnabled: true,
+ expectedGreeting: "Hello, world! Welcome to the future of MCP! 🎉",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ // Create feature checker based on test case
+ checker := func(_ context.Context, flagName string) (bool, error) {
+ if flagName == RemoteMCPEnthusiasticGreeting {
+ return tt.featureFlagEnabled, nil
+ }
+ return false, nil
+ }
+
+ // Create deps with the checker
+ deps := NewBaseDeps(
+ nil, nil, nil, nil,
+ translations.NullTranslationHelper,
+ FeatureFlags{},
+ 0,
+ checker,
+ )
+
+ // Get the tool and its handler
+ tool := HelloWorldTool(translations.NullTranslationHelper)
+ handler := tool.Handler(deps)
+
+ // Call the handler with deps in context
+ ctx := ContextWithDeps(context.Background(), deps)
+ result, err := handler(ctx, &mcp.CallToolRequest{
+ Params: &mcp.CallToolParamsRaw{
+ Arguments: json.RawMessage(`{}`),
+ },
+ })
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ require.Len(t, result.Content, 1)
+
+ // Parse the response - should be TextContent
+ textContent, ok := result.Content[0].(*mcp.TextContent)
+ require.True(t, ok, "expected content to be TextContent")
+
+ var response map[string]any
+ err = json.Unmarshal([]byte(textContent.Text), &response)
+ require.NoError(t, err)
+
+ // Verify the greeting matches expected based on feature flag
+ assert.Equal(t, tt.expectedGreeting, response["greeting"])
+ })
+ }
+}
+
+func TestHelloWorld_ConditionalBehavior_Config(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ insiderMode bool
+ expectedGreeting string
+ }{
+ {
+ name: "Experimental disabled - default greeting",
+ insiderMode: false,
+ expectedGreeting: "Hello, world!",
+ },
+ {
+ name: "Experimental enabled - experimental greeting",
+ insiderMode: true,
+ expectedGreeting: "Hello, world! Experimental features are enabled! 🚀",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ t.Parallel()
+
+ // Create deps with the checker
+ deps := NewBaseDeps(
+ nil, nil, nil, nil,
+ translations.NullTranslationHelper,
+ FeatureFlags{InsiderMode: tt.insiderMode},
+ 0,
+ nil,
+ )
+
+ // Get the tool and its handler
+ tool := HelloWorldTool(translations.NullTranslationHelper)
+ handler := tool.Handler(deps)
+
+ // Call the handler with deps in context
+ ctx := ContextWithDeps(context.Background(), deps)
+ result, err := handler(ctx, &mcp.CallToolRequest{
+ Params: &mcp.CallToolParamsRaw{
+ Arguments: json.RawMessage(`{}`),
+ },
+ })
+ require.NoError(t, err)
+ require.NotNil(t, result)
+ require.Len(t, result.Content, 1)
+
+ // Parse the response - should be TextContent
+ textContent, ok := result.Content[0].(*mcp.TextContent)
+ require.True(t, ok, "expected content to be TextContent")
+
+ var response map[string]any
+ err = json.Unmarshal([]byte(textContent.Text), &response)
+ require.NoError(t, err)
+
+ // Verify the greeting matches expected based on feature flag
+ assert.Equal(t, tt.expectedGreeting, response["greeting"])
+ })
+ }
+}
diff --git a/pkg/github/issues.go b/pkg/github/issues.go
index 63174c9e9..62e1a0bac 100644
--- a/pkg/github/issues.go
+++ b/pkg/github/issues.go
@@ -1609,6 +1609,104 @@ func (d *mvpDescription) String() string {
return sb.String()
}
+// linkedPullRequest represents a PR linked to an issue by Copilot.
+type linkedPullRequest struct {
+ Number int
+ URL string
+ Title string
+ State string
+ CreatedAt time.Time
+}
+
+// pollConfigKey is a context key for polling configuration.
+type pollConfigKey struct{}
+
+// PollConfig configures the PR polling behavior.
+type PollConfig struct {
+ MaxAttempts int
+ Delay time.Duration
+}
+
+// ContextWithPollConfig returns a context with polling configuration.
+// Use this in tests to reduce or disable polling.
+func ContextWithPollConfig(ctx context.Context, config PollConfig) context.Context {
+ return context.WithValue(ctx, pollConfigKey{}, config)
+}
+
+// getPollConfig returns the polling configuration from context, or defaults.
+func getPollConfig(ctx context.Context) PollConfig {
+ if config, ok := ctx.Value(pollConfigKey{}).(PollConfig); ok {
+ return config
+ }
+ // Default: 9 attempts with 1s delay = 8s max wait
+ // Based on observed latency in remote server: p50 ~5s, p90 ~7s
+ return PollConfig{MaxAttempts: 9, Delay: 1 * time.Second}
+}
+
+// findLinkedCopilotPR searches for a PR created by the copilot-swe-agent bot that references the given issue.
+// It queries the issue's timeline for CrossReferencedEvent items from PRs authored by copilot-swe-agent.
+// The createdAfter parameter filters to only return PRs created after the specified time.
+func findLinkedCopilotPR(ctx context.Context, client *githubv4.Client, owner, repo string, issueNumber int, createdAfter time.Time) (*linkedPullRequest, error) {
+ // Query timeline items looking for CrossReferencedEvent from PRs by copilot-swe-agent
+ var query struct {
+ Repository struct {
+ Issue struct {
+ TimelineItems struct {
+ Nodes []struct {
+ TypeName string `graphql:"__typename"`
+ CrossReferencedEvent struct {
+ Source struct {
+ PullRequest struct {
+ Number int
+ URL string
+ Title string
+ State string
+ CreatedAt githubv4.DateTime
+ Author struct {
+ Login string
+ }
+ } `graphql:"... on PullRequest"`
+ }
+ } `graphql:"... on CrossReferencedEvent"`
+ }
+ } `graphql:"timelineItems(first: 20, itemTypes: [CROSS_REFERENCED_EVENT])"`
+ } `graphql:"issue(number: $number)"`
+ } `graphql:"repository(owner: $owner, name: $name)"`
+ }
+
+ variables := map[string]any{
+ "owner": githubv4.String(owner),
+ "name": githubv4.String(repo),
+ "number": githubv4.Int(issueNumber), //nolint:gosec // Issue numbers are always small positive integers
+ }
+
+ if err := client.Query(ctx, &query, variables); err != nil {
+ return nil, err
+ }
+
+ // Look for a PR from copilot-swe-agent created after the assignment time
+ for _, node := range query.Repository.Issue.TimelineItems.Nodes {
+ if node.TypeName != "CrossReferencedEvent" {
+ continue
+ }
+ pr := node.CrossReferencedEvent.Source.PullRequest
+ if pr.Number > 0 && pr.Author.Login == "copilot-swe-agent" {
+ // Only return PRs created after the assignment time
+ if pr.CreatedAt.Time.After(createdAfter) {
+ return &linkedPullRequest{
+ Number: pr.Number,
+ URL: pr.URL,
+ Title: pr.Title,
+ State: pr.State,
+ CreatedAt: pr.CreatedAt.Time,
+ }, nil
+ }
+ }
+ }
+
+ return nil, nil
+}
+
func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.ServerTool {
description := mvpDescription{
summary: "Assign Copilot to a specific issue in a GitHub repository.",
@@ -1650,17 +1748,22 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server
Type: "string",
Description: "Git reference (e.g., branch) that the agent will start its work from. If not specified, defaults to the repository's default branch",
},
+ "custom_instructions": {
+ Type: "string",
+ Description: "Optional custom instructions to guide the agent beyond the issue body. Use this to provide additional context, constraints, or guidance that is not captured in the issue description",
+ },
},
Required: []string{"owner", "repo", "issue_number"},
},
},
[]scopes.Scope{scopes.Repo},
- func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
+ func(ctx context.Context, deps ToolDependencies, request *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
var params struct {
- Owner string `mapstructure:"owner"`
- Repo string `mapstructure:"repo"`
- IssueNumber int32 `mapstructure:"issue_number"`
- BaseRef string `mapstructure:"base_ref"`
+ Owner string `mapstructure:"owner"`
+ Repo string `mapstructure:"repo"`
+ IssueNumber int32 `mapstructure:"issue_number"`
+ BaseRef string `mapstructure:"base_ref"`
+ CustomInstructions string `mapstructure:"custom_instructions"`
}
if err := mapstructure.Decode(args, ¶ms); err != nil {
return utils.NewToolResultError(err.Error()), nil, nil
@@ -1775,6 +1878,12 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server
agentAssignment.BaseRef = &baseRef
}
+ // Add custom instructions if provided
+ if params.CustomInstructions != "" {
+ customInstructions := githubv4.String(params.CustomInstructions)
+ agentAssignment.CustomInstructions = &customInstructions
+ }
+
// Execute the updateIssue mutation with the GraphQL-Features header
// This header is required for the agent assignment API which is not GA yet
var updateIssueMutation struct {
@@ -1791,6 +1900,9 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server
// The header will be read by the HTTP transport if it's configured to do so
ctxWithFeatures := withGraphQLFeatures(ctx, "issues_copilot_assignment_api_support")
+ // Capture the time before assignment to filter out older PRs during polling
+ assignmentTime := time.Now().UTC()
+
if err := client.Mutate(
ctxWithFeatures,
&updateIssueMutation,
@@ -1804,7 +1916,78 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server
return nil, nil, fmt.Errorf("failed to update issue with agent assignment: %w", err)
}
- return utils.NewToolResultText("successfully assigned copilot to issue"), nil, nil
+ // Poll for a linked PR created by Copilot after the assignment
+ pollConfig := getPollConfig(ctx)
+
+ // Get progress token from request for sending progress notifications
+ progressToken := request.Params.GetProgressToken()
+
+ // Send initial progress notification that assignment succeeded and polling is starting
+ if progressToken != nil && request.Session != nil && pollConfig.MaxAttempts > 0 {
+ _ = request.Session.NotifyProgress(ctx, &mcp.ProgressNotificationParams{
+ ProgressToken: progressToken,
+ Progress: 0,
+ Total: float64(pollConfig.MaxAttempts),
+ Message: "Copilot assigned to issue, waiting for PR creation...",
+ })
+ }
+
+ var linkedPR *linkedPullRequest
+ for attempt := range pollConfig.MaxAttempts {
+ if attempt > 0 {
+ time.Sleep(pollConfig.Delay)
+ }
+
+ // Send progress notification if progress token is available
+ if progressToken != nil && request.Session != nil {
+ _ = request.Session.NotifyProgress(ctx, &mcp.ProgressNotificationParams{
+ ProgressToken: progressToken,
+ Progress: float64(attempt + 1),
+ Total: float64(pollConfig.MaxAttempts),
+ Message: fmt.Sprintf("Waiting for Copilot to create PR... (attempt %d/%d)", attempt+1, pollConfig.MaxAttempts),
+ })
+ }
+
+ pr, err := findLinkedCopilotPR(ctx, client, params.Owner, params.Repo, int(params.IssueNumber), assignmentTime)
+ if err != nil {
+ // Polling errors are non-fatal, continue to next attempt
+ continue
+ }
+ if pr != nil {
+ linkedPR = pr
+ break
+ }
+ }
+
+ // Build the result
+ result := map[string]any{
+ "message": "successfully assigned copilot to issue",
+ "issue_number": int(updateIssueMutation.UpdateIssue.Issue.Number),
+ "issue_url": string(updateIssueMutation.UpdateIssue.Issue.URL),
+ "owner": params.Owner,
+ "repo": params.Repo,
+ }
+
+ // Add PR info if found during polling
+ if linkedPR != nil {
+ result["pull_request"] = map[string]any{
+ "number": linkedPR.Number,
+ "url": linkedPR.URL,
+ "title": linkedPR.Title,
+ "state": linkedPR.State,
+ }
+ result["message"] = "successfully assigned copilot to issue - pull request created"
+ } else {
+ result["message"] = "successfully assigned copilot to issue - pull request pending"
+ result["note"] = "The pull request may still be in progress. Once created, the PR number can be used to check job status, or check the issue timeline for updates."
+ }
+
+ r, err := json.Marshal(result)
+ if err != nil {
+ return utils.NewToolResultError(fmt.Sprintf("failed to marshal response: %s", err)), nil, nil
+ }
+
+ return utils.NewToolResultText(string(r)), result, nil
})
}
diff --git a/pkg/github/issues_test.go b/pkg/github/issues_test.go
index 21e78874a..a338efcba 100644
--- a/pkg/github/issues_test.go
+++ b/pkg/github/issues_test.go
@@ -2086,6 +2086,7 @@ func TestAssignCopilotToIssue(t *testing.T) {
assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "repo")
assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "issue_number")
assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "base_ref")
+ assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "custom_instructions")
assert.ElementsMatch(t, tool.InputSchema.(*jsonschema.Schema).Required, []string{"owner", "repo", "issue_number"})
// Helper function to create pointer to githubv4.String
@@ -2638,6 +2639,116 @@ func TestAssignCopilotToIssue(t *testing.T) {
),
),
},
+ {
+ name: "successful assignment with custom_instructions specified",
+ requestArgs: map[string]any{
+ "owner": "owner",
+ "repo": "repo",
+ "issue_number": float64(123),
+ "custom_instructions": "Please ensure all code follows PEP 8 style guidelines and includes comprehensive docstrings",
+ },
+ mockedClient: githubv4mock.NewMockedHTTPClient(
+ githubv4mock.NewQueryMatcher(
+ struct {
+ Repository struct {
+ SuggestedActors struct {
+ Nodes []struct {
+ Bot struct {
+ ID githubv4.ID
+ Login githubv4.String
+ TypeName string `graphql:"__typename"`
+ } `graphql:"... on Bot"`
+ }
+ PageInfo struct {
+ HasNextPage bool
+ EndCursor string
+ }
+ } `graphql:"suggestedActors(first: 100, after: $endCursor, capabilities: CAN_BE_ASSIGNED)"`
+ } `graphql:"repository(owner: $owner, name: $name)"`
+ }{},
+ map[string]any{
+ "owner": githubv4.String("owner"),
+ "name": githubv4.String("repo"),
+ "endCursor": (*githubv4.String)(nil),
+ },
+ githubv4mock.DataResponse(map[string]any{
+ "repository": map[string]any{
+ "suggestedActors": map[string]any{
+ "nodes": []any{
+ map[string]any{
+ "id": githubv4.ID("copilot-swe-agent-id"),
+ "login": githubv4.String("copilot-swe-agent"),
+ "__typename": "Bot",
+ },
+ },
+ },
+ },
+ }),
+ ),
+ githubv4mock.NewQueryMatcher(
+ struct {
+ Repository struct {
+ ID githubv4.ID
+ Issue struct {
+ ID githubv4.ID
+ Assignees struct {
+ Nodes []struct {
+ ID githubv4.ID
+ }
+ } `graphql:"assignees(first: 100)"`
+ } `graphql:"issue(number: $number)"`
+ } `graphql:"repository(owner: $owner, name: $name)"`
+ }{},
+ map[string]any{
+ "owner": githubv4.String("owner"),
+ "name": githubv4.String("repo"),
+ "number": githubv4.Int(123),
+ },
+ githubv4mock.DataResponse(map[string]any{
+ "repository": map[string]any{
+ "id": githubv4.ID("test-repo-id"),
+ "issue": map[string]any{
+ "id": githubv4.ID("test-issue-id"),
+ "assignees": map[string]any{
+ "nodes": []any{},
+ },
+ },
+ },
+ }),
+ ),
+ githubv4mock.NewMutationMatcher(
+ struct {
+ UpdateIssue struct {
+ Issue struct {
+ ID githubv4.ID
+ Number githubv4.Int
+ URL githubv4.String
+ }
+ } `graphql:"updateIssue(input: $input)"`
+ }{},
+ UpdateIssueInput{
+ ID: githubv4.ID("test-issue-id"),
+ AssigneeIDs: []githubv4.ID{githubv4.ID("copilot-swe-agent-id")},
+ AgentAssignment: &AgentAssignmentInput{
+ BaseRef: nil,
+ CustomAgent: ptrGitHubv4String(""),
+ CustomInstructions: ptrGitHubv4String("Please ensure all code follows PEP 8 style guidelines and includes comprehensive docstrings"),
+ TargetRepositoryID: githubv4.ID("test-repo-id"),
+ },
+ },
+ nil,
+ githubv4mock.DataResponse(map[string]any{
+ "updateIssue": map[string]any{
+ "issue": map[string]any{
+ "id": githubv4.ID("test-issue-id"),
+ "number": githubv4.Int(123),
+ "url": githubv4.String("https://github.com/owner/repo/issues/123"),
+ },
+ },
+ }),
+ ),
+ ),
+ },
}
for _, tc := range tests {
@@ -2654,8 +2765,12 @@ func TestAssignCopilotToIssue(t *testing.T) {
// Create call request
request := createMCPRequest(tc.requestArgs)
+ // Disable polling in tests to avoid timeouts
+ ctx := ContextWithPollConfig(context.Background(), PollConfig{MaxAttempts: 0})
+ ctx = ContextWithDeps(ctx, deps)
+
// Call handler
- result, err := handler(ContextWithDeps(context.Background(), deps), &request)
+ result, err := handler(ctx, &request)
require.NoError(t, err)
textContent := getTextResult(t, result)
@@ -2667,7 +2782,16 @@ func TestAssignCopilotToIssue(t *testing.T) {
}
require.False(t, result.IsError, fmt.Sprintf("expected there to be no tool error, text was %s", textContent.Text))
- require.Equal(t, textContent.Text, "successfully assigned copilot to issue")
+
+ // Verify the JSON response contains expected fields
+ var response map[string]any
+ err = json.Unmarshal([]byte(textContent.Text), &response)
+ require.NoError(t, err, "response should be valid JSON")
+ assert.Equal(t, float64(123), response["issue_number"])
+ assert.Equal(t, "https://github.com/owner/repo/issues/123", response["issue_url"])
+ assert.Equal(t, "owner", response["owner"])
+ assert.Equal(t, "repo", response["repo"])
+ assert.Contains(t, response["message"], "successfully assigned copilot to issue")
})
}
}
diff --git a/pkg/github/notifications.go b/pkg/github/notifications.go
index 1de24fb0d..1d695beb3 100644
--- a/pkg/github/notifications.go
+++ b/pkg/github/notifications.go
@@ -231,7 +231,7 @@ func DismissNotification(t translations.TranslationHelperFunc) inventory.ServerT
}
defer func() { _ = resp.Body.Close() }()
- if resp.StatusCode != http.StatusResetContent && resp.StatusCode != http.StatusOK {
+ if resp.StatusCode != http.StatusResetContent && resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK {
body, err := io.ReadAll(resp.Body)
if err != nil {
return utils.NewToolResultErrorFromErr("failed to read response body", err), nil, nil
diff --git a/pkg/github/notifications_test.go b/pkg/github/notifications_test.go
index 936a70df4..d2124ae3d 100644
--- a/pkg/github/notifications_test.go
+++ b/pkg/github/notifications_test.go
@@ -472,7 +472,19 @@ func Test_DismissNotification(t *testing.T) {
expectRead: true,
},
{
- name: "mark as done",
+ name: "mark as done with 204 response",
+ mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
+ DeleteNotificationsThreadsByThreadID: mockResponse(t, http.StatusNoContent, nil),
+ }),
+ requestArgs: map[string]interface{}{
+ "threadID": "123",
+ "state": "done",
+ },
+ expectError: false,
+ expectDone: true,
+ },
+ {
+ name: "mark as done with 200 response",
mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{
DeleteNotificationsThreadsByThreadID: mockResponse(t, http.StatusOK, nil),
}),
diff --git a/pkg/github/scope_filter_test.go b/pkg/github/scope_filter_test.go
index 451d1a64e..9cdd4db19 100644
--- a/pkg/github/scope_filter_test.go
+++ b/pkg/github/scope_filter_test.go
@@ -167,11 +167,12 @@ func TestCreateToolScopeFilter_Integration(t *testing.T) {
filter := CreateToolScopeFilter([]string{"repo"})
// Build inventory with the filter
- inv := inventory.NewBuilder().
+ inv, err := inventory.NewBuilder().
SetTools(tools).
WithToolsets([]string{"test"}).
WithFilter(filter).
Build()
+ require.NoError(t, err)
// Get available tools
availableTools := inv.AvailableTools(context.Background())
diff --git a/pkg/github/server_test.go b/pkg/github/server_test.go
index a59cd9a93..e91fc851e 100644
--- a/pkg/github/server_test.go
+++ b/pkg/github/server_test.go
@@ -51,10 +51,11 @@ func (s stubDeps) GetRawClient(ctx context.Context) (*raw.Client, error) {
return nil, nil
}
-func (s stubDeps) GetRepoAccessCache() *lockdown.RepoAccessCache { return s.repoAccessCache }
-func (s stubDeps) GetT() translations.TranslationHelperFunc { return s.t }
-func (s stubDeps) GetFlags() FeatureFlags { return s.flags }
-func (s stubDeps) GetContentWindowSize() int { return s.contentWindowSize }
+func (s stubDeps) GetRepoAccessCache() *lockdown.RepoAccessCache { return s.repoAccessCache }
+func (s stubDeps) GetT() translations.TranslationHelperFunc { return s.t }
+func (s stubDeps) GetFlags() FeatureFlags { return s.flags }
+func (s stubDeps) GetContentWindowSize() int { return s.contentWindowSize }
+func (s stubDeps) IsFeatureEnabled(_ context.Context, _ string) bool { return false }
// Helper functions to create stub client functions for error testing
func stubClientFnFromHTTP(httpClient *http.Client) func(context.Context) (*github.Client, error) {
@@ -83,6 +84,7 @@ func stubRepoAccessCache(client *githubv4.Client, ttl time.Duration) *lockdown.R
func stubFeatureFlags(enabledFlags map[string]bool) FeatureFlags {
return FeatureFlags{
LockdownMode: enabledFlags["lockdown-mode"],
+ InsiderMode: enabledFlags["insider-mode"],
}
}
diff --git a/pkg/github/tools.go b/pkg/github/tools.go
index b15c4fc9a..4384b730d 100644
--- a/pkg/github/tools.go
+++ b/pkg/github/tools.go
@@ -309,7 +309,8 @@ func ToStringPtr(s string) *string {
// GenerateToolsetsHelp generates the help text for the toolsets flag
func GenerateToolsetsHelp() string {
// Get toolset group to derive defaults and available toolsets
- r := NewInventory(stubTranslator).Build()
+ // Build() can only fail if WithTools specifies invalid tools - not used here
+ r, _ := NewInventory(stubTranslator).Build()
// Format default tools from metadata using strings.Builder
var defaultBuf strings.Builder
@@ -391,7 +392,8 @@ func AddDefaultToolset(result []string) []string {
result = RemoveToolset(result, string(ToolsetMetadataDefault.ID))
// Get default toolset IDs from the Inventory
- r := NewInventory(stubTranslator).Build()
+ // Build() can only fail if WithTools specifies invalid tools - not used here
+ r, _ := NewInventory(stubTranslator).Build()
for _, id := range r.DefaultToolsetIDs() {
if !seen[string(id)] {
result = append(result, string(id))
@@ -443,7 +445,8 @@ func CleanTools(toolNames []string) []string {
// GetDefaultToolsetIDs returns the IDs of toolsets marked as Default.
// This is a convenience function that builds an inventory to determine defaults.
func GetDefaultToolsetIDs() []string {
- r := NewInventory(stubTranslator).Build()
+ // Build() can only fail if WithTools specifies invalid tools - not used here
+ r, _ := NewInventory(stubTranslator).Build()
ids := r.DefaultToolsetIDs()
result := make([]string, len(ids))
for i, id := range ids {
diff --git a/pkg/github/toolset_icons_test.go b/pkg/github/toolset_icons_test.go
index fd9cec462..7cfe4bef7 100644
--- a/pkg/github/toolset_icons_test.go
+++ b/pkg/github/toolset_icons_test.go
@@ -13,7 +13,8 @@ import (
// This prevents broken icon references from being merged.
func TestAllToolsetIconsExist(t *testing.T) {
// Get all available toolsets from the inventory
- inv := NewInventory(stubTranslator).Build()
+ inv, err := NewInventory(stubTranslator).Build()
+ require.NoError(t, err)
toolsets := inv.AvailableToolsets()
// Also test remote-only toolsets
@@ -72,7 +73,8 @@ func TestToolsetMetadataHasIcons(t *testing.T) {
"default": true, // Meta-toolset
}
- inv := NewInventory(stubTranslator).Build()
+ inv, err := NewInventory(stubTranslator).Build()
+ require.NoError(t, err)
toolsets := inv.AvailableToolsets()
for _, ts := range toolsets {
diff --git a/pkg/github/transport.go b/pkg/github/transport.go
new file mode 100644
index 000000000..0a4372b23
--- /dev/null
+++ b/pkg/github/transport.go
@@ -0,0 +1,47 @@
+package github
+
+import (
+ "net/http"
+ "strings"
+)
+
+// GraphQLFeaturesTransport is an http.RoundTripper that adds GraphQL-Features
+// header to requests based on context values. This is required for using
+// non-GA GraphQL API features like the agent assignment API.
+//
+// This transport is used internally by the MCP server and is also exported
+// for library consumers who need to build their own HTTP clients with
+// GraphQL feature flag support.
+//
+// Usage:
+//
+// httpClient := &http.Client{
+// Transport: &github.GraphQLFeaturesTransport{
+// Transport: http.DefaultTransport,
+// },
+// }
+// gqlClient := githubv4.NewClient(httpClient)
+//
+// Then use withGraphQLFeatures(ctx, "feature_name") when calling GraphQL operations.
+type GraphQLFeaturesTransport struct {
+ // Transport is the underlying HTTP transport. If nil, http.DefaultTransport is used.
+ Transport http.RoundTripper
+}
+
+// RoundTrip implements http.RoundTripper.
+func (t *GraphQLFeaturesTransport) RoundTrip(req *http.Request) (*http.Response, error) {
+ transport := t.Transport
+ if transport == nil {
+ transport = http.DefaultTransport
+ }
+
+ // Clone the request to avoid mutating the original
+ req = req.Clone(req.Context())
+
+ // Check for GraphQL-Features in context and add header if present
+ if features := GetGraphQLFeatures(req.Context()); len(features) > 0 {
+ req.Header.Set("GraphQL-Features", strings.Join(features, ", "))
+ }
+
+ return transport.RoundTrip(req)
+}
diff --git a/pkg/github/transport_test.go b/pkg/github/transport_test.go
new file mode 100644
index 000000000..c98108255
--- /dev/null
+++ b/pkg/github/transport_test.go
@@ -0,0 +1,151 @@
+package github
+
+import (
+ "context"
+ "net/http"
+ "net/http/httptest"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestGraphQLFeaturesTransport(t *testing.T) {
+ t.Parallel()
+
+ tests := []struct {
+ name string
+ features []string
+ expectedHeader string
+ hasHeader bool
+ }{
+ {
+ name: "no features in context",
+ features: nil,
+ expectedHeader: "",
+ hasHeader: false,
+ },
+ {
+ name: "single feature in context",
+ features: []string{"issues_copilot_assignment_api_support"},
+ expectedHeader: "issues_copilot_assignment_api_support",
+ hasHeader: true,
+ },
+ {
+ name: "multiple features in context",
+ features: []string{"feature1", "feature2", "feature3"},
+ expectedHeader: "feature1, feature2, feature3",
+ hasHeader: true,
+ },
+ {
+ name: "empty features slice",
+ features: []string{},
+ expectedHeader: "",
+ hasHeader: false,
+ },
+ }
+
+ for _, tc := range tests {
+ t.Run(tc.name, func(t *testing.T) {
+ t.Parallel()
+
+ var capturedHeader string
+ var headerExists bool
+
+ // Create a test server that captures the request header
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ capturedHeader = r.Header.Get("GraphQL-Features")
+ headerExists = r.Header.Get("GraphQL-Features") != ""
+ w.WriteHeader(http.StatusOK)
+ }))
+ defer server.Close()
+
+ // Create the transport
+ transport := &GraphQLFeaturesTransport{
+ Transport: http.DefaultTransport,
+ }
+
+ // Create a request
+ ctx := context.Background()
+ if tc.features != nil {
+ ctx = withGraphQLFeatures(ctx, tc.features...)
+ }
+
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, server.URL, nil)
+ require.NoError(t, err)
+
+ // Execute the request
+ resp, err := transport.RoundTrip(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ // Verify the header
+ assert.Equal(t, tc.hasHeader, headerExists)
+ if tc.hasHeader {
+ assert.Equal(t, tc.expectedHeader, capturedHeader)
+ }
+ })
+ }
+}
+
+func TestGraphQLFeaturesTransport_NilTransport(t *testing.T) {
+ t.Parallel()
+
+ var capturedHeader string
+
+ // Create a test server
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ capturedHeader = r.Header.Get("GraphQL-Features")
+ w.WriteHeader(http.StatusOK)
+ }))
+ defer server.Close()
+
+ // Create the transport with nil Transport (should use DefaultTransport)
+ transport := &GraphQLFeaturesTransport{
+ Transport: nil,
+ }
+
+ // Create a request with features
+ ctx := withGraphQLFeatures(context.Background(), "test_feature")
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, server.URL, nil)
+ require.NoError(t, err)
+
+ // Execute the request
+ resp, err := transport.RoundTrip(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ // Verify the header was added
+ assert.Equal(t, "test_feature", capturedHeader)
+}
+
+func TestGraphQLFeaturesTransport_DoesNotMutateOriginalRequest(t *testing.T) {
+ t.Parallel()
+
+ // Create a test server
+ server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
+ w.WriteHeader(http.StatusOK)
+ }))
+ defer server.Close()
+
+ // Create the transport
+ transport := &GraphQLFeaturesTransport{
+ Transport: http.DefaultTransport,
+ }
+
+ // Create a request with features
+ ctx := withGraphQLFeatures(context.Background(), "test_feature")
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, server.URL, nil)
+ require.NoError(t, err)
+
+ // Store the original header value
+ originalHeader := req.Header.Get("GraphQL-Features")
+
+ // Execute the request
+ resp, err := transport.RoundTrip(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ // Verify the original request was not mutated
+ assert.Equal(t, originalHeader, req.Header.Get("GraphQL-Features"))
+}
diff --git a/pkg/inventory/builder.go b/pkg/inventory/builder.go
index 0400c2a24..58abb8ad1 100644
--- a/pkg/inventory/builder.go
+++ b/pkg/inventory/builder.go
@@ -2,6 +2,7 @@ package inventory
import (
"context"
+ "fmt"
"sort"
"strings"
)
@@ -101,6 +102,7 @@ func (b *Builder) WithToolsets(toolsetIDs []string) *Builder {
// WithTools specifies additional tools that bypass toolset filtering.
// These tools are additive - they will be included even if their toolset is not enabled.
// Read-only filtering still applies to these tools.
+// Input is cleaned (trimmed, deduplicated) during Build().
// Deprecated tool aliases are automatically resolved to their canonical names during Build().
// Returns self for chaining.
func (b *Builder) WithTools(toolNames []string) *Builder {
@@ -127,11 +129,33 @@ func (b *Builder) WithFilter(filter ToolFilter) *Builder {
return b
}
+// cleanTools trims whitespace and removes duplicates from tool names.
+// Empty strings after trimming are excluded.
+func cleanTools(tools []string) []string {
+ seen := make(map[string]bool)
+ var cleaned []string
+ for _, name := range tools {
+ trimmed := strings.TrimSpace(name)
+ if trimmed == "" {
+ continue
+ }
+ if !seen[trimmed] {
+ seen[trimmed] = true
+ cleaned = append(cleaned, trimmed)
+ }
+ }
+ return cleaned
+}
+
// Build creates the final Inventory with all configuration applied.
// This processes toolset filtering, tool name resolution, and sets up
// the inventory for use. The returned Inventory is ready for use with
// AvailableTools(), RegisterAll(), etc.
-func (b *Builder) Build() *Inventory {
+//
+// Build returns an error if any tools specified via WithTools() are not recognized
+// (i.e., they don't exist in the tool set and are not deprecated aliases).
+// This ensures invalid tool configurations fail fast at build time.
+func (b *Builder) Build() (*Inventory, error) {
r := &Inventory{
tools: b.tools,
resourceTemplates: b.resourceTemplates,
@@ -145,10 +169,19 @@ func (b *Builder) Build() *Inventory {
// Process toolsets and pre-compute metadata in a single pass
r.enabledToolsets, r.unrecognizedToolsets, r.toolsetIDs, r.toolsetIDSet, r.defaultToolsetIDs, r.toolsetDescriptions = b.processToolsets()
- // Process additional tools (resolve aliases)
+ // Build set of valid tool names for validation
+ validToolNames := make(map[string]bool, len(b.tools))
+ for i := range b.tools {
+ validToolNames[b.tools[i].Tool.Name] = true
+ }
+
+ // Process additional tools (clean, resolve aliases, and track unrecognized)
if len(b.additionalTools) > 0 {
- r.additionalTools = make(map[string]bool, len(b.additionalTools))
- for _, name := range b.additionalTools {
+ cleanedTools := cleanTools(b.additionalTools)
+
+ r.additionalTools = make(map[string]bool, len(cleanedTools))
+ var unrecognizedTools []string
+ for _, name := range cleanedTools {
// Always include the original name - this handles the case where
// the tool exists but is controlled by a feature flag that's OFF.
r.additionalTools[name] = true
@@ -157,11 +190,19 @@ func (b *Builder) Build() *Inventory {
// the new consolidated tool is available.
if canonical, isAlias := b.deprecatedAliases[name]; isAlias {
r.additionalTools[canonical] = true
+ } else if !validToolNames[name] {
+ // Not a valid tool and not a deprecated alias - track as unrecognized
+ unrecognizedTools = append(unrecognizedTools, name)
}
}
+
+ // Error out if there are unrecognized tools
+ if len(unrecognizedTools) > 0 {
+ return nil, fmt.Errorf("unrecognized tools: %s", strings.Join(unrecognizedTools, ", "))
+ }
}
- return r
+ return r, nil
}
// processToolsets processes the toolsetIDs configuration and returns:
diff --git a/pkg/inventory/registry_test.go b/pkg/inventory/registry_test.go
index 136f8e523..bb3337af0 100644
--- a/pkg/inventory/registry_test.go
+++ b/pkg/inventory/registry_test.go
@@ -7,8 +7,18 @@ import (
"testing"
"github.com/modelcontextprotocol/go-sdk/mcp"
+ "github.com/stretchr/testify/require"
)
+// mustBuild is a test helper that calls Build() and fails the test if an error occurs.
+// Use this for tests where Build() is not expected to fail.
+func mustBuild(t *testing.T, b *Builder) *Inventory {
+ t.Helper()
+ inv, err := b.Build()
+ require.NoError(t, err)
+ return inv
+}
+
// testToolsetMetadata returns a ToolsetMetadata for testing
func testToolsetMetadata(id string) ToolsetMetadata {
return ToolsetMetadata{
@@ -65,7 +75,7 @@ func mockTool(name string, toolsetID string, readOnly bool) ServerTool {
}
func TestNewRegistryEmpty(t *testing.T) {
- reg := NewBuilder().Build()
+ reg := mustBuild(t, NewBuilder())
if len(reg.AvailableTools(context.Background())) != 0 {
t.Fatalf("Expected tools to be empty")
}
@@ -84,7 +94,7 @@ func TestNewRegistryWithTools(t *testing.T) {
mockTool("tool3", "toolset2", true),
}
- reg := NewBuilder().SetTools(tools).Build()
+ reg := mustBuild(t, NewBuilder().SetTools(tools))
if len(reg.AllTools()) != 3 {
t.Errorf("Expected 3 tools, got %d", len(reg.AllTools()))
@@ -98,7 +108,7 @@ func TestAvailableTools_NoFilters(t *testing.T) {
mockTool("tool_c", "toolset2", true),
}
- reg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).Build()
+ reg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}))
available := reg.AvailableTools(context.Background())
if len(available) != 3 {
@@ -121,14 +131,14 @@ func TestWithReadOnly(t *testing.T) {
}
// Build without read-only - should have both tools
- reg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).Build()
+ reg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}))
allTools := reg.AvailableTools(context.Background())
if len(allTools) != 2 {
t.Fatalf("Expected 2 tools without read-only, got %d", len(allTools))
}
// Build with read-only - should filter out write tools
- readOnlyReg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithReadOnly(true).Build()
+ readOnlyReg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithReadOnly(true))
readOnlyTools := readOnlyReg.AvailableTools(context.Background())
if len(readOnlyTools) != 1 {
t.Fatalf("Expected 1 tool in read-only, got %d", len(readOnlyTools))
@@ -146,14 +156,14 @@ func TestWithToolsets(t *testing.T) {
}
// Build with all toolsets
- allReg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).Build()
+ allReg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}))
allTools := allReg.AvailableTools(context.Background())
if len(allTools) != 3 {
t.Fatalf("Expected 3 tools without filter, got %d", len(allTools))
}
// Build with specific toolsets
- filteredReg := NewBuilder().SetTools(tools).WithToolsets([]string{"toolset1", "toolset3"}).Build()
+ filteredReg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"toolset1", "toolset3"}))
filteredTools := filteredReg.AvailableTools(context.Background())
if len(filteredTools) != 2 {
@@ -177,7 +187,7 @@ func TestWithToolsetsTrimsWhitespace(t *testing.T) {
}
// Whitespace should be trimmed
- filteredReg := NewBuilder().SetTools(tools).WithToolsets([]string{" toolset1 ", " toolset2 "}).Build()
+ filteredReg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{" toolset1 ", " toolset2 "}))
filteredTools := filteredReg.AvailableTools(context.Background())
if len(filteredTools) != 2 {
@@ -191,7 +201,7 @@ func TestWithToolsetsDeduplicates(t *testing.T) {
}
// Duplicates should be removed
- filteredReg := NewBuilder().SetTools(tools).WithToolsets([]string{"toolset1", "toolset1", " toolset1 "}).Build()
+ filteredReg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"toolset1", "toolset1", " toolset1 "}))
filteredTools := filteredReg.AvailableTools(context.Background())
if len(filteredTools) != 1 {
@@ -205,7 +215,7 @@ func TestWithToolsetsIgnoresEmptyStrings(t *testing.T) {
}
// Empty strings should be ignored
- filteredReg := NewBuilder().SetTools(tools).WithToolsets([]string{"", "toolset1", " ", ""}).Build()
+ filteredReg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"", "toolset1", " ", ""}))
filteredTools := filteredReg.AvailableTools(context.Background())
if len(filteredTools) != 1 {
@@ -253,7 +263,7 @@ func TestUnrecognizedToolsets(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
- filtered := NewBuilder().SetTools(tools).WithToolsets(tt.input).Build()
+ filtered := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets(tt.input))
unrecognized := filtered.UnrecognizedToolsets()
if len(unrecognized) != len(tt.expectedUnrecognized) {
@@ -270,6 +280,109 @@ func TestUnrecognizedToolsets(t *testing.T) {
}
}
+func TestBuildErrorsOnUnrecognizedTools(t *testing.T) {
+ tools := []ServerTool{
+ mockTool("tool1", "toolset1", true),
+ mockTool("tool2", "toolset2", true),
+ }
+
+ deprecatedAliases := map[string]string{
+ "old_tool": "tool1",
+ }
+
+ tests := []struct {
+ name string
+ withTools []string
+ expectError bool
+ errorContains string
+ }{
+ {
+ name: "all valid",
+ withTools: []string{"tool1", "tool2"},
+ expectError: false,
+ },
+ {
+ name: "one invalid",
+ withTools: []string{"tool1", "blabla"},
+ expectError: true,
+ errorContains: "blabla",
+ },
+ {
+ name: "multiple invalid",
+ withTools: []string{"invalid1", "tool1", "invalid2"},
+ expectError: true,
+ errorContains: "invalid1",
+ },
+ {
+ name: "deprecated alias is valid",
+ withTools: []string{"old_tool"},
+ expectError: false,
+ },
+ {
+ name: "mixed valid and deprecated alias",
+ withTools: []string{"old_tool", "tool2"},
+ expectError: false,
+ },
+ {
+ name: "empty input",
+ withTools: []string{},
+ expectError: false,
+ },
+ {
+ name: "whitespace trimmed from valid tool",
+ withTools: []string{" tool1 ", " tool2 "},
+ expectError: false,
+ },
+ {
+ name: "whitespace trimmed from invalid tool",
+ withTools: []string{" invalid_tool "},
+ expectError: true,
+ errorContains: "invalid_tool",
+ },
+ {
+ name: "duplicate tools deduplicated",
+ withTools: []string{"tool1", "tool1"},
+ expectError: false,
+ },
+ {
+ name: "duplicate invalid tools deduplicated",
+ withTools: []string{"blabla", "blabla"},
+ expectError: true,
+ errorContains: "blabla",
+ },
+ {
+ name: "mixed whitespace and duplicates",
+ withTools: []string{" tool1 ", "tool1", " tool1 "},
+ expectError: false,
+ },
+ {
+ name: "empty strings ignored",
+ withTools: []string{"", "tool1", " ", ""},
+ expectError: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ inv, err := NewBuilder().
+ SetTools(tools).
+ WithDeprecatedAliases(deprecatedAliases).
+ WithToolsets([]string{"all"}).
+ WithTools(tt.withTools).
+ Build()
+
+ if tt.expectError {
+ require.Error(t, err, "Expected error for unrecognized tools")
+ require.Contains(t, err.Error(), tt.errorContains)
+ require.Nil(t, inv)
+ } else {
+ require.NoError(t, err)
+ require.NotNil(t, inv)
+ }
+ })
+ }
+}
+
func TestWithTools(t *testing.T) {
tools := []ServerTool{
mockTool("tool1", "toolset1", true),
@@ -279,7 +392,7 @@ func TestWithTools(t *testing.T) {
// WithTools adds additional tools that bypass toolset filtering
// When combined with WithToolsets([]), only the additional tools should be available
- filteredReg := NewBuilder().SetTools(tools).WithToolsets([]string{}).WithTools([]string{"tool1", "tool3"}).Build()
+ filteredReg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{}).WithTools([]string{"tool1", "tool3"}))
filteredTools := filteredReg.AvailableTools(context.Background())
if len(filteredTools) != 2 {
@@ -304,7 +417,7 @@ func TestChainedFilters(t *testing.T) {
}
// Chain read-only and toolset filter
- filtered := NewBuilder().SetTools(tools).WithReadOnly(true).WithToolsets([]string{"toolset1"}).Build()
+ filtered := mustBuild(t, NewBuilder().SetTools(tools).WithReadOnly(true).WithToolsets([]string{"toolset1"}))
result := filtered.AvailableTools(context.Background())
if len(result) != 1 {
@@ -322,7 +435,7 @@ func TestToolsetIDs(t *testing.T) {
mockTool("tool3", "toolset_b", true), // duplicate toolset
}
- reg := NewBuilder().SetTools(tools).Build()
+ reg := mustBuild(t, NewBuilder().SetTools(tools))
ids := reg.ToolsetIDs()
if len(ids) != 2 {
@@ -341,7 +454,7 @@ func TestToolsetDescriptions(t *testing.T) {
mockTool("tool2", "toolset2", true),
}
- reg := NewBuilder().SetTools(tools).Build()
+ reg := mustBuild(t, NewBuilder().SetTools(tools))
descriptions := reg.ToolsetDescriptions()
if len(descriptions) != 2 {
@@ -360,7 +473,7 @@ func TestToolsForToolset(t *testing.T) {
mockTool("tool3", "toolset2", true),
}
- reg := NewBuilder().SetTools(tools).Build()
+ reg := mustBuild(t, NewBuilder().SetTools(tools))
toolset1Tools := reg.ToolsForToolset("toolset1")
if len(toolset1Tools) != 2 {
@@ -373,10 +486,10 @@ func TestWithDeprecatedAliases(t *testing.T) {
mockTool("new_name", "toolset1", true),
}
- reg := NewBuilder().SetTools(tools).WithDeprecatedAliases(map[string]string{
+ reg := mustBuild(t, NewBuilder().SetTools(tools).WithDeprecatedAliases(map[string]string{
"old_name": "new_name",
"get_issue": "issue_read",
- }).Build()
+ }))
// Test resolving aliases
resolved, aliasesUsed := reg.ResolveToolAliases([]string{"old_name"})
@@ -394,10 +507,10 @@ func TestResolveToolAliases(t *testing.T) {
mockTool("some_tool", "toolset1", true),
}
- reg := NewBuilder().SetTools(tools).
+ reg := mustBuild(t, NewBuilder().SetTools(tools).
WithDeprecatedAliases(map[string]string{
"get_issue": "issue_read",
- }).Build()
+ }))
// Test resolving a mix of aliases and canonical names
input := []string{"get_issue", "some_tool"}
@@ -426,7 +539,7 @@ func TestFindToolByName(t *testing.T) {
mockTool("issue_read", "toolset1", true),
}
- reg := NewBuilder().SetTools(tools).Build()
+ reg := mustBuild(t, NewBuilder().SetTools(tools))
// Find by name
tool, toolsetID, err := reg.FindToolByName("issue_read")
@@ -456,7 +569,7 @@ func TestWithToolsAdditive(t *testing.T) {
// Test WithTools bypasses toolset filtering
// Enable only toolset2, but add issue_read as additional tool
- filtered := NewBuilder().SetTools(tools).WithToolsets([]string{"toolset2"}).WithTools([]string{"issue_read"}).Build()
+ filtered := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"toolset2"}).WithTools([]string{"issue_read"}))
available := filtered.AvailableTools(context.Background())
if len(available) != 2 {
@@ -476,7 +589,7 @@ func TestWithToolsAdditive(t *testing.T) {
}
// Test WithTools respects read-only mode
- readOnlyFiltered := NewBuilder().SetTools(tools).WithReadOnly(true).WithTools([]string{"issue_write"}).Build()
+ readOnlyFiltered := mustBuild(t, NewBuilder().SetTools(tools).WithReadOnly(true).WithTools([]string{"issue_write"}))
available = readOnlyFiltered.AvailableTools(context.Background())
// issue_write should be excluded because read-only applies to additional tools too
@@ -486,12 +599,10 @@ func TestWithToolsAdditive(t *testing.T) {
}
}
- // Test WithTools with non-existent tool (should not error, just won't match anything)
- nonexistent := NewBuilder().SetTools(tools).WithToolsets([]string{}).WithTools([]string{"nonexistent"}).Build()
- available = nonexistent.AvailableTools(context.Background())
- if len(available) != 0 {
- t.Errorf("expected 0 tools for non-existent additional tool, got %d", len(available))
- }
+ // Test WithTools with non-existent tool (should error during Build)
+ _, err := NewBuilder().SetTools(tools).WithToolsets([]string{}).WithTools([]string{"nonexistent"}).Build()
+ require.Error(t, err, "expected error for non-existent tool")
+ require.Contains(t, err.Error(), "nonexistent")
}
func TestWithToolsResolvesAliases(t *testing.T) {
@@ -500,13 +611,12 @@ func TestWithToolsResolvesAliases(t *testing.T) {
}
// Using deprecated alias should resolve to canonical name
- filtered := NewBuilder().SetTools(tools).
+ filtered := mustBuild(t, NewBuilder().SetTools(tools).
WithDeprecatedAliases(map[string]string{
"get_issue": "issue_read",
}).
WithToolsets([]string{}).
- WithTools([]string{"get_issue"}).
- Build()
+ WithTools([]string{"get_issue"}))
available := filtered.AvailableTools(context.Background())
if len(available) != 1 {
@@ -522,7 +632,7 @@ func TestHasToolset(t *testing.T) {
mockTool("tool1", "toolset1", true),
}
- reg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).Build()
+ reg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}))
if !reg.HasToolset("toolset1") {
t.Error("expected HasToolset to return true for existing toolset")
@@ -539,14 +649,14 @@ func TestEnabledToolsetIDs(t *testing.T) {
}
// Without filter, all toolsets are enabled
- reg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).Build()
+ reg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}))
ids := reg.EnabledToolsetIDs()
if len(ids) != 2 {
t.Fatalf("Expected 2 enabled toolset IDs, got %d", len(ids))
}
// With filter
- filtered := NewBuilder().SetTools(tools).WithToolsets([]string{"toolset1"}).Build()
+ filtered := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"toolset1"}))
filteredIDs := filtered.EnabledToolsetIDs()
if len(filteredIDs) != 1 {
t.Fatalf("Expected 1 enabled toolset ID, got %d", len(filteredIDs))
@@ -563,7 +673,7 @@ func TestAllTools(t *testing.T) {
}
// Even with read-only filter, AllTools returns everything
- readOnlyReg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithReadOnly(true).Build()
+ readOnlyReg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithReadOnly(true))
allTools := readOnlyReg.AllTools()
if len(allTools) != 2 {
@@ -628,7 +738,7 @@ func TestForMCPRequest_Initialize(t *testing.T) {
mockPrompt("prompt1", "repos"),
}
- reg := NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"}).Build()
+ reg := mustBuild(t, NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"}))
filtered := reg.ForMCPRequest(MCPMethodInitialize, "")
// Initialize should return empty - capabilities come from ServerOptions
@@ -655,7 +765,7 @@ func TestForMCPRequest_ToolsList(t *testing.T) {
mockPrompt("prompt1", "repos"),
}
- reg := NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"}).Build()
+ reg := mustBuild(t, NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"}))
filtered := reg.ForMCPRequest(MCPMethodToolsList, "")
// tools/list should return all tools, no resources or prompts
@@ -677,7 +787,7 @@ func TestForMCPRequest_ToolsCall(t *testing.T) {
mockTool("list_repos", "repos", true),
}
- reg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).Build()
+ reg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}))
filtered := reg.ForMCPRequest(MCPMethodToolsCall, "get_me")
available := filtered.AvailableTools(context.Background())
@@ -694,7 +804,7 @@ func TestForMCPRequest_ToolsCall_NotFound(t *testing.T) {
mockTool("get_me", "context", true),
}
- reg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).Build()
+ reg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}))
filtered := reg.ForMCPRequest(MCPMethodToolsCall, "nonexistent")
if len(filtered.AvailableTools(context.Background())) != 0 {
@@ -708,11 +818,11 @@ func TestForMCPRequest_ToolsCall_DeprecatedAlias(t *testing.T) {
mockTool("list_commits", "repos", true),
}
- reg := NewBuilder().SetTools(tools).
+ reg := mustBuild(t, NewBuilder().SetTools(tools).
WithToolsets([]string{"all"}).
WithDeprecatedAliases(map[string]string{
"old_get_me": "get_me",
- }).Build()
+ }))
// Request using the deprecated alias
filtered := reg.ForMCPRequest(MCPMethodToolsCall, "old_get_me")
@@ -732,7 +842,7 @@ func TestForMCPRequest_ToolsCall_RespectsFilters(t *testing.T) {
}
// Apply read-only filter at build time, then ForMCPRequest
- reg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithReadOnly(true).Build()
+ reg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithReadOnly(true))
filtered := reg.ForMCPRequest(MCPMethodToolsCall, "create_issue")
// The tool exists in the filtered group, but AvailableTools respects read-only
@@ -754,7 +864,7 @@ func TestForMCPRequest_ResourcesList(t *testing.T) {
mockPrompt("prompt1", "repos"),
}
- reg := NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"}).Build()
+ reg := mustBuild(t, NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"}))
filtered := reg.ForMCPRequest(MCPMethodResourcesList, "")
if len(filtered.AvailableTools(context.Background())) != 0 {
@@ -774,7 +884,7 @@ func TestForMCPRequest_ResourcesRead(t *testing.T) {
mockResource("res2", "repos", "branch://{owner}/{repo}/{branch}"),
}
- reg := NewBuilder().SetResources(resources).WithToolsets([]string{"all"}).Build()
+ reg := mustBuild(t, NewBuilder().SetResources(resources).WithToolsets([]string{"all"}))
// Pass a concrete URI - all resources remain registered, SDK handles matching
filtered := reg.ForMCPRequest(MCPMethodResourcesRead, "repo://owner/repo")
@@ -796,7 +906,7 @@ func TestForMCPRequest_PromptsList(t *testing.T) {
mockPrompt("prompt2", "issues"),
}
- reg := NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"}).Build()
+ reg := mustBuild(t, NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"}))
filtered := reg.ForMCPRequest(MCPMethodPromptsList, "")
if len(filtered.AvailableTools(context.Background())) != 0 {
@@ -816,7 +926,7 @@ func TestForMCPRequest_PromptsGet(t *testing.T) {
mockPrompt("prompt2", "issues"),
}
- reg := NewBuilder().SetPrompts(prompts).WithToolsets([]string{"all"}).Build()
+ reg := mustBuild(t, NewBuilder().SetPrompts(prompts).WithToolsets([]string{"all"}))
filtered := reg.ForMCPRequest(MCPMethodPromptsGet, "prompt1")
available := filtered.AvailablePrompts(context.Background())
@@ -839,7 +949,7 @@ func TestForMCPRequest_UnknownMethod(t *testing.T) {
mockPrompt("prompt1", "repos"),
}
- reg := NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"}).Build()
+ reg := mustBuild(t, NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"}))
filtered := reg.ForMCPRequest("unknown/method", "")
// Unknown methods should return empty
@@ -866,7 +976,7 @@ func TestForMCPRequest_DoesNotMutateOriginal(t *testing.T) {
mockPrompt("prompt1", "repos"),
}
- original := NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"}).Build()
+ original := mustBuild(t, NewBuilder().SetTools(tools).SetResources(resources).SetPrompts(prompts).WithToolsets([]string{"all"}))
filtered := original.ForMCPRequest(MCPMethodToolsCall, "tool1")
// Original should be unchanged
@@ -901,10 +1011,9 @@ func TestForMCPRequest_ChainedWithOtherFilters(t *testing.T) {
}
// Chain: default toolsets -> read-only -> specific method
- reg := NewBuilder().SetTools(tools).
+ reg := mustBuild(t, NewBuilder().SetTools(tools).
WithToolsets([]string{"default"}).
- WithReadOnly(true).
- Build()
+ WithReadOnly(true))
filtered := reg.ForMCPRequest(MCPMethodToolsList, "")
available := filtered.AvailableTools(context.Background())
@@ -942,7 +1051,7 @@ func TestForMCPRequest_ResourcesTemplatesList(t *testing.T) {
mockResource("res1", "repos", "repo://{owner}/{repo}"),
}
- reg := NewBuilder().SetTools(tools).SetResources(resources).WithToolsets([]string{"all"}).Build()
+ reg := mustBuild(t, NewBuilder().SetTools(tools).SetResources(resources).WithToolsets([]string{"all"}))
filtered := reg.ForMCPRequest(MCPMethodResourcesTemplatesList, "")
// Same behavior as resources/list
@@ -992,7 +1101,7 @@ func TestFeatureFlagEnable(t *testing.T) {
}
// Without feature checker, tool with FeatureFlagEnable should be excluded
- reg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).Build()
+ reg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}))
available := reg.AvailableTools(context.Background())
if len(available) != 1 {
t.Fatalf("Expected 1 tool without feature checker, got %d", len(available))
@@ -1003,7 +1112,7 @@ func TestFeatureFlagEnable(t *testing.T) {
// With feature checker returning false, tool should still be excluded
checkerFalse := func(_ context.Context, _ string) (bool, error) { return false, nil }
- regFalse := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checkerFalse).Build()
+ regFalse := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checkerFalse))
availableFalse := regFalse.AvailableTools(context.Background())
if len(availableFalse) != 1 {
t.Fatalf("Expected 1 tool with false checker, got %d", len(availableFalse))
@@ -1013,7 +1122,7 @@ func TestFeatureFlagEnable(t *testing.T) {
checkerTrue := func(_ context.Context, flag string) (bool, error) {
return flag == "my_feature", nil
}
- regTrue := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checkerTrue).Build()
+ regTrue := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checkerTrue))
availableTrue := regTrue.AvailableTools(context.Background())
if len(availableTrue) != 2 {
t.Fatalf("Expected 2 tools with true checker, got %d", len(availableTrue))
@@ -1027,7 +1136,7 @@ func TestFeatureFlagDisable(t *testing.T) {
}
// Without feature checker, tool with FeatureFlagDisable should be included (flag is false)
- reg := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).Build()
+ reg := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}))
available := reg.AvailableTools(context.Background())
if len(available) != 2 {
t.Fatalf("Expected 2 tools without feature checker, got %d", len(available))
@@ -1037,7 +1146,7 @@ func TestFeatureFlagDisable(t *testing.T) {
checkerTrue := func(_ context.Context, flag string) (bool, error) {
return flag == "kill_switch", nil
}
- regFiltered := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checkerTrue).Build()
+ regFiltered := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checkerTrue))
availableFiltered := regFiltered.AvailableTools(context.Background())
if len(availableFiltered) != 1 {
t.Fatalf("Expected 1 tool with kill_switch enabled, got %d", len(availableFiltered))
@@ -1055,21 +1164,21 @@ func TestFeatureFlagBoth(t *testing.T) {
// Enable flag not set -> excluded
checker1 := func(_ context.Context, _ string) (bool, error) { return false, nil }
- reg1 := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checker1).Build()
+ reg1 := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checker1))
if len(reg1.AvailableTools(context.Background())) != 0 {
t.Error("Tool should be excluded when enable flag is false")
}
// Enable flag set, disable flag not set -> included
checker2 := func(_ context.Context, flag string) (bool, error) { return flag == "new_feature", nil }
- reg2 := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checker2).Build()
+ reg2 := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checker2))
if len(reg2.AvailableTools(context.Background())) != 1 {
t.Error("Tool should be included when enable flag is true and disable flag is false")
}
// Enable flag set, disable flag also set -> excluded (disable wins)
checker3 := func(_ context.Context, _ string) (bool, error) { return true, nil }
- reg3 := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checker3).Build()
+ reg3 := mustBuild(t, NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checker3))
if len(reg3.AvailableTools(context.Background())) != 0 {
t.Error("Tool should be excluded when both flags are true (disable wins)")
}
@@ -1084,7 +1193,7 @@ func TestFeatureFlagError(t *testing.T) {
checkerError := func(_ context.Context, _ string) (bool, error) {
return false, fmt.Errorf("simulated error")
}
- reg := NewBuilder().SetTools(tools).WithFeatureChecker(checkerError).Build()
+ reg := mustBuild(t, NewBuilder().SetTools(tools).WithFeatureChecker(checkerError))
available := reg.AvailableTools(context.Background())
if len(available) != 0 {
t.Errorf("Expected 0 tools when checker errors, got %d", len(available))
@@ -1102,7 +1211,7 @@ func TestFeatureFlagResources(t *testing.T) {
}
// Without checker, resource with enable flag should be excluded
- reg := NewBuilder().SetResources(resources).WithToolsets([]string{"all"}).Build()
+ reg := mustBuild(t, NewBuilder().SetResources(resources).WithToolsets([]string{"all"}))
available := reg.AvailableResourceTemplates(context.Background())
if len(available) != 1 {
t.Fatalf("Expected 1 resource without checker, got %d", len(available))
@@ -1110,7 +1219,7 @@ func TestFeatureFlagResources(t *testing.T) {
// With checker returning true, both should be included
checker := func(_ context.Context, _ string) (bool, error) { return true, nil }
- regWithChecker := NewBuilder().SetResources(resources).WithToolsets([]string{"all"}).WithFeatureChecker(checker).Build()
+ regWithChecker := mustBuild(t, NewBuilder().SetResources(resources).WithToolsets([]string{"all"}).WithFeatureChecker(checker))
if len(regWithChecker.AvailableResourceTemplates(context.Background())) != 2 {
t.Errorf("Expected 2 resources with checker, got %d", len(regWithChecker.AvailableResourceTemplates(context.Background())))
}
@@ -1127,7 +1236,7 @@ func TestFeatureFlagPrompts(t *testing.T) {
}
// Without checker, prompt with enable flag should be excluded
- reg := NewBuilder().SetPrompts(prompts).WithToolsets([]string{"all"}).Build()
+ reg := mustBuild(t, NewBuilder().SetPrompts(prompts).WithToolsets([]string{"all"}))
available := reg.AvailablePrompts(context.Background())
if len(available) != 1 {
t.Fatalf("Expected 1 prompt without checker, got %d", len(available))
@@ -1135,7 +1244,7 @@ func TestFeatureFlagPrompts(t *testing.T) {
// With checker returning true, both should be included
checker := func(_ context.Context, _ string) (bool, error) { return true, nil }
- regWithChecker := NewBuilder().SetPrompts(prompts).WithToolsets([]string{"all"}).WithFeatureChecker(checker).Build()
+ regWithChecker := mustBuild(t, NewBuilder().SetPrompts(prompts).WithToolsets([]string{"all"}).WithFeatureChecker(checker))
if len(regWithChecker.AvailablePrompts(context.Background())) != 2 {
t.Errorf("Expected 2 prompts with checker, got %d", len(regWithChecker.AvailablePrompts(context.Background())))
}
@@ -1218,7 +1327,7 @@ func TestServerToolEnabled(t *testing.T) {
tool := mockTool("test_tool", "toolset1", true)
tool.Enabled = tt.enabledFunc
- reg := NewBuilder().SetTools([]ServerTool{tool}).WithToolsets([]string{"all"}).Build()
+ reg := mustBuild(t, NewBuilder().SetTools([]ServerTool{tool}).WithToolsets([]string{"all"}))
available := reg.AvailableTools(context.Background())
if len(available) != tt.expectedCount {
@@ -1250,7 +1359,7 @@ func TestServerToolEnabledWithContext(t *testing.T) {
return user != nil && user.(string) == "authorized", nil
}
- reg := NewBuilder().SetTools([]ServerTool{tool}).WithToolsets([]string{"all"}).Build()
+ reg := mustBuild(t, NewBuilder().SetTools([]ServerTool{tool}).WithToolsets([]string{"all"}))
// Without user in context - tool should be excluded
available := reg.AvailableTools(context.Background())
@@ -1286,11 +1395,10 @@ func TestBuilderWithFilter(t *testing.T) {
return tool.Tool.Name != "tool2", nil
}
- reg := NewBuilder().
+ reg := mustBuild(t, NewBuilder().
SetTools(tools).
WithToolsets([]string{"all"}).
- WithFilter(filter).
- Build()
+ WithFilter(filter))
available := reg.AvailableTools(context.Background())
if len(available) != 2 {
@@ -1322,12 +1430,11 @@ func TestBuilderWithMultipleFilters(t *testing.T) {
return tool.Tool.Name != "tool3", nil
}
- reg := NewBuilder().
+ reg := mustBuild(t, NewBuilder().
SetTools(tools).
WithToolsets([]string{"all"}).
WithFilter(filter1).
- WithFilter(filter2).
- Build()
+ WithFilter(filter2))
available := reg.AvailableTools(context.Background())
if len(available) != 2 {
@@ -1357,11 +1464,10 @@ func TestBuilderFilterError(t *testing.T) {
return false, fmt.Errorf("filter error")
}
- reg := NewBuilder().
+ reg := mustBuild(t, NewBuilder().
SetTools(tools).
WithToolsets([]string{"all"}).
- WithFilter(filter).
- Build()
+ WithFilter(filter))
available := reg.AvailableTools(context.Background())
if len(available) != 0 {
@@ -1387,11 +1493,10 @@ func TestBuilderFilterWithContext(t *testing.T) {
return true, nil
}
- reg := NewBuilder().
+ reg := mustBuild(t, NewBuilder().
SetTools(tools).
WithToolsets([]string{"all"}).
- WithFilter(filter).
- Build()
+ WithFilter(filter))
// With public scope - private_tool should be excluded
ctxPublic := context.WithValue(context.Background(), scopeKey, "public")
@@ -1420,10 +1525,9 @@ func TestEnabledAndFeatureFlagInteraction(t *testing.T) {
}
// Feature flag not enabled - tool should be excluded despite Enabled returning true
- reg1 := NewBuilder().
+ reg1 := mustBuild(t, NewBuilder().
SetTools([]ServerTool{tool}).
- WithToolsets([]string{"all"}).
- Build()
+ WithToolsets([]string{"all"}))
available1 := reg1.AvailableTools(context.Background())
if len(available1) != 0 {
t.Error("Tool should be excluded when feature flag is not enabled")
@@ -1433,11 +1537,10 @@ func TestEnabledAndFeatureFlagInteraction(t *testing.T) {
checker := func(_ context.Context, flag string) (bool, error) {
return flag == "my_feature", nil
}
- reg2 := NewBuilder().
+ reg2 := mustBuild(t, NewBuilder().
SetTools([]ServerTool{tool}).
WithToolsets([]string{"all"}).
- WithFeatureChecker(checker).
- Build()
+ WithFeatureChecker(checker))
available2 := reg2.AvailableTools(context.Background())
if len(available2) != 1 {
t.Error("Tool should be included when both Enabled and feature flag pass")
@@ -1447,11 +1550,10 @@ func TestEnabledAndFeatureFlagInteraction(t *testing.T) {
tool.Enabled = func(_ context.Context) (bool, error) {
return false, nil
}
- reg3 := NewBuilder().
+ reg3 := mustBuild(t, NewBuilder().
SetTools([]ServerTool{tool}).
WithToolsets([]string{"all"}).
- WithFeatureChecker(checker).
- Build()
+ WithFeatureChecker(checker))
available3 := reg3.AvailableTools(context.Background())
if len(available3) != 0 {
t.Error("Tool should be excluded when Enabled returns false")
@@ -1469,11 +1571,10 @@ func TestEnabledAndBuilderFilterInteraction(t *testing.T) {
return false, nil
}
- reg := NewBuilder().
+ reg := mustBuild(t, NewBuilder().
SetTools([]ServerTool{tool}).
WithToolsets([]string{"all"}).
- WithFilter(filter).
- Build()
+ WithFilter(filter))
available := reg.AvailableTools(context.Background())
if len(available) != 0 {
@@ -1497,12 +1598,11 @@ func TestAllFiltersInteraction(t *testing.T) {
}
// All conditions pass - tool should be included
- reg := NewBuilder().
+ reg := mustBuild(t, NewBuilder().
SetTools([]ServerTool{tool}).
WithToolsets([]string{"all"}).
WithFeatureChecker(checker).
- WithFilter(filter).
- Build()
+ WithFilter(filter))
available := reg.AvailableTools(context.Background())
if len(available) != 1 {
@@ -1514,12 +1614,11 @@ func TestAllFiltersInteraction(t *testing.T) {
return false, nil
}
- reg2 := NewBuilder().
+ reg2 := mustBuild(t, NewBuilder().
SetTools([]ServerTool{tool}).
WithToolsets([]string{"all"}).
WithFeatureChecker(checker).
- WithFilter(filterFalse).
- Build()
+ WithFilter(filterFalse))
available2 := reg2.AvailableTools(context.Background())
if len(available2) != 0 {
@@ -1538,11 +1637,10 @@ func TestFilteredTools(t *testing.T) {
return tool.Tool.Name == "tool1", nil
}
- reg := NewBuilder().
+ reg := mustBuild(t, NewBuilder().
SetTools(tools).
WithToolsets([]string{"all"}).
- WithFilter(filter).
- Build()
+ WithFilter(filter))
filtered, err := reg.FilteredTools(context.Background())
if err != nil {
@@ -1565,11 +1663,10 @@ func TestFilteredToolsMatchesAvailableTools(t *testing.T) {
mockTool("tool3", "toolset2", true),
}
- reg := NewBuilder().
+ reg := mustBuild(t, NewBuilder().
SetTools(tools).
WithToolsets([]string{"toolset1"}).
- WithReadOnly(true).
- Build()
+ WithReadOnly(true))
ctx := context.Background()
filtered, err := reg.FilteredTools(ctx)
@@ -1619,13 +1716,12 @@ func TestFilteringOrder(t *testing.T) {
return true, nil
}
- reg := NewBuilder().
+ reg := mustBuild(t, NewBuilder().
SetTools([]ServerTool{tool}).
WithToolsets([]string{"all"}).
WithReadOnly(true). // This will exclude the tool (it's not read-only)
WithFeatureChecker(checker).
- WithFilter(filter).
- Build()
+ WithFilter(filter))
_ = reg.AvailableTools(context.Background())
@@ -1653,10 +1749,9 @@ func TestForMCPRequest_ToolsCall_FeatureFlaggedVariants(t *testing.T) {
}
// Test 1: Flag is OFF - first tool variant should be available
- regFlagOff := NewBuilder().
+ regFlagOff := mustBuild(t, NewBuilder().
SetTools(tools).
- WithToolsets([]string{"all"}).
- Build()
+ WithToolsets([]string{"all"}))
filteredOff := regFlagOff.ForMCPRequest(MCPMethodToolsCall, "get_job_logs")
availableOff := filteredOff.AvailableTools(context.Background())
if len(availableOff) != 1 {
@@ -1671,11 +1766,10 @@ func TestForMCPRequest_ToolsCall_FeatureFlaggedVariants(t *testing.T) {
checker := func(_ context.Context, flag string) (bool, error) {
return flag == "consolidated_flag", nil
}
- regFlagOn := NewBuilder().
+ regFlagOn := mustBuild(t, NewBuilder().
SetTools(tools).
WithToolsets([]string{"all"}).
- WithFeatureChecker(checker).
- Build()
+ WithFeatureChecker(checker))
filteredOn := regFlagOn.ForMCPRequest(MCPMethodToolsCall, "get_job_logs")
availableOn := filteredOn.AvailableTools(context.Background())
if len(availableOn) != 1 {
@@ -1707,12 +1801,11 @@ func TestWithTools_DeprecatedAliasAndFeatureFlag(t *testing.T) {
// Test 1: Flag OFF - old_tool should be available via direct name match
// (not via alias resolution to new_tool, since old_tool still exists)
- regFlagOff := NewBuilder().
+ regFlagOff := mustBuild(t, NewBuilder().
SetTools(tools).
WithDeprecatedAliases(deprecatedAliases).
WithToolsets([]string{}). // No toolsets enabled
- WithTools([]string{"old_tool"}). // Explicitly request old tool
- Build()
+ WithTools([]string{"old_tool"})) // Explicitly request old tool
availableOff := regFlagOff.AvailableTools(context.Background())
if len(availableOff) != 1 {
t.Fatalf("Flag OFF: Expected 1 tool, got %d", len(availableOff))
@@ -1725,13 +1818,12 @@ func TestWithTools_DeprecatedAliasAndFeatureFlag(t *testing.T) {
checker := func(_ context.Context, flag string) (bool, error) {
return flag == "my_flag", nil
}
- regFlagOn := NewBuilder().
+ regFlagOn := mustBuild(t, NewBuilder().
SetTools(tools).
WithDeprecatedAliases(deprecatedAliases).
WithToolsets([]string{}). // No toolsets enabled
WithTools([]string{"old_tool"}). // Request old tool name
- WithFeatureChecker(checker).
- Build()
+ WithFeatureChecker(checker))
availableOn := regFlagOn.AvailableTools(context.Background())
if len(availableOn) != 1 {
t.Fatalf("Flag ON: Expected 1 tool, got %d", len(availableOn))
diff --git a/pkg/tooldiscovery/search.go b/pkg/tooldiscovery/search.go
new file mode 100644
index 000000000..e7adc029b
--- /dev/null
+++ b/pkg/tooldiscovery/search.go
@@ -0,0 +1,314 @@
+package tooldiscovery
+
+import (
+ "sort"
+ "strings"
+
+ "github.com/google/jsonschema-go/jsonschema"
+ "github.com/lithammer/fuzzysearch/fuzzy"
+ "github.com/modelcontextprotocol/go-sdk/mcp"
+)
+
+type SearchResult struct {
+ Tool mcp.Tool `json:"tool"`
+ Score float64 `json:"score"`
+ MatchedIn []string `json:"matchedIn"` // Signals that contributed to scoring (e.g. name:token, description, parameter:token).
+}
+
+const (
+ DefaultMaxSearchResults = 3
+
+ // Scoring weights used by scoreTool.
+ substringMatchScore = 5
+ exactTokensMatchScore = 2.5
+ descriptionMatchScore = 2
+ prefixMatchScore = 1.5
+ parameterMatchScore = 1
+)
+
+// SearchOptions configures search behavior.
+type SearchOptions struct {
+ MaxResults int `json:"maxResults"` // Maximum number of results to return (default: 3)
+}
+
+// Search returns the most relevant tools for a free-text query.
+//
+// Prefer using SearchTools and passing an explicit tool list. This function is
+// kept for API compatibility and currently searches an empty tool set.
+func Search(query string, options ...SearchOptions) ([]SearchResult, error) {
+ return SearchTools(nil, query, options...)
+}
+
+// SearchTools is like Search, but searches across the provided tool list.
+//
+// Matching uses a weighted combination of:
+// - tool name matches (strongest)
+// - description matches
+// - input parameter name matches (JSON schema property names)
+// - fuzzy similarity as a tie-breaker
+//
+// Empty or whitespace-only queries return (nil, nil).
+func SearchTools(tools []mcp.Tool, query string, options ...SearchOptions) ([]SearchResult, error) {
+ maxResults := getMaxResults(options)
+
+ query = strings.TrimSpace(query)
+ if query == "" {
+ return nil, nil
+ }
+
+ queryLower := strings.ToLower(query)
+ queryTokens := strings.Fields(queryLower)
+ normalizedQueryCompact := strings.ReplaceAll(strings.ReplaceAll(queryLower, " ", ""), "_", "")
+
+ results := make([]SearchResult, 0, len(tools))
+ for _, tool := range tools {
+ score, matchedIn := scoreTool(tool, queryLower, queryTokens, normalizedQueryCompact)
+ results = append(results, SearchResult{
+ Tool: tool,
+ Score: score,
+ MatchedIn: matchedIn,
+ })
+ }
+
+ sort.Slice(results, func(i, j int) bool { return results[i].Score > results[j].Score })
+
+ // Filter out low-relevance results
+ const minScore = 1.0
+ filtered := results[:0]
+ for _, r := range results {
+ if r.Score > minScore {
+ filtered = append(filtered, r)
+ }
+ }
+ results = filtered
+
+ // Limit results
+ if len(results) > maxResults {
+ results = results[:maxResults]
+ }
+
+ return results, nil
+}
+
+// scoreTool assigns a relevance score to a tool for the given query.
+//
+// It combines several signals (substrings, token coverage, and similarity) from:
+// - tool name
+// - tool description
+// - input parameter names (schema property names)
+//
+// MatchedIn records which signals contributed to the score for debugging/tuning.
+func scoreTool(
+ tool mcp.Tool,
+ queryLower string,
+ queryTokens []string,
+ normalizedQueryCompact string,
+) (score float64, matchedIn []string) {
+ nameLower := strings.ToLower(tool.Name)
+ descLower := strings.ToLower(tool.Description)
+
+ normalizedNameCompact := strings.ReplaceAll(nameLower, "_", "")
+ nameTokens := splitTokens(nameLower)
+ propertyNames := lowerInputPropertyNames(tool.InputSchema)
+
+ matches := newMatchTracker(3)
+ score = 0.0
+
+ // Strong boosts for direct substring matches
+ if strings.Contains(nameLower, queryLower) {
+ score += substringMatchScore
+ matches.Add("name:substring")
+ }
+ if strings.HasPrefix(nameLower, queryLower) {
+ score += prefixMatchScore
+ matches.Add("name:prefix")
+ }
+ if normalizedNameCompact == normalizedQueryCompact && len(queryTokens) > 1 {
+ score += exactTokensMatchScore
+ matches.Add("name:exact-tokens")
+ }
+ if strings.Contains(descLower, queryLower) {
+ score += descriptionMatchScore
+ matches.Add("description")
+ }
+
+ for _, prop := range propertyNames {
+ if strings.Contains(prop, queryLower) {
+ score += parameterMatchScore
+ matches.Add("parameter")
+ }
+ }
+
+ matchedTokens := make(map[string]struct{})
+
+ // Token-level matches for multi-word queries
+ for _, token := range queryTokens {
+ if strings.Contains(nameLower, token) {
+ score++
+ matchedTokens[token] = struct{}{}
+ matches.Add("name:token")
+ } else if strings.Contains(descLower, token) {
+ score += 0.6
+ matchedTokens[token] = struct{}{}
+ matches.Add("description:token")
+ }
+
+ for _, prop := range propertyNames {
+ if strings.Contains(prop, token) {
+ // Only credit the first parameter match per token to avoid double-counting
+ score += 0.4
+ matchedTokens[token] = struct{}{}
+ matches.Add("parameter:token")
+ break
+ }
+ }
+ }
+
+ tokenCoverage := float64(len(matchedTokens))
+ score += tokenCoverage * 0.8
+ if len(queryTokens) > 1 && len(matchedTokens) == len(queryTokens) {
+ score += 2 // bonus when all tokens are matched somewhere
+ }
+
+ // Prefer names that cover query tokens directly, with fewer extra tokens
+ nameTokenMatches := 0
+ for _, qt := range queryTokens {
+ for _, nt := range nameTokens {
+ if strings.Contains(nt, qt) {
+ nameTokenMatches++
+ break
+ }
+ }
+ }
+ if nameTokenMatches == len(queryTokens) {
+ score += 4.0 // all tokens present in name tokens
+ if len(nameTokens) == len(queryTokens) {
+ score += 2.0 // exact token count match (e.g., issue_write vs sub_issue_write)
+ }
+ }
+ extraTokens := len(nameTokens) - nameTokenMatches
+ if extraTokens > 0 {
+ score -= float64(extraTokens) * 0.5 // stronger penalty for extra unrelated tokens
+ }
+
+ // Similarity scores to soften ordering among close matches
+ nameSim := normalizedSimilarity(nameLower, queryLower)
+ descSim := normalizedSimilarity(descLower, queryLower)
+
+ var propSim float64
+ for _, prop := range propertyNames {
+ if sim := normalizedSimilarity(prop, queryLower); sim > propSim {
+ propSim = sim
+ }
+ }
+
+ searchText := nameLower + " " + descLower
+ if len(propertyNames) > 0 {
+ searchText += " " + strings.Join(propertyNames, " ")
+ }
+ fuzzySim := normalizedSimilarity(searchText, queryLower)
+
+ score += nameSim * 2
+ score += descSim * 0.8
+ score += propSim * 0.6
+ score += fuzzySim * 0.5
+
+ return score, matches.List()
+}
+
+func getMaxResults(options []SearchOptions) int {
+ maxResults := DefaultMaxSearchResults
+ if len(options) > 0 && options[0].MaxResults > 0 {
+ maxResults = options[0].MaxResults
+ }
+ return maxResults
+}
+
+func lowerInputPropertyNames(inputSchema any) []string {
+ if inputSchema == nil {
+ return nil
+ }
+
+ // From the server, this is commonly a *jsonschema.Schema.
+ if schema, ok := inputSchema.(*jsonschema.Schema); ok {
+ if len(schema.Properties) == 0 {
+ return nil
+ }
+ out := make([]string, 0, len(schema.Properties))
+ for prop := range schema.Properties {
+ out = append(out, strings.ToLower(prop))
+ }
+ return out
+ }
+
+ // From the client (or when unmarshaled), schemas arrive as map[string]any.
+ if schema, ok := inputSchema.(map[string]any); ok {
+ propsAny, ok := schema["properties"]
+ if !ok {
+ return nil
+ }
+ props, ok := propsAny.(map[string]any)
+ if !ok || len(props) == 0 {
+ return nil
+ }
+ out := make([]string, 0, len(props))
+ for prop := range props {
+ out = append(out, strings.ToLower(prop))
+ }
+ return out
+ }
+
+ return nil
+}
+
+type matchTracker struct {
+ list []string
+ seen map[string]struct{}
+}
+
+func newMatchTracker(capacity int) *matchTracker {
+ return &matchTracker{
+ list: make([]string, 0, capacity),
+ seen: make(map[string]struct{}, capacity),
+ }
+}
+
+func (m *matchTracker) Add(part string) {
+ if _, ok := m.seen[part]; ok {
+ return
+ }
+ m.seen[part] = struct{}{}
+ m.list = append(m.list, part)
+}
+
+func (m *matchTracker) List() []string {
+ return m.list
+}
+
+func normalizedSimilarity(a, b string) float64 {
+ if len(a) == 0 || len(b) == 0 {
+ return 0
+ }
+
+ distance := fuzzy.LevenshteinDistance(a, b)
+ maxLen := len(a)
+ if len(b) > maxLen {
+ maxLen = len(b)
+ }
+
+ similarity := 1 - (float64(distance) / float64(maxLen))
+ if similarity < 0 {
+ return 0
+ }
+
+ return similarity
+}
+
+func splitTokens(s string) []string {
+ if s == "" {
+ return nil
+ }
+ return strings.FieldsFunc(s, func(r rune) bool {
+ return r == '_' || r == '-' || r == ' '
+ })
+}
diff --git a/pkg/tooldiscovery/search_test.go b/pkg/tooldiscovery/search_test.go
new file mode 100644
index 000000000..79d6fe8dd
--- /dev/null
+++ b/pkg/tooldiscovery/search_test.go
@@ -0,0 +1,57 @@
+package tooldiscovery
+
+import (
+ "testing"
+
+ "github.com/google/jsonschema-go/jsonschema"
+ "github.com/modelcontextprotocol/go-sdk/mcp"
+ "github.com/stretchr/testify/require"
+)
+
+func TestSearchTools_EmptyQueryReturnsNil(t *testing.T) {
+ results, err := SearchTools([]mcp.Tool{{Name: "issue_list"}}, " ")
+ require.NoError(t, err)
+ require.Nil(t, results)
+}
+
+func TestSearchTools_FindsByName(t *testing.T) {
+ tools := []mcp.Tool{
+ {Name: "issue_list", Description: "List issues"},
+ {Name: "repo_get", Description: "Get repository"},
+ }
+
+ results, err := SearchTools(tools, "issue", SearchOptions{MaxResults: 10})
+ require.NoError(t, err)
+ require.NotEmpty(t, results)
+ require.Equal(t, "issue_list", results[0].Tool.Name)
+}
+
+func TestSearchTools_FindsByParameterName_JSONSchema(t *testing.T) {
+ tools := []mcp.Tool{
+ {
+ Name: "unrelated_tool",
+ Description: "does something else",
+ InputSchema: &jsonschema.Schema{Properties: map[string]*jsonschema.Schema{"owner": {}}},
+ },
+ }
+
+ results, err := SearchTools(tools, "owner", SearchOptions{MaxResults: 10})
+ require.NoError(t, err)
+ require.NotEmpty(t, results)
+ require.Equal(t, "unrelated_tool", results[0].Tool.Name)
+}
+
+func TestSearchTools_FindsByParameterName_MapSchema(t *testing.T) {
+ tools := []mcp.Tool{
+ {
+ Name: "unrelated_tool",
+ Description: "does something else",
+ InputSchema: map[string]any{"properties": map[string]any{"repo": map[string]any{}}},
+ },
+ }
+
+ results, err := SearchTools(tools, "repo", SearchOptions{MaxResults: 10})
+ require.NoError(t, err)
+ require.NotEmpty(t, results)
+ require.Equal(t, "unrelated_tool", results[0].Tool.Name)
+}
diff --git a/third-party-licenses.darwin.md b/third-party-licenses.darwin.md
index fb4392fb9..8217c7707 100644
--- a/third-party-licenses.darwin.md
+++ b/third-party-licenses.darwin.md
@@ -17,13 +17,14 @@ The following packages are included for the amd64, arm64 architectures.
- [github.com/github/github-mcp-server](https://pkg.go.dev/github.com/github/github-mcp-server) ([MIT](https://github.com/github/github-mcp-server/blob/HEAD/LICENSE))
- [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE))
- [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE))
- - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.4.0/LICENSE))
+ - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.5.0/LICENSE))
- [github.com/google/go-github/v79/github](https://pkg.go.dev/github.com/google/go-github/v79/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v79.0.0/LICENSE))
- [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.1.0/LICENSE))
- [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.4.2/LICENSE))
- [github.com/gorilla/css/scanner](https://pkg.go.dev/github.com/gorilla/css/scanner) ([BSD-3-Clause](https://github.com/gorilla/css/blob/v1.0.1/LICENSE))
- [github.com/josephburnett/jd/v2](https://pkg.go.dev/github.com/josephburnett/jd/v2) ([MIT](https://github.com/josephburnett/jd/blob/v1.9.2/LICENSE))
- [github.com/josharian/intern](https://pkg.go.dev/github.com/josharian/intern) ([MIT](https://github.com/josharian/intern/blob/v1.0.0/license.md))
+ - [github.com/lithammer/fuzzysearch/fuzzy](https://pkg.go.dev/github.com/lithammer/fuzzysearch/fuzzy) ([MIT](https://github.com/lithammer/fuzzysearch/blob/v1.1.8/LICENSE))
- [github.com/mailru/easyjson](https://pkg.go.dev/github.com/mailru/easyjson) ([MIT](https://github.com/mailru/easyjson/blob/v0.7.7/LICENSE))
- [github.com/microcosm-cc/bluemonday](https://pkg.go.dev/github.com/microcosm-cc/bluemonday) ([BSD-3-Clause](https://github.com/microcosm-cc/bluemonday/blob/v1.0.27/LICENSE.md))
- [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.2.0/LICENSE))
diff --git a/third-party-licenses.linux.md b/third-party-licenses.linux.md
index 564f20dcb..981e388e5 100644
--- a/third-party-licenses.linux.md
+++ b/third-party-licenses.linux.md
@@ -17,13 +17,14 @@ The following packages are included for the 386, amd64, arm64 architectures.
- [github.com/github/github-mcp-server](https://pkg.go.dev/github.com/github/github-mcp-server) ([MIT](https://github.com/github/github-mcp-server/blob/HEAD/LICENSE))
- [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE))
- [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE))
- - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.4.0/LICENSE))
+ - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.5.0/LICENSE))
- [github.com/google/go-github/v79/github](https://pkg.go.dev/github.com/google/go-github/v79/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v79.0.0/LICENSE))
- [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.1.0/LICENSE))
- [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.4.2/LICENSE))
- [github.com/gorilla/css/scanner](https://pkg.go.dev/github.com/gorilla/css/scanner) ([BSD-3-Clause](https://github.com/gorilla/css/blob/v1.0.1/LICENSE))
- [github.com/josephburnett/jd/v2](https://pkg.go.dev/github.com/josephburnett/jd/v2) ([MIT](https://github.com/josephburnett/jd/blob/v1.9.2/LICENSE))
- [github.com/josharian/intern](https://pkg.go.dev/github.com/josharian/intern) ([MIT](https://github.com/josharian/intern/blob/v1.0.0/license.md))
+ - [github.com/lithammer/fuzzysearch/fuzzy](https://pkg.go.dev/github.com/lithammer/fuzzysearch/fuzzy) ([MIT](https://github.com/lithammer/fuzzysearch/blob/v1.1.8/LICENSE))
- [github.com/mailru/easyjson](https://pkg.go.dev/github.com/mailru/easyjson) ([MIT](https://github.com/mailru/easyjson/blob/v0.7.7/LICENSE))
- [github.com/microcosm-cc/bluemonday](https://pkg.go.dev/github.com/microcosm-cc/bluemonday) ([BSD-3-Clause](https://github.com/microcosm-cc/bluemonday/blob/v1.0.27/LICENSE.md))
- [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.2.0/LICENSE))
diff --git a/third-party-licenses.windows.md b/third-party-licenses.windows.md
index 6b4dcfb97..ae0e2389e 100644
--- a/third-party-licenses.windows.md
+++ b/third-party-licenses.windows.md
@@ -17,7 +17,7 @@ The following packages are included for the 386, amd64, arm64 architectures.
- [github.com/github/github-mcp-server](https://pkg.go.dev/github.com/github/github-mcp-server) ([MIT](https://github.com/github/github-mcp-server/blob/HEAD/LICENSE))
- [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE))
- [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE))
- - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.4.0/LICENSE))
+ - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.5.0/LICENSE))
- [github.com/google/go-github/v79/github](https://pkg.go.dev/github.com/google/go-github/v79/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v79.0.0/LICENSE))
- [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.1.0/LICENSE))
- [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.4.2/LICENSE))
@@ -25,6 +25,7 @@ The following packages are included for the 386, amd64, arm64 architectures.
- [github.com/inconshreveable/mousetrap](https://pkg.go.dev/github.com/inconshreveable/mousetrap) ([Apache-2.0](https://github.com/inconshreveable/mousetrap/blob/v1.1.0/LICENSE))
- [github.com/josephburnett/jd/v2](https://pkg.go.dev/github.com/josephburnett/jd/v2) ([MIT](https://github.com/josephburnett/jd/blob/v1.9.2/LICENSE))
- [github.com/josharian/intern](https://pkg.go.dev/github.com/josharian/intern) ([MIT](https://github.com/josharian/intern/blob/v1.0.0/license.md))
+ - [github.com/lithammer/fuzzysearch/fuzzy](https://pkg.go.dev/github.com/lithammer/fuzzysearch/fuzzy) ([MIT](https://github.com/lithammer/fuzzysearch/blob/v1.1.8/LICENSE))
- [github.com/mailru/easyjson](https://pkg.go.dev/github.com/mailru/easyjson) ([MIT](https://github.com/mailru/easyjson/blob/v0.7.7/LICENSE))
- [github.com/microcosm-cc/bluemonday](https://pkg.go.dev/github.com/microcosm-cc/bluemonday) ([BSD-3-Clause](https://github.com/microcosm-cc/bluemonday/blob/v1.0.27/LICENSE.md))
- [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.2.0/LICENSE))
diff --git a/third-party/github.com/lithammer/fuzzysearch/fuzzy/LICENSE b/third-party/github.com/lithammer/fuzzysearch/fuzzy/LICENSE
new file mode 100644
index 000000000..dee3d1de2
--- /dev/null
+++ b/third-party/github.com/lithammer/fuzzysearch/fuzzy/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 Peter Lithammer
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.