From e61997c5da2f02be29b5c33731055c54ccad8343 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 24 Nov 2025 12:02:23 +0000
Subject: [PATCH 1/6] Initial plan
From 1ebfc36b90198254b6c892cd94cf8f94eb5a2e0d Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 24 Nov 2025 12:18:35 +0000
Subject: [PATCH 2/6] Migrate projects toolset to modelcontextprotocol/go-sdk
Co-authored-by: omgitsads <4619+omgitsads@users.noreply.github.com>
---
.../__toolsnaps__/add_project_item.snap | 41 +-
.../__toolsnaps__/delete_project_item.snap | 35 +-
pkg/github/__toolsnaps__/get_project.snap | 30 +-
.../__toolsnaps__/get_project_field.snap | 36 +-
.../__toolsnaps__/get_project_item.snap | 40 +-
.../__toolsnaps__/list_project_fields.snap | 42 +-
.../__toolsnaps__/list_project_items.snap | 50 +-
pkg/github/__toolsnaps__/list_projects.snap | 40 +-
.../__toolsnaps__/update_project_item.snap | 42 +-
pkg/github/projects.go | 866 ++++++++++--------
pkg/github/projects_test.go | 137 +--
11 files changed, 730 insertions(+), 629 deletions(-)
diff --git a/pkg/github/__toolsnaps__/add_project_item.snap b/pkg/github/__toolsnaps__/add_project_item.snap
index 143c04eb91..08f4953706 100644
--- a/pkg/github/__toolsnaps__/add_project_item.snap
+++ b/pkg/github/__toolsnaps__/add_project_item.snap
@@ -1,48 +1,47 @@
{
"annotations": {
- "title": "Add project item",
- "readOnlyHint": false
+ "title": "Add project item"
},
"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": {
- "description": "The numeric ID of the issue or pull request to add to the project.",
- "type": "number"
+ "type": "number",
+ "description": "The numeric ID of the issue or pull request to add to the project."
},
"item_type": {
+ "type": "string",
"description": "The item's type, either issue or pull_request.",
"enum": [
"issue",
"pull_request"
- ],
- "type": "string"
+ ]
},
"owner": {
- "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"
+ "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."
},
"owner_type": {
+ "type": "string",
"description": "Owner type",
"enum": [
"user",
"org"
- ],
- "type": "string"
+ ]
},
"project_number": {
- "description": "The project's number.",
- "type": "number"
+ "type": "number",
+ "description": "The project's 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__/delete_project_item.snap b/pkg/github/__toolsnaps__/delete_project_item.snap
index 0de1336a0a..d768df10fb 100644
--- a/pkg/github/__toolsnaps__/delete_project_item.snap
+++ b/pkg/github/__toolsnaps__/delete_project_item.snap
@@ -1,39 +1,38 @@
{
"annotations": {
- "title": "Delete project item",
- "readOnlyHint": false
+ "title": "Delete project item"
},
"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": {
- "description": "The internal project item ID to delete from the project (not the issue or pull request ID).",
- "type": "number"
+ "type": "number",
+ "description": "The internal project item ID to delete from the project (not the issue or pull request ID)."
},
"owner": {
- "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"
+ "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."
},
"owner_type": {
+ "type": "string",
"description": "Owner type",
"enum": [
"user",
"org"
- ],
- "type": "string"
+ ]
},
"project_number": {
- "description": "The project's number.",
- "type": "number"
+ "type": "number",
+ "description": "The project's 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__/get_project.snap b/pkg/github/__toolsnaps__/get_project.snap
index db060e427d..8194b7358e 100644
--- a/pkg/github/__toolsnaps__/get_project.snap
+++ b/pkg/github/__toolsnaps__/get_project.snap
@@ -1,34 +1,34 @@
{
"annotations": {
- "title": "Get project",
- "readOnlyHint": true
+ "readOnlyHint": true,
+ "title": "Get project"
},
"description": "Get Project for a user or org",
"inputSchema": {
+ "type": "object",
+ "required": [
+ "project_number",
+ "owner_type",
+ "owner"
+ ],
"properties": {
"owner": {
- "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"
+ "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."
},
"owner_type": {
+ "type": "string",
"description": "Owner type",
"enum": [
"user",
"org"
- ],
- "type": "string"
+ ]
},
"project_number": {
- "description": "The project's number",
- "type": "number"
+ "type": "number",
+ "description": "The project's 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 65d6f86f1d..0df557a032 100644
--- a/pkg/github/__toolsnaps__/get_project_field.snap
+++ b/pkg/github/__toolsnaps__/get_project_field.snap
@@ -1,39 +1,39 @@
{
"annotations": {
- "title": "Get project field",
- "readOnlyHint": true
+ "readOnlyHint": true,
+ "title": "Get project field"
},
"description": "Get Project field for a user or org",
"inputSchema": {
+ "type": "object",
+ "required": [
+ "owner_type",
+ "owner",
+ "project_number",
+ "field_id"
+ ],
"properties": {
"field_id": {
- "description": "The field's id.",
- "type": "number"
+ "type": "number",
+ "description": "The field's id."
},
"owner": {
- "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"
+ "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."
},
"owner_type": {
+ "type": "string",
"description": "Owner type",
"enum": [
"user",
"org"
- ],
- "type": "string"
+ ]
},
"project_number": {
- "description": "The project's number.",
- "type": "number"
+ "type": "number",
+ "description": "The project's 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 36eb7bb633..d77c49c1ef 100644
--- a/pkg/github/__toolsnaps__/get_project_item.snap
+++ b/pkg/github/__toolsnaps__/get_project_item.snap
@@ -1,46 +1,46 @@
{
"annotations": {
- "title": "Get project item",
- "readOnlyHint": true
+ "readOnlyHint": true,
+ "title": "Get project item"
},
"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": {
- "description": "The item's ID.",
- "type": "number"
+ "type": "number",
+ "description": "The item's ID."
},
"owner": {
- "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"
+ "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."
},
"owner_type": {
+ "type": "string",
"description": "Owner type",
"enum": [
"user",
"org"
- ],
- "type": "string"
+ ]
},
"project_number": {
- "description": "The project's number.",
- "type": "number"
+ "type": "number",
+ "description": "The project's 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__/list_project_fields.snap b/pkg/github/__toolsnaps__/list_project_fields.snap
index c543e69d7d..6bef185079 100644
--- a/pkg/github/__toolsnaps__/list_project_fields.snap
+++ b/pkg/github/__toolsnaps__/list_project_fields.snap
@@ -1,46 +1,46 @@
{
"annotations": {
- "title": "List project fields",
- "readOnlyHint": true
+ "readOnlyHint": true,
+ "title": "List project fields"
},
"description": "List Project fields for a user or org",
"inputSchema": {
+ "type": "object",
+ "required": [
+ "owner_type",
+ "owner",
+ "project_number"
+ ],
"properties": {
"after": {
- "description": "Forward pagination cursor from previous pageInfo.nextCursor.",
- "type": "string"
+ "type": "string",
+ "description": "Forward pagination cursor from previous pageInfo.nextCursor."
},
"before": {
- "description": "Backward pagination cursor from previous pageInfo.prevCursor (rare).",
- "type": "string"
+ "type": "string",
+ "description": "Backward pagination cursor from previous pageInfo.prevCursor (rare)."
},
"owner": {
- "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"
+ "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."
},
"owner_type": {
+ "type": "string",
"description": "Owner type",
"enum": [
"user",
"org"
- ],
- "type": "string"
+ ]
},
"per_page": {
- "description": "Results per page (max 50)",
- "type": "number"
+ "type": "number",
+ "description": "Results per page (max 50)"
},
"project_number": {
- "description": "The project's number.",
- "type": "number"
+ "type": "number",
+ "description": "The project's 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 38d3cb509d..bceb5d9eb0 100644
--- a/pkg/github/__toolsnaps__/list_project_items.snap
+++ b/pkg/github/__toolsnaps__/list_project_items.snap
@@ -1,57 +1,57 @@
{
"annotations": {
- "title": "List project items",
- "readOnlyHint": true
+ "readOnlyHint": true,
+ "title": "List project items"
},
"description": "Search project items with advanced filtering",
"inputSchema": {
+ "type": "object",
+ "required": [
+ "owner_type",
+ "owner",
+ "project_number"
+ ],
"properties": {
"after": {
- "description": "Forward pagination cursor from previous pageInfo.nextCursor.",
- "type": "string"
+ "type": "string",
+ "description": "Forward pagination cursor from previous pageInfo.nextCursor."
},
"before": {
- "description": "Backward pagination cursor from previous pageInfo.prevCursor (rare).",
- "type": "string"
+ "type": "string",
+ "description": "Backward pagination cursor from previous pageInfo.prevCursor (rare)."
},
"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": {
- "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"
+ "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."
},
"owner_type": {
+ "type": "string",
"description": "Owner type",
"enum": [
"user",
"org"
- ],
- "type": "string"
+ ]
},
"per_page": {
- "description": "Results per page (max 50)",
- "type": "number"
+ "type": "number",
+ "description": "Results per page (max 50)"
},
"project_number": {
- "description": "The project's number.",
- "type": "number"
+ "type": "number",
+ "description": "The project's number."
},
"query": {
- "description": "Query string for advanced filtering of project items using GitHub's project filtering syntax.",
- "type": "string"
+ "type": "string",
+ "description": "Query string for advanced filtering of project items using GitHub's project filtering syntax."
}
- },
- "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 8a035271cf..f48e262173 100644
--- a/pkg/github/__toolsnaps__/list_projects.snap
+++ b/pkg/github/__toolsnaps__/list_projects.snap
@@ -1,45 +1,45 @@
{
"annotations": {
- "title": "List projects",
- "readOnlyHint": true
+ "readOnlyHint": true,
+ "title": "List projects"
},
"description": "List Projects for a user or organization",
"inputSchema": {
+ "type": "object",
+ "required": [
+ "owner_type",
+ "owner"
+ ],
"properties": {
"after": {
- "description": "Forward pagination cursor from previous pageInfo.nextCursor.",
- "type": "string"
+ "type": "string",
+ "description": "Forward pagination cursor from previous pageInfo.nextCursor."
},
"before": {
- "description": "Backward pagination cursor from previous pageInfo.prevCursor (rare).",
- "type": "string"
+ "type": "string",
+ "description": "Backward pagination cursor from previous pageInfo.prevCursor (rare)."
},
"owner": {
- "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"
+ "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."
},
"owner_type": {
+ "type": "string",
"description": "Owner type",
"enum": [
"user",
"org"
- ],
- "type": "string"
+ ]
},
"per_page": {
- "description": "Results per page (max 50)",
- "type": "number"
+ "type": "number",
+ "description": "Results per page (max 50)"
},
"query": {
- "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"
+ "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\"."
}
- },
- "required": [
- "owner_type",
- "owner"
- ],
- "type": "object"
+ }
},
"name": "list_projects"
}
\ 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 6c8648503a..8f5afaa583 100644
--- a/pkg/github/__toolsnaps__/update_project_item.snap
+++ b/pkg/github/__toolsnaps__/update_project_item.snap
@@ -1,45 +1,43 @@
{
"annotations": {
- "title": "Update project item",
- "readOnlyHint": false
+ "title": "Update project item"
},
"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": {
- "description": "The unique identifier of the project item. This is not the issue or pull request ID.",
- "type": "number"
+ "type": "number",
+ "description": "The unique identifier of the project item. This is not the issue or pull request ID."
},
"owner": {
- "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"
+ "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."
},
"owner_type": {
+ "type": "string",
"description": "Owner type",
"enum": [
"user",
"org"
- ],
- "type": "string"
+ ]
},
"project_number": {
- "description": "The project's number.",
- "type": "number"
+ "type": "number",
+ "description": "The project's number."
},
"updated_field": {
- "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\"}",
- "properties": {},
- "type": "object"
+ "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": [
- "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/projects.go b/pkg/github/projects.go
index 6710a4f6fb..79dfb25cee 100644
--- a/pkg/github/projects.go
+++ b/pkg/github/projects.go
@@ -1,5 +1,3 @@
-//go:build ignore
-
package github
import (
@@ -12,9 +10,10 @@ import (
ghErrors "github.com/github/github-mcp-server/pkg/errors"
"github.com/github/github-mcp-server/pkg/translations"
+ "github.com/github/github-mcp-server/pkg/utils"
"github.com/google/go-github/v79/github"
- "github.com/mark3labs/mcp-go/mcp"
- "github.com/mark3labs/mcp-go/server"
+ "github.com/google/jsonschema-go/jsonschema"
+ "github.com/modelcontextprotocol/go-sdk/mcp"
)
const (
@@ -25,56 +24,69 @@ const (
MaxProjectsPerPage = 50
)
-func ListProjects(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
- return mcp.NewTool("list_projects",
- mcp.WithDescription(t("TOOL_LIST_PROJECTS_DESCRIPTION", `List Projects for a user or organization`)),
- mcp.WithToolAnnotation(mcp.ToolAnnotation{
+func ListProjects(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) {
+ return mcp.Tool{
+ Name: "list_projects",
+ Description: t("TOOL_LIST_PROJECTS_DESCRIPTION", `List Projects for a user or organization`),
+ Annotations: &mcp.ToolAnnotations{
Title: t("TOOL_LIST_PROJECTS_USER_TITLE", "List projects"),
- ReadOnlyHint: ToBoolPtr(true),
- }),
- mcp.WithString("owner_type",
- mcp.Required(), mcp.Description("Owner type"), mcp.Enum("user", "org"),
- ),
- mcp.WithString("owner",
- mcp.Required(),
- mcp.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."),
- ),
- mcp.WithString("query",
- mcp.Description(`Filter projects by title text and open/closed state; permitted qualifiers: is:open, is:closed; examples: "roadmap is:open", "is:open feature planning".`),
- ),
- mcp.WithNumber("per_page",
- mcp.Description(fmt.Sprintf("Results per page (max %d)", MaxProjectsPerPage)),
- ),
- mcp.WithString("after",
- mcp.Description("Forward pagination cursor from previous pageInfo.nextCursor."),
- ),
- mcp.WithString("before",
- mcp.Description("Backward pagination cursor from previous pageInfo.prevCursor (rare)."),
- ),
- ), func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
- owner, err := RequiredParam[string](req, "owner")
+ ReadOnlyHint: true,
+ },
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "owner_type": {
+ Type: "string",
+ Description: "Owner type",
+ Enum: []any{"user", "org"},
+ },
+ "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.",
+ },
+ "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".`,
+ },
+ "per_page": {
+ Type: "number",
+ Description: fmt.Sprintf("Results per page (max %d)", MaxProjectsPerPage),
+ },
+ "after": {
+ Type: "string",
+ Description: "Forward pagination cursor from previous pageInfo.nextCursor.",
+ },
+ "before": {
+ Type: "string",
+ Description: "Backward pagination cursor from previous pageInfo.prevCursor (rare).",
+ },
+ },
+ Required: []string{"owner_type", "owner"},
+ },
+ }, func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
+ owner, err := RequiredParam[string](args, "owner")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- ownerType, err := RequiredParam[string](req, "owner_type")
+ ownerType, err := RequiredParam[string](args, "owner_type")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- queryStr, err := OptionalParam[string](req, "query")
+ queryStr, err := OptionalParam[string](args, "query")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- pagination, err := extractPaginationOptions(req)
+ pagination, err := extractPaginationOptionsFromArgs(args)
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
client, err := getClient(ctx)
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
var resp *github.Response
@@ -102,7 +114,7 @@ func ListProjects(getClient GetClientFn, t translations.TranslationHelperFunc) (
"failed to list projects",
resp,
err,
- ), nil
+ ), nil, nil
}
defer func() { _ = resp.Body.Close() }()
@@ -117,53 +129,60 @@ func ListProjects(getClient GetClientFn, t translations.TranslationHelperFunc) (
r, err := json.Marshal(response)
if err != nil {
- return nil, fmt.Errorf("failed to marshal response: %w", err)
+ return nil, nil, fmt.Errorf("failed to marshal response: %w", err)
}
- return mcp.NewToolResultText(string(r)), nil
+ return utils.NewToolResultText(string(r)), nil, nil
}
}
-func GetProject(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
- return mcp.NewTool("get_project",
- mcp.WithDescription(t("TOOL_GET_PROJECT_DESCRIPTION", "Get Project for a user or org")),
- mcp.WithToolAnnotation(mcp.ToolAnnotation{
+func GetProject(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) {
+ return mcp.Tool{
+ Name: "get_project",
+ Description: t("TOOL_GET_PROJECT_DESCRIPTION", "Get Project for a user or org"),
+ Annotations: &mcp.ToolAnnotations{
Title: t("TOOL_GET_PROJECT_USER_TITLE", "Get project"),
- ReadOnlyHint: ToBoolPtr(true),
- }),
- mcp.WithNumber("project_number",
- mcp.Required(),
- mcp.Description("The project's number"),
- ),
- mcp.WithString("owner_type",
- mcp.Required(),
- mcp.Description("Owner type"),
- mcp.Enum("user", "org"),
- ),
- mcp.WithString("owner",
- mcp.Required(),
- mcp.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."),
- ),
- ), func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+ ReadOnlyHint: true,
+ },
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "project_number": {
+ Type: "number",
+ Description: "The project's number",
+ },
+ "owner_type": {
+ Type: "string",
+ Description: "Owner type",
+ Enum: []any{"user", "org"},
+ },
+ "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.",
+ },
+ },
+ Required: []string{"project_number", "owner_type", "owner"},
+ },
+ }, func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
- projectNumber, err := RequiredInt(req, "project_number")
+ projectNumber, err := RequiredInt(args, "project_number")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- owner, err := RequiredParam[string](req, "owner")
+ owner, err := RequiredParam[string](args, "owner")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- ownerType, err := RequiredParam[string](req, "owner_type")
+ ownerType, err := RequiredParam[string](args, "owner_type")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
client, err := getClient(ctx)
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
var resp *github.Response
@@ -179,80 +198,91 @@ func GetProject(getClient GetClientFn, t translations.TranslationHelperFunc) (to
"failed to get project",
resp,
err,
- ), nil
+ ), nil, nil
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
body, err := io.ReadAll(resp.Body)
if err != nil {
- return nil, fmt.Errorf("failed to read response body: %w", err)
+ return nil, nil, fmt.Errorf("failed to read response body: %w", err)
}
- return mcp.NewToolResultError(fmt.Sprintf("failed to get project: %s", string(body))), nil
+ return utils.NewToolResultError(fmt.Sprintf("failed to get project: %s", string(body))), nil, nil
}
minimalProject := convertToMinimalProject(project)
r, err := json.Marshal(minimalProject)
if err != nil {
- return nil, fmt.Errorf("failed to marshal response: %w", err)
+ return nil, nil, fmt.Errorf("failed to marshal response: %w", err)
}
- return mcp.NewToolResultText(string(r)), nil
+ return utils.NewToolResultText(string(r)), nil, nil
}
}
-func ListProjectFields(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
- return mcp.NewTool("list_project_fields",
- mcp.WithDescription(t("TOOL_LIST_PROJECT_FIELDS_DESCRIPTION", "List Project fields for a user or org")),
- mcp.WithToolAnnotation(mcp.ToolAnnotation{
+func ListProjectFields(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) {
+ return mcp.Tool{
+ Name: "list_project_fields",
+ Description: t("TOOL_LIST_PROJECT_FIELDS_DESCRIPTION", "List Project fields for a user or org"),
+ Annotations: &mcp.ToolAnnotations{
Title: t("TOOL_LIST_PROJECT_FIELDS_USER_TITLE", "List project fields"),
- ReadOnlyHint: ToBoolPtr(true),
- }),
- mcp.WithString("owner_type",
- mcp.Required(),
- mcp.Description("Owner type"),
- mcp.Enum("user", "org")),
- mcp.WithString("owner",
- mcp.Required(),
- mcp.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."),
- ),
- mcp.WithNumber("project_number",
- mcp.Required(),
- mcp.Description("The project's number."),
- ),
- mcp.WithNumber("per_page",
- mcp.Description(fmt.Sprintf("Results per page (max %d)", MaxProjectsPerPage)),
- ),
- mcp.WithString("after",
- mcp.Description("Forward pagination cursor from previous pageInfo.nextCursor."),
- ),
- mcp.WithString("before",
- mcp.Description("Backward pagination cursor from previous pageInfo.prevCursor (rare)."),
- ),
- ), func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
- owner, err := RequiredParam[string](req, "owner")
+ ReadOnlyHint: true,
+ },
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "owner_type": {
+ Type: "string",
+ Description: "Owner type",
+ Enum: []any{"user", "org"},
+ },
+ "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.",
+ },
+ "project_number": {
+ Type: "number",
+ Description: "The project's number.",
+ },
+ "per_page": {
+ Type: "number",
+ Description: fmt.Sprintf("Results per page (max %d)", MaxProjectsPerPage),
+ },
+ "after": {
+ Type: "string",
+ Description: "Forward pagination cursor from previous pageInfo.nextCursor.",
+ },
+ "before": {
+ Type: "string",
+ Description: "Backward pagination cursor from previous pageInfo.prevCursor (rare).",
+ },
+ },
+ Required: []string{"owner_type", "owner", "project_number"},
+ },
+ }, func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
+ owner, err := RequiredParam[string](args, "owner")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- ownerType, err := RequiredParam[string](req, "owner_type")
+ ownerType, err := RequiredParam[string](args, "owner_type")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- projectNumber, err := RequiredInt(req, "project_number")
+ projectNumber, err := RequiredInt(args, "project_number")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- pagination, err := extractPaginationOptions(req)
+ pagination, err := extractPaginationOptionsFromArgs(args)
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
client, err := getClient(ctx)
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
var resp *github.Response
@@ -273,7 +303,7 @@ func ListProjectFields(getClient GetClientFn, t translations.TranslationHelperFu
"failed to list project fields",
resp,
err,
- ), nil
+ ), nil, nil
}
defer func() { _ = resp.Body.Close() }()
@@ -284,54 +314,64 @@ func ListProjectFields(getClient GetClientFn, t translations.TranslationHelperFu
r, err := json.Marshal(response)
if err != nil {
- return nil, fmt.Errorf("failed to marshal response: %w", err)
+ return nil, nil, fmt.Errorf("failed to marshal response: %w", err)
}
- return mcp.NewToolResultText(string(r)), nil
+ return utils.NewToolResultText(string(r)), nil, nil
}
}
-func GetProjectField(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
- return mcp.NewTool("get_project_field",
- mcp.WithDescription(t("TOOL_GET_PROJECT_FIELD_DESCRIPTION", "Get Project field for a user or org")),
- mcp.WithToolAnnotation(mcp.ToolAnnotation{
+func GetProjectField(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) {
+ return mcp.Tool{
+ Name: "get_project_field",
+ Description: t("TOOL_GET_PROJECT_FIELD_DESCRIPTION", "Get Project field for a user or org"),
+ Annotations: &mcp.ToolAnnotations{
Title: t("TOOL_GET_PROJECT_FIELD_USER_TITLE", "Get project field"),
- ReadOnlyHint: ToBoolPtr(true),
- }),
- mcp.WithString("owner_type",
- mcp.Required(),
- mcp.Description("Owner type"), mcp.Enum("user", "org")),
- mcp.WithString("owner",
- mcp.Required(),
- mcp.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."),
- ),
- mcp.WithNumber("project_number",
- mcp.Required(),
- mcp.Description("The project's number.")),
- mcp.WithNumber("field_id",
- mcp.Required(),
- mcp.Description("The field's id."),
- ),
- ), func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
- owner, err := RequiredParam[string](req, "owner")
+ ReadOnlyHint: true,
+ },
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "owner_type": {
+ Type: "string",
+ Description: "Owner type",
+ Enum: []any{"user", "org"},
+ },
+ "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.",
+ },
+ "project_number": {
+ Type: "number",
+ Description: "The project's number.",
+ },
+ "field_id": {
+ Type: "number",
+ Description: "The field's id.",
+ },
+ },
+ Required: []string{"owner_type", "owner", "project_number", "field_id"},
+ },
+ }, func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
+ owner, err := RequiredParam[string](args, "owner")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- ownerType, err := RequiredParam[string](req, "owner_type")
+ ownerType, err := RequiredParam[string](args, "owner_type")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- projectNumber, err := RequiredInt(req, "project_number")
+ projectNumber, err := RequiredInt(args, "project_number")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- fieldID, err := RequiredBigInt(req, "field_id")
+ fieldID, err := RequiredBigInt(args, "field_id")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
client, err := getClient(ctx)
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
var resp *github.Response
@@ -348,95 +388,110 @@ func GetProjectField(getClient GetClientFn, t translations.TranslationHelperFunc
"failed to get project field",
resp,
err,
- ), nil
+ ), nil, nil
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
body, err := io.ReadAll(resp.Body)
if err != nil {
- return nil, fmt.Errorf("failed to read response body: %w", err)
+ return nil, nil, fmt.Errorf("failed to read response body: %w", err)
}
- return mcp.NewToolResultError(fmt.Sprintf("failed to get project field: %s", string(body))), nil
+ return utils.NewToolResultError(fmt.Sprintf("failed to get project field: %s", string(body))), nil, nil
}
r, err := json.Marshal(projectField)
if err != nil {
- return nil, fmt.Errorf("failed to marshal response: %w", err)
+ return nil, nil, fmt.Errorf("failed to marshal response: %w", err)
}
- return mcp.NewToolResultText(string(r)), nil
+ return utils.NewToolResultText(string(r)), nil, nil
}
}
-func ListProjectItems(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
- return mcp.NewTool("list_project_items",
- mcp.WithDescription(t("TOOL_LIST_PROJECT_ITEMS_DESCRIPTION", `Search project items with advanced filtering`)),
- mcp.WithToolAnnotation(mcp.ToolAnnotation{
+func ListProjectItems(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) {
+ return mcp.Tool{
+ Name: "list_project_items",
+ Description: t("TOOL_LIST_PROJECT_ITEMS_DESCRIPTION", `Search project items with advanced filtering`),
+ Annotations: &mcp.ToolAnnotations{
Title: t("TOOL_LIST_PROJECT_ITEMS_USER_TITLE", "List project items"),
- ReadOnlyHint: ToBoolPtr(true),
- }),
- mcp.WithString("owner_type",
- mcp.Required(),
- mcp.Description("Owner type"),
- mcp.Enum("user", "org"),
- ),
- mcp.WithString("owner",
- mcp.Required(),
- mcp.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."),
- ),
- mcp.WithNumber("project_number", mcp.Required(),
- mcp.Description("The project's number."),
- ),
- mcp.WithString("query",
- mcp.Description(`Query string for advanced filtering of project items using GitHub's project filtering syntax.`),
- ),
- mcp.WithNumber("per_page",
- mcp.Description(fmt.Sprintf("Results per page (max %d)", MaxProjectsPerPage)),
- ),
- mcp.WithString("after",
- mcp.Description("Forward pagination cursor from previous pageInfo.nextCursor."),
- ),
- mcp.WithString("before",
- mcp.Description("Backward pagination cursor from previous pageInfo.prevCursor (rare)."),
- ),
- mcp.WithArray("fields",
- mcp.Description("Field IDs to include (e.g. [\"102589\", \"985201\"]). CRITICAL: Always provide to get field values. Without this, only titles returned."),
- mcp.WithStringItems(),
- ),
- ), func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
- owner, err := RequiredParam[string](req, "owner")
+ ReadOnlyHint: true,
+ },
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "owner_type": {
+ Type: "string",
+ Description: "Owner type",
+ Enum: []any{"user", "org"},
+ },
+ "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.",
+ },
+ "project_number": {
+ Type: "number",
+ Description: "The project's number.",
+ },
+ "query": {
+ Type: "string",
+ Description: `Query string for advanced filtering of project items using GitHub's project filtering syntax.`,
+ },
+ "per_page": {
+ Type: "number",
+ Description: fmt.Sprintf("Results per page (max %d)", MaxProjectsPerPage),
+ },
+ "after": {
+ Type: "string",
+ Description: "Forward pagination cursor from previous pageInfo.nextCursor.",
+ },
+ "before": {
+ Type: "string",
+ Description: "Backward pagination cursor from previous pageInfo.prevCursor (rare).",
+ },
+ "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: &jsonschema.Schema{
+ Type: "string",
+ },
+ },
+ },
+ Required: []string{"owner_type", "owner", "project_number"},
+ },
+ }, func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
+ owner, err := RequiredParam[string](args, "owner")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- ownerType, err := RequiredParam[string](req, "owner_type")
+ ownerType, err := RequiredParam[string](args, "owner_type")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- projectNumber, err := RequiredInt(req, "project_number")
+ projectNumber, err := RequiredInt(args, "project_number")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- queryStr, err := OptionalParam[string](req, "query")
+ queryStr, err := OptionalParam[string](args, "query")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- fields, err := OptionalBigIntArrayParam(req, "fields")
+ fields, err := OptionalBigIntArrayParam(args, "fields")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- pagination, err := extractPaginationOptions(req)
+ pagination, err := extractPaginationOptionsFromArgs(args)
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
client, err := getClient(ctx)
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
var resp *github.Response
@@ -466,7 +521,7 @@ func ListProjectItems(getClient GetClientFn, t translations.TranslationHelperFun
ProjectListFailedError,
resp,
err,
- ), nil
+ ), nil, nil
}
defer func() { _ = resp.Body.Close() }()
@@ -477,68 +532,78 @@ func ListProjectItems(getClient GetClientFn, t translations.TranslationHelperFun
r, err := json.Marshal(response)
if err != nil {
- return nil, fmt.Errorf("failed to marshal response: %w", err)
+ return nil, nil, fmt.Errorf("failed to marshal response: %w", err)
}
- return mcp.NewToolResultText(string(r)), nil
+ return utils.NewToolResultText(string(r)), nil, nil
}
}
-func GetProjectItem(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
- return mcp.NewTool("get_project_item",
- mcp.WithDescription(t("TOOL_GET_PROJECT_ITEM_DESCRIPTION", "Get a specific Project item for a user or org")),
- mcp.WithToolAnnotation(mcp.ToolAnnotation{
+func GetProjectItem(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) {
+ return mcp.Tool{
+ Name: "get_project_item",
+ Description: t("TOOL_GET_PROJECT_ITEM_DESCRIPTION", "Get a specific Project item for a user or org"),
+ Annotations: &mcp.ToolAnnotations{
Title: t("TOOL_GET_PROJECT_ITEM_USER_TITLE", "Get project item"),
- ReadOnlyHint: ToBoolPtr(true),
- }),
- mcp.WithString("owner_type",
- mcp.Required(),
- mcp.Description("Owner type"),
- mcp.Enum("user", "org"),
- ),
- mcp.WithString("owner",
- mcp.Required(),
- mcp.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."),
- ),
- mcp.WithNumber("project_number",
- mcp.Required(),
- mcp.Description("The project's number."),
- ),
- mcp.WithNumber("item_id",
- mcp.Required(),
- mcp.Description("The item's ID."),
- ),
- mcp.WithArray("fields",
- mcp.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."),
- mcp.WithStringItems(),
- ),
- ), func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
- owner, err := RequiredParam[string](req, "owner")
+ ReadOnlyHint: true,
+ },
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "owner_type": {
+ Type: "string",
+ Description: "Owner type",
+ Enum: []any{"user", "org"},
+ },
+ "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.",
+ },
+ "project_number": {
+ Type: "number",
+ Description: "The project's number.",
+ },
+ "item_id": {
+ Type: "number",
+ Description: "The item's ID.",
+ },
+ "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: &jsonschema.Schema{
+ Type: "string",
+ },
+ },
+ },
+ Required: []string{"owner_type", "owner", "project_number", "item_id"},
+ },
+ }, func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
+ owner, err := RequiredParam[string](args, "owner")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- ownerType, err := RequiredParam[string](req, "owner_type")
+ ownerType, err := RequiredParam[string](args, "owner_type")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- projectNumber, err := RequiredInt(req, "project_number")
+ projectNumber, err := RequiredInt(args, "project_number")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- itemID, err := RequiredBigInt(req, "item_id")
+ itemID, err := RequiredBigInt(args, "item_id")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- fields, err := OptionalBigIntArrayParam(req, "fields")
+ fields, err := OptionalBigIntArrayParam(args, "fields")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
client, err := getClient(ctx)
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
var resp *github.Response
@@ -562,76 +627,84 @@ func GetProjectItem(getClient GetClientFn, t translations.TranslationHelperFunc)
"failed to get project item",
resp,
err,
- ), nil
+ ), nil, nil
}
defer func() { _ = resp.Body.Close() }()
r, err := json.Marshal(projectItem)
if err != nil {
- return nil, fmt.Errorf("failed to marshal response: %w", err)
+ return nil, nil, fmt.Errorf("failed to marshal response: %w", err)
}
- return mcp.NewToolResultText(string(r)), nil
+ return utils.NewToolResultText(string(r)), nil, nil
}
}
-func AddProjectItem(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
- return mcp.NewTool("add_project_item",
- mcp.WithDescription(t("TOOL_ADD_PROJECT_ITEM_DESCRIPTION", "Add a specific Project item for a user or org")),
- mcp.WithToolAnnotation(mcp.ToolAnnotation{
+func AddProjectItem(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) {
+ return mcp.Tool{
+ Name: "add_project_item",
+ Description: t("TOOL_ADD_PROJECT_ITEM_DESCRIPTION", "Add a specific Project item for a user or org"),
+ Annotations: &mcp.ToolAnnotations{
Title: t("TOOL_ADD_PROJECT_ITEM_USER_TITLE", "Add project item"),
- ReadOnlyHint: ToBoolPtr(false),
- }),
- mcp.WithString("owner_type",
- mcp.Required(),
- mcp.Description("Owner type"), mcp.Enum("user", "org"),
- ),
- mcp.WithString("owner",
- mcp.Required(),
- mcp.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."),
- ),
- mcp.WithNumber("project_number",
- mcp.Required(),
- mcp.Description("The project's number."),
- ),
- mcp.WithString("item_type",
- mcp.Required(),
- mcp.Description("The item's type, either issue or pull_request."),
- mcp.Enum("issue", "pull_request"),
- ),
- mcp.WithNumber("item_id",
- mcp.Required(),
- mcp.Description("The numeric ID of the issue or pull request to add to the project."),
- ),
- ), func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
- owner, err := RequiredParam[string](req, "owner")
+ ReadOnlyHint: false,
+ },
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "owner_type": {
+ Type: "string",
+ Description: "Owner type",
+ Enum: []any{"user", "org"},
+ },
+ "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.",
+ },
+ "project_number": {
+ Type: "number",
+ Description: "The project's number.",
+ },
+ "item_type": {
+ Type: "string",
+ Description: "The item's type, either issue or pull_request.",
+ Enum: []any{"issue", "pull_request"},
+ },
+ "item_id": {
+ Type: "number",
+ Description: "The numeric ID of the issue or pull request to add to the project.",
+ },
+ },
+ Required: []string{"owner_type", "owner", "project_number", "item_type", "item_id"},
+ },
+ }, func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
+ owner, err := RequiredParam[string](args, "owner")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- ownerType, err := RequiredParam[string](req, "owner_type")
+ ownerType, err := RequiredParam[string](args, "owner_type")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- projectNumber, err := RequiredInt(req, "project_number")
+ projectNumber, err := RequiredInt(args, "project_number")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- itemID, err := RequiredBigInt(req, "item_id")
+ itemID, err := RequiredBigInt(args, "item_id")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
- itemType, err := RequiredParam[string](req, "item_type")
+ itemType, err := RequiredParam[string](args, "item_type")
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
if itemType != "issue" && itemType != "pull_request" {
- return mcp.NewToolResultError("item_type must be either 'issue' or 'pull_request'"), nil
+ return utils.NewToolResultError("item_type must be either 'issue' or 'pull_request'"), nil, nil
}
client, err := getClient(ctx)
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
newItem := &github.AddProjectItemOptions{
@@ -653,89 +726,97 @@ func AddProjectItem(getClient GetClientFn, t translations.TranslationHelperFunc)
ProjectAddFailedError,
resp,
err,
- ), nil
+ ), nil, nil
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusCreated {
body, err := io.ReadAll(resp.Body)
if err != nil {
- return nil, fmt.Errorf("failed to read response body: %w", err)
+ return nil, nil, fmt.Errorf("failed to read response body: %w", err)
}
- return mcp.NewToolResultError(fmt.Sprintf("%s: %s", ProjectAddFailedError, string(body))), nil
+ return utils.NewToolResultError(fmt.Sprintf("%s: %s", ProjectAddFailedError, string(body))), nil, nil
}
r, err := json.Marshal(addedItem)
if err != nil {
- return nil, fmt.Errorf("failed to marshal response: %w", err)
+ return nil, nil, fmt.Errorf("failed to marshal response: %w", err)
}
- return mcp.NewToolResultText(string(r)), nil
+ return utils.NewToolResultText(string(r)), nil, nil
}
}
-func UpdateProjectItem(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
- return mcp.NewTool("update_project_item",
- mcp.WithDescription(t("TOOL_UPDATE_PROJECT_ITEM_DESCRIPTION", "Update a specific Project item for a user or org")),
- mcp.WithToolAnnotation(mcp.ToolAnnotation{
+func UpdateProjectItem(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) {
+ return mcp.Tool{
+ Name: "update_project_item",
+ Description: t("TOOL_UPDATE_PROJECT_ITEM_DESCRIPTION", "Update a specific Project item for a user or org"),
+ Annotations: &mcp.ToolAnnotations{
Title: t("TOOL_UPDATE_PROJECT_ITEM_USER_TITLE", "Update project item"),
- ReadOnlyHint: ToBoolPtr(false),
- }),
- mcp.WithString("owner_type",
- mcp.Required(), mcp.Description("Owner type"),
- mcp.Enum("user", "org"),
- ),
- mcp.WithString("owner",
- mcp.Required(),
- mcp.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."),
- ),
- mcp.WithNumber("project_number",
- mcp.Required(),
- mcp.Description("The project's number."),
- ),
- mcp.WithNumber("item_id",
- mcp.Required(),
- mcp.Description("The unique identifier of the project item. This is not the issue or pull request ID."),
- ),
- mcp.WithObject("updated_field",
- mcp.Required(),
- mcp.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\"}"),
- ),
- ), func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
- owner, err := RequiredParam[string](req, "owner")
- if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
- }
- ownerType, err := RequiredParam[string](req, "owner_type")
- if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
- }
- projectNumber, err := RequiredInt(req, "project_number")
- if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
- }
- itemID, err := RequiredBigInt(req, "item_id")
- if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
- }
-
- rawUpdatedField, exists := req.GetArguments()["updated_field"]
+ ReadOnlyHint: false,
+ },
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "owner_type": {
+ Type: "string",
+ Description: "Owner type",
+ Enum: []any{"user", "org"},
+ },
+ "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.",
+ },
+ "project_number": {
+ Type: "number",
+ Description: "The project's number.",
+ },
+ "item_id": {
+ Type: "number",
+ Description: "The unique identifier of the project item. This is not the issue or pull request ID.",
+ },
+ "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: []string{"owner_type", "owner", "project_number", "item_id", "updated_field"},
+ },
+ }, func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
+ owner, err := RequiredParam[string](args, "owner")
+ if err != nil {
+ return utils.NewToolResultError(err.Error()), nil, nil
+ }
+ ownerType, err := RequiredParam[string](args, "owner_type")
+ if err != nil {
+ return utils.NewToolResultError(err.Error()), nil, nil
+ }
+ projectNumber, err := RequiredInt(args, "project_number")
+ if err != nil {
+ return utils.NewToolResultError(err.Error()), nil, nil
+ }
+ itemID, err := RequiredBigInt(args, "item_id")
+ if err != nil {
+ return utils.NewToolResultError(err.Error()), nil, nil
+ }
+
+ rawUpdatedField, exists := args["updated_field"]
if !exists {
- return mcp.NewToolResultError("missing required parameter: updated_field"), nil
+ return utils.NewToolResultError("missing required parameter: updated_field"), nil, nil
}
fieldValue, ok := rawUpdatedField.(map[string]any)
if !ok || fieldValue == nil {
- return mcp.NewToolResultError("field_value must be an object"), nil
+ return utils.NewToolResultError("field_value must be an object"), nil, nil
}
updatePayload, err := buildUpdateProjectItem(fieldValue)
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
client, err := getClient(ctx)
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
var resp *github.Response
@@ -752,70 +833,77 @@ func UpdateProjectItem(getClient GetClientFn, t translations.TranslationHelperFu
ProjectUpdateFailedError,
resp,
err,
- ), nil
+ ), nil, nil
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusOK {
body, err := io.ReadAll(resp.Body)
if err != nil {
- return nil, fmt.Errorf("failed to read response body: %w", err)
+ return nil, nil, fmt.Errorf("failed to read response body: %w", err)
}
- return mcp.NewToolResultError(fmt.Sprintf("%s: %s", ProjectUpdateFailedError, string(body))), nil
+ return utils.NewToolResultError(fmt.Sprintf("%s: %s", ProjectUpdateFailedError, string(body))), nil, nil
}
r, err := json.Marshal(updatedItem)
if err != nil {
- return nil, fmt.Errorf("failed to marshal response: %w", err)
+ return nil, nil, fmt.Errorf("failed to marshal response: %w", err)
}
- return mcp.NewToolResultText(string(r)), nil
+ return utils.NewToolResultText(string(r)), nil, nil
}
}
-func DeleteProjectItem(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
- return mcp.NewTool("delete_project_item",
- mcp.WithDescription(t("TOOL_DELETE_PROJECT_ITEM_DESCRIPTION", "Delete a specific Project item for a user or org")),
- mcp.WithToolAnnotation(mcp.ToolAnnotation{
+func DeleteProjectItem(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) {
+ return mcp.Tool{
+ Name: "delete_project_item",
+ Description: t("TOOL_DELETE_PROJECT_ITEM_DESCRIPTION", "Delete a specific Project item for a user or org"),
+ Annotations: &mcp.ToolAnnotations{
Title: t("TOOL_DELETE_PROJECT_ITEM_USER_TITLE", "Delete project item"),
- ReadOnlyHint: ToBoolPtr(false),
- }),
- mcp.WithString("owner_type",
- mcp.Required(),
- mcp.Description("Owner type"),
- mcp.Enum("user", "org"),
- ),
- mcp.WithString("owner",
- mcp.Required(),
- mcp.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."),
- ),
- mcp.WithNumber("project_number",
- mcp.Required(),
- mcp.Description("The project's number."),
- ),
- mcp.WithNumber("item_id",
- mcp.Required(),
- mcp.Description("The internal project item ID to delete from the project (not the issue or pull request ID)."),
- ),
- ), func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
- owner, err := RequiredParam[string](req, "owner")
- if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
- }
- ownerType, err := RequiredParam[string](req, "owner_type")
- if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
- }
- projectNumber, err := RequiredInt(req, "project_number")
- if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
- }
- itemID, err := RequiredBigInt(req, "item_id")
- if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ ReadOnlyHint: false,
+ },
+ InputSchema: &jsonschema.Schema{
+ Type: "object",
+ Properties: map[string]*jsonschema.Schema{
+ "owner_type": {
+ Type: "string",
+ Description: "Owner type",
+ Enum: []any{"user", "org"},
+ },
+ "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.",
+ },
+ "project_number": {
+ Type: "number",
+ Description: "The project's number.",
+ },
+ "item_id": {
+ Type: "number",
+ Description: "The internal project item ID to delete from the project (not the issue or pull request ID).",
+ },
+ },
+ Required: []string{"owner_type", "owner", "project_number", "item_id"},
+ },
+ }, func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
+ owner, err := RequiredParam[string](args, "owner")
+ if err != nil {
+ return utils.NewToolResultError(err.Error()), nil, nil
+ }
+ ownerType, err := RequiredParam[string](args, "owner_type")
+ if err != nil {
+ return utils.NewToolResultError(err.Error()), nil, nil
+ }
+ projectNumber, err := RequiredInt(args, "project_number")
+ if err != nil {
+ return utils.NewToolResultError(err.Error()), nil, nil
+ }
+ itemID, err := RequiredBigInt(args, "item_id")
+ if err != nil {
+ return utils.NewToolResultError(err.Error()), nil, nil
}
client, err := getClient(ctx)
if err != nil {
- return mcp.NewToolResultError(err.Error()), nil
+ return utils.NewToolResultError(err.Error()), nil, nil
}
var resp *github.Response
@@ -830,18 +918,18 @@ func DeleteProjectItem(getClient GetClientFn, t translations.TranslationHelperFu
ProjectDeleteFailedError,
resp,
err,
- ), nil
+ ), nil, nil
}
defer func() { _ = resp.Body.Close() }()
if resp.StatusCode != http.StatusNoContent {
body, err := io.ReadAll(resp.Body)
if err != nil {
- return nil, fmt.Errorf("failed to read response body: %w", err)
+ return nil, nil, fmt.Errorf("failed to read response body: %w", err)
}
- return mcp.NewToolResultError(fmt.Sprintf("%s: %s", ProjectDeleteFailedError, string(body))), nil
+ return utils.NewToolResultError(fmt.Sprintf("%s: %s", ProjectDeleteFailedError, string(body))), nil, nil
}
- return mcp.NewToolResultText("project item successfully deleted"), nil
+ return utils.NewToolResultText("project item successfully deleted"), nil, nil
}
}
@@ -922,8 +1010,8 @@ func buildPageInfo(resp *github.Response) pageInfo {
}
}
-func extractPaginationOptions(request mcp.CallToolRequest) (github.ListProjectsPaginationOptions, error) {
- perPage, err := OptionalIntParamWithDefault(request, "per_page", MaxProjectsPerPage)
+func extractPaginationOptionsFromArgs(args map[string]any) (github.ListProjectsPaginationOptions, error) {
+ perPage, err := OptionalIntParamWithDefault(args, "per_page", MaxProjectsPerPage)
if err != nil {
return github.ListProjectsPaginationOptions{}, err
}
@@ -931,12 +1019,12 @@ func extractPaginationOptions(request mcp.CallToolRequest) (github.ListProjectsP
perPage = MaxProjectsPerPage
}
- after, err := OptionalParam[string](request, "after")
+ after, err := OptionalParam[string](args, "after")
if err != nil {
return github.ListProjectsPaginationOptions{}, err
}
- before, err := OptionalParam[string](request, "before")
+ before, err := OptionalParam[string](args, "before")
if err != nil {
return github.ListProjectsPaginationOptions{}, err
}
diff --git a/pkg/github/projects_test.go b/pkg/github/projects_test.go
index 6cc4f6cc49..e2814c8f9d 100644
--- a/pkg/github/projects_test.go
+++ b/pkg/github/projects_test.go
@@ -1,5 +1,3 @@
-//go:build ignore
-
package github
import (
@@ -12,6 +10,7 @@ import (
"github.com/github/github-mcp-server/internal/toolsnaps"
"github.com/github/github-mcp-server/pkg/translations"
gh "github.com/google/go-github/v79/github"
+ "github.com/google/jsonschema-go/jsonschema"
"github.com/migueleliasweb/go-github-mock/src/mock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -24,11 +23,13 @@ func Test_ListProjects(t *testing.T) {
assert.Equal(t, "list_projects", tool.Name)
assert.NotEmpty(t, tool.Description)
- assert.Contains(t, tool.InputSchema.Properties, "owner")
- assert.Contains(t, tool.InputSchema.Properties, "owner_type")
- assert.Contains(t, tool.InputSchema.Properties, "query")
- assert.Contains(t, tool.InputSchema.Properties, "per_page")
- assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "owner_type"})
+ schema, ok := tool.InputSchema.(*jsonschema.Schema)
+ require.True(t, ok, "InputSchema should be a *jsonschema.Schema")
+ assert.Contains(t, schema.Properties, "owner")
+ assert.Contains(t, schema.Properties, "owner_type")
+ assert.Contains(t, schema.Properties, "query")
+ assert.Contains(t, schema.Properties, "per_page")
+ assert.ElementsMatch(t, schema.Required, []string{"owner", "owner_type"})
// API returns full ProjectV2 objects; we only need minimal fields for decoding.
orgProjects := []map[string]any{{"id": 1, "node_id": "NODE1", "title": "Org Project"}}
@@ -142,7 +143,7 @@ func Test_ListProjects(t *testing.T) {
client := gh.NewClient(tc.mockedClient)
_, handler := ListProjects(stubGetClientFn(client), translations.NullTranslationHelper)
request := createMCPRequest(tc.requestArgs)
- result, err := handler(context.Background(), request)
+ result, _, err := handler(context.Background(), &request, tc.requestArgs)
require.NoError(t, err)
if tc.expectError {
@@ -182,10 +183,12 @@ func Test_GetProject(t *testing.T) {
assert.Equal(t, "get_project", tool.Name)
assert.NotEmpty(t, tool.Description)
- assert.Contains(t, tool.InputSchema.Properties, "project_number")
- assert.Contains(t, tool.InputSchema.Properties, "owner")
- assert.Contains(t, tool.InputSchema.Properties, "owner_type")
- assert.ElementsMatch(t, tool.InputSchema.Required, []string{"project_number", "owner", "owner_type"})
+ schema, ok := tool.InputSchema.(*jsonschema.Schema)
+ require.True(t, ok, "InputSchema should be a *jsonschema.Schema")
+ assert.Contains(t, schema.Properties, "project_number")
+ assert.Contains(t, schema.Properties, "owner")
+ assert.Contains(t, schema.Properties, "owner_type")
+ assert.ElementsMatch(t, schema.Required, []string{"project_number", "owner", "owner_type"})
project := map[string]any{"id": 123, "title": "Project Title"}
@@ -276,7 +279,7 @@ func Test_GetProject(t *testing.T) {
client := gh.NewClient(tc.mockedClient)
_, handler := GetProject(stubGetClientFn(client), translations.NullTranslationHelper)
request := createMCPRequest(tc.requestArgs)
- result, err := handler(context.Background(), request)
+ result, _, err := handler(context.Background(), &request, tc.requestArgs)
require.NoError(t, err)
if tc.expectError {
@@ -313,11 +316,13 @@ func Test_ListProjectFields(t *testing.T) {
assert.Equal(t, "list_project_fields", tool.Name)
assert.NotEmpty(t, tool.Description)
- assert.Contains(t, tool.InputSchema.Properties, "owner_type")
- assert.Contains(t, tool.InputSchema.Properties, "owner")
- assert.Contains(t, tool.InputSchema.Properties, "project_number")
- assert.Contains(t, tool.InputSchema.Properties, "per_page")
- assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner_type", "owner", "project_number"})
+ schema, ok := tool.InputSchema.(*jsonschema.Schema)
+ require.True(t, ok, "InputSchema should be a *jsonschema.Schema")
+ assert.Contains(t, schema.Properties, "owner_type")
+ assert.Contains(t, schema.Properties, "owner")
+ assert.Contains(t, schema.Properties, "project_number")
+ assert.Contains(t, schema.Properties, "per_page")
+ assert.ElementsMatch(t, schema.Required, []string{"owner_type", "owner", "project_number"})
orgFields := []map[string]any{{"id": 101, "name": "Status", "data_type": "single_select"}}
userFields := []map[string]any{{"id": 201, "name": "Priority", "data_type": "single_select"}}
@@ -423,7 +428,7 @@ func Test_ListProjectFields(t *testing.T) {
client := gh.NewClient(tc.mockedClient)
_, handler := ListProjectFields(stubGetClientFn(client), translations.NullTranslationHelper)
request := createMCPRequest(tc.requestArgs)
- result, err := handler(context.Background(), request)
+ result, _, err := handler(context.Background(), &request, tc.requestArgs)
require.NoError(t, err)
if tc.expectError {
@@ -465,11 +470,13 @@ func Test_GetProjectField(t *testing.T) {
assert.Equal(t, "get_project_field", tool.Name)
assert.NotEmpty(t, tool.Description)
- assert.Contains(t, tool.InputSchema.Properties, "owner_type")
- assert.Contains(t, tool.InputSchema.Properties, "owner")
- assert.Contains(t, tool.InputSchema.Properties, "project_number")
- assert.Contains(t, tool.InputSchema.Properties, "field_id")
- assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner_type", "owner", "project_number", "field_id"})
+ schema, ok := tool.InputSchema.(*jsonschema.Schema)
+ require.True(t, ok, "InputSchema should be a *jsonschema.Schema")
+ assert.Contains(t, schema.Properties, "owner_type")
+ assert.Contains(t, schema.Properties, "owner")
+ assert.Contains(t, schema.Properties, "project_number")
+ assert.Contains(t, schema.Properties, "field_id")
+ assert.ElementsMatch(t, schema.Required, []string{"owner_type", "owner", "project_number", "field_id"})
orgField := map[string]any{"id": 101, "name": "Status", "dataType": "single_select"}
userField := map[string]any{"id": 202, "name": "Priority", "dataType": "single_select"}
@@ -578,7 +585,7 @@ func Test_GetProjectField(t *testing.T) {
client := gh.NewClient(tc.mockedClient)
_, handler := GetProjectField(stubGetClientFn(client), translations.NullTranslationHelper)
request := createMCPRequest(tc.requestArgs)
- result, err := handler(context.Background(), request)
+ result, _, err := handler(context.Background(), &request, tc.requestArgs)
require.NoError(t, err)
if tc.expectError {
@@ -621,13 +628,15 @@ func Test_ListProjectItems(t *testing.T) {
assert.Equal(t, "list_project_items", tool.Name)
assert.NotEmpty(t, tool.Description)
- assert.Contains(t, tool.InputSchema.Properties, "owner_type")
- assert.Contains(t, tool.InputSchema.Properties, "owner")
- assert.Contains(t, tool.InputSchema.Properties, "project_number")
- assert.Contains(t, tool.InputSchema.Properties, "query")
- assert.Contains(t, tool.InputSchema.Properties, "per_page")
- assert.Contains(t, tool.InputSchema.Properties, "fields")
- assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner_type", "owner", "project_number"})
+ schema, ok := tool.InputSchema.(*jsonschema.Schema)
+ require.True(t, ok, "InputSchema should be a *jsonschema.Schema")
+ assert.Contains(t, schema.Properties, "owner_type")
+ assert.Contains(t, schema.Properties, "owner")
+ assert.Contains(t, schema.Properties, "project_number")
+ assert.Contains(t, schema.Properties, "query")
+ assert.Contains(t, schema.Properties, "per_page")
+ assert.Contains(t, schema.Properties, "fields")
+ assert.ElementsMatch(t, schema.Required, []string{"owner_type", "owner", "project_number"})
orgItems := []map[string]any{
{"id": 301, "content_type": "Issue", "project_node_id": "PR_1", "fields": []map[string]any{
@@ -779,7 +788,7 @@ func Test_ListProjectItems(t *testing.T) {
client := gh.NewClient(tc.mockedClient)
_, handler := ListProjectItems(stubGetClientFn(client), translations.NullTranslationHelper)
request := createMCPRequest(tc.requestArgs)
- result, err := handler(context.Background(), request)
+ result, _, err := handler(context.Background(), &request, tc.requestArgs)
require.NoError(t, err)
if tc.expectError {
@@ -821,12 +830,14 @@ func Test_GetProjectItem(t *testing.T) {
assert.Equal(t, "get_project_item", tool.Name)
assert.NotEmpty(t, tool.Description)
- assert.Contains(t, tool.InputSchema.Properties, "owner_type")
- assert.Contains(t, tool.InputSchema.Properties, "owner")
- assert.Contains(t, tool.InputSchema.Properties, "project_number")
- assert.Contains(t, tool.InputSchema.Properties, "item_id")
- assert.Contains(t, tool.InputSchema.Properties, "fields")
- assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner_type", "owner", "project_number", "item_id"})
+ schema, ok := tool.InputSchema.(*jsonschema.Schema)
+ require.True(t, ok, "InputSchema should be a *jsonschema.Schema")
+ assert.Contains(t, schema.Properties, "owner_type")
+ assert.Contains(t, schema.Properties, "owner")
+ assert.Contains(t, schema.Properties, "project_number")
+ assert.Contains(t, schema.Properties, "item_id")
+ assert.Contains(t, schema.Properties, "fields")
+ assert.ElementsMatch(t, schema.Required, []string{"owner_type", "owner", "project_number", "item_id"})
orgItem := map[string]any{
"id": 301,
@@ -971,7 +982,7 @@ func Test_GetProjectItem(t *testing.T) {
client := gh.NewClient(tc.mockedClient)
_, handler := GetProjectItem(stubGetClientFn(client), translations.NullTranslationHelper)
request := createMCPRequest(tc.requestArgs)
- result, err := handler(context.Background(), request)
+ result, _, err := handler(context.Background(), &request, tc.requestArgs)
require.NoError(t, err)
if tc.expectError {
@@ -1014,12 +1025,14 @@ func Test_AddProjectItem(t *testing.T) {
assert.Equal(t, "add_project_item", tool.Name)
assert.NotEmpty(t, tool.Description)
- assert.Contains(t, tool.InputSchema.Properties, "owner_type")
- assert.Contains(t, tool.InputSchema.Properties, "owner")
- assert.Contains(t, tool.InputSchema.Properties, "project_number")
- assert.Contains(t, tool.InputSchema.Properties, "item_type")
- assert.Contains(t, tool.InputSchema.Properties, "item_id")
- assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner_type", "owner", "project_number", "item_type", "item_id"})
+ schema, ok := tool.InputSchema.(*jsonschema.Schema)
+ require.True(t, ok, "InputSchema should be a *jsonschema.Schema")
+ assert.Contains(t, schema.Properties, "owner_type")
+ assert.Contains(t, schema.Properties, "owner")
+ assert.Contains(t, schema.Properties, "project_number")
+ assert.Contains(t, schema.Properties, "item_type")
+ assert.Contains(t, schema.Properties, "item_id")
+ assert.ElementsMatch(t, schema.Required, []string{"owner_type", "owner", "project_number", "item_type", "item_id"})
orgItem := map[string]any{
"id": 601,
@@ -1196,7 +1209,7 @@ func Test_AddProjectItem(t *testing.T) {
_, handler := AddProjectItem(stubGetClientFn(client), translations.NullTranslationHelper)
request := createMCPRequest(tc.requestArgs)
- result, err := handler(context.Background(), request)
+ result, _, err := handler(context.Background(), &request, tc.requestArgs)
require.NoError(t, err)
if tc.expectError {
@@ -1248,12 +1261,14 @@ func Test_UpdateProjectItem(t *testing.T) {
assert.Equal(t, "update_project_item", tool.Name)
assert.NotEmpty(t, tool.Description)
- assert.Contains(t, tool.InputSchema.Properties, "owner_type")
- assert.Contains(t, tool.InputSchema.Properties, "owner")
- assert.Contains(t, tool.InputSchema.Properties, "project_number")
- assert.Contains(t, tool.InputSchema.Properties, "item_id")
- assert.Contains(t, tool.InputSchema.Properties, "updated_field")
- assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner_type", "owner", "project_number", "item_id", "updated_field"})
+ schema, ok := tool.InputSchema.(*jsonschema.Schema)
+ require.True(t, ok, "InputSchema should be a *jsonschema.Schema")
+ assert.Contains(t, schema.Properties, "owner_type")
+ assert.Contains(t, schema.Properties, "owner")
+ assert.Contains(t, schema.Properties, "project_number")
+ assert.Contains(t, schema.Properties, "item_id")
+ assert.Contains(t, schema.Properties, "updated_field")
+ assert.ElementsMatch(t, schema.Required, []string{"owner_type", "owner", "project_number", "item_id", "updated_field"})
orgUpdatedItem := map[string]any{
"id": 801,
@@ -1475,7 +1490,7 @@ func Test_UpdateProjectItem(t *testing.T) {
client := gh.NewClient(tc.mockedClient)
_, handler := UpdateProjectItem(stubGetClientFn(client), translations.NullTranslationHelper)
request := createMCPRequest(tc.requestArgs)
- result, err := handler(context.Background(), request)
+ result, _, err := handler(context.Background(), &request, tc.requestArgs)
require.NoError(t, err)
if tc.expectError {
@@ -1523,11 +1538,13 @@ func Test_DeleteProjectItem(t *testing.T) {
assert.Equal(t, "delete_project_item", tool.Name)
assert.NotEmpty(t, tool.Description)
- assert.Contains(t, tool.InputSchema.Properties, "owner_type")
- assert.Contains(t, tool.InputSchema.Properties, "owner")
- assert.Contains(t, tool.InputSchema.Properties, "project_number")
- assert.Contains(t, tool.InputSchema.Properties, "item_id")
- assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner_type", "owner", "project_number", "item_id"})
+ schema, ok := tool.InputSchema.(*jsonschema.Schema)
+ require.True(t, ok, "InputSchema should be a *jsonschema.Schema")
+ assert.Contains(t, schema.Properties, "owner_type")
+ assert.Contains(t, schema.Properties, "owner")
+ assert.Contains(t, schema.Properties, "project_number")
+ assert.Contains(t, schema.Properties, "item_id")
+ assert.ElementsMatch(t, schema.Required, []string{"owner_type", "owner", "project_number", "item_id"})
tests := []struct {
name string
@@ -1637,7 +1654,7 @@ func Test_DeleteProjectItem(t *testing.T) {
client := gh.NewClient(tc.mockedClient)
_, handler := DeleteProjectItem(stubGetClientFn(client), translations.NullTranslationHelper)
request := createMCPRequest(tc.requestArgs)
- result, err := handler(context.Background(), request)
+ result, _, err := handler(context.Background(), &request, tc.requestArgs)
require.NoError(t, err)
if tc.expectError {
From b36036a47f1ec8242077ff14b258494725303f5a Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 24 Nov 2025 12:19:39 +0000
Subject: [PATCH 3/6] Update documentation after projects migration
Co-authored-by: omgitsads <4619+omgitsads@users.noreply.github.com>
---
README.md | 254 +-----------------------------------------
docs/remote-server.md | 5 -
2 files changed, 5 insertions(+), 254 deletions(-)
diff --git a/README.md b/README.md
index 7c4884074b..63e5032e45 100644
--- a/README.md
+++ b/README.md
@@ -398,20 +398,15 @@ The following sets of tools are available:
| `code_security` | Code security related tools, such as GitHub Code Scanning |
| `dependabot` | Dependabot tools |
| `discussions` | GitHub Discussions related tools |
-| `experiments` | Experimental features that are not considered stable yet |
| `gists` | GitHub Gist related tools |
| `git` | GitHub Git API related tools for low-level Git operations |
| `issues` | GitHub Issues related tools |
| `labels` | GitHub Labels related tools |
| `notifications` | GitHub Notifications related tools |
-| `orgs` | GitHub Organization related tools |
-| `projects` | GitHub Projects related tools |
| `pull_requests` | GitHub Pull Request related tools |
| `repos` | GitHub Repository related tools |
| `secret_protection` | Secret protection related tools, such as GitHub Secret Scanning |
| `security_advisories` | Security advisories related tools |
-| `stargazers` | GitHub Stargazers related tools |
-| `users` | GitHub User related tools |
### Additional Toolsets in Remote GitHub MCP Server
@@ -657,15 +652,10 @@ The following sets of tools are available:
- `owner`: Repository owner (string, required)
- `repo`: Repository name (string, required)
-- **get_label** - Get a specific label from a repository.
- - `name`: Label name. (string, required)
- - `owner`: Repository owner (username or organization name) (string, required)
- - `repo`: Repository name (string, required)
-
- **issue_read** - Get issue details
- `issue_number`: The number of the issue (number, required)
- - `method`: The read operation to perform on a single issue.
-Options are:
+ - `method`: The read operation to perform on a single issue.
+Options are:
1. get - Get details of a specific issue.
2. get_comments - Get issue comments.
3. get_sub_issues - Get sub-issues of the issue.
@@ -683,8 +673,8 @@ Options are:
- `issue_number`: Issue number to update (number, optional)
- `labels`: Labels to apply to this issue (string[], optional)
- `method`: Write operation to perform on a single issue.
-Options are:
-- 'create' - creates a new issue.
+Options are:
+- 'create' - creates a new issue.
- 'update' - updates an existing issue.
(string, required)
- `milestone`: Milestone number (number, optional)
@@ -764,7 +754,7 @@ Options are:
Notifications
- **dismiss_notification** - Dismiss notification
- - `state`: The new state of the notification (read/done) (string, optional)
+ - `state`: The new state of the notification (read/done) (string, required)
- `threadID`: The ID of the notification thread (string, required)
- **get_notification_details** - Get notification details
@@ -797,89 +787,6 @@ Options are:
-Organizations
-
-- **search_orgs** - Search organizations
- - `order`: Sort order (string, optional)
- - `page`: Page number for pagination (min 1) (number, optional)
- - `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
- - `query`: Organization search query. Examples: 'microsoft', 'location:california', 'created:>=2025-01-01'. Search is automatically scoped to type:org. (string, required)
- - `sort`: Sort field by category (string, optional)
-
-
-
-
-
-Projects
-
-- **add_project_item** - Add project item
- - `item_id`: The numeric ID of the issue or pull request to add to the project. (number, required)
- - `item_type`: The item's type, either issue or pull_request. (string, required)
- - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required)
- - `owner_type`: Owner type (string, required)
- - `project_number`: The project's number. (number, required)
-
-- **delete_project_item** - Delete project item
- - `item_id`: The internal project item ID to delete from the project (not the issue or pull request ID). (number, required)
- - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required)
- - `owner_type`: Owner type (string, required)
- - `project_number`: The project's number. (number, required)
-
-- **get_project** - Get project
- - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required)
- - `owner_type`: Owner type (string, required)
- - `project_number`: The project's number (number, required)
-
-- **get_project_field** - Get project field
- - `field_id`: The field's id. (number, required)
- - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required)
- - `owner_type`: Owner type (string, required)
- - `project_number`: The project's number. (number, required)
-
-- **get_project_item** - Get project item
- - `fields`: Specific list of field IDs to include in the response (e.g. ["102589", "985201", "169875"]). If not provided, only the title field is included. (string[], optional)
- - `item_id`: The item's ID. (number, required)
- - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required)
- - `owner_type`: Owner type (string, required)
- - `project_number`: The project's number. (number, required)
-
-- **list_project_fields** - List project fields
- - `after`: Forward pagination cursor from previous pageInfo.nextCursor. (string, optional)
- - `before`: Backward pagination cursor from previous pageInfo.prevCursor (rare). (string, optional)
- - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required)
- - `owner_type`: Owner type (string, required)
- - `per_page`: Results per page (max 50) (number, optional)
- - `project_number`: The project's number. (number, required)
-
-- **list_project_items** - List project items
- - `after`: Forward pagination cursor from previous pageInfo.nextCursor. (string, optional)
- - `before`: Backward pagination cursor from previous pageInfo.prevCursor (rare). (string, optional)
- - `fields`: Field IDs to include (e.g. ["102589", "985201"]). CRITICAL: Always provide to get field values. Without this, only titles returned. (string[], optional)
- - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required)
- - `owner_type`: Owner type (string, required)
- - `per_page`: Results per page (max 50) (number, optional)
- - `project_number`: The project's number. (number, required)
- - `query`: Query string for advanced filtering of project items using GitHub's project filtering syntax. (string, optional)
-
-- **list_projects** - List projects
- - `after`: Forward pagination cursor from previous pageInfo.nextCursor. (string, optional)
- - `before`: Backward pagination cursor from previous pageInfo.prevCursor (rare). (string, optional)
- - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required)
- - `owner_type`: Owner type (string, required)
- - `per_page`: Results per page (max 50) (number, optional)
- - `query`: Filter projects by title text and open/closed state; permitted qualifiers: is:open, is:closed; examples: "roadmap is:open", "is:open feature planning". (string, optional)
-
-- **update_project_item** - Update project item
- - `item_id`: The unique identifier of the project item. This is not the issue or pull request ID. (number, required)
- - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required)
- - `owner_type`: Owner type (string, required)
- - `project_number`: The project's number. (number, required)
- - `updated_field`: 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"} (object, required)
-
-
-
-
-
Pull Requests
- **add_comment_to_pending_review** - Add review comment to the requester's latest pending pull request review
@@ -985,123 +892,6 @@ Possible options:
-Repositories
-
-- **create_branch** - Create branch
- - `branch`: Name for new branch (string, required)
- - `from_branch`: Source branch (defaults to repo default) (string, optional)
- - `owner`: Repository owner (string, required)
- - `repo`: Repository name (string, required)
-
-- **create_or_update_file** - Create or update file
- - `branch`: Branch to create/update the file in (string, required)
- - `content`: Content of the file (string, required)
- - `message`: Commit message (string, required)
- - `owner`: Repository owner (username or organization) (string, required)
- - `path`: Path where to create/update the file (string, required)
- - `repo`: Repository name (string, required)
- - `sha`: Required if updating an existing file. The blob SHA of the file being replaced. (string, optional)
-
-- **create_repository** - Create repository
- - `autoInit`: Initialize with README (boolean, optional)
- - `description`: Repository description (string, optional)
- - `name`: Repository name (string, required)
- - `organization`: Organization to create the repository in (omit to create in your personal account) (string, optional)
- - `private`: Whether repo should be private (boolean, optional)
-
-- **delete_file** - Delete file
- - `branch`: Branch to delete the file from (string, required)
- - `message`: Commit message (string, required)
- - `owner`: Repository owner (username or organization) (string, required)
- - `path`: Path to the file to delete (string, required)
- - `repo`: Repository name (string, required)
-
-- **fork_repository** - Fork repository
- - `organization`: Organization to fork to (string, optional)
- - `owner`: Repository owner (string, required)
- - `repo`: Repository name (string, required)
-
-- **get_commit** - Get commit details
- - `include_diff`: Whether to include file diffs and stats in the response. Default is true. (boolean, optional)
- - `owner`: Repository owner (string, required)
- - `page`: Page number for pagination (min 1) (number, optional)
- - `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
- - `repo`: Repository name (string, required)
- - `sha`: Commit SHA, branch name, or tag name (string, required)
-
-- **get_file_contents** - Get file or directory contents
- - `owner`: Repository owner (username or organization) (string, required)
- - `path`: Path to file/directory (directories must end with a slash '/') (string, optional)
- - `ref`: Accepts optional git refs such as `refs/tags/{tag}`, `refs/heads/{branch}` or `refs/pull/{pr_number}/head` (string, optional)
- - `repo`: Repository name (string, required)
- - `sha`: Accepts optional commit SHA. If specified, it will be used instead of ref (string, optional)
-
-- **get_latest_release** - Get latest release
- - `owner`: Repository owner (string, required)
- - `repo`: Repository name (string, required)
-
-- **get_release_by_tag** - Get a release by tag name
- - `owner`: Repository owner (string, required)
- - `repo`: Repository name (string, required)
- - `tag`: Tag name (e.g., 'v1.0.0') (string, required)
-
-- **get_tag** - Get tag details
- - `owner`: Repository owner (string, required)
- - `repo`: Repository name (string, required)
- - `tag`: Tag name (string, required)
-
-- **list_branches** - List branches
- - `owner`: Repository owner (string, required)
- - `page`: Page number for pagination (min 1) (number, optional)
- - `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
- - `repo`: Repository name (string, required)
-
-- **list_commits** - List commits
- - `author`: Author username or email address to filter commits by (string, optional)
- - `owner`: Repository owner (string, required)
- - `page`: Page number for pagination (min 1) (number, optional)
- - `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
- - `repo`: Repository name (string, required)
- - `sha`: Commit SHA, branch or tag name to list commits of. If not provided, uses the default branch of the repository. If a commit SHA is provided, will list commits up to that SHA. (string, optional)
-
-- **list_releases** - List releases
- - `owner`: Repository owner (string, required)
- - `page`: Page number for pagination (min 1) (number, optional)
- - `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
- - `repo`: Repository name (string, required)
-
-- **list_tags** - List tags
- - `owner`: Repository owner (string, required)
- - `page`: Page number for pagination (min 1) (number, optional)
- - `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
- - `repo`: Repository name (string, required)
-
-- **push_files** - Push files to repository
- - `branch`: Branch to push to (string, required)
- - `files`: Array of file objects to push, each object with path (string) and content (string) (object[], required)
- - `message`: Commit message (string, required)
- - `owner`: Repository owner (string, required)
- - `repo`: Repository name (string, required)
-
-- **search_code** - Search code
- - `order`: Sort order for results (string, optional)
- - `page`: Page number for pagination (min 1) (number, optional)
- - `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
- - `query`: 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. (string, required)
- - `sort`: Sort field ('indexed' only) (string, optional)
-
-- **search_repositories** - Search repositories
- - `minimal_output`: Return minimal repository information (default: true). When false, returns full GitHub API repository objects. (boolean, optional)
- - `order`: Sort order (string, optional)
- - `page`: Page number for pagination (min 1) (number, optional)
- - `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
- - `query`: Repository search query. Examples: 'machine learning in:name stars:>1000 language:python', 'topic:react', 'user:facebook'. Supports advanced search syntax for precise filtering. (string, required)
- - `sort`: Sort repositories by field, defaults to best match (string, optional)
-
-
-
-
-
Secret Protection
- **get_secret_scanning_alert** - Get secret scanning alert
@@ -1151,40 +941,6 @@ Possible options:
- `sort`: Sort field. (string, optional)
- `state`: Filter by advisory state. (string, optional)
-
-
-
-
-Stargazers
-
-- **list_starred_repositories** - List starred repositories
- - `direction`: The direction to sort the results by. (string, optional)
- - `page`: Page number for pagination (min 1) (number, optional)
- - `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
- - `sort`: How to sort the results. Can be either 'created' (when the repository was starred) or 'updated' (when the repository was last pushed to). (string, optional)
- - `username`: Username to list starred repositories for. Defaults to the authenticated user. (string, optional)
-
-- **star_repository** - Star repository
- - `owner`: Repository owner (string, required)
- - `repo`: Repository name (string, required)
-
-- **unstar_repository** - Unstar repository
- - `owner`: Repository owner (string, required)
- - `repo`: Repository name (string, required)
-
-
-
-
-
-Users
-
-- **search_users** - Search users
- - `order`: Sort order (string, optional)
- - `page`: Page number for pagination (min 1) (number, optional)
- - `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
- - `query`: User search query. Examples: 'john smith', 'location:seattle', 'followers:>100'. Search is automatically scoped to type:user. (string, required)
- - `sort`: Sort users by number of followers or repositories, or when the person joined GitHub. (string, optional)
-
diff --git a/docs/remote-server.md b/docs/remote-server.md
index b263d70aae..9c06e9e356 100644
--- a/docs/remote-server.md
+++ b/docs/remote-server.md
@@ -24,20 +24,15 @@ Below is a table of available toolsets for the remote GitHub MCP Server. Each to
| Code Security | Code security related tools, such as GitHub Code Scanning | https://api.githubcopilot.com/mcp/x/code_security | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-code_security&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcode_security%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/code_security/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-code_security&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcode_security%2Freadonly%22%7D) |
| Dependabot | Dependabot tools | https://api.githubcopilot.com/mcp/x/dependabot | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-dependabot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdependabot%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/dependabot/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-dependabot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdependabot%2Freadonly%22%7D) |
| Discussions | GitHub Discussions related tools | https://api.githubcopilot.com/mcp/x/discussions | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-discussions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdiscussions%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/discussions/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-discussions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdiscussions%2Freadonly%22%7D) |
-| Experiments | Experimental features that are not considered stable yet | https://api.githubcopilot.com/mcp/x/experiments | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-experiments&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fexperiments%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/experiments/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-experiments&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fexperiments%2Freadonly%22%7D) |
| Gists | GitHub Gist related tools | https://api.githubcopilot.com/mcp/x/gists | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-gists&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgists%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/gists/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-gists&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgists%2Freadonly%22%7D) |
| Git | GitHub Git API related tools for low-level Git operations | https://api.githubcopilot.com/mcp/x/git | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-git&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgit%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/git/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-git&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgit%2Freadonly%22%7D) |
| Issues | GitHub Issues related tools | https://api.githubcopilot.com/mcp/x/issues | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-issues&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fissues%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/issues/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-issues&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fissues%2Freadonly%22%7D) |
| Labels | GitHub Labels related tools | https://api.githubcopilot.com/mcp/x/labels | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-labels&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Flabels%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/labels/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-labels&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Flabels%2Freadonly%22%7D) |
| Notifications | GitHub Notifications related tools | https://api.githubcopilot.com/mcp/x/notifications | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-notifications&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fnotifications%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/notifications/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-notifications&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fnotifications%2Freadonly%22%7D) |
-| Organizations | GitHub Organization related tools | https://api.githubcopilot.com/mcp/x/orgs | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-orgs&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Forgs%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/orgs/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-orgs&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Forgs%2Freadonly%22%7D) |
-| Projects | GitHub Projects related tools | https://api.githubcopilot.com/mcp/x/projects | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-projects&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fprojects%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/projects/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-projects&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fprojects%2Freadonly%22%7D) |
| Pull Requests | GitHub Pull Request related tools | https://api.githubcopilot.com/mcp/x/pull_requests | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-pull_requests&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fpull_requests%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/pull_requests/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-pull_requests&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fpull_requests%2Freadonly%22%7D) |
| Repositories | GitHub Repository related tools | https://api.githubcopilot.com/mcp/x/repos | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-repos&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Frepos%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/repos/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-repos&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Frepos%2Freadonly%22%7D) |
| Secret Protection | Secret protection related tools, such as GitHub Secret Scanning | https://api.githubcopilot.com/mcp/x/secret_protection | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-secret_protection&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecret_protection%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/secret_protection/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-secret_protection&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecret_protection%2Freadonly%22%7D) |
| Security Advisories | Security advisories related tools | https://api.githubcopilot.com/mcp/x/security_advisories | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-security_advisories&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecurity_advisories%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/security_advisories/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-security_advisories&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecurity_advisories%2Freadonly%22%7D) |
-| Stargazers | GitHub Stargazers related tools | https://api.githubcopilot.com/mcp/x/stargazers | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-stargazers&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fstargazers%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/stargazers/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-stargazers&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fstargazers%2Freadonly%22%7D) |
-| Users | GitHub User related tools | https://api.githubcopilot.com/mcp/x/users | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-users&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fusers%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/users/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-users&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fusers%2Freadonly%22%7D) |
From 4a0112ea6755a88b90ec79c0ce233e33a4885e13 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Mon, 24 Nov 2025 12:22:24 +0000
Subject: [PATCH 4/6] Enable projects toolset after migration
Co-authored-by: omgitsads <4619+omgitsads@users.noreply.github.com>
---
pkg/github/tools.go | 29 +++++++++++++++--------------
1 file changed, 15 insertions(+), 14 deletions(-)
diff --git a/pkg/github/tools.go b/pkg/github/tools.go
index 9ef0987b87..cc423b2484 100644
--- a/pkg/github/tools.go
+++ b/pkg/github/tools.go
@@ -324,20 +324,20 @@ func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetG
toolsets.NewServerTool(UpdateGist(getClient, t)),
)
- // projects := toolsets.NewToolset(ToolsetMetadataProjects.ID, ToolsetMetadataProjects.Description).
- // AddReadTools(
- // toolsets.NewServerTool(ListProjects(getClient, t)),
- // toolsets.NewServerTool(GetProject(getClient, t)),
- // toolsets.NewServerTool(ListProjectFields(getClient, t)),
- // toolsets.NewServerTool(GetProjectField(getClient, t)),
- // toolsets.NewServerTool(ListProjectItems(getClient, t)),
- // toolsets.NewServerTool(GetProjectItem(getClient, t)),
- // ).
- // AddWriteTools(
- // toolsets.NewServerTool(AddProjectItem(getClient, t)),
- // toolsets.NewServerTool(DeleteProjectItem(getClient, t)),
- // toolsets.NewServerTool(UpdateProjectItem(getClient, t)),
- // )
+ projects := toolsets.NewToolset(ToolsetMetadataProjects.ID, ToolsetMetadataProjects.Description).
+ AddReadTools(
+ toolsets.NewServerTool(ListProjects(getClient, t)),
+ toolsets.NewServerTool(GetProject(getClient, t)),
+ toolsets.NewServerTool(ListProjectFields(getClient, t)),
+ toolsets.NewServerTool(GetProjectField(getClient, t)),
+ toolsets.NewServerTool(ListProjectItems(getClient, t)),
+ toolsets.NewServerTool(GetProjectItem(getClient, t)),
+ ).
+ AddWriteTools(
+ toolsets.NewServerTool(AddProjectItem(getClient, t)),
+ toolsets.NewServerTool(DeleteProjectItem(getClient, t)),
+ toolsets.NewServerTool(UpdateProjectItem(getClient, t)),
+ )
// stargazers := toolsets.NewToolset(ToolsetMetadataStargazers.ID, ToolsetMetadataStargazers.Description).
// AddReadTools(
// toolsets.NewServerTool(ListStarredRepositories(getClient, t)),
@@ -378,6 +378,7 @@ func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetG
// tsg.AddToolset(projects)
// tsg.AddToolset(stargazers)
tsg.AddToolset(labels)
+ tsg.AddToolset(projects)
return tsg
}
From 1b4520432fdfe45e7b3a497cb1e3043b3697c42f Mon Sep 17 00:00:00 2001
From: LuluBeatson
Date: Mon, 24 Nov 2025 13:31:44 +0000
Subject: [PATCH 5/6] nit: keep toolsets in original order, remove dupe
---
pkg/github/tools.go | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/pkg/github/tools.go b/pkg/github/tools.go
index cc423b2484..b6dd5695af 100644
--- a/pkg/github/tools.go
+++ b/pkg/github/tools.go
@@ -375,10 +375,9 @@ func DefaultToolsetGroup(readOnly bool, getClient GetClientFn, getGQLClient GetG
tsg.AddToolset(discussions)
tsg.AddToolset(gists)
tsg.AddToolset(securityAdvisories)
- // tsg.AddToolset(projects)
+ tsg.AddToolset(projects)
// tsg.AddToolset(stargazers)
tsg.AddToolset(labels)
- tsg.AddToolset(projects)
return tsg
}
From 7edb968b355d222e6c07ab67ef2f793ee2b4a15d Mon Sep 17 00:00:00 2001
From: LuluBeatson
Date: Mon, 24 Nov 2025 13:54:30 +0000
Subject: [PATCH 6/6] revert docs changes
---
README.md | 254 +++++++++++++++++++++++++++++++++++++++++-
docs/remote-server.md | 5 +
2 files changed, 254 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index 63e5032e45..7c4884074b 100644
--- a/README.md
+++ b/README.md
@@ -398,15 +398,20 @@ The following sets of tools are available:
| `code_security` | Code security related tools, such as GitHub Code Scanning |
| `dependabot` | Dependabot tools |
| `discussions` | GitHub Discussions related tools |
+| `experiments` | Experimental features that are not considered stable yet |
| `gists` | GitHub Gist related tools |
| `git` | GitHub Git API related tools for low-level Git operations |
| `issues` | GitHub Issues related tools |
| `labels` | GitHub Labels related tools |
| `notifications` | GitHub Notifications related tools |
+| `orgs` | GitHub Organization related tools |
+| `projects` | GitHub Projects related tools |
| `pull_requests` | GitHub Pull Request related tools |
| `repos` | GitHub Repository related tools |
| `secret_protection` | Secret protection related tools, such as GitHub Secret Scanning |
| `security_advisories` | Security advisories related tools |
+| `stargazers` | GitHub Stargazers related tools |
+| `users` | GitHub User related tools |
### Additional Toolsets in Remote GitHub MCP Server
@@ -652,10 +657,15 @@ The following sets of tools are available:
- `owner`: Repository owner (string, required)
- `repo`: Repository name (string, required)
+- **get_label** - Get a specific label from a repository.
+ - `name`: Label name. (string, required)
+ - `owner`: Repository owner (username or organization name) (string, required)
+ - `repo`: Repository name (string, required)
+
- **issue_read** - Get issue details
- `issue_number`: The number of the issue (number, required)
- - `method`: The read operation to perform on a single issue.
-Options are:
+ - `method`: The read operation to perform on a single issue.
+Options are:
1. get - Get details of a specific issue.
2. get_comments - Get issue comments.
3. get_sub_issues - Get sub-issues of the issue.
@@ -673,8 +683,8 @@ Options are:
- `issue_number`: Issue number to update (number, optional)
- `labels`: Labels to apply to this issue (string[], optional)
- `method`: Write operation to perform on a single issue.
-Options are:
-- 'create' - creates a new issue.
+Options are:
+- 'create' - creates a new issue.
- 'update' - updates an existing issue.
(string, required)
- `milestone`: Milestone number (number, optional)
@@ -754,7 +764,7 @@ Options are:
Notifications
- **dismiss_notification** - Dismiss notification
- - `state`: The new state of the notification (read/done) (string, required)
+ - `state`: The new state of the notification (read/done) (string, optional)
- `threadID`: The ID of the notification thread (string, required)
- **get_notification_details** - Get notification details
@@ -787,6 +797,89 @@ Options are:
+Organizations
+
+- **search_orgs** - Search organizations
+ - `order`: Sort order (string, optional)
+ - `page`: Page number for pagination (min 1) (number, optional)
+ - `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
+ - `query`: Organization search query. Examples: 'microsoft', 'location:california', 'created:>=2025-01-01'. Search is automatically scoped to type:org. (string, required)
+ - `sort`: Sort field by category (string, optional)
+
+
+
+
+
+Projects
+
+- **add_project_item** - Add project item
+ - `item_id`: The numeric ID of the issue or pull request to add to the project. (number, required)
+ - `item_type`: The item's type, either issue or pull_request. (string, required)
+ - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required)
+ - `owner_type`: Owner type (string, required)
+ - `project_number`: The project's number. (number, required)
+
+- **delete_project_item** - Delete project item
+ - `item_id`: The internal project item ID to delete from the project (not the issue or pull request ID). (number, required)
+ - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required)
+ - `owner_type`: Owner type (string, required)
+ - `project_number`: The project's number. (number, required)
+
+- **get_project** - Get project
+ - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required)
+ - `owner_type`: Owner type (string, required)
+ - `project_number`: The project's number (number, required)
+
+- **get_project_field** - Get project field
+ - `field_id`: The field's id. (number, required)
+ - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required)
+ - `owner_type`: Owner type (string, required)
+ - `project_number`: The project's number. (number, required)
+
+- **get_project_item** - Get project item
+ - `fields`: Specific list of field IDs to include in the response (e.g. ["102589", "985201", "169875"]). If not provided, only the title field is included. (string[], optional)
+ - `item_id`: The item's ID. (number, required)
+ - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required)
+ - `owner_type`: Owner type (string, required)
+ - `project_number`: The project's number. (number, required)
+
+- **list_project_fields** - List project fields
+ - `after`: Forward pagination cursor from previous pageInfo.nextCursor. (string, optional)
+ - `before`: Backward pagination cursor from previous pageInfo.prevCursor (rare). (string, optional)
+ - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required)
+ - `owner_type`: Owner type (string, required)
+ - `per_page`: Results per page (max 50) (number, optional)
+ - `project_number`: The project's number. (number, required)
+
+- **list_project_items** - List project items
+ - `after`: Forward pagination cursor from previous pageInfo.nextCursor. (string, optional)
+ - `before`: Backward pagination cursor from previous pageInfo.prevCursor (rare). (string, optional)
+ - `fields`: Field IDs to include (e.g. ["102589", "985201"]). CRITICAL: Always provide to get field values. Without this, only titles returned. (string[], optional)
+ - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required)
+ - `owner_type`: Owner type (string, required)
+ - `per_page`: Results per page (max 50) (number, optional)
+ - `project_number`: The project's number. (number, required)
+ - `query`: Query string for advanced filtering of project items using GitHub's project filtering syntax. (string, optional)
+
+- **list_projects** - List projects
+ - `after`: Forward pagination cursor from previous pageInfo.nextCursor. (string, optional)
+ - `before`: Backward pagination cursor from previous pageInfo.prevCursor (rare). (string, optional)
+ - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required)
+ - `owner_type`: Owner type (string, required)
+ - `per_page`: Results per page (max 50) (number, optional)
+ - `query`: Filter projects by title text and open/closed state; permitted qualifiers: is:open, is:closed; examples: "roadmap is:open", "is:open feature planning". (string, optional)
+
+- **update_project_item** - Update project item
+ - `item_id`: The unique identifier of the project item. This is not the issue or pull request ID. (number, required)
+ - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required)
+ - `owner_type`: Owner type (string, required)
+ - `project_number`: The project's number. (number, required)
+ - `updated_field`: 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"} (object, required)
+
+
+
+
+
Pull Requests
- **add_comment_to_pending_review** - Add review comment to the requester's latest pending pull request review
@@ -892,6 +985,123 @@ Possible options:
+Repositories
+
+- **create_branch** - Create branch
+ - `branch`: Name for new branch (string, required)
+ - `from_branch`: Source branch (defaults to repo default) (string, optional)
+ - `owner`: Repository owner (string, required)
+ - `repo`: Repository name (string, required)
+
+- **create_or_update_file** - Create or update file
+ - `branch`: Branch to create/update the file in (string, required)
+ - `content`: Content of the file (string, required)
+ - `message`: Commit message (string, required)
+ - `owner`: Repository owner (username or organization) (string, required)
+ - `path`: Path where to create/update the file (string, required)
+ - `repo`: Repository name (string, required)
+ - `sha`: Required if updating an existing file. The blob SHA of the file being replaced. (string, optional)
+
+- **create_repository** - Create repository
+ - `autoInit`: Initialize with README (boolean, optional)
+ - `description`: Repository description (string, optional)
+ - `name`: Repository name (string, required)
+ - `organization`: Organization to create the repository in (omit to create in your personal account) (string, optional)
+ - `private`: Whether repo should be private (boolean, optional)
+
+- **delete_file** - Delete file
+ - `branch`: Branch to delete the file from (string, required)
+ - `message`: Commit message (string, required)
+ - `owner`: Repository owner (username or organization) (string, required)
+ - `path`: Path to the file to delete (string, required)
+ - `repo`: Repository name (string, required)
+
+- **fork_repository** - Fork repository
+ - `organization`: Organization to fork to (string, optional)
+ - `owner`: Repository owner (string, required)
+ - `repo`: Repository name (string, required)
+
+- **get_commit** - Get commit details
+ - `include_diff`: Whether to include file diffs and stats in the response. Default is true. (boolean, optional)
+ - `owner`: Repository owner (string, required)
+ - `page`: Page number for pagination (min 1) (number, optional)
+ - `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
+ - `repo`: Repository name (string, required)
+ - `sha`: Commit SHA, branch name, or tag name (string, required)
+
+- **get_file_contents** - Get file or directory contents
+ - `owner`: Repository owner (username or organization) (string, required)
+ - `path`: Path to file/directory (directories must end with a slash '/') (string, optional)
+ - `ref`: Accepts optional git refs such as `refs/tags/{tag}`, `refs/heads/{branch}` or `refs/pull/{pr_number}/head` (string, optional)
+ - `repo`: Repository name (string, required)
+ - `sha`: Accepts optional commit SHA. If specified, it will be used instead of ref (string, optional)
+
+- **get_latest_release** - Get latest release
+ - `owner`: Repository owner (string, required)
+ - `repo`: Repository name (string, required)
+
+- **get_release_by_tag** - Get a release by tag name
+ - `owner`: Repository owner (string, required)
+ - `repo`: Repository name (string, required)
+ - `tag`: Tag name (e.g., 'v1.0.0') (string, required)
+
+- **get_tag** - Get tag details
+ - `owner`: Repository owner (string, required)
+ - `repo`: Repository name (string, required)
+ - `tag`: Tag name (string, required)
+
+- **list_branches** - List branches
+ - `owner`: Repository owner (string, required)
+ - `page`: Page number for pagination (min 1) (number, optional)
+ - `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
+ - `repo`: Repository name (string, required)
+
+- **list_commits** - List commits
+ - `author`: Author username or email address to filter commits by (string, optional)
+ - `owner`: Repository owner (string, required)
+ - `page`: Page number for pagination (min 1) (number, optional)
+ - `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
+ - `repo`: Repository name (string, required)
+ - `sha`: Commit SHA, branch or tag name to list commits of. If not provided, uses the default branch of the repository. If a commit SHA is provided, will list commits up to that SHA. (string, optional)
+
+- **list_releases** - List releases
+ - `owner`: Repository owner (string, required)
+ - `page`: Page number for pagination (min 1) (number, optional)
+ - `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
+ - `repo`: Repository name (string, required)
+
+- **list_tags** - List tags
+ - `owner`: Repository owner (string, required)
+ - `page`: Page number for pagination (min 1) (number, optional)
+ - `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
+ - `repo`: Repository name (string, required)
+
+- **push_files** - Push files to repository
+ - `branch`: Branch to push to (string, required)
+ - `files`: Array of file objects to push, each object with path (string) and content (string) (object[], required)
+ - `message`: Commit message (string, required)
+ - `owner`: Repository owner (string, required)
+ - `repo`: Repository name (string, required)
+
+- **search_code** - Search code
+ - `order`: Sort order for results (string, optional)
+ - `page`: Page number for pagination (min 1) (number, optional)
+ - `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
+ - `query`: 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. (string, required)
+ - `sort`: Sort field ('indexed' only) (string, optional)
+
+- **search_repositories** - Search repositories
+ - `minimal_output`: Return minimal repository information (default: true). When false, returns full GitHub API repository objects. (boolean, optional)
+ - `order`: Sort order (string, optional)
+ - `page`: Page number for pagination (min 1) (number, optional)
+ - `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
+ - `query`: Repository search query. Examples: 'machine learning in:name stars:>1000 language:python', 'topic:react', 'user:facebook'. Supports advanced search syntax for precise filtering. (string, required)
+ - `sort`: Sort repositories by field, defaults to best match (string, optional)
+
+
+
+
+
Secret Protection
- **get_secret_scanning_alert** - Get secret scanning alert
@@ -941,6 +1151,40 @@ Possible options:
- `sort`: Sort field. (string, optional)
- `state`: Filter by advisory state. (string, optional)
+
+
+
+
+Stargazers
+
+- **list_starred_repositories** - List starred repositories
+ - `direction`: The direction to sort the results by. (string, optional)
+ - `page`: Page number for pagination (min 1) (number, optional)
+ - `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
+ - `sort`: How to sort the results. Can be either 'created' (when the repository was starred) or 'updated' (when the repository was last pushed to). (string, optional)
+ - `username`: Username to list starred repositories for. Defaults to the authenticated user. (string, optional)
+
+- **star_repository** - Star repository
+ - `owner`: Repository owner (string, required)
+ - `repo`: Repository name (string, required)
+
+- **unstar_repository** - Unstar repository
+ - `owner`: Repository owner (string, required)
+ - `repo`: Repository name (string, required)
+
+
+
+
+
+Users
+
+- **search_users** - Search users
+ - `order`: Sort order (string, optional)
+ - `page`: Page number for pagination (min 1) (number, optional)
+ - `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
+ - `query`: User search query. Examples: 'john smith', 'location:seattle', 'followers:>100'. Search is automatically scoped to type:user. (string, required)
+ - `sort`: Sort users by number of followers or repositories, or when the person joined GitHub. (string, optional)
+
diff --git a/docs/remote-server.md b/docs/remote-server.md
index 9c06e9e356..b263d70aae 100644
--- a/docs/remote-server.md
+++ b/docs/remote-server.md
@@ -24,15 +24,20 @@ Below is a table of available toolsets for the remote GitHub MCP Server. Each to
| Code Security | Code security related tools, such as GitHub Code Scanning | https://api.githubcopilot.com/mcp/x/code_security | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-code_security&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcode_security%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/code_security/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-code_security&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcode_security%2Freadonly%22%7D) |
| Dependabot | Dependabot tools | https://api.githubcopilot.com/mcp/x/dependabot | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-dependabot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdependabot%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/dependabot/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-dependabot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdependabot%2Freadonly%22%7D) |
| Discussions | GitHub Discussions related tools | https://api.githubcopilot.com/mcp/x/discussions | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-discussions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdiscussions%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/discussions/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-discussions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdiscussions%2Freadonly%22%7D) |
+| Experiments | Experimental features that are not considered stable yet | https://api.githubcopilot.com/mcp/x/experiments | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-experiments&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fexperiments%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/experiments/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-experiments&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fexperiments%2Freadonly%22%7D) |
| Gists | GitHub Gist related tools | https://api.githubcopilot.com/mcp/x/gists | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-gists&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgists%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/gists/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-gists&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgists%2Freadonly%22%7D) |
| Git | GitHub Git API related tools for low-level Git operations | https://api.githubcopilot.com/mcp/x/git | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-git&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgit%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/git/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-git&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgit%2Freadonly%22%7D) |
| Issues | GitHub Issues related tools | https://api.githubcopilot.com/mcp/x/issues | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-issues&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fissues%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/issues/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-issues&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fissues%2Freadonly%22%7D) |
| Labels | GitHub Labels related tools | https://api.githubcopilot.com/mcp/x/labels | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-labels&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Flabels%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/labels/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-labels&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Flabels%2Freadonly%22%7D) |
| Notifications | GitHub Notifications related tools | https://api.githubcopilot.com/mcp/x/notifications | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-notifications&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fnotifications%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/notifications/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-notifications&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fnotifications%2Freadonly%22%7D) |
+| Organizations | GitHub Organization related tools | https://api.githubcopilot.com/mcp/x/orgs | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-orgs&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Forgs%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/orgs/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-orgs&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Forgs%2Freadonly%22%7D) |
+| Projects | GitHub Projects related tools | https://api.githubcopilot.com/mcp/x/projects | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-projects&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fprojects%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/projects/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-projects&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fprojects%2Freadonly%22%7D) |
| Pull Requests | GitHub Pull Request related tools | https://api.githubcopilot.com/mcp/x/pull_requests | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-pull_requests&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fpull_requests%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/pull_requests/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-pull_requests&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fpull_requests%2Freadonly%22%7D) |
| Repositories | GitHub Repository related tools | https://api.githubcopilot.com/mcp/x/repos | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-repos&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Frepos%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/repos/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-repos&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Frepos%2Freadonly%22%7D) |
| Secret Protection | Secret protection related tools, such as GitHub Secret Scanning | https://api.githubcopilot.com/mcp/x/secret_protection | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-secret_protection&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecret_protection%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/secret_protection/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-secret_protection&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecret_protection%2Freadonly%22%7D) |
| Security Advisories | Security advisories related tools | https://api.githubcopilot.com/mcp/x/security_advisories | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-security_advisories&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecurity_advisories%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/security_advisories/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-security_advisories&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecurity_advisories%2Freadonly%22%7D) |
+| Stargazers | GitHub Stargazers related tools | https://api.githubcopilot.com/mcp/x/stargazers | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-stargazers&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fstargazers%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/stargazers/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-stargazers&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fstargazers%2Freadonly%22%7D) |
+| Users | GitHub User related tools | https://api.githubcopilot.com/mcp/x/users | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-users&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fusers%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/users/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-users&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fusers%2Freadonly%22%7D) |