Skip to content

Commit b8dc0d5

Browse files
committed
add create pull request functionality with UI support and insiders
1 parent 2b0d8cc commit b8dc0d5

File tree

14 files changed

+1048
-73
lines changed

14 files changed

+1048
-73
lines changed

internal/ghmcp/server.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ func NewMCPServer(cfg MCPServerConfig) (*mcp.Server, error) {
181181
WithToolsets(enabledToolsets).
182182
WithTools(cfg.EnabledTools).
183183
WithFeatureChecker(featureChecker).
184+
WithInsidersMode(cfg.InsidersMode).
184185
WithServerInstructions()
185186

186187
// Apply token scope filtering if scopes are known (for PAT filtering)
@@ -250,8 +251,10 @@ func NewMCPServer(cfg MCPServerConfig) (*mcp.Server, error) {
250251
// enable toolsets or tools explicitly that do need registration).
251252
inventory.RegisterAll(context.Background(), ghServer, deps)
252253

253-
// Register MCP App UI resources (static resources for tool UI)
254-
github.RegisterUIResources(ghServer)
254+
// Register MCP App UI resources (static resources for tool UI) - insiders only
255+
if cfg.InsidersMode {
256+
github.RegisterUIResources(ghServer)
257+
}
255258

256259
// Register dynamic toolset management tools (enable/disable) - these are separate
257260
// meta-tools that control the inventory, not part of the inventory itself

pkg/github/__toolsnaps__/create_pull_request.snap

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
{
2+
"_meta": {
3+
"ui": {
4+
"resourceUri": "ui://github-mcp-server/pr-write",
5+
"visibility": [
6+
"model",
7+
"app"
8+
]
9+
}
10+
},
211
"annotations": {
312
"title": "Open new pull request"
413
},
5-
"description": "Create a new pull request in a GitHub repository.",
14+
"description": "Create a new pull request in a GitHub repository.\n\nWhen show_ui is true, an interactive form is displayed for the user to fill in PR details. Use show_ui when:\n- Creating a new PR and you want user input on the details\n- The user hasn't specified all required fields (title, head, base, etc.)\n- Interactive feedback would be valuable (branch selection, reviewers, labels)\n\nWhen show_ui is false or omitted, the PR is created directly with the provided parameters.",
615
"inputSchema": {
716
"properties": {
817
"base": {
@@ -33,17 +42,18 @@
3342
"description": "Repository name",
3443
"type": "string"
3544
},
45+
"show_ui": {
46+
"description": "If true, show an interactive form for the user to fill in PR details. If false or omitted, create the PR directly with the provided parameters.",
47+
"type": "boolean"
48+
},
3649
"title": {
3750
"description": "PR title",
3851
"type": "string"
3952
}
4053
},
4154
"required": [
4255
"owner",
43-
"repo",
44-
"title",
45-
"head",
46-
"base"
56+
"repo"
4757
],
4858
"type": "object"
4959
},

pkg/github/__toolsnaps__/issue_write.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@
6161
"type": "string"
6262
},
6363
"show_ui": {
64-
"description": "If true, show an interactive form for the user to fill in issue details. If false or omitted, create/update the issue directly with the provided parameters. Use show_ui when you want user input or when not all fields are specified.",
64+
"description": "If true, show an interactive form for the user to fill in issue details. If false or omitted, create/update the issue directly with the provided parameters.",
6565
"type": "boolean"
6666
},
6767
"state": {

pkg/github/context_tools.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ func GetMe(t translations.TranslationHelperFunc) inventory.ServerTool {
5454
// Use json.RawMessage to ensure "properties" is included even when empty.
5555
// OpenAI strict mode requires the properties field to be present.
5656
InputSchema: json.RawMessage(`{"type":"object","properties":{}}`),
57-
// MCP Apps UI metadata - links this tool to its UI resource
5857
Meta: mcp.Meta{
5958
"ui": map[string]any{
6059
"resourceUri": GetMeUIResourceURI,

pkg/github/issues.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1172,7 +1172,6 @@ When show_ui is false or omitted, the issue is created/updated directly with the
11721172
Title: t("TOOL_ISSUE_WRITE_USER_TITLE", "Create or update issue."),
11731173
ReadOnlyHint: false,
11741174
},
1175-
// MCP Apps UI metadata - links this tool to its UI resource
11761175
Meta: mcp.Meta{
11771176
"ui": map[string]any{
11781177
"resourceUri": IssueWriteUIResourceURI,
@@ -1184,7 +1183,7 @@ When show_ui is false or omitted, the issue is created/updated directly with the
11841183
Properties: map[string]*jsonschema.Schema{
11851184
"show_ui": {
11861185
Type: "boolean",
1187-
Description: "If true, show an interactive form for the user to fill in issue details. If false or omitted, create/update the issue directly with the provided parameters. Use show_ui when you want user input or when not all fields are specified.",
1186+
Description: "If true, show an interactive form for the user to fill in issue details. If false or omitted, create/update the issue directly with the provided parameters.",
11881187
},
11891188
"method": {
11901189
Type: "string",
@@ -1277,9 +1276,9 @@ Options are:
12771276
return utils.NewToolResultError(err.Error()), nil, nil
12781277
}
12791278

1280-
// If show_ui is true, return a message indicating the UI should be shown
1279+
// If show_ui is true and insiders mode is enabled, return a message indicating the UI should be shown
12811280
// The host will detect the UI metadata and display the form
1282-
if showUI {
1281+
if showUI && deps.GetFlags().InsidersMode {
12831282
if method == "update" {
12841283
issueNumber, numErr := RequiredInt(args, "issue_number")
12851284
if numErr != nil {

pkg/github/pullrequests.go

Lines changed: 73 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -468,57 +468,75 @@ func GetPullRequestReviews(ctx context.Context, client *github.Client, cache *lo
468468
return utils.NewToolResultText(string(r)), nil
469469
}
470470

471+
// PullRequestWriteUIResourceURI is the URI for the create_pull_request tool's MCP App UI resource.
472+
const PullRequestWriteUIResourceURI = "ui://github-mcp-server/pr-write"
473+
471474
// CreatePullRequest creates a tool to create a new pull request.
472475
func CreatePullRequest(t translations.TranslationHelperFunc) inventory.ServerTool {
473-
schema := &jsonschema.Schema{
474-
Type: "object",
475-
Properties: map[string]*jsonschema.Schema{
476-
"owner": {
477-
Type: "string",
478-
Description: "Repository owner",
479-
},
480-
"repo": {
481-
Type: "string",
482-
Description: "Repository name",
483-
},
484-
"title": {
485-
Type: "string",
486-
Description: "PR title",
487-
},
488-
"body": {
489-
Type: "string",
490-
Description: "PR description",
491-
},
492-
"head": {
493-
Type: "string",
494-
Description: "Branch containing changes",
495-
},
496-
"base": {
497-
Type: "string",
498-
Description: "Branch to merge into",
499-
},
500-
"draft": {
501-
Type: "boolean",
502-
Description: "Create as draft PR",
503-
},
504-
"maintainer_can_modify": {
505-
Type: "boolean",
506-
Description: "Allow maintainer edits",
507-
},
508-
},
509-
Required: []string{"owner", "repo", "title", "head", "base"},
510-
}
511-
512476
return NewTool(
513477
ToolsetMetadataPullRequests,
514478
mcp.Tool{
515-
Name: "create_pull_request",
516-
Description: t("TOOL_CREATE_PULL_REQUEST_DESCRIPTION", "Create a new pull request in a GitHub repository."),
479+
Name: "create_pull_request",
480+
Description: t("TOOL_CREATE_PULL_REQUEST_DESCRIPTION", `Create a new pull request in a GitHub repository.
481+
482+
When show_ui is true, an interactive form is displayed for the user to fill in PR details. Use show_ui when:
483+
- Creating a new PR and you want user input on the details
484+
- The user hasn't specified all required fields (title, head, base, etc.)
485+
- Interactive feedback would be valuable (branch selection, reviewers, labels)
486+
487+
When show_ui is false or omitted, the PR is created directly with the provided parameters.`),
517488
Annotations: &mcp.ToolAnnotations{
518489
Title: t("TOOL_CREATE_PULL_REQUEST_USER_TITLE", "Open new pull request"),
519490
ReadOnlyHint: false,
520491
},
521-
InputSchema: schema,
492+
Meta: mcp.Meta{
493+
"ui": map[string]any{
494+
"resourceUri": PullRequestWriteUIResourceURI,
495+
"visibility": []string{"model", "app"},
496+
},
497+
},
498+
InputSchema: &jsonschema.Schema{
499+
Type: "object",
500+
Properties: map[string]*jsonschema.Schema{
501+
"show_ui": {
502+
Type: "boolean",
503+
Description: "If true, show an interactive form for the user to fill in PR details. If false or omitted, create the PR directly with the provided parameters.",
504+
},
505+
"owner": {
506+
Type: "string",
507+
Description: "Repository owner",
508+
},
509+
"repo": {
510+
Type: "string",
511+
Description: "Repository name",
512+
},
513+
"title": {
514+
Type: "string",
515+
Description: "PR title",
516+
},
517+
"body": {
518+
Type: "string",
519+
Description: "PR description",
520+
},
521+
"head": {
522+
Type: "string",
523+
Description: "Branch containing changes",
524+
},
525+
"base": {
526+
Type: "string",
527+
Description: "Branch to merge into",
528+
},
529+
"draft": {
530+
Type: "boolean",
531+
Description: "Create as draft PR",
532+
},
533+
"maintainer_can_modify": {
534+
Type: "boolean",
535+
Description: "Allow maintainer edits",
536+
},
537+
},
538+
Required: []string{"owner", "repo"},
539+
},
522540
},
523541
[]scopes.Scope{scopes.Repo},
524542
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
@@ -530,6 +548,19 @@ func CreatePullRequest(t translations.TranslationHelperFunc) inventory.ServerToo
530548
if err != nil {
531549
return utils.NewToolResultError(err.Error()), nil, nil
532550
}
551+
552+
// Check if UI mode is requested
553+
showUI, err := OptionalParam[bool](args, "show_ui")
554+
if err != nil {
555+
return utils.NewToolResultError(err.Error()), nil, nil
556+
}
557+
558+
// If show_ui is true and insiders mode is enabled, return a message indicating the UI should be shown
559+
if showUI && deps.GetFlags().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
533564
title, err := RequiredParam[string](args, "title")
534565
if err != nil {
535566
return utils.NewToolResultError(err.Error()), nil, nil

pkg/github/pullrequests_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2031,6 +2031,7 @@ func Test_CreatePullRequest(t *testing.T) {
20312031
assert.Equal(t, "create_pull_request", tool.Name)
20322032
assert.NotEmpty(t, tool.Description)
20332033
schema := tool.InputSchema.(*jsonschema.Schema)
2034+
assert.Contains(t, schema.Properties, "show_ui")
20342035
assert.Contains(t, schema.Properties, "owner")
20352036
assert.Contains(t, schema.Properties, "repo")
20362037
assert.Contains(t, schema.Properties, "title")
@@ -2039,7 +2040,7 @@ func Test_CreatePullRequest(t *testing.T) {
20392040
assert.Contains(t, schema.Properties, "base")
20402041
assert.Contains(t, schema.Properties, "draft")
20412042
assert.Contains(t, schema.Properties, "maintainer_can_modify")
2042-
assert.ElementsMatch(t, schema.Required, []string{"owner", "repo", "title", "head", "base"})
2043+
assert.ElementsMatch(t, schema.Required, []string{"owner", "repo"})
20432044

20442045
// Setup mock PR for success case
20452046
mockPR := &github.PullRequest{

pkg/github/ui_resources.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,26 @@ func RegisterUIResources(s *mcp.Server) {
6464
}, nil
6565
},
6666
)
67+
68+
// Register the create_pull_request UI resource
69+
s.AddResource(
70+
&mcp.Resource{
71+
URI: PullRequestWriteUIResourceURI,
72+
Name: "pr_write_ui",
73+
Description: "MCP App UI for creating GitHub pull requests",
74+
MIMEType: "text/html",
75+
},
76+
func(_ context.Context, _ *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) {
77+
html := MustGetUIAsset("pr-write.html")
78+
return &mcp.ReadResourceResult{
79+
Contents: []*mcp.ResourceContents{
80+
{
81+
URI: PullRequestWriteUIResourceURI,
82+
MIMEType: "text/html",
83+
Text: html,
84+
},
85+
},
86+
}, nil
87+
},
88+
)
6789
}

0 commit comments

Comments
 (0)