Skip to content

Commit 3096894

Browse files
committed
Merge remote-tracking branch 'origin/mcp-ui-apps-3' into mcp-ui-apps-3-http-stack
2 parents ee5ca8e + fcefcaa commit 3096894

File tree

20 files changed

+1155
-154
lines changed

20 files changed

+1155
-154
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,14 @@ The following sets of tools are available:
10441044
- `startSide`: For multi-line comments, the starting side of the diff that the comment applies to. LEFT indicates the previous state, RIGHT indicates the new state (string, optional)
10451045
- `subjectType`: The level at which the comment is targeted (string, required)
10461046

1047+
- **add_reply_to_pull_request_comment** - Add reply to pull request comment
1048+
- **Required OAuth Scopes**: `repo`
1049+
- `body`: The text of the reply (string, required)
1050+
- `commentId`: The ID of the comment to reply to (number, required)
1051+
- `owner`: Repository owner (string, required)
1052+
- `pullNumber`: Pull request number (number, required)
1053+
- `repo`: Repository name (string, required)
1054+
10471055
- **create_pull_request** - Open new pull request
10481056
- **Required OAuth Scopes**: `repo`
10491057
- `base`: Branch to merge into (string, optional)

internal/ghmcp/server.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,8 @@ func NewStdioMCPServer(ctx context.Context, cfg github.MCPServerConfig) (*mcp.Se
136136
WithToolsets(github.ResolvedEnabledToolsets(cfg.DynamicToolsets, cfg.EnabledToolsets, cfg.EnabledTools)).
137137
WithTools(github.CleanTools(cfg.EnabledTools)).
138138
WithServerInstructions().
139-
WithFeatureChecker(featureChecker)
139+
WithFeatureChecker(featureChecker).
140+
WithInsidersMode(cfg.InsidersMode)
140141

141142
// Apply token scope filtering if scopes are known (for PAT filtering)
142143
if cfg.TokenScopes != nil {
@@ -153,8 +154,10 @@ func NewStdioMCPServer(ctx context.Context, cfg github.MCPServerConfig) (*mcp.Se
153154
return nil, fmt.Errorf("failed to create GitHub MCP server: %w", err)
154155
}
155156

156-
// Register MCP App UI resources (static resources for tool UI) - insiders only
157-
if cfg.InsidersMode {
157+
// Register MCP App UI resources if available (requires running script/build-ui).
158+
// We check availability to allow Insiders mode to work for non-UI features
159+
// even when UI assets haven't been built.
160+
if cfg.InsidersMode && github.UIAssetsAvailable() {
158161
github.RegisterUIResources(ghServer)
159162
}
160163

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"annotations": {
3+
"title": "Add reply to pull request comment"
4+
},
5+
"description": "Add a reply to an existing pull request comment. This creates a new comment that is linked as a reply to the specified comment.",
6+
"inputSchema": {
7+
"properties": {
8+
"body": {
9+
"description": "The text of the reply",
10+
"type": "string"
11+
},
12+
"commentId": {
13+
"description": "The ID of the comment to reply to",
14+
"type": "number"
15+
},
16+
"owner": {
17+
"description": "Repository owner",
18+
"type": "string"
19+
},
20+
"pullNumber": {
21+
"description": "Pull request number",
22+
"type": "number"
23+
},
24+
"repo": {
25+
"description": "Repository name",
26+
"type": "string"
27+
}
28+
},
29+
"required": [
30+
"owner",
31+
"repo",
32+
"pullNumber",
33+
"commentId",
34+
"body"
35+
],
36+
"type": "object"
37+
},
38+
"name": "add_reply_to_pull_request_comment"
39+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"annotations": {
3+
"readOnlyHint": true,
4+
"title": "Get UI data"
5+
},
6+
"description": "Fetch UI data for MCP Apps (labels, assignees, milestones, issue types, branches).",
7+
"inputSchema": {
8+
"properties": {
9+
"method": {
10+
"description": "The type of data to fetch",
11+
"enum": [
12+
"labels",
13+
"assignees",
14+
"milestones",
15+
"issue_types",
16+
"branches"
17+
],
18+
"type": "string"
19+
},
20+
"owner": {
21+
"description": "Repository owner (required for all methods)",
22+
"type": "string"
23+
},
24+
"repo": {
25+
"description": "Repository name (required for labels, assignees, milestones, branches)",
26+
"type": "string"
27+
}
28+
},
29+
"required": [
30+
"method",
31+
"owner"
32+
],
33+
"type": "object"
34+
},
35+
"name": "ui_get"
36+
}

pkg/github/helper_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ const (
7272
PutReposPullsMergeByOwnerByRepoByPullNumber = "PUT /repos/{owner}/{repo}/pulls/{pull_number}/merge"
7373
PutReposPullsUpdateBranchByOwnerByRepoByPullNumber = "PUT /repos/{owner}/{repo}/pulls/{pull_number}/update-branch"
7474
PostReposPullsRequestedReviewersByOwnerByRepoByPullNumber = "POST /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers"
75+
PostReposPullsCommentsByOwnerByRepoByPullNumber = "POST /repos/{owner}/{repo}/pulls/{pull_number}/comments"
7576

7677
// Notifications endpoints
7778
GetNotifications = "GET /notifications"

pkg/github/issues.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1277,8 +1277,15 @@ Options are:
12771277
return utils.NewToolResultError(err.Error()), nil, nil
12781278
}
12791279

1280-
// When insiders mode is enabled, show UI - the host will detect the UI metadata and display the form
1281-
if deps.GetFlags(ctx).InsidersMode {
1280+
// When insiders mode is enabled and this is an initial request (no title provided),
1281+
// show UI - the host will detect the UI metadata and display the form.
1282+
// If title is provided, this is a submission from the UI form, so proceed with the operation.
1283+
title, err := OptionalParam[string](args, "title")
1284+
if err != nil {
1285+
return utils.NewToolResultError(err.Error()), nil, nil
1286+
}
1287+
1288+
if deps.GetFlags(ctx).InsidersMode && title == "" {
12821289
if method == "update" {
12831290
issueNumber, numErr := RequiredInt(args, "issue_number")
12841291
if numErr != nil {
@@ -1289,11 +1296,6 @@ Options are:
12891296
return utils.NewToolResultText(fmt.Sprintf("Ready to create an issue in %s/%s. The interactive form will be displayed.", owner, repo)), nil, nil
12901297
}
12911298

1292-
title, err := OptionalParam[string](args, "title")
1293-
if err != nil {
1294-
return utils.NewToolResultError(err.Error()), nil, nil
1295-
}
1296-
12971299
// Optional parameters
12981300
body, err := OptionalParam[string](args, "body")
12991301
if err != nil {

pkg/github/pullrequests.go

Lines changed: 111 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -555,25 +555,36 @@ func CreatePullRequest(t translations.TranslationHelperFunc) inventory.ServerToo
555555
return utils.NewToolResultError(err.Error()), nil, nil
556556
}
557557

558-
// When insiders mode is enabled, show UI - the host will detect the UI metadata and display the form
559-
if deps.GetFlags(ctx).InsidersMode {
560-
return utils.NewToolResultText(fmt.Sprintf("Ready to create a pull request in %s/%s. The interactive form will be displayed.", owner, repo)), nil, nil
561-
}
562-
563-
// When not using UI, title/head/base are required
564-
title, err := RequiredParam[string](args, "title")
558+
// Get optional params needed for InsidersMode check
559+
title, err := OptionalParam[string](args, "title")
565560
if err != nil {
566561
return utils.NewToolResultError(err.Error()), nil, nil
567562
}
568-
head, err := RequiredParam[string](args, "head")
563+
head, err := OptionalParam[string](args, "head")
569564
if err != nil {
570565
return utils.NewToolResultError(err.Error()), nil, nil
571566
}
572-
base, err := RequiredParam[string](args, "base")
567+
base, err := OptionalParam[string](args, "base")
573568
if err != nil {
574569
return utils.NewToolResultError(err.Error()), nil, nil
575570
}
576571

572+
// When insiders mode is enabled and required params are missing, show UI
573+
if deps.GetFlags(ctx).InsidersMode && (title == "" || head == "" || base == "") {
574+
return utils.NewToolResultText(fmt.Sprintf("Ready to create a pull request in %s/%s. The interactive form will be displayed.", owner, repo)), nil, nil
575+
}
576+
577+
// When creating PR, title/head/base are required
578+
if title == "" {
579+
return utils.NewToolResultError("missing required parameter: title"), nil, nil
580+
}
581+
if head == "" {
582+
return utils.NewToolResultError("missing required parameter: head"), nil, nil
583+
}
584+
if base == "" {
585+
return utils.NewToolResultError("missing required parameter: base"), nil, nil
586+
}
587+
577588
body, err := OptionalParam[string](args, "body")
578589
if err != nil {
579590
return utils.NewToolResultError(err.Error()), nil, nil
@@ -933,6 +944,97 @@ func UpdatePullRequest(t translations.TranslationHelperFunc) inventory.ServerToo
933944
})
934945
}
935946

947+
// AddReplyToPullRequestComment creates a tool to add a reply to an existing PR comment.
948+
func AddReplyToPullRequestComment(t translations.TranslationHelperFunc) inventory.ServerTool {
949+
schema := &jsonschema.Schema{
950+
Type: "object",
951+
Properties: map[string]*jsonschema.Schema{
952+
"owner": {
953+
Type: "string",
954+
Description: "Repository owner",
955+
},
956+
"repo": {
957+
Type: "string",
958+
Description: "Repository name",
959+
},
960+
"pullNumber": {
961+
Type: "number",
962+
Description: "Pull request number",
963+
},
964+
"commentId": {
965+
Type: "number",
966+
Description: "The ID of the comment to reply to",
967+
},
968+
"body": {
969+
Type: "string",
970+
Description: "The text of the reply",
971+
},
972+
},
973+
Required: []string{"owner", "repo", "pullNumber", "commentId", "body"},
974+
}
975+
976+
return NewTool(
977+
ToolsetMetadataPullRequests,
978+
mcp.Tool{
979+
Name: "add_reply_to_pull_request_comment",
980+
Description: t("TOOL_ADD_REPLY_TO_PULL_REQUEST_COMMENT_DESCRIPTION", "Add a reply to an existing pull request comment. This creates a new comment that is linked as a reply to the specified comment."),
981+
Annotations: &mcp.ToolAnnotations{
982+
Title: t("TOOL_ADD_REPLY_TO_PULL_REQUEST_COMMENT_USER_TITLE", "Add reply to pull request comment"),
983+
ReadOnlyHint: false,
984+
},
985+
InputSchema: schema,
986+
},
987+
[]scopes.Scope{scopes.Repo},
988+
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
989+
owner, err := RequiredParam[string](args, "owner")
990+
if err != nil {
991+
return utils.NewToolResultError(err.Error()), nil, nil
992+
}
993+
repo, err := RequiredParam[string](args, "repo")
994+
if err != nil {
995+
return utils.NewToolResultError(err.Error()), nil, nil
996+
}
997+
pullNumber, err := RequiredInt(args, "pullNumber")
998+
if err != nil {
999+
return utils.NewToolResultError(err.Error()), nil, nil
1000+
}
1001+
commentID, err := RequiredInt(args, "commentId")
1002+
if err != nil {
1003+
return utils.NewToolResultError(err.Error()), nil, nil
1004+
}
1005+
body, err := RequiredParam[string](args, "body")
1006+
if err != nil {
1007+
return utils.NewToolResultError(err.Error()), nil, nil
1008+
}
1009+
1010+
client, err := deps.GetClient(ctx)
1011+
if err != nil {
1012+
return utils.NewToolResultErrorFromErr("failed to get GitHub client", err), nil, nil
1013+
}
1014+
1015+
comment, resp, err := client.PullRequests.CreateCommentInReplyTo(ctx, owner, repo, pullNumber, body, int64(commentID))
1016+
if err != nil {
1017+
return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to add reply to pull request comment", resp, err), nil, nil
1018+
}
1019+
defer func() { _ = resp.Body.Close() }()
1020+
1021+
if resp.StatusCode != http.StatusCreated {
1022+
bodyBytes, err := io.ReadAll(resp.Body)
1023+
if err != nil {
1024+
return utils.NewToolResultErrorFromErr("failed to read response body", err), nil, nil
1025+
}
1026+
return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to add reply to pull request comment", resp, bodyBytes), nil, nil
1027+
}
1028+
1029+
r, err := json.Marshal(comment)
1030+
if err != nil {
1031+
return utils.NewToolResultErrorFromErr("failed to marshal response", err), nil, nil
1032+
}
1033+
1034+
return utils.NewToolResultText(string(r)), nil, nil
1035+
})
1036+
}
1037+
9361038
// ListPullRequests creates a tool to list and filter repository pull requests.
9371039
func ListPullRequests(t translations.TranslationHelperFunc) inventory.ServerTool {
9381040
schema := &jsonschema.Schema{

0 commit comments

Comments
 (0)