Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
提交
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions pkg/github/__toolsnaps__/sub_issue_write.snap
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
},
"method": {
"description": "The action to perform on a single sub-issue\nOptions are:\n- 'add' - add a sub-issue to a parent issue in a GitHub repository.\n- 'remove' - remove a sub-issue from a parent issue in a GitHub repository.\n- 'reprioritize' - change the order of sub-issues within a parent issue in a GitHub repository. Use either 'after_id' or 'before_id' to specify the new position.\nWrites issue hierarchy. To move a sub-issue to a new parent, use `add` with `replace_parent=true`; there is no writable parent field.\n",
"enum": [
"add",
"remove",
"reprioritize"
],
"type": "string"
},
"owner": {
Expand Down
6 changes: 3 additions & 3 deletions pkg/github/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,7 @@ Use this tool to list workflows in a repository, or list workflow runs, jobs, an
result, payload, err := listWorkflowArtifacts(ctx, client, owner, repo, resourceIDInt, pagination)
return attachIFC(result), payload, err
default:
return utils.新建ToolResultError(fmt.Sprintf("unknown method: %s", method)), nil, nil
return unknownMethodError(method, actionsMethodListWorkflows, actionsMethodListWorkflowRuns, actionsMethodListWorkflowJobs, actionsMethodListWorkflowArtifacts), nil, nil
}
},
)
Expand Down Expand Up @@ -519,7 +519,7 @@ Use this tool to get details about individual workflows, workflow runs, jobs, an
result, payload, err := getWorkflowRunLogsURL(ctx, client, owner, repo, resourceIDInt)
return attachIFC(result), payload, err
default:
return utils.新建ToolResultError(fmt.Sprintf("unknown method: %s", method)), nil, nil
return unknownMethodError(method, actionsMethodGetWorkflow, actionsMethodGetWorkflowRun, actionsMethodGetWorkflowJob, actionsMethodDownloadWorkflowArtifact, actionsMethodGetWorkflowRunUsage, actionsMethodGetWorkflowRunLogsURL), nil, nil
}
},
)
Expand Down Expand Up @@ -636,7 +636,7 @@ func 操作RunTrigger(t translations.TranslationHelperFunc) inventory.ServerToo
case actionsMethodDeleteWorkflowRunLogs:
return deleteWorkflowRunLogs(ctx, client, owner, repo, int64(runID))
default:
return utils.新建ToolResultError(fmt.Sprintf("unknown method: %s", method)), nil, nil
return unknownMethodError(method, actionsMethodRunWorkflow, actionsMethodRerunWorkflowRun, actionsMethodRerunFailedJobs, actionsMethodCancelWorkflowRun, actionsMethodDeleteWorkflowRunLogs), nil, nil
}
},
)
Expand Down
183 changes: 183 additions & 0 deletions pkg/github/dispatch_errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package github

import (
"context"
"maps"
"net/http"
"strings"
"testing"

"github.com/github/github-mcp-server/pkg/inventory"
"github.com/github/github-mcp-server/pkg/translations"
"github.com/google/jsonschema-go/jsonschema"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestAffectedDispatchersReportSupportedMethods(t *testing.T) {
t.Parallel()

tests := []struct {
name string
tool func() inventory.ServerTool
requestArg map[string]any
}{
{
name: "pull request read",
tool: func() inventory.ServerTool {
return PullRequestRead(translations.NullTranslationHelper)
},
requestArg: map[string]any{
"owner": "owner",
"repo": "repo",
"pullNumber": float64(1),
},
},
{
name: "issue read",
tool: func() inventory.ServerTool {
return IssueRead(translations.NullTranslationHelper)
},
requestArg: map[string]any{
"owner": "owner",
"repo": "repo",
"issue_number": float64(1),
},
},
{
name: "sub issue write",
tool: func() inventory.ServerTool {
return SubIssueWrite(translations.NullTranslationHelper)
},
requestArg: map[string]any{
"owner": "owner",
"repo": "repo",
"issue_number": float64(1),
"sub_issue_id": float64(2),
},
},
{
name: "actions list",
tool: func() inventory.ServerTool {
return 操作List(translations.NullTranslationHelper)
},
requestArg: map[string]any{
"owner": "owner",
"repo": "repo",
"resource_id": "1",
},
},
{
name: "actions get",
tool: func() inventory.ServerTool {
return 操作Get(translations.NullTranslationHelper)
},
requestArg: map[string]any{
"owner": "owner",
"repo": "repo",
"resource_id": "1",
},
},
{
name: "actions run",
tool: func() inventory.ServerTool {
return 操作RunTrigger(translations.NullTranslationHelper)
},
requestArg: map[string]any{
"owner": "owner",
"repo": "repo",
"run_id": float64(1),
},
},
{
name: "projects list",
tool: func() inventory.ServerTool {
return 项目List(translations.NullTranslationHelper)
},
requestArg: map[string]any{
"owner": "owner",
"owner_type": "org",
},
},
{
name: "projects get",
tool: func() inventory.ServerTool {
return 项目Get(translations.NullTranslationHelper)
},
requestArg: map[string]any{
"owner": "owner",
"owner_type": "org",
"project_number": float64(1),
},
},
{
name: "projects write",
tool: func() inventory.ServerTool {
return 项目Write(translations.NullTranslationHelper)
},
requestArg: map[string]any{
"owner": "owner",
"owner_type": "org",
"project_number": float64(1),
},
},
{
name: "ui get",
tool: func() inventory.ServerTool {
return UIGet(translations.NullTranslationHelper)
},
requestArg: map[string]any{
"owner": "owner",
},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
tool := tc.tool()
schema := tool.Tool.InputSchema.(*jsonschema.Schema)
methodSchema := schema.Properties["method"]
require.NotNil(t, methodSchema)
require.NotEmpty(t, methodSchema.Enum)

methods := make([]string, len(methodSchema.Enum))
for i, method := range methodSchema.Enum {
methods[i] = method.(string)
}

args := make(map[string]any, len(tc.requestArg)+1)
maps.Copy(args, tc.requestArg)
args["method"] = "unknown_method"

client := must新建GHClient(t, MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}))
deps := BaseDeps{Client: client, GQLClient: defaultGQLClient}
request := createMCPRequest(args)
result, err := tool.Handler(deps)(ContextWithDeps(context.Background(), deps), &request)

require.NoError(t, err)
require.True(t, result.IsError)
assert.Equal(t,
"unknown method: unknown_method. Supported methods are: "+strings.Join(methods, ", "),
getErrorResult(t, result).Text,
)
})
}
}

func TestPullRequestReviewWriteMissingMethodIsRequired(t *testing.T) {
t.Parallel()

tool := PullRequestReviewWrite(translations.NullTranslationHelper)
deps := BaseDeps{GQLClient: defaultGQLClient}
request := createMCPRequest(map[string]any{
"owner": "owner",
"repo": "repo",
"pullNumber": float64(1),
})

result, err := tool.Handler(deps)(ContextWithDeps(context.Background(), deps), &request)

require.NoError(t, err)
require.True(t, result.IsError)
assert.Equal(t, "missing required parameter: method", getErrorResult(t, result).Text)
}
5 changes: 3 additions & 2 deletions pkg/github/issues.go
Original file line number Diff line number Diff line change
Expand Up @@ -888,7 +888,7 @@ func IssueRead(t translations.TranslationHelperFunc) inventory.ServerTool {
result, err := GetIssue标签(ctx, gqlClient, owner, repo, issueNumber)
return attachIFC(result), nil, err
default:
return utils.新建ToolResultError(fmt.Sprintf("unknown method: %s", method)), nil, nil
return unknownMethodError(method, "get", "get_comments", "get_sub_issues", "get_parent", "get_labels"), nil, nil
}
})
}
Expand Down Expand Up @@ -1587,6 +1587,7 @@ func SubIssueWrite(t translations.TranslationHelperFunc) inventory.ServerTool {
"- 'remove' - remove a sub-issue from a parent issue in a GitHub repository.\n" +
"- 'reprioritize' - change the order of sub-issues within a parent issue in a GitHub repository. Use either 'after_id' or 'before_id' to specify the new position.\n" +
"Writes issue hierarchy. To move a sub-issue to a new parent, use `add` with `replace_parent=true`; there is no writable parent field.\n",
Enum: []any{"add", "remove", "reprioritize"},
},
"owner": {
Type: "string",
Expand Down Expand Up @@ -1674,7 +1675,7 @@ func SubIssueWrite(t translations.TranslationHelperFunc) inventory.ServerTool {
result, err := ReprioritizeSubIssue(ctx, client, owner, repo, issueNumber, subIssueID, afterID, beforeID)
return result, nil, err
default:
return utils.新建ToolResultError(fmt.Sprintf("unknown method: %s", method)), nil, nil
return unknownMethodError(method, "add", "remove", "reprioritize"), nil, nil
}
})
st.FeatureFlagDisable = []string{FeatureFlag问题Granular}
Expand Down
17 changes: 17 additions & 0 deletions pkg/github/method_errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package github

import (
"fmt"
"strings"

"github.com/github/github-mcp-server/pkg/utils"
"github.com/modelcontextprotocol/go-sdk/mcp"
)

func unknownMethodError(method string, supportedMethods ...string) *mcp.CallToolResult {
return utils.新建ToolResultError(fmt.Sprintf(
"unknown method: %s. Supported methods are: %s",
method,
strings.Join(supportedMethods, ", "),
))
}
8 changes: 4 additions & 4 deletions pkg/github/projects.go
Original file line number Diff line number Diff line change
Expand Up @@ -437,10 +437,10 @@ Use this tool to list projects for a user or organization, or list project field
result = attachStaticIFCLabel(ctx, deps, result, ifc.LabelProjectContent(isPrivate))
return result, payload, err
default:
return utils.新建ToolResultError(fmt.Sprintf("unknown method: %s", method)), nil, nil
return unknownMethodError(method, projectsMethodList项目, projectsMethodListProjectFields, projectsMethodListProjectItems, projectsMethodListProjectStatusUpdates, projectsMethodListProjectViews), nil, nil
}
default:
return utils.新建ToolResultError(fmt.Sprintf("unknown method: %s", method)), nil, nil
return unknownMethodError(method, projectsMethodList项目, projectsMethodListProjectFields, projectsMethodListProjectItems, projectsMethodListProjectStatusUpdates, projectsMethodListProjectViews), nil, nil
}
},
)
Expand Down Expand Up @@ -642,7 +642,7 @@ Use this tool to get details about individual projects, project fields, project
}
return result, payload, err
default:
return utils.新建ToolResultError(fmt.Sprintf("unknown method: %s", method)), nil, nil
return unknownMethodError(method, projectsMethodGetProject, projectsMethodGetProjectField, projectsMethodGetProjectItem, projectsMethodGetProjectStatusUpdate, projectsMethodGetProjectView), nil, nil
}
},
)
Expand Down Expand Up @@ -1037,7 +1037,7 @@ func 项目Write(t translations.TranslationHelperFunc) inventory.ServerTool {
case projectsMethodDeleteProjectView:
return deleteProjectView(ctx, gqlClient, args, owner, ownerType, projectNumber)
default:
return utils.新建ToolResultError(fmt.Sprintf("unknown method: %s", method)), nil, nil
return unknownMethodError(method, projectsMethodAddProjectItem, projectsMethodUpdateProjectItem, projectsMethodUpdateProjectItems, projectsMethodDeleteProjectItem, projectsMethodCreateProjectStatusUpdate, projectsMethodCreateProjectView, projectsMethodUpdateProjectView, projectsMethodDeleteProjectView, projectsMethodCreateProject, projectsMethodCreateIterationField), nil, nil
}
},
)
Expand Down
10 changes: 8 additions & 2 deletions pkg/github/pullrequests.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ Possible options:
result, err := GetPullRequestCheckRuns(ctx, client, owner, repo, pullNumber, pagination)
return attachIFC(result), nil, err
default:
return utils.新建ToolResultError(fmt.Sprintf("unknown method: %s", method)), nil, nil
return unknownMethodError(method, "get", "get_diff", "get_status", "get_files", "get_commits", "get_review_comments", "get_reviews", "get_comments", "get_check_runs"), nil, nil
}
})
}
Expand Down Expand Up @@ -1836,10 +1836,16 @@ Available methods:
},
[]scopes.Scope{scopes.Repo},
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
method, err := RequiredParam[string](args, "method")
if err != nil {
return utils.新建ToolResultError(err.Error()), nil, nil
}

var params PullRequestReviewWriteParams
if err := mapstructure.WeakDecode(args, &params); err != nil {
return utils.新建ToolResultError(err.Error()), nil, nil
}
params.Method = method

// Given our owner, repo and PR number, lookup the GQL ID of the PR.
client, err := deps.GetGQLClient(ctx)
Expand All @@ -1864,7 +1870,7 @@ Available methods:
result, err := ResolveReviewThread(ctx, client, params.ThreadID, false)
return result, nil, err
default:
return utils.新建ToolResultError(fmt.Sprintf("unknown method: %s", params.Method)), nil, nil
return unknownMethodError(params.Method, "create", "submit_pending", "delete_pending", "resolve_thread", "unresolve_thread"), nil, nil
}
})
st.FeatureFlagDisable = []string{FeatureFlagPullRequestsGranular}
Expand Down
2 changes: 1 addition & 1 deletion pkg/github/ui_tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func UIGet(t translations.TranslationHelperFunc) inventory.ServerTool {
case "reviewers":
return uiGetReviewers(ctx, deps, args, owner)
default:
return utils.新建ToolResultError(fmt.Sprintf("unknown method: %s", method)), nil, nil
return unknownMethodError(method, "labels", "assignees", "milestones", "issue_types", "branches", "issue_fields", "reviewers"), nil, nil
}
})
st.FeatureFlagEnable = MCPAppsFeatureFlag
Expand Down