Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
67a4793
PoC full flow (hello world example)
tommaso-moro Jan 7, 2026
7d2b463
add avatar resource domain
tommaso-moro Jan 7, 2026
7606aec
add postmessage logic and richer UI
tommaso-moro Jan 7, 2026
02ce9bf
add create issue ui
tommaso-moro Jan 15, 2026
d53ff41
update ui for issue creatioon
tommaso-moro Jan 15, 2026
e3d5db7
fix
tommaso-moro Jan 15, 2026
b149642
ignore banner
tommaso-moro Jan 15, 2026
92aabf2
update docs after rebase
mattdholloway Feb 2, 2026
8b55d08
update toolsnap for get_me
mattdholloway Feb 2, 2026
06270c7
new UI changes
mattdholloway Feb 2, 2026
a770719
update docs
mattdholloway Feb 2, 2026
23de0bc
update workflows that need ui build
mattdholloway Feb 2, 2026
e2a8582
add UI diff
mattdholloway Feb 2, 2026
5bb6228
fix build ui step for windows runners to use git bash
mattdholloway Feb 2, 2026
157ba4d
fix UI diff
mattdholloway Feb 2, 2026
c927390
refactor issue creation UI
mattdholloway Feb 3, 2026
6d0eda6
add AvatarWithFallback component and update UserCard to use it; enhan…
mattdholloway Feb 3, 2026
2b0d8cc
fix formatting of button labels
mattdholloway Feb 3, 2026
b8dc0d5
add create pull request functionality with UI support and insiders
mattdholloway Feb 3, 2026
2d186da
update docs
mattdholloway Feb 3, 2026
072f5c4
add test for insiders mode handling in ServerTool schema
mattdholloway Feb 3, 2026
adc1053
remove `show_ui` param for now
mattdholloway Feb 4, 2026
ee0a440
make insiders mode metadata stripping generic
mattdholloway Feb 4, 2026
2b8f062
remove ui diff
mattdholloway Feb 4, 2026
6070471
Merge branch 'main' into mcp-ui-apps-3
mattdholloway Feb 4, 2026
d0f0334
Merge branch 'mcp-ui-apps-3' of https://github.com/github/github-mcp-…
mattdholloway Feb 4, 2026
37da563
fix CI
mattdholloway Feb 4, 2026
8b23e49
remove redundant mention of old app name
mattdholloway Feb 4, 2026
12af958
add node types to fix ide issues for ts code
mattdholloway Feb 4, 2026
e61c2d2
remove unused TriangleDownIcon import
mattdholloway Feb 4, 2026
54ce61d
update @primer/behaviors and electron-to-chromium versions in package…
mattdholloway Feb 4, 2026
03aad7f
add check to ensure base and head are not the same when creating a ne…
mattdholloway Feb 4, 2026
ad09947
remove old show_ui
mattdholloway Feb 4, 2026
d5e263e
fix gitignore for dist so builds dont break
mattdholloway Feb 4, 2026
dca4d52
add tests for insiders mode handling and metadata stripping in Server…
mattdholloway Feb 4, 2026
932750c
remove unused state and components from CreatePRApp
mattdholloway Feb 4, 2026
16469f8
fix ui build
mattdholloway Feb 4, 2026
d0085a7
update docker build to fix npm issue
mattdholloway Feb 4, 2026
7ce5e08
remove reference to show_ui
mattdholloway Feb 4, 2026
3413b71
Merge branch 'main' into mcp-ui-apps-3
mattdholloway Feb 5, 2026
e20cef3
allow insiders to work for non-ui features
mattdholloway Feb 5, 2026
8a0edfb
formalise insiders inventory support
mattdholloway Feb 5, 2026
d564d98
update docs
mattdholloway Feb 5, 2026
f034e19
fix overflow issues and replace pull request dropdown with matching U…
mattdholloway Feb 5, 2026
934e83d
fix createpullrequest test
mattdholloway Feb 5, 2026
a1922b2
consolidate fetching tools under `ui_get` tool to remove toolset deps
mattdholloway Feb 5, 2026
a47064d
fix issue data prefill in issue_write form
mattdholloway Feb 5, 2026
be4c4c7
fix link component when updating issue
mattdholloway Feb 5, 2026
69579b2
fix avatar URL
mattdholloway Feb 5, 2026
b2164a1
fix broken issue update logic
mattdholloway Feb 5, 2026
fcefcaa
remove dbg
mattdholloway Feb 5, 2026
826b518
Merge remote-tracking branch 'origin/main' into mcp-ui-apps-3
mattdholloway Feb 6, 2026
8fa72ad
fix for new GetFlags
mattdholloway Feb 6, 2026
00b9213
revert to original required fields for create_pull_request
mattdholloway Feb 6, 2026
922fa9f
fix for UI form submission
mattdholloway Feb 6, 2026
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
12 changes: 12 additions & 0 deletions .github/workflows/code-scanning.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ jobs:
go-version: ${{ fromJSON(steps.resolve-environment.outputs.environment).configuration.go.version }}
cache: false

- name: Set up Node.js
if: matrix.language == 'go'
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
cache-dependency-path: ui/package-lock.json

- name: Build UI
if: matrix.language == 'go'
run: script/build-ui

- name: Autobuild
uses: github/codeql-action/autobuild@v4

Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/docs-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ jobs:
- name: Checkout code
uses: actions/checkout@v6

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
cache-dependency-path: ui/package-lock.json

- name: Build UI
run: script/build-ui

- name: Set up Go
uses: actions/setup-go@v6
with:
Expand Down
12 changes: 12 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ jobs:
- name: Check out code
uses: actions/checkout@v6

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
cache-dependency-path: ui/package-lock.json

- name: Build UI
shell: bash
run: script/build-ui

- name: Set up Go
uses: actions/setup-go@v6
with:
Expand All @@ -34,6 +45,7 @@ jobs:
run: go mod tidy -diff

- name: Run unit tests
shell: bash
run: script/test

- name: Build
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/goreleaser.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ jobs:
- name: Check out code
uses: actions/checkout@v6

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
cache-dependency-path: ui/package-lock.json

- name: Build UI
run: script/build-ui

- name: Set up Go
uses: actions/setup-go@v6
with:
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/license-check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ jobs:
GH_TOKEN: ${{ github.token }}
run: gh pr checkout ${{ github.event.pull_request.number }}

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
cache-dependency-path: ui/package-lock.json

- name: Build UI
run: script/build-ui

- name: Set up Go
uses: actions/setup-go@v6
with:
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
cache-dependency-path: ui/package-lock.json
- name: Build UI
run: script/build-ui
- uses: actions/setup-go@v6
with:
go-version: stable
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/mcp-diff.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ jobs:
with:
fetch-depth: 0

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Build UI
run: script/build-ui

- name: Run MCP Server Diff
uses: SamMorrowDrums/[email protected]
with:
Expand Down
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,12 @@ e2e.test

.history
conformance-report/

# UI build artifacts
ui/dist/
ui/node_modules/

# Embedded UI assets (built from ui/)
pkg/github/ui_dist/*
!pkg/github/ui_dist/.gitkeep
!pkg/github/ui_dist/.placeholder.html
17 changes: 15 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
FROM node:20-alpine AS ui-build
WORKDIR /app
COPY ui/package*.json ./ui/
RUN cd ui && npm ci
COPY ui/ ./ui/
# Create output directory and build - vite outputs directly to pkg/github/ui_dist/
RUN mkdir -p ./pkg/github/ui_dist && \
cd ui && npm run build

FROM golang:1.25.6-alpine AS build
ARG VERSION="dev"

Expand All @@ -8,11 +17,15 @@ WORKDIR /build
RUN --mount=type=cache,target=/var/cache/apk \
apk add git

# Copy source code (including ui_dist placeholder)
COPY . .

# Copy built UI assets over the placeholder
COPY --from=ui-build /app/pkg/github/ui_dist/* ./pkg/github/ui_dist/

# Build the server
# go build automatically download required module dependencies to /go/pkg/mod
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
--mount=type=bind,target=. \
CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=${VERSION} -X main.commit=$(git rev-parse HEAD) -X main.date=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
-o /bin/github-mcp-server ./cmd/github-mcp-server

Expand Down
10 changes: 9 additions & 1 deletion internal/ghmcp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ func NewStdioMCPServer(ctx context.Context, cfg github.MCPServerConfig) (*mcp.Se
WithToolsets(github.ResolvedEnabledToolsets(cfg.DynamicToolsets, cfg.EnabledToolsets, cfg.EnabledTools)).
WithTools(github.CleanTools(cfg.EnabledTools)).
WithServerInstructions().
WithFeatureChecker(featureChecker)
WithFeatureChecker(featureChecker).
WithInsidersMode(cfg.InsidersMode)

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

// Register MCP App UI resources if available (requires running script/build-ui).
// We check availability to allow Insiders mode to work for non-UI features
// even when UI assets haven't been built.
if cfg.InsidersMode && github.UIAssetsAvailable() {
github.RegisterUIResources(ghServer)
}

ghServer.AddReceivingMiddleware(addUserAgentsMiddleware(cfg, clients.rest, clients.gqlHTTP))

return ghServer, nil
Expand Down
9 changes: 9 additions & 0 deletions pkg/github/__toolsnaps__/create_pull_request.snap
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
{
"_meta": {
"ui": {
"resourceUri": "ui://github-mcp-server/pr-write",
"visibility": [
"model",
"app"
]
}
},
"annotations": {
"title": "Open new pull request"
},
Expand Down
5 changes: 5 additions & 0 deletions pkg/github/__toolsnaps__/get_me.snap
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
{
"_meta": {
"ui": {
"resourceUri": "ui://github-mcp-server/get-me"
}
},
"annotations": {
"readOnlyHint": true,
"title": "Get my user profile"
Expand Down
9 changes: 9 additions & 0 deletions pkg/github/__toolsnaps__/issue_write.snap
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
{
"_meta": {
"ui": {
"resourceUri": "ui://github-mcp-server/issue-write",
"visibility": [
"model",
"app"
]
}
},
"annotations": {
"title": "Create or update issue."
},
Expand Down
36 changes: 36 additions & 0 deletions pkg/github/__toolsnaps__/ui_get.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"annotations": {
"readOnlyHint": true,
"title": "Get UI data"
},
"description": "Fetch UI data for MCP Apps (labels, assignees, milestones, issue types, branches).",
"inputSchema": {
"properties": {
"method": {
"description": "The type of data to fetch",
"enum": [
"labels",
"assignees",
"milestones",
"issue_types",
"branches"
],
"type": "string"
},
"owner": {
"description": "Repository owner (required for all methods)",
"type": "string"
},
"repo": {
"description": "Repository name (required for labels, assignees, milestones, branches)",
"type": "string"
}
},
"required": [
"method",
"owner"
],
"type": "object"
},
"name": "ui_get"
}
8 changes: 8 additions & 0 deletions pkg/github/context_tools.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ import (
"github.com/shurcooL/githubv4"
)

// GetMeUIResourceURI is the URI for the get_me tool's MCP App UI resource.
const GetMeUIResourceURI = "ui://github-mcp-server/get-me"

// UserDetails contains additional fields about a GitHub user not already
// present in MinimalUser. Used by get_me context tool but omitted from search_users.
type UserDetails struct {
Expand Down Expand Up @@ -51,6 +54,11 @@ func GetMe(t translations.TranslationHelperFunc) inventory.ServerTool {
// Use json.RawMessage to ensure "properties" is included even when empty.
// OpenAI strict mode requires the properties field to be present.
InputSchema: json.RawMessage(`{"type":"object","properties":{}}`),
Meta: mcp.Meta{
"ui": map[string]any{
"resourceUri": GetMeUIResourceURI,
},
},
},
nil,
func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, _ map[string]any) (*mcp.CallToolResult, any, error) {
Expand Down
26 changes: 26 additions & 0 deletions pkg/github/issues.go
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,9 @@ func SearchIssues(t translations.TranslationHelperFunc) inventory.ServerTool {
}

// IssueWrite creates a tool to create a new or update an existing issue in a GitHub repository.
// IssueWriteUIResourceURI is the URI for the issue_write tool's MCP App UI resource.
const IssueWriteUIResourceURI = "ui://github-mcp-server/issue-write"

func IssueWrite(t translations.TranslationHelperFunc) inventory.ServerTool {
return NewTool(
ToolsetMetadataIssues,
Expand All @@ -1004,6 +1007,12 @@ func IssueWrite(t translations.TranslationHelperFunc) inventory.ServerTool {
Title: t("TOOL_ISSUE_WRITE_USER_TITLE", "Create or update issue."),
ReadOnlyHint: false,
},
Meta: mcp.Meta{
"ui": map[string]any{
"resourceUri": IssueWriteUIResourceURI,
"visibility": []string{"model", "app"},
},
},
InputSchema: &jsonschema.Schema{
Type: "object",
Properties: map[string]*jsonschema.Schema{
Expand Down Expand Up @@ -1091,6 +1100,23 @@ Options are:
if err != nil {
return utils.NewToolResultError(err.Error()), nil, nil
}

// When insiders mode is enabled, check if this is a UI form submission.
// The UI sends _ui_submitted=true to distinguish form submissions from LLM calls.
// Without this flag, always show the UI so the user can review/edit before submitting.
uiSubmitted, _ := OptionalParam[bool](args, "_ui_submitted")

if deps.GetFlags(ctx).InsidersMode && !uiSubmitted {
if method == "update" {
issueNumber, numErr := RequiredInt(args, "issue_number")
if numErr != nil {
return utils.NewToolResultError("issue_number is required for update method"), nil, nil
}
return utils.NewToolResultText(fmt.Sprintf("Ready to update issue #%d in %s/%s. The interactive form will be displayed.", issueNumber, owner, repo)), nil, nil
}
return utils.NewToolResultText(fmt.Sprintf("Ready to create an issue in %s/%s. The interactive form will be displayed.", owner, repo)), nil, nil
}

title, err := OptionalParam[string](args, "title")
if err != nil {
return utils.NewToolResultError(err.Error()), nil, nil
Expand Down
Loading