Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions .github/actions/build-ui/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ runs:
steps:
- name: Cache UI artifacts
id: cache-ui
uses: actions/cache@v5
uses: actions/cache@v6
with:
path: |
pkg/github/ui_dist/get-me.html
Expand All @@ -18,9 +18,9 @@ runs:

- name: Set up Node.js
if: steps.cache-ui.outputs.cache-hit != 'true'
uses: actions/setup-node@v6
uses: actions/setup-node@v7
with:
node-version: "22"
node-version-file: ui/package.json
cache: npm
cache-dependency-path: ui/package-lock.json

Expand Down
4 changes: 4 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ updates:
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "npm"
directory: "/ui"
schedule:
interval: "weekly"
- package-ecosystem: "docker"
directory: "/"
schedule:
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/code-scanning.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
uses: actions/checkout@v7

- name: Initialize CodeQL
uses: github/codeql-action/init@v4.37.9
uses: github/codeql-action/init@v4.38.2
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
Expand All @@ -61,13 +61,13 @@ jobs:
threat-models: [ ]
- name: Setup proxy for registries
id: proxy
uses: github/codeql-action/start-proxy@v4.37.9
uses: github/codeql-action/start-proxy@v4.38.2
with:
registries_credentials: ${{ secrets.GITHUB_REGISTRIES_PROXY }}
language: ${{ matrix.language }}

- name: Configure
uses: github/codeql-action/resolve-environment@v4.37.9
uses: github/codeql-action/resolve-environment@v4.38.2
id: resolve-environment
with:
language: ${{ matrix.language }}
Expand All @@ -82,7 +82,7 @@ jobs:
if: matrix.language == 'javascript'
uses: actions/setup-node@v7
with:
node-version: "20"
node-version-file: ui/package.json
cache: "npm"
cache-dependency-path: ui/package-lock.json

Expand All @@ -91,10 +91,10 @@ jobs:
uses: ./.github/actions/build-ui

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

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4.37.9
uses: github/codeql-action/analyze@v4.38.2
env:
CODEQL_PROXY_HOST: ${{ steps.proxy.outputs.proxy_host }}
CODEQL_PROXY_PORT: ${{ steps.proxy.outputs.proxy_port }}
Expand Down
25 changes: 14 additions & 11 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,13 @@ jobs:
if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 #v4.1.2
with:
cosign-release: "v2.2.4"
cosign-release: "v3.1.3"

# Set up BuildKit Docker container builder to be able to build
# multi-platform images and export cache
# https://github.com/docker/setup-buildx-action
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1

# Login against a Docker registry except on PR
# https://github.com/docker/login-action
Expand Down Expand Up @@ -106,7 +106,7 @@ jobs:
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
Expand All @@ -116,23 +116,26 @@ jobs:
cache-to: type=gha,mode=max
platforms: linux/amd64,linux/arm64
build-args: |
VERSION=${{ github.ref_name }}
VERSION=${{ github.ref_type == 'tag' && github.ref_name || 'dev' }}
secrets: |
oauth_client_id=${{ secrets.OAUTH_CLIENT_ID }}
oauth_client_secret=${{ secrets.OAUTH_CLIENT_SECRET }}

# Sign the resulting Docker image digest except on PRs.
# This will only write to the public Rekor transparency log when the Docker
# repository is public to avoid leaking data. If you would like to publish
# transparency data even for private images, pass --force to cosign below.
# Publish both v2-compatible .sig tags and native Sigstore bundles.
# --yes also consents to publishing signing metadata to the public log.
# https://github.com/sigstore/cosign
- name: Sign the published Docker image
- name: Sign the published Docker image (legacy)
if: ${{ github.event_name != 'pull_request' }}
env:
# https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-an-intermediate-environment-variable
TAGS: ${{ steps.meta.outputs.tags }}
DIGEST: ${{ steps.build-and-push.outputs.digest }}
# This step uses the identity token to provision an ephemeral certificate
# against the sigstore community Fulcio instance.
run: echo "${TAGS}" | xargs -I {} cosign sign --yes {}@${DIGEST}

run: cosign sign --yes --new-bundle-format=false --use-signing-config=false --registry-referrers-mode=legacy "${REGISTRY}/${IMAGE_NAME}@${DIGEST}"

- name: Sign the published Docker image (native bundle)
if: ${{ github.event_name != 'pull_request' }}
env:
DIGEST: ${{ steps.build-and-push.outputs.digest }}
run: cosign sign --yes "${REGISTRY}/${IMAGE_NAME}@${DIGEST}"
4 changes: 2 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ jobs:
uses: ./.github/actions/build-ui
- uses: actions/setup-go@v7
with:
go-version: '1.26'
go-version-file: 'go.mod'
- name: golangci-lint
uses: golangci/golangci-lint-action@v9
with:
# sync with script/lint
version: v2.9
version: v2.14.0
10 changes: 7 additions & 3 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,17 @@ linters:
- examples$
- internal/githubv4mock
rules:
# v2.9.0 misreports SA4006 for Go 1.26 new(expr); retain go-github's helper until the linter is upgraded.
- linters:
- staticcheck
text: '(go)?github\.Ptr is deprecated: use the new builtin instead\.'
- linters:
- revive
text: "var-naming: avoid package names that conflict with Go standard library package names"
- linters:
- modernize
text: "newexpr:"
settings:
modernize:
disable:
- newexpr
staticcheck:
checks:
- "all"
Expand Down
23 changes: 21 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,27 @@ Thanks for contributing and for helping us build toolsets that are truly valuabl

These are one time installations required to be able to test your changes locally as part of the pull request (PR) submission process.

1. Install Go [through download](https://go.dev/doc/install) | [through Homebrew](https://formulae.brew.sh/formula/go)
2. [Install golangci-lint v2](https://golangci-lint.run/welcome/install/#local-installation)
1. Install Go 1.26.8 or later [through download](https://go.dev/doc/install) | [through Homebrew](https://formulae.brew.sh/formula/go)
2. [Install golangci-lint v2.14.0](https://golangci-lint.run/welcome/install/#local-installation), or let `script/lint` install the repository-pinned version. The pinned version supports both Go 1.26 and Go 1.27.

### UI development

Use Node.js 26.x for the UI in `ui/`. This matches the Docker UI build stage
and `@types/node`; GitHub Actions reads the Node version from `ui/package.json`.
Node 26 is a supported Current release, with LTS scheduled for October 2026.

From `ui/`, run `npm ci`, `npm run typecheck`, and `npm run build`.
Type checking uses the native TypeScript 7 compiler through `tsc`; Vite handles
transpilation and bundling separately. No TypeScript compiler API integration,
typescript-eslint, or ts-node is required.

### MCP Apps UI

The `ui/` views use React 19 and Primer React 38. With Node.js 20.19+ or 22.12+, run `cd ui && npm ci && npm run typecheck && npm run build && npm audit` before `script/test`. The build writes self-contained HTML to `pkg/github/ui_dist/`, which the Go server embeds; these generated files are not committed.

Primer 38 no longer exports `Box` or accepts `sx`/styled-system props. Use semantic HTML, native `style` props for dynamic/layout styles, and CSS Modules for nested selectors. `AppProvider` loads Primer's primitive tokens and light/dark themes, so use CSS variables rather than JavaScript theme values. Custom element typings must augment `react/jsx-runtime` rather than the global `JSX` namespace.

For UI dependency upgrades, compare all four views (`get-me`, `issue-write`, `pr-write`, and `pr-edit`) in light/dark themes and at narrow widths in an MCP Apps host, including menus, Markdown editing/preview, and completed-result views. Include before/after screenshots in the pull request.

## Submitting a pull request

Expand Down
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,13 @@ For a complete overview of all installation options, see our **[Installation Gui
### Build from source

If you don't have Docker, you can use `go build` to build the binary in the
`cmd/github-mcp-server` directory, and use the `github-mcp-server stdio` command with the `GITHUB_PERSONAL_ACCESS_TOKEN` environment variable set to your token. To specify the output location of the build, use the `-o` flag. You should configure your server to use the built executable as its `command`. For example:
`cmd/github-mcp-server` directory, and use the `github-mcp-server stdio` command with the `GITHUB_PERSONAL_ACCESS_TOKEN` environment variable set to your token. To specify the output location of the build, use the `-o` flag. You should configure your server to use the built executable as its `command`.

STDIO API requests identify the server as `github-mcp-server/<version>` and retain the upstream MCP client's name/version in parentheses when available. Control characters, quotes, backslashes and parentheses in client metadata are escaped so the HTTP header remains valid; ordinary names, versions, spaces and printable Unicode are preserved. Release builds keep their release version. Source and default Docker builds with revision metadata use `vcs-<full-commit-sha>`. The Docker publishing workflow preserves tag versions, including prereleases, and lets branch, pull-request and nightly builds use the linked source revision. The `-dirty` marker applies only when both the revision and modified state come from embedded VCS metadata, never to a valid, explicitly supplied `main.commit`. This is a VCS build identifier, not a release number.

Build the complete package with `go build -o github-mcp-server ./cmd/github-mcp-server` from a Git checkout to embed its VCS revision. For builds without VCS metadata, supply the actual release with `-ldflags '-X main.version=<release>'` or the full source revision with `-ldflags '-X main.commit=<sha>'`. Valid metadata is selected in this order: explicit release, explicit source revision, embedded VCS revision, then installed main-module version. Valid explicit revisions remain authoritative even if build-context filtering changes embedded VCS metadata. Missing or malformed candidates fall through to the next usable source. If none is available, STDIO still starts with the development label `dev` and emits a warning on stderr, leaving stdout available for the MCP protocol. Supply real release or revision metadata when version-specific attribution is needed.

For example:

```JSON
{
Expand Down Expand Up @@ -704,6 +710,7 @@ The following sets of tools are available:
<summary><picture><source media="(prefers-color-scheme: dark)" srcset="pkg/octicons/icons/person-dark.png"><source media="(prefers-color-scheme: light)" srcset="pkg/octicons/icons/person-light.png"><img src="pkg/octicons/icons/person-light.png" width="20" height="20" alt="person"></picture> Context</summary>

- **get_me** - Get my user profile
- **MCP App UI**: `ui://github-mcp-server/get-me`
- No parameters required

- **get_team_members** - Get team members
Expand All @@ -715,6 +722,12 @@ The following sets of tools are available:
- **OAuth Challenge Scopes**: `read:org`
- `user`: Username to get teams for. If not provided, uses the authenticated user. (string, optional)

- **ui_get** - Get UI data
- **OAuth Challenge Scopes**: `repo`, `read:org`
- `method`: The type of data to fetch (string, required)
- `owner`: Repository owner (required for all methods) (string, required)
- `repo`: Repository name (required for labels, assignees, milestones, branches, issue fields, reviewers) (string, optional)

</details>

<details>
Expand Down Expand Up @@ -983,6 +996,7 @@ The following sets of tools are available:

- **issue_write** - Create or update issue/pull request
- **OAuth Challenge Scopes**: `repo`
- **MCP App UI**: `ui://github-mcp-server/issue-write`
- `assignees`: Usernames to assign to this issue (string[], optional)
- `body`: Issue body content (string, optional)
- `duplicate_of`: Issue number that this issue is a duplicate of. Required when state_reason is 'duplicate'. (number, optional)
Expand Down Expand Up @@ -1238,6 +1252,7 @@ The following sets of tools are available:

- **create_pull_request** - Open new pull request
- **OAuth Challenge Scopes**: `repo`
- **MCP App UI**: `ui://github-mcp-server/pr-write`
- `base`: Branch to merge into (string, required)
- `body`: PR description (string, optional)
- `draft`: Create as draft PR (boolean, optional)
Expand Down Expand Up @@ -1316,6 +1331,7 @@ The following sets of tools are available:

- **update_pull_request** - Edit pull request
- **OAuth Challenge Scopes**: `repo`
- **MCP App UI**: `ui://github-mcp-server/pr-edit`
- `base`: New base branch name (string, optional)
- `body`: New description (string, optional)
- `draft`: Mark pull request as draft (true) or ready for review (false) (boolean, optional)
Expand Down
4 changes: 1 addition & 3 deletions cmd/github-mcp-server/generate_docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,9 +222,7 @@ func writeToolDoc(buf *strings.Builder, tool inventory.ServerTool) {
fmt.Fprintf(buf, " - **OAuth Challenge Scopes**: `%s`\n", strings.Join(scopes, "`, `"))
}

// MCP App UI metadata (only rendered when the remote_mcp_ui_apps flag
// applied to the inventory; for the no-flags README this section is
// stripped by inventory.ToolsForRegistration before rendering).
// MCP App UI metadata.
if ui, ok := tool.Tool.Meta["ui"].(map[string]any); ok {
if uri, ok := ui["resourceUri"].(string); ok && uri != "" {
fmt.Fprintf(buf, " - **MCP App UI**: `%s`\n", uri)
Expand Down
11 changes: 9 additions & 2 deletions cmd/github-mcp-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"os"
"runtime/debug"
"strings"
"time"

Expand Down Expand Up @@ -38,7 +39,13 @@ var (
Use: "stdio",
Short: "Start stdio server",
Long: `Start a server that communicates via standard input/output streams using JSON-RPC messages.`,
RunE: func(_ *cobra.Command, _ []string) error {
RunE: func(cmd *cobra.Command, _ []string) error {
info, _ := debug.ReadBuildInfo()
serverVersion := resolveServerVersion(version, commit, info)
if serverVersion == developmentServerVersion {
cmd.PrintErrln("Warning: no usable server build version metadata; using development version dev")
}

token := viper.GetString("personal_access_token")
appID := viper.GetString("app-id")
appInstallationID := viper.GetString("app-installation-id")
Expand Down Expand Up @@ -110,7 +117,7 @@ var (

ttl := viper.GetDuration("repo-access-cache-ttl")
stdioServerConfig := ghmcp.StdioServerConfig{
Version: version,
Version: serverVersion,
Host: viper.GetString("host"),
Token: token,
EnabledToolsets: enabledToolsets,
Expand Down
75 changes: 75 additions & 0 deletions cmd/github-mcp-server/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package main

import (
"encoding/hex"
"runtime/debug"
"strings"
"unicode"
)

const developmentServerVersion = "dev"

func resolveServerVersion(release, revision string, info *debug.BuildInfo) string {
isPlaceholder := func(value string) bool {
switch value {
case "", "version", "dev", "unknown", "(devel)":
return true
default:
return false
}
}
validRelease := func(value string) bool {
if isPlaceholder(value) {
return false
}
for _, r := range value {
if r > unicode.MaxASCII || r <= ' ' || r == 127 || strings.ContainsRune("()<>@,;:\\\"/[]?={}", r) {
return false
}
}
return true
}
if validRelease(release) {
return release
}

var vcsRevision string
var dirty bool
if info != nil {
for _, setting := range info.Settings {
switch setting.Key {
case "vcs.revision":
vcsRevision = setting.Value
case "vcs.modified":
dirty = setting.Value == "true"
}
}
}
revisions := []struct {
value string
dirty bool
}{
{value: revision},
{value: vcsRevision, dirty: dirty},
}
for _, candidate := range revisions {
if len(candidate.value) != 40 && len(candidate.value) != 64 {
continue
}
if _, err := hex.DecodeString(candidate.value); err != nil {
continue
}
if strings.Trim(candidate.value, "0") == "" {
continue
}
resolved := "vcs-" + strings.ToLower(candidate.value)
if candidate.dirty {
resolved += "-dirty"
}
return resolved
}
if info != nil && validRelease(info.Main.Version) {
return info.Main.Version
}
return developmentServerVersion
}
Loading
Loading