Add initial support for MCP Apps for select tools under Insiders#1957
Add initial support for MCP Apps for select tools under Insiders#1957mattdholloway wants to merge 43 commits intomainfrom
Conversation
…ce CreateIssueApp to manage existing issue data
…server into mcp-ui-apps-3
There was a problem hiding this comment.
Pull request overview
This pull request adds support for MCP Apps (Model Context Protocol Applications), which enable rich interactive UIs for select MCP tools. The feature is gated behind an Insiders mode flag and adds React-based interfaces for three tools: get_me, issue_write, and create_pull_request.
Changes:
- Adds React 18-based UI infrastructure using Vite, Primer Design System, and the MCP ext-apps framework
- Implements three interactive UIs (get_me, issue-write, pr-write) with form validation and real-time data loading
- Extends the Go backend with UI resource registration, embedding, and insiders mode gating
- Adds two supporting tools (
list_assignees,list_milestones) for populating UI dropdowns - Updates CI/CD workflows and Dockerfile to build and embed UI assets
- Modifies tool schemas to make certain fields optional when UIs are available
Reviewed changes
Copilot reviewed 37 out of 40 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| ui/vite.config.ts | Vite configuration for building single-file HTML apps |
| ui/tsconfig.json | TypeScript configuration for React apps |
| ui/package.json | Dependencies including React 18, Primer, and MCP ext-apps |
| ui/src/hooks/useMcpApp.ts | Custom React hook for MCP app integration |
| ui/src/components/MarkdownEditor.tsx | Markdown editor using GitHub's markdown toolbar |
| ui/src/components/AppProvider.tsx | Theme provider wrapper for Primer React |
| ui/src/apps/get-me/App.tsx | User profile display UI |
| ui/src/apps/issue-write/App.tsx | Issue creation/update form UI |
| ui/src/apps/pr-write/App.tsx | Pull request creation form UI |
| script/build-ui | Script to build React apps into single HTML files |
| pkg/github/ui_embed.go | Go embed directive for built UI assets |
| pkg/github/ui_resources.go | MCP resource registration for UI HTML |
| pkg/inventory/builder.go | Adds insiders mode support with metadata stripping |
| pkg/github/issues.go | Adds list_assignees and list_milestones tools, updates issue_write |
| pkg/github/pullrequests.go | Updates create_pull_request with UI metadata |
| pkg/github/context_tools.go | Adds UI metadata to get_me tool |
| internal/ghmcp/server.go | Wires up insiders mode flag and UI resource registration |
| Dockerfile | Multi-stage build with Node.js for UI compilation |
| .github/workflows/* | Updates workflows to build UI before Go compilation |
| README.md | Documents new tools in the tools list |
Comments suppressed due to low confidence (1)
pkg/github/ui_embed.go:32
- The MustGetUIAsset function will panic if a UI asset file is missing or fails to load. While this is acceptable for required assets at startup, it could cause confusing errors if the build process was incomplete. Consider adding a check during server startup that verifies all expected UI assets exist and provides a clear error message if script/build-ui hasn't been run yet.
// MustGetUIAsset reads a UI asset and panics if it fails.
// Use this when the asset is required for server operation.
func MustGetUIAsset(name string) string {
html, err := GetUIAsset(name)
if err != nil {
panic("failed to load UI asset " + name + ": " + err.Error())
}
return html
}
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 40 out of 43 changed files in this pull request and generated 7 comments.
Comments suppressed due to low confidence (2)
ui/src/apps/pr-write/App.tsx:415
- The pr-write UI collects reviewers, labels, and milestones from the user (state variables on lines 184-196), but these values are not sent to the
create_pull_requesttool in the handleSubmit function (lines 406-415). The GitHub API's create PR endpoint doesn't support setting these fields directly - they would need to be set via separate API calls after PR creation. Either: 1) Remove these UI elements since they're not functional, 2) Make follow-up API calls to set these after PR creation, or 3) Document that these fields are for future implementation.
pkg/inventory/builder.go:365 - The function
stripInsidersMetaFromToolreturnsnilwhen no insiders keys are found, but the caller instripInsidersFeaturesuses this to decide whether to use the stripped tool or the original. This creates an unnecessary copy of all tools even when no changes are made. Consider returning a boolean indicating whether stripping was needed, or only modifying tools that actually need it to avoid unnecessary allocations.
// stripInsidersFeatures removes insiders-only features from tools.
// This includes Meta keys listed in insidersOnlyMetaKeys.
func stripInsidersFeatures(tools []ServerTool) []ServerTool {
result := make([]ServerTool, len(tools))
for i, tool := range tools {
if stripped := stripInsidersMetaFromTool(tool); stripped != nil {
result[i] = *stripped
} else {
result[i] = tool
}
}
return result
}
| <!DOCTYPE html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>Create GitHub Issue</title> | ||
| </head> | ||
| <body> | ||
| <div id="root"></div> | ||
| <script type="module" src="./App.tsx"></script> | ||
| </body> | ||
| </html> |
There was a problem hiding this comment.
The issue-write and pr-write HTML files don't have Content-Security-Policy headers like get-me does. For consistency and security, all three apps should have CSP headers. The get-me app needs to load images from GitHub's avatar CDN, but the other apps may have similar requirements (e.g., loading user avatars in the assignee picker). Consider adding appropriate CSP headers to these files as well.
| setToolInput(args); | ||
| onToolInput?.(args); | ||
| }; | ||
| app.onerror = console.error; |
There was a problem hiding this comment.
The console.error is called directly on line 42 without proper error handling or context. Consider using a more robust error handling approach that logs context about what failed, or allows the parent component to handle errors through the onError callback pattern.
bddf923 to
7ce5e08
Compare
tommaso-moro
left a comment
There was a problem hiding this comment.
Great work on this, and it's really cool to see the Primer design system in action here! 🚀
A few comments:
I see you added two tools (list_milestones and list_assignees) that are meant to be used only by the Apps in the iframes. My concerns are:
- I am worried about how this will this play out if users are configuring specific tools in their server config. From my understanding, we'd be introducing a dependency of some tools on other tools (suddenly,
issue_writerelies onlist_assigneesfor example). This is a problem because users are not aware of these dependencies (and shouldn't be) when configuring their server. So if they don't enable bothissue_writeandlist_assigneesat the same time, things would break I assume. I wonder if there is another way of doing this. Wdyt? - (Less important for these 2 tools but relevant if we build more) These tools appear in
tools/listand consume context. Now it's only 2 additional tools, but if we follow this pattern when building more UIs we can easily end up having a lot of tools which can be expensive context-wise
Does the input schema change from Required: []string{"owner", "repo", "title", "head", "base"} to Required: []string{"owner", "repo"} affect non-insiders users ?
I noticed some issues with the UI/UX when trying this out locally. Some related to general spacing and layout (probs easy to fix) but other seem like core markdown functionality that is broken, this is a blocker even for an Insiders ship. Here are some of the main ones:
1- The 'Add a quote' functionality in the markdown editor is broken
2- The repository selection dropdown is too wide and overflows outside the app's window.
Demo:
Screen.Recording.2026-02-05.at.16.23.56.mov
3- Missing spacing between the bottom dropdowns. Also, can't scroll horizontally to reveal the ones past "Milestone"
4- The top part of the PR creation UI overflows
5- Lists (both numbered and bullet points) don't fully work, as pressing "enter" doesn't create a new list item, it just creates a new line. This is not super high priority but should ideally be fixed.
Demo:
Screen.Recording.2026-02-05.at.16.22.56.mov
6- Missing spacing here between "create as draft" and "allow maintainer edits"
At first glance, it seems that a lot of the markdown issues are due to the fact that MarkdownEditor.tsx is using a simple textarea instead of a markdown editor. There may be an official markdown editor from the Primer lib or similar that could be used?
(nit: I think the React code can be improved but haven't reviewed it because I assume the priority in this PR is just to make it work for experimental testing)
| Description: "Allow maintainer edits", | ||
| }, | ||
| }, | ||
| Required: []string{"owner", "repo"}, |
There was a problem hiding this comment.
Why did Required change from Required: []string{"owner", "repo", "title", "head", "base"} to Required: []string{"owner", "repo"} ?
There was a problem hiding this comment.
This changed so that the model doesn't need to provide these fields upfront, they can be filled by the user in the UI



Summary
Based on initial work by @tommaso-moro
This PR adds support for MCP Apps, enabling rich interactive UIs for MCP tools.
New MCP App UIs
get_me- User profile display with avatar and statsissue_write- Create/update issues with pickers for labels, assignees, milestones, and issue typescreate_pull_request- Create PRs with branch selection, reviewers, labels, and milestonesImplementation
ui/): React + Primer design system, built to single HTML files viascript/build-uilist_assigneesandlist_milestonesfor picker dataInsiders Mode
All MCP Apps functionality gated behind Insiders
insidersOnlyMetaKeysmechanism for future experimental featuresThese new UIs require a new React app that exists under the
uifolder with components made available in iframes for clients that support the feature. This has been extensively tested under VSCode-Insiders.This will require additional support in Remote MCP to ensure Insiders and resources support works correctly.
Why
As part of https://github.com/github/copilot-mcp-core/issues/1125
What changed
ui/directory with React + Primer design system apps forget_me,issue_write, andcreate_pull_requestscript/build-uito compile React apps into single HTML filespkg/github/ui_embed.goto embed built HTML assets into binarypkg/github/ui_resources.goto register UI resources with the MCP serverlist_assigneesandlist_milestonestools for fetching picker dataMeta.ui.resourceUri) toget_me,issue_write, andcreate_pull_requesttoolsWithInsidersMode()to inventory builder to gate experimental featuresinsidersOnlyMetaKeysmechanism to strip insiders-only metadata when disabled.vscode/mcp.jsonwithGITHUB_INSIDERSenvironment variableMCP 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