Conversation
…ce CreateIssueApp to manage existing issue data
…server into mcp-ui-apps-3
Remove advanced features to be kept in mcp-ui-apps-advanced: - Strip labels, assignees, milestones, issue types, repo picker from issue-write - Strip repo picker, branch selectors from pr-write - Delete ui_get tool (ui_tools.go, ui_tools_test.go, ui_get.snap) - Remove UIGet registration from tools.go Basic forms retain: title, body, submit with _ui_submitted, draft/regular split button (PR), MarkdownEditor, and SuccessView.
Add proper spacing between icon, title text, and repo name in the header bar for both issue-write and create-pull-request forms.
This reverts commit 24174b9.
53a14c8 to
fce0c3d
Compare
# Conflicts: # Dockerfile # pkg/github/issues.go # pkg/github/pullrequests.go # pkg/inventory/builder.go # ui/package-lock.json # ui/src/apps/issue-write/App.tsx # ui/src/apps/pr-write/App.tsx # ui/src/components/MarkdownEditor.tsx
There was a problem hiding this comment.
Pull request overview
This pull request introduces a new backend ui_get tool to fetch UI metadata (labels, assignees, milestones, issue types, branches) for MCP Apps and integrates it into the PR and issue creation UIs to enable dynamic repository selection, branch selection, and metadata management.
Changes:
- Added new insiders-only
ui_getbackend tool with comprehensive tests - Enhanced PR creation UI with repository search and dynamic branch selection
- Enhanced issue creation UI with repository search and metadata selection (labels, assignees, milestones, issue types)
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/github/ui_tools.go | Implements the new ui_get tool with methods for fetching labels, assignees, milestones, issue types, and branches |
| pkg/github/ui_tools_test.go | Adds comprehensive unit tests for the ui_get tool covering various scenarios and error conditions |
| pkg/github/tools.go | Registers the new ui_get tool in the AllTools function |
| pkg/github/toolsnaps/ui_get.snap | Adds tool schema snapshot for API change tracking |
| ui/src/apps/pr-write/App.tsx | Adds repository picker, branch selectors with filtering, and validation to prevent invalid branch selections |
| ui/src/apps/issue-write/App.tsx | Adds repository picker, metadata selectors (labels, assignees, milestones, issue types), prefill logic for existing issues, and visual display of selected metadata |
| ui/package-lock.json | Updates peer dependency versions from @modelcontextprotocol/sdk |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
|
@mattdholloway I've opened a new pull request, #2012, to work on those changes. Once the pull request is ready, I'll request review from you. |
* Initial plan * Implement pagination for uiGetBranches function Co-authored-by: mattdholloway <918573+mattdholloway@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: mattdholloway <918573+mattdholloway@users.noreply.github.com>
| Description githubv4.String | ||
| } | ||
| TotalCount githubv4.Int | ||
| } `graphql:"labels(first: 100)"` |
There was a problem hiding this comment.
The GraphQL query only fetches the first 100 labels without pagination. Repositories with more than 100 labels will have incomplete data. Consider implementing GraphQL pagination (using cursor-based pagination with after parameter and pageInfo { hasNextPage, endCursor }) similar to how REST pagination is implemented in uiGetAssignees and uiGetMilestones, or document this as a known limitation.
| }; | ||
|
|
||
| loadBranches(); | ||
| }, [owner, repo, app, callTool]); |
There was a problem hiding this comment.
The useEffect references baseBranch in the conditional check at line 247, but baseBranch is not included in the dependency array at line 261. This can lead to stale closures where the effect uses an outdated value of baseBranch. However, adding baseBranch to the dependency array would cause the effect to re-run whenever baseBranch changes, which may not be the intended behavior. Consider either adding baseBranch to the dependencies if the effect should re-run when it changes, or restructuring the logic to avoid referencing it in the effect closure.
| }, [owner, repo, app, callTool]); | |
| }, [owner, repo, app, callTool, baseBranch]); |
| func Test_UIGet(t *testing.T) { | ||
| // Verify tool definition | ||
| serverTool := UIGet(translations.NullTranslationHelper) | ||
| tool := serverTool.Tool | ||
| require.NoError(t, toolsnaps.Test(tool.Name, tool)) | ||
|
|
||
| assert.Equal(t, "ui_get", tool.Name) | ||
| assert.NotEmpty(t, tool.Description) | ||
| assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "method") | ||
| assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "owner") | ||
| assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "repo") | ||
| assert.ElementsMatch(t, tool.InputSchema.(*jsonschema.Schema).Required, []string{"method", "owner"}) | ||
| assert.True(t, tool.Annotations.ReadOnlyHint, "ui_get should be read-only") | ||
| assert.True(t, serverTool.InsidersOnly, "ui_get should be insiders only") | ||
|
|
||
| // Setup mock data | ||
| mockAssignees := []*github.User{ | ||
| {Login: github.Ptr("user1"), AvatarURL: github.Ptr("https://avatars.githubusercontent.com/u/1")}, | ||
| {Login: github.Ptr("user2"), AvatarURL: github.Ptr("https://avatars.githubusercontent.com/u/2")}, | ||
| } | ||
|
|
||
| mockBranches := []*github.Branch{ | ||
| {Name: github.Ptr("main"), Protected: github.Ptr(true)}, | ||
| {Name: github.Ptr("feature"), Protected: github.Ptr(false)}, | ||
| } | ||
|
|
||
| tests := []struct { | ||
| name string | ||
| mockedClient *http.Client | ||
| requestArgs map[string]any | ||
| expectError bool | ||
| expectedErrMsg string | ||
| validateResult func(t *testing.T, response map[string]any) | ||
| }{ | ||
| { | ||
| name: "successful assignees fetch", | ||
| mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ | ||
| "GET /repos/owner/repo/assignees": mockResponse(t, http.StatusOK, mockAssignees), | ||
| }), | ||
| requestArgs: map[string]any{ | ||
| "method": "assignees", | ||
| "owner": "owner", | ||
| "repo": "repo", | ||
| }, | ||
| expectError: false, | ||
| validateResult: func(t *testing.T, response map[string]any) { | ||
| assert.Contains(t, response, "assignees") | ||
| assert.Contains(t, response, "totalCount") | ||
| }, | ||
| }, | ||
| { | ||
| name: "successful branches fetch", | ||
| mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ | ||
| "GET /repos/owner/repo/branches": mockResponse(t, http.StatusOK, mockBranches), | ||
| }), | ||
| requestArgs: map[string]any{ | ||
| "method": "branches", | ||
| "owner": "owner", | ||
| "repo": "repo", | ||
| }, | ||
| expectError: false, | ||
| validateResult: func(t *testing.T, response map[string]any) { | ||
| assert.Contains(t, response, "branches") | ||
| assert.Contains(t, response, "totalCount") | ||
| }, | ||
| }, | ||
| { | ||
| name: "missing method parameter", | ||
| mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), | ||
| requestArgs: map[string]any{ | ||
| "owner": "owner", | ||
| "repo": "repo", | ||
| }, | ||
| expectError: true, | ||
| expectedErrMsg: "missing required parameter: method", | ||
| }, | ||
| { | ||
| name: "missing owner parameter", | ||
| mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), | ||
| requestArgs: map[string]any{ | ||
| "method": "assignees", | ||
| "repo": "repo", | ||
| }, | ||
| expectError: true, | ||
| expectedErrMsg: "missing required parameter: owner", | ||
| }, | ||
| { | ||
| name: "missing repo parameter for assignees", | ||
| mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), | ||
| requestArgs: map[string]any{ | ||
| "method": "assignees", | ||
| "owner": "owner", | ||
| }, | ||
| expectError: true, | ||
| expectedErrMsg: "missing required parameter: repo", | ||
| }, | ||
| { | ||
| name: "unknown method", | ||
| mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), | ||
| requestArgs: map[string]any{ | ||
| "method": "unknown", | ||
| "owner": "owner", | ||
| "repo": "repo", | ||
| }, | ||
| expectError: true, | ||
| expectedErrMsg: "unknown method: unknown", | ||
| }, | ||
| } | ||
|
|
||
| for _, tc := range tests { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| // Setup client with mock | ||
| client := github.NewClient(tc.mockedClient) | ||
| deps := BaseDeps{ | ||
| Client: client, | ||
| } | ||
| handler := serverTool.Handler(deps) | ||
|
|
||
| // Create call request | ||
| request := createMCPRequest(tc.requestArgs) | ||
|
|
||
| // Call handler | ||
| result, err := handler(ContextWithDeps(context.Background(), deps), &request) | ||
|
|
||
| // Verify results | ||
| if tc.expectError { | ||
| if err != nil { | ||
| assert.Contains(t, err.Error(), tc.expectedErrMsg) | ||
| return | ||
| } | ||
| require.NotNil(t, result) | ||
| require.True(t, result.IsError) | ||
| errorContent := getErrorResult(t, result) | ||
| assert.Contains(t, errorContent.Text, tc.expectedErrMsg) | ||
| return | ||
| } | ||
|
|
||
| require.NoError(t, err) | ||
| require.NotNil(t, result) | ||
| require.False(t, result.IsError) | ||
| textContent := getTextResult(t, result) | ||
|
|
||
| var response map[string]any | ||
| err = json.Unmarshal([]byte(textContent.Text), &response) | ||
| require.NoError(t, err) | ||
|
|
||
| if tc.validateResult != nil { | ||
| tc.validateResult(t, response) | ||
| } | ||
| }) | ||
| } | ||
| } |
There was a problem hiding this comment.
Test coverage is incomplete for ui_get tool. The tests only cover the "assignees" and "branches" methods, but not "labels", "milestones", or "issue_types". Add test cases for these methods to ensure they work correctly and handle errors properly. This is especially important for "labels" which uses GraphQL instead of REST API.
Summary
This pull request introduces a new backend tool for fetching UI-related repository data and integrates it into the PR creation UI, enabling dynamic branch selection and repository search. It also includes comprehensive tests for the new tool.
Why
Expands on #1957
What changed
Backend: UI Data Fetch Tool
ui_gettool to provide UI data (labels, assignees, milestones, issue types, branches) for MCP Apps, including its schema and logic for fetching data using GitHub APIs. [1] [2]ui_gettool into the server toolset, making it available as an insiders-only feature.Frontend: Pull Request Creation UI Enhancements
ui_gettool. [1] [2]Summary of Most Important Changes
Backend: New Tool for UI Data
ui_gettool, enabling MCP Apps to fetch labels, assignees, milestones, issue types, and branches for a repository, with robust error handling and schema validation. [1] [2]ui_get, ensuring correct behavior and error responses for different scenarios.ui_getas an insiders-only tool in the server toolset.Frontend: PR Creation UI Improvements
MCP impact
Prompts tested (tool changes only)
Security / limits
Tool renaming
deprecated_tool_aliases.goNote: if you're renaming tools, you must add the tool aliases. For more information on how to do so, please refer to the official docs.
Lint & tests
./script/lint./script/testDocs