Skip to content

Commit 772ac52

Browse files
authored
Merge branch 'main' into add-issue-dependencies
2 parents d5f4fd7 + b5397f6 commit 772ac52

78 files changed

Lines changed: 7425 additions & 678 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/docker-publish.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,13 @@ jobs:
5454
# multi-platform images and export cache
5555
# https://github.com/docker/setup-buildx-action
5656
- name: Set up Docker Buildx
57-
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
57+
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
5858

5959
# Login against a Docker registry except on PR
6060
# https://github.com/docker/login-action
6161
- name: Log into registry ${{ env.REGISTRY }}
6262
if: github.event_name != 'pull_request'
63-
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
63+
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
6464
with:
6565
registry: ${{ env.REGISTRY }}
6666
username: ${{ github.actor }}
@@ -70,7 +70,7 @@ jobs:
7070
# https://github.com/docker/metadata-action
7171
- name: Extract Docker metadata
7272
id: meta
73-
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
73+
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
7474
with:
7575
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
7676
tags: |
@@ -106,7 +106,7 @@ jobs:
106106
# https://github.com/docker/build-push-action
107107
- name: Build and push Docker image
108108
id: build-and-push
109-
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
109+
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
110110
with:
111111
context: .
112112
push: ${{ github.event_name != 'pull_request' }}

.github/workflows/mcp-diff.yml

Lines changed: 91 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,32 +19,48 @@ jobs:
1919
with:
2020
fetch-depth: 0
2121

22+
- name: Set up Go
23+
uses: actions/setup-go@v6
24+
with:
25+
go-version-file: go.mod
26+
2227
- name: Build UI
2328
uses: ./.github/actions/build-ui
2429

30+
- name: Stash UI artifacts for baseline checkout
31+
# mcp-server-diff checks the baseline ref out into a separate working
32+
# directory and runs install_command there. Without these prebuilt
33+
# artifacts, pkg/github/ui_dist/ would be empty on the baseline side
34+
# and UIAssetsAvailable() would return false, producing a false-positive
35+
# diff that "adds" _meta.ui to MCP Apps tools on every PR.
36+
run: |
37+
mkdir -p "${RUNNER_TEMP}/ui_dist"
38+
cp pkg/github/ui_dist/*.html "${RUNNER_TEMP}/ui_dist/"
39+
40+
- name: Generate diff configurations
41+
id: configs
42+
# The generator imports pkg/github so any new entry in
43+
# AllowedFeatureFlags is automatically diffed without touching this
44+
# workflow. See script/print-mcp-diff-configs/main.go.
45+
run: |
46+
{
47+
echo 'configurations<<MCP_DIFF_EOF'
48+
go run ./script/print-mcp-diff-configs
49+
echo 'MCP_DIFF_EOF'
50+
} >> "$GITHUB_OUTPUT"
51+
2552
- name: Run MCP Server Diff
2653
uses: SamMorrowDrums/mcp-server-diff@v2.3.5
2754
with:
28-
setup_go: "true"
29-
install_command: go mod download
55+
setup_go: "false"
56+
install_command: |
57+
go mod download
58+
mkdir -p pkg/github/ui_dist
59+
cp "${RUNNER_TEMP}"/ui_dist/*.html pkg/github/ui_dist/
3060
start_command: go run ./cmd/github-mcp-server stdio
3161
env_vars: |
3262
GITHUB_PERSONAL_ACCESS_TOKEN=test-token
33-
configurations: |
34-
[
35-
{"name": "default", "args": ""},
36-
{"name": "read-only", "args": "--read-only"},
37-
{"name": "toolsets-repos", "args": "--toolsets=repos"},
38-
{"name": "toolsets-issues", "args": "--toolsets=issues"},
39-
{"name": "toolsets-context", "args": "--toolsets=context"},
40-
{"name": "toolsets-pull_requests", "args": "--toolsets=pull_requests"},
41-
{"name": "toolsets-repos,issues", "args": "--toolsets=repos,issues"},
42-
{"name": "toolsets-issues,context", "args": "--toolsets=issues,context"},
43-
{"name": "toolsets-all", "args": "--toolsets=all"},
44-
{"name": "tools-get_me", "args": "--tools=get_me"},
45-
{"name": "tools-get_me,list_issues", "args": "--tools=get_me,list_issues"},
46-
{"name": "toolsets-repos+read-only", "args": "--toolsets=repos --read-only"}
47-
]
63+
configurations: ${{ steps.configs.outputs.configurations }}
4864

4965
- name: Add interpretation note
5066
if: always()
@@ -58,3 +74,61 @@ jobs:
5874
echo "- New tools/toolsets added" >> $GITHUB_STEP_SUMMARY
5975
echo "- Tool descriptions updated" >> $GITHUB_STEP_SUMMARY
6076
echo "- Capability changes (intentional improvements)" >> $GITHUB_STEP_SUMMARY
77+
78+
mcp-diff-http:
79+
runs-on: ubuntu-latest
80+
81+
steps:
82+
- name: Check out code
83+
uses: actions/checkout@v6
84+
with:
85+
fetch-depth: 0
86+
87+
- name: Set up Go
88+
uses: actions/setup-go@v6
89+
with:
90+
go-version-file: go.mod
91+
92+
- name: Build UI
93+
uses: ./.github/actions/build-ui
94+
95+
- name: Stash UI artifacts for baseline checkout
96+
# See the stdio job above for rationale: the action's baseline checkout
97+
# has no UI artifacts unless we hand them over via RUNNER_TEMP.
98+
run: |
99+
mkdir -p "${RUNNER_TEMP}/ui_dist"
100+
cp pkg/github/ui_dist/*.html "${RUNNER_TEMP}/ui_dist/"
101+
102+
- name: Generate diff configurations
103+
id: configs
104+
# See script/print-mcp-diff-configs/main.go. The http-headers variant
105+
# points every config at a shared HTTP server started by the action
106+
# and carries per-config settings via X-MCP-* headers, mirroring how
107+
# the remote server is invoked in production (server-side defaults +
108+
# per-user header overrides).
109+
run: |
110+
{
111+
echo 'configurations<<MCP_DIFF_EOF'
112+
go run ./script/print-mcp-diff-configs -transport http-headers
113+
echo 'MCP_DIFF_EOF'
114+
} >> "$GITHUB_OUTPUT"
115+
116+
- name: Run MCP Server Diff (streamable-http)
117+
uses: SamMorrowDrums/mcp-server-diff@v2.3.5
118+
with:
119+
setup_go: "false"
120+
install_command: |
121+
go mod download
122+
mkdir -p pkg/github/ui_dist
123+
cp "${RUNNER_TEMP}"/ui_dist/*.html pkg/github/ui_dist/
124+
http_start_command: go run ./cmd/github-mcp-server http --port 8082
125+
http_startup_wait_ms: "5000"
126+
configurations: ${{ steps.configs.outputs.configurations }}
127+
128+
- name: Add interpretation note
129+
if: always()
130+
run: |
131+
echo "" >> $GITHUB_STEP_SUMMARY
132+
echo "---" >> $GITHUB_STEP_SUMMARY
133+
echo "" >> $GITHUB_STEP_SUMMARY
134+
echo "ℹ️ **Note:** This job exercises the streamable-http transport against a shared server, with per-config settings supplied via X-MCP-* request headers." >> $GITHUB_STEP_SUMMARY

.gitignore

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,9 @@ bin/
1717
.DS_Store
1818

1919
# binary
20-
github-mcp-server
21-
mcpcurl
22-
e2e.test
20+
/github-mcp-server
21+
/mcpcurl
22+
/e2e.test
2323

2424
.history
2525
conformance-report/

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM node:26-alpine@sha256:e71ac5e964b9201072425d59d2e876359efa25dc96bb1768cb73295728d6e4ea AS ui-build
1+
FROM node:26-alpine@sha256:7c6af15abe4e3de859690e7db171d0d711bf37d27528eddfe625b2fe89e097f8 AS ui-build
22
WORKDIR /app
33
COPY ui/package*.json ./ui/
44
RUN cd ui && npm ci

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ To keep your GitHub PAT secure and reusable across different MCP hosts:
212212

213213
```bash
214214
# CLI usage
215-
claude mcp update github -e GITHUB_PERSONAL_ACCESS_TOKEN=$GITHUB_PAT
215+
claude mcp add github -e GITHUB_PERSONAL_ACCESS_TOKEN=$GITHUB_PAT -- docker run -i --rm -e GITHUB_PERSONAL_ACCESS_TOKEN ghcr.io/github/github-mcp-server
216216

217217
# In config files (where supported)
218218
"env": {
@@ -829,7 +829,7 @@ The following sets of tools are available:
829829
- `owner`: Repository owner (string, required)
830830
- `repo`: Repository name (string, required)
831831

832-
- **get_label** - Get a specific label from a repository.
832+
- **get_label** - Get a specific label from a repository
833833
- **Required OAuth Scopes**: `repo`
834834
- `name`: Label name. (string, required)
835835
- `owner`: Repository owner (username or organization name) (string, required)
@@ -874,7 +874,7 @@ The following sets of tools are available:
874874
- `perPage`: Results per page for pagination (min 1, max 100) (number, optional)
875875
- `repo`: The name of the repository (string, required)
876876

877-
- **issue_write** - Create or update issue.
877+
- **issue_write** - Create or update issue
878878
- **Required OAuth Scopes**: `repo`
879879
- `assignees`: Usernames to assign to this issue (string[], optional)
880880
- `body`: Issue body content (string, optional)
@@ -943,13 +943,13 @@ The following sets of tools are available:
943943

944944
<summary><picture><source media="(prefers-color-scheme: dark)" srcset="pkg/octicons/icons/tag-dark.png"><source media="(prefers-color-scheme: light)" srcset="pkg/octicons/icons/tag-light.png"><img src="pkg/octicons/icons/tag-light.png" width="20" height="20" alt="tag"></picture> Labels</summary>
945945

946-
- **get_label** - Get a specific label from a repository.
946+
- **get_label** - Get a specific label from a repository
947947
- **Required OAuth Scopes**: `repo`
948948
- `name`: Label name. (string, required)
949949
- `owner`: Repository owner (username or organization name) (string, required)
950950
- `repo`: Repository name (string, required)
951951

952-
- **label_write** - Write operations on repository labels.
952+
- **label_write** - Write operations on repository labels
953953
- **Required OAuth Scopes**: `repo`
954954
- `color`: Label color as 6-character hex code without '#' prefix (e.g., 'f29513'). Required for 'create', optional for 'update'. (string, optional)
955955
- `description`: Label description text. Optional for 'create' and 'update'. (string, optional)
@@ -1149,7 +1149,7 @@ The following sets of tools are available:
11491149
- `pullNumber`: Pull request number (number, required)
11501150
- `repo`: Repository name (string, required)
11511151

1152-
- **pull_request_review_write** - Write operations (create, submit, delete) on pull request reviews.
1152+
- **pull_request_review_write** - Write operations (create, submit, delete) on pull request reviews
11531153
- **Required OAuth Scopes**: `repo`
11541154
- `body`: Review comment text (string, optional)
11551155
- `commitID`: SHA of commit to review (string, optional)
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"os"
7+
"reflect"
8+
"sort"
9+
"strings"
10+
11+
"github.com/github/github-mcp-server/pkg/github"
12+
"github.com/github/github-mcp-server/pkg/inventory"
13+
"github.com/github/github-mcp-server/pkg/translations"
14+
)
15+
16+
// generateInsidersFeaturesDocs refreshes the auto-generated section of
17+
// docs/insiders-features.md with the tools and schemas affected by each
18+
// Insiders feature flag.
19+
func generateInsidersFeaturesDocs(docsPath string) error {
20+
body := generateFlaggedToolsDoc(github.InsidersFeatureFlags, "_No Insiders-only tool changes._")
21+
return rewriteAutomatedSection(docsPath, "START AUTOMATED INSIDERS TOOLS", "END AUTOMATED INSIDERS TOOLS", body)
22+
}
23+
24+
// generateFeatureFlagsDocs refreshes the auto-generated section of
25+
// docs/feature-flags.md with the tools and schemas affected by each
26+
// user-controllable feature flag.
27+
func generateFeatureFlagsDocs(docsPath string) error {
28+
body := generateFlaggedToolsDoc(github.AllowedFeatureFlags, "_No user-controllable feature flags affect tool registration._")
29+
return rewriteAutomatedSection(docsPath, "START AUTOMATED FEATURE FLAG TOOLS", "END AUTOMATED FEATURE FLAG TOOLS", body)
30+
}
31+
32+
// generateFlaggedToolsDoc renders, for each flag in the input set, the tools
33+
// whose registration or definition differs from the default user experience.
34+
// Each affected tool is printed with its full schema using the same writer
35+
// used by the README so the output style stays consistent.
36+
func generateFlaggedToolsDoc(flags []string, emptyMessage string) string {
37+
t, _ := translations.TranslationHelper()
38+
defaultTools := indexToolsByName(buildInventoryWithFlags(t, nil).ToolsForRegistration(context.Background()))
39+
40+
var buf strings.Builder
41+
hasAny := false
42+
43+
for _, flag := range flags {
44+
affected := flaggedToolDiff(t, flag, defaultTools)
45+
if len(affected) == 0 {
46+
continue
47+
}
48+
49+
if hasAny {
50+
buf.WriteString("\n\n")
51+
}
52+
hasAny = true
53+
54+
fmt.Fprintf(&buf, "### `%s`\n\n", flag)
55+
for i, tool := range affected {
56+
writeToolDoc(&buf, tool)
57+
if i < len(affected)-1 {
58+
buf.WriteString("\n\n")
59+
}
60+
}
61+
}
62+
63+
if !hasAny {
64+
return emptyMessage
65+
}
66+
// Leading/trailing newlines around the body produce blank lines between
67+
// our content and the surrounding marker comments, so the trailing comment
68+
// doesn't get absorbed into the final list item by markdown renderers.
69+
return "\n" + strings.TrimSuffix(buf.String(), "\n") + "\n"
70+
}
71+
72+
// flaggedToolDiff returns the tools whose definition (input schema or meta)
73+
// differs from the default-flagged inventory when only the given flag is on,
74+
// plus tools that exist only in the flag-on inventory. Results are sorted by
75+
// tool name.
76+
func flaggedToolDiff(t translations.TranslationHelperFunc, flag string, defaultTools map[string]inventory.ServerTool) []inventory.ServerTool {
77+
flagTools := buildInventoryWithFlags(t, map[string]bool{flag: true}).ToolsForRegistration(context.Background())
78+
79+
out := make([]inventory.ServerTool, 0)
80+
seen := make(map[string]struct{}, len(flagTools))
81+
82+
for _, tool := range flagTools {
83+
if _, ok := seen[tool.Tool.Name]; ok {
84+
continue
85+
}
86+
seen[tool.Tool.Name] = struct{}{}
87+
88+
baseline, hadBaseline := defaultTools[tool.Tool.Name]
89+
if hadBaseline && reflect.DeepEqual(tool.Tool.InputSchema, baseline.Tool.InputSchema) && reflect.DeepEqual(tool.Tool.Meta, baseline.Tool.Meta) {
90+
continue
91+
}
92+
out = append(out, tool)
93+
}
94+
95+
sort.Slice(out, func(i, j int) bool { return out[i].Tool.Name < out[j].Tool.Name })
96+
return out
97+
}
98+
99+
// buildInventoryWithFlags constructs an inventory whose feature checker treats
100+
// the given flags as enabled and every other flag as disabled. Passing nil
101+
// produces the default-flagged inventory.
102+
func buildInventoryWithFlags(t translations.TranslationHelperFunc, enabled map[string]bool) *inventory.Inventory {
103+
checker := func(_ context.Context, flag string) (bool, error) {
104+
return enabled[flag], nil
105+
}
106+
inv, _ := github.NewInventory(t).
107+
WithToolsets([]string{"all"}).
108+
WithFeatureChecker(checker).
109+
Build()
110+
return inv
111+
}
112+
113+
// indexToolsByName returns a map keyed by tool name. When duplicates exist
114+
// (e.g. flag-gated dual registrations), the first occurrence wins, mirroring
115+
// AvailableTools' deterministic sort order.
116+
func indexToolsByName(tools []inventory.ServerTool) map[string]inventory.ServerTool {
117+
out := make(map[string]inventory.ServerTool, len(tools))
118+
for _, tool := range tools {
119+
if _, ok := out[tool.Tool.Name]; ok {
120+
continue
121+
}
122+
out[tool.Tool.Name] = tool
123+
}
124+
return out
125+
}
126+
127+
// rewriteAutomatedSection reads a markdown file, replaces the content between
128+
// the named markers with body, and writes it back.
129+
func rewriteAutomatedSection(path, startMarker, endMarker, body string) error {
130+
content, err := os.ReadFile(path) //#nosec G304
131+
if err != nil {
132+
return fmt.Errorf("failed to read docs file: %w", err)
133+
}
134+
updated, err := replaceSection(string(content), startMarker, endMarker, body)
135+
if err != nil {
136+
return err
137+
}
138+
return os.WriteFile(path, []byte(updated), 0600) //#nosec G306
139+
}

0 commit comments

Comments
 (0)