From 017ab278d0dc0a34110ca182c1b9af3b8de0d132 Mon Sep 17 00:00:00 2001 From: Mathew Goldsborough <1759329+mgoldsborough@users.noreply.github.com> Date: Fri, 26 Dec 2025 09:17:22 -1000 Subject: [PATCH 1/9] Add HTTP transport and MCPB packaging support - Add `http` subcommand with StreamableHTTP transport - Add manifest.json and .mcpbignore for MCPB bundling - Add mcpb-bundle.yml workflow to create bundles on release --- .github/workflows/mcpb-bundle.yml | 135 ++++++++++++++++++++++++++ .mcpbignore | 41 ++++++++ cmd/github-mcp-server/main.go | 57 +++++++++++ internal/ghmcp/server.go | 152 ++++++++++++++++++++++++++++++ manifest.json | 17 ++++ 5 files changed, 402 insertions(+) create mode 100644 .github/workflows/mcpb-bundle.yml create mode 100644 .mcpbignore create mode 100644 manifest.json diff --git a/.github/workflows/mcpb-bundle.yml b/.github/workflows/mcpb-bundle.yml new file mode 100644 index 0000000000..9205c8b1cd --- /dev/null +++ b/.github/workflows/mcpb-bundle.yml @@ -0,0 +1,135 @@ +name: MCPB Bundle + +on: + push: + tags: + - "v*" + workflow_dispatch: + inputs: + tag: + description: 'Release tag to build bundle for (e.g., v1.0.0)' + required: true + +permissions: + contents: write + +jobs: + build-bundle: + runs-on: ubuntu-latest + strategy: + matrix: + include: + - arch: x86_64 + goarch: amd64 + - arch: arm64 + goarch: arm64 + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Determine version + id: version + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + TAG="${{ github.event.inputs.tag }}" + else + TAG="${{ github.ref_name }}" + fi + VERSION="${TAG#v}" + echo "tag=$TAG" >> $GITHUB_OUTPUT + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Building MCPB bundle for $TAG (version $VERSION)" + + - name: Wait for GoReleaser + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG="${{ steps.version.outputs.tag }}" + ARCHIVE="github-mcp-server_Linux_${{ matrix.arch }}.tar.gz" + + echo "Waiting for GoReleaser to publish $ARCHIVE..." + for i in {1..30}; do + if gh release view "$TAG" --json assets -q ".assets[].name" 2>/dev/null | grep -q "$ARCHIVE"; then + echo "✅ Release asset ready" + exit 0 + fi + echo "⏳ Waiting for release assets ($i/30)..." + sleep 20 + done + echo "❌ Timeout waiting for release assets" + exit 1 + + - name: Download release binary + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG="${{ steps.version.outputs.tag }}" + ARCHIVE="github-mcp-server_Linux_${{ matrix.arch }}.tar.gz" + + echo "Downloading $ARCHIVE from release $TAG..." + gh release download "$TAG" --pattern "$ARCHIVE" --dir /tmp + + # Extract the binary + mkdir -p /tmp/extracted + tar -xzf "/tmp/$ARCHIVE" -C /tmp/extracted + + echo "Extracted contents:" + ls -la /tmp/extracted + + - name: Update manifest version + run: | + VERSION="${{ steps.version.outputs.version }}" + # Update version in manifest.json + sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"$VERSION\"/" manifest.json + echo "Updated manifest.json:" + cat manifest.json + + - name: Create MCPB bundle + run: | + VERSION="${{ steps.version.outputs.version }}" + BUNDLE_NAME="mcp-github-v${VERSION}-linux-${{ matrix.goarch }}.mcpb" + BUNDLE_DIR="/tmp/mcpb-bundle" + + # Create bundle structure + mkdir -p "$BUNDLE_DIR/bin" + + # Copy manifest + cp manifest.json "$BUNDLE_DIR/" + + # Copy binary (it's in the root of the extracted archive) + cp /tmp/extracted/github-mcp-server "$BUNDLE_DIR/bin/" + chmod +x "$BUNDLE_DIR/bin/github-mcp-server" + + echo "Bundle contents:" + ls -la "$BUNDLE_DIR" + ls -la "$BUNDLE_DIR/bin" + + # Create the bundle (tar.gz) + cd "$BUNDLE_DIR" + tar -czvf "/tmp/$BUNDLE_NAME" . + + echo "Created bundle: /tmp/$BUNDLE_NAME" + ls -la "/tmp/$BUNDLE_NAME" + + # Calculate SHA256 + SHA256=$(sha256sum "/tmp/$BUNDLE_NAME" | cut -d' ' -f1) + echo "SHA256: $SHA256" + echo "$SHA256" > "/tmp/${BUNDLE_NAME}.sha256" + + # Store for upload step + echo "bundle_name=$BUNDLE_NAME" >> $GITHUB_ENV + echo "bundle_path=/tmp/$BUNDLE_NAME" >> $GITHUB_ENV + echo "sha256_path=/tmp/${BUNDLE_NAME}.sha256" >> $GITHUB_ENV + + - name: Upload bundle to release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAG="${{ steps.version.outputs.tag }}" + + echo "Uploading ${{ env.bundle_name }} to release $TAG..." + gh release upload "$TAG" "${{ env.bundle_path }}" --clobber + gh release upload "$TAG" "${{ env.sha256_path }}" --clobber + + echo "Uploaded successfully!" diff --git a/.mcpbignore b/.mcpbignore new file mode 100644 index 0000000000..3dea7d11ea --- /dev/null +++ b/.mcpbignore @@ -0,0 +1,41 @@ +# Git +.git/ +.github/ + +# Go build artifacts +*.exe +*.test +e2e.test + +# Development +.vscode/ +.idea/ +*.md +!README.md + +# Test files +e2e/ +*_test.go + +# Source code (we only need the binary) +cmd/ +internal/ +pkg/ +script/ +third-party/ +docs/ + +# Go modules (binary is statically linked) +go.mod +go.sum + +# Other +*.yaml +*.yml +*.json +!manifest.json +Dockerfile +.dockerignore +.golangci.yml +Makefile +LICENSE diff --git a/cmd/github-mcp-server/main.go b/cmd/github-mcp-server/main.go index cfb68be4eb..0fb9945ee1 100644 --- a/cmd/github-mcp-server/main.go +++ b/cmd/github-mcp-server/main.go @@ -88,6 +88,58 @@ var ( return ghmcp.RunStdioServer(stdioServerConfig) }, } + + httpCmd = &cobra.Command{ + Use: "http", + Short: "Start HTTP server", + Long: `Start a server that communicates via HTTP using the Streamable HTTP transport.`, + RunE: func(_ *cobra.Command, _ []string) error { + token := viper.GetString("personal_access_token") + if token == "" { + return errors.New("GITHUB_PERSONAL_ACCESS_TOKEN not set") + } + + var enabledToolsets []string + if viper.IsSet("toolsets") { + if err := viper.UnmarshalKey("toolsets", &enabledToolsets); err != nil { + return fmt.Errorf("failed to unmarshal toolsets: %w", err) + } + } + + var enabledTools []string + if viper.IsSet("tools") { + if err := viper.UnmarshalKey("tools", &enabledTools); err != nil { + return fmt.Errorf("failed to unmarshal tools: %w", err) + } + } + + var enabledFeatures []string + if viper.IsSet("features") { + if err := viper.UnmarshalKey("features", &enabledFeatures); err != nil { + return fmt.Errorf("failed to unmarshal features: %w", err) + } + } + + ttl := viper.GetDuration("repo-access-cache-ttl") + httpServerConfig := ghmcp.HTTPServerConfig{ + Version: version, + Host: viper.GetString("host"), + Token: token, + EnabledToolsets: enabledToolsets, + EnabledTools: enabledTools, + EnabledFeatures: enabledFeatures, + DynamicToolsets: viper.GetBool("dynamic_toolsets"), + ReadOnly: viper.GetBool("read-only"), + ExportTranslations: viper.GetBool("export-translations"), + LogFilePath: viper.GetString("log-file"), + ContentWindowSize: viper.GetInt("content-window-size"), + LockdownMode: viper.GetBool("lockdown-mode"), + RepoAccessCacheTTL: &ttl, + Port: viper.GetInt("port"), + } + return ghmcp.RunHTTPServer(httpServerConfig) + }, + } ) func init() { @@ -124,8 +176,13 @@ func init() { _ = viper.BindPFlag("lockdown-mode", rootCmd.PersistentFlags().Lookup("lockdown-mode")) _ = viper.BindPFlag("repo-access-cache-ttl", rootCmd.PersistentFlags().Lookup("repo-access-cache-ttl")) + // Add http command flags + httpCmd.Flags().Int("port", 8000, "Port to listen on for HTTP connections") + _ = viper.BindPFlag("port", httpCmd.Flags().Lookup("port")) + // Add subcommands rootCmd.AddCommand(stdioCmd) + rootCmd.AddCommand(httpCmd) } func initConfig() { diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index 9859e2e9bb..76742f11be 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -314,6 +314,51 @@ type StdioServerConfig struct { RepoAccessCacheTTL *time.Duration } +// HTTPServerConfig contains configuration for running the MCP server over HTTP. +type HTTPServerConfig struct { + // Version of the server + Version string + + // GitHub Host to target for API requests (e.g. github.com or github.enterprise.com) + Host string + + // GitHub Token to authenticate with the GitHub API + Token string + + // EnabledToolsets is a list of toolsets to enable + EnabledToolsets []string + + // EnabledTools is a list of specific tools to enable (additive to toolsets) + EnabledTools []string + + // EnabledFeatures is a list of feature flags that are enabled + EnabledFeatures []string + + // Whether to enable dynamic toolsets + DynamicToolsets bool + + // ReadOnly indicates if we should only register read-only tools + ReadOnly bool + + // ExportTranslations indicates if we should export translations + ExportTranslations bool + + // Path to the log file if not stderr + LogFilePath string + + // Content window size + ContentWindowSize int + + // LockdownMode indicates if we should enable lockdown mode + LockdownMode bool + + // RepoAccessCacheTTL overrides the default TTL for repository access cache entries. + RepoAccessCacheTTL *time.Duration + + // Port to listen on for HTTP connections + Port int +} + // RunStdioServer is not concurrent safe. func RunStdioServer(cfg StdioServerConfig) error { // Create app context @@ -398,6 +443,113 @@ func RunStdioServer(cfg StdioServerConfig) error { return nil } +// RunHTTPServer starts an HTTP server that serves the MCP protocol via Streamable HTTP. +func RunHTTPServer(cfg HTTPServerConfig) error { + // Create app context + ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer stop() + + t, dumpTranslations := translations.TranslationHelper() + + var slogHandler slog.Handler + var logOutput io.Writer + if cfg.LogFilePath != "" { + file, err := os.OpenFile(cfg.LogFilePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600) + if err != nil { + return fmt.Errorf("failed to open log file: %w", err) + } + logOutput = file + slogHandler = slog.NewTextHandler(logOutput, &slog.HandlerOptions{Level: slog.LevelDebug}) + } else { + logOutput = os.Stderr + slogHandler = slog.NewTextHandler(logOutput, &slog.HandlerOptions{Level: slog.LevelInfo}) + } + logger := slog.New(slogHandler) + logger.Info("starting HTTP server", "version", cfg.Version, "host", cfg.Host, "port", cfg.Port, "dynamicToolsets", cfg.DynamicToolsets, "readOnly", cfg.ReadOnly, "lockdownEnabled", cfg.LockdownMode) + + ghServer, err := NewMCPServer(MCPServerConfig{ + Version: cfg.Version, + Host: cfg.Host, + Token: cfg.Token, + EnabledToolsets: cfg.EnabledToolsets, + EnabledTools: cfg.EnabledTools, + EnabledFeatures: cfg.EnabledFeatures, + DynamicToolsets: cfg.DynamicToolsets, + ReadOnly: cfg.ReadOnly, + Translator: t, + ContentWindowSize: cfg.ContentWindowSize, + LockdownMode: cfg.LockdownMode, + Logger: logger, + RepoAccessTTL: cfg.RepoAccessCacheTTL, + }) + if err != nil { + return fmt.Errorf("failed to create MCP server: %w", err) + } + + if cfg.ExportTranslations { + dumpTranslations() + } + + // Create HTTP mux with health and MCP endpoints + mux := http.NewServeMux() + + // Health check endpoint + mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"status":"ok"}`)) + }) + + // MCP endpoint using Streamable HTTP transport + mcpHandler := mcp.NewStreamableHTTPHandler( + func(r *http.Request) *mcp.Server { + return ghServer + }, + &mcp.StreamableHTTPOptions{ + Stateless: true, + Logger: logger, + }, + ) + mux.Handle("/mcp", mcpHandler) + + // Create HTTP server + addr := fmt.Sprintf(":%d", cfg.Port) + server := &http.Server{ + Addr: addr, + Handler: mux, + } + + // Start server in goroutine + errC := make(chan error, 1) + go func() { + logger.Info("listening", "addr", addr) + fmt.Fprintf(os.Stderr, "GitHub MCP Server running on http://0.0.0.0%s\n", addr) + fmt.Fprintf(os.Stderr, " MCP endpoint: http://0.0.0.0%s/mcp\n", addr) + fmt.Fprintf(os.Stderr, " Health check: http://0.0.0.0%s/health\n", addr) + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + errC <- err + } + }() + + // Wait for shutdown signal + select { + case <-ctx.Done(): + logger.Info("shutting down server", "signal", "context done") + shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := server.Shutdown(shutdownCtx); err != nil { + logger.Error("error shutting down server", "error", err) + } + case err := <-errC: + if err != nil { + logger.Error("error running server", "error", err) + return fmt.Errorf("error running server: %w", err) + } + } + + return nil +} + type apiHost struct { baseRESTURL *url.URL graphqlURL *url.URL diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000000..cf3d0d2772 --- /dev/null +++ b/manifest.json @@ -0,0 +1,17 @@ +{ + "manifest_version": "0.3", + "name": "ai.nimbletools/github", + "version": "0.26.3-mcpb.1", + "description": "GitHub MCP server for repository management, issues, PRs, and workflow automation", + "author": { + "name": "GitHub (packaged by NimbleBrain Inc)" + }, + "server": { + "type": "binary", + "entry_point": "bin/github-mcp-server", + "mcp_config": { + "command": "${__dirname}/bin/github-mcp-server", + "args": ["http", "--port", "8000"] + } + } +} From 61fd02c1194030f73d2683cf496cf64b5d4deb5a Mon Sep 17 00:00:00 2001 From: Mathew Goldsborough <1759329+mgoldsborough@users.noreply.github.com> Date: Sun, 4 Jan 2026 21:36:18 -1000 Subject: [PATCH 2/9] Migrate to mcpb-pack v2 format - Update package name to @nimblebraininc/github - Change mcp_config to use stdio transport for mpak compatibility - Add user_config for GITHUB_PERSONAL_ACCESS_TOKEN - Update workflow trigger to release:published - Add darwin-arm64 to build matrix - Map GoReleaser naming conventions (Darwin/Linux, x86_64/arm64) --- .github/workflows/mcpb-bundle.yml | 66 +++++++++++++++---------------- manifest.json | 17 +++++++- 2 files changed, 46 insertions(+), 37 deletions(-) diff --git a/.github/workflows/mcpb-bundle.yml b/.github/workflows/mcpb-bundle.yml index 9205c8b1cd..e688afff49 100644 --- a/.github/workflows/mcpb-bundle.yml +++ b/.github/workflows/mcpb-bundle.yml @@ -1,9 +1,8 @@ -name: MCPB Bundle +name: Build MCPB Bundle on: - push: - tags: - - "v*" + release: + types: [published] workflow_dispatch: inputs: tag: @@ -12,17 +11,29 @@ on: permissions: contents: write + id-token: write jobs: - build-bundle: - runs-on: ubuntu-latest + build: strategy: matrix: include: - - arch: x86_64 - goarch: amd64 - - arch: arm64 - goarch: arm64 + - os: linux + arch: amd64 + runner: ubuntu-latest + goreleaser_os: Linux + goreleaser_arch: x86_64 + - os: linux + arch: arm64 + runner: ubuntu-24.04-arm + goreleaser_os: Linux + goreleaser_arch: arm64 + - os: darwin + arch: arm64 + runner: macos-latest + goreleaser_os: Darwin + goreleaser_arch: arm64 + runs-on: ${{ matrix.runner }} steps: - name: Checkout @@ -34,38 +45,19 @@ jobs: if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then TAG="${{ github.event.inputs.tag }}" else - TAG="${{ github.ref_name }}" + TAG="${{ github.event.release.tag_name }}" fi VERSION="${TAG#v}" echo "tag=$TAG" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT echo "Building MCPB bundle for $TAG (version $VERSION)" - - name: Wait for GoReleaser - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - TAG="${{ steps.version.outputs.tag }}" - ARCHIVE="github-mcp-server_Linux_${{ matrix.arch }}.tar.gz" - - echo "Waiting for GoReleaser to publish $ARCHIVE..." - for i in {1..30}; do - if gh release view "$TAG" --json assets -q ".assets[].name" 2>/dev/null | grep -q "$ARCHIVE"; then - echo "✅ Release asset ready" - exit 0 - fi - echo "⏳ Waiting for release assets ($i/30)..." - sleep 20 - done - echo "❌ Timeout waiting for release assets" - exit 1 - - name: Download release binary env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | TAG="${{ steps.version.outputs.tag }}" - ARCHIVE="github-mcp-server_Linux_${{ matrix.arch }}.tar.gz" + ARCHIVE="github-mcp-server_${{ matrix.goreleaser_os }}_${{ matrix.goreleaser_arch }}.tar.gz" echo "Downloading $ARCHIVE from release $TAG..." gh release download "$TAG" --pattern "$ARCHIVE" --dir /tmp @@ -80,15 +72,15 @@ jobs: - name: Update manifest version run: | VERSION="${{ steps.version.outputs.version }}" - # Update version in manifest.json - sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"$VERSION\"/" manifest.json + jq --arg v "$VERSION" '.version = $v' manifest.json > manifest.tmp.json + mv manifest.tmp.json manifest.json echo "Updated manifest.json:" cat manifest.json - name: Create MCPB bundle run: | VERSION="${{ steps.version.outputs.version }}" - BUNDLE_NAME="mcp-github-v${VERSION}-linux-${{ matrix.goarch }}.mcpb" + BUNDLE_NAME="nimblebraininc-github-${VERSION}-${{ matrix.os }}-${{ matrix.arch }}.mcpb" BUNDLE_DIR="/tmp/mcpb-bundle" # Create bundle structure @@ -113,7 +105,11 @@ jobs: ls -la "/tmp/$BUNDLE_NAME" # Calculate SHA256 - SHA256=$(sha256sum "/tmp/$BUNDLE_NAME" | cut -d' ' -f1) + if command -v sha256sum &> /dev/null; then + SHA256=$(sha256sum "/tmp/$BUNDLE_NAME" | cut -d' ' -f1) + else + SHA256=$(shasum -a 256 "/tmp/$BUNDLE_NAME" | cut -d' ' -f1) + fi echo "SHA256: $SHA256" echo "$SHA256" > "/tmp/${BUNDLE_NAME}.sha256" diff --git a/manifest.json b/manifest.json index cf3d0d2772..2d5efe209e 100644 --- a/manifest.json +++ b/manifest.json @@ -1,17 +1,30 @@ { "manifest_version": "0.3", - "name": "ai.nimbletools/github", + "name": "@nimblebraininc/github", "version": "0.26.3-mcpb.1", "description": "GitHub MCP server for repository management, issues, PRs, and workflow automation", "author": { "name": "GitHub (packaged by NimbleBrain Inc)" }, + "repository": "https://github.com/github/github-mcp-server", + "user_config": { + "personal_access_token": { + "type": "string", + "title": "GitHub Personal Access Token", + "description": "Create at https://github.com/settings/tokens with appropriate scopes for repo, issues, PRs", + "sensitive": true, + "required": true + } + }, "server": { "type": "binary", "entry_point": "bin/github-mcp-server", "mcp_config": { "command": "${__dirname}/bin/github-mcp-server", - "args": ["http", "--port", "8000"] + "args": ["stdio"], + "env": { + "GITHUB_PERSONAL_ACCESS_TOKEN": "${user_config.personal_access_token}" + } } } } From 6e148e1947016d0c463e56674b0db485446b3b1e Mon Sep 17 00:00:00 2001 From: Mathew Goldsborough <1759329+mgoldsborough@users.noreply.github.com> Date: Sun, 4 Jan 2026 21:40:11 -1000 Subject: [PATCH 3/9] Simplify workflow to use mcpb-pack@v2 action --- .github/workflows/mcpb-bundle.yml | 75 ++++--------------------------- 1 file changed, 9 insertions(+), 66 deletions(-) diff --git a/.github/workflows/mcpb-bundle.yml b/.github/workflows/mcpb-bundle.yml index e688afff49..7a72cd2712 100644 --- a/.github/workflows/mcpb-bundle.yml +++ b/.github/workflows/mcpb-bundle.yml @@ -36,8 +36,7 @@ jobs: runs-on: ${{ matrix.runner }} steps: - - name: Checkout - uses: actions/checkout@v4 + - uses: actions/checkout@v4 - name: Determine version id: version @@ -50,82 +49,26 @@ jobs: VERSION="${TAG#v}" echo "tag=$TAG" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT - echo "Building MCPB bundle for $TAG (version $VERSION)" - - name: Download release binary + - name: Download binary from GoReleaser env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | TAG="${{ steps.version.outputs.tag }}" ARCHIVE="github-mcp-server_${{ matrix.goreleaser_os }}_${{ matrix.goreleaser_arch }}.tar.gz" - echo "Downloading $ARCHIVE from release $TAG..." gh release download "$TAG" --pattern "$ARCHIVE" --dir /tmp - - # Extract the binary - mkdir -p /tmp/extracted - tar -xzf "/tmp/$ARCHIVE" -C /tmp/extracted - - echo "Extracted contents:" - ls -la /tmp/extracted + mkdir -p bin + tar -xzf "/tmp/$ARCHIVE" -C /tmp + cp /tmp/github-mcp-server bin/ + chmod +x bin/github-mcp-server - name: Update manifest version run: | VERSION="${{ steps.version.outputs.version }}" jq --arg v "$VERSION" '.version = $v' manifest.json > manifest.tmp.json mv manifest.tmp.json manifest.json - echo "Updated manifest.json:" - cat manifest.json - - - name: Create MCPB bundle - run: | - VERSION="${{ steps.version.outputs.version }}" - BUNDLE_NAME="nimblebraininc-github-${VERSION}-${{ matrix.os }}-${{ matrix.arch }}.mcpb" - BUNDLE_DIR="/tmp/mcpb-bundle" - - # Create bundle structure - mkdir -p "$BUNDLE_DIR/bin" - - # Copy manifest - cp manifest.json "$BUNDLE_DIR/" - - # Copy binary (it's in the root of the extracted archive) - cp /tmp/extracted/github-mcp-server "$BUNDLE_DIR/bin/" - chmod +x "$BUNDLE_DIR/bin/github-mcp-server" - - echo "Bundle contents:" - ls -la "$BUNDLE_DIR" - ls -la "$BUNDLE_DIR/bin" - - # Create the bundle (tar.gz) - cd "$BUNDLE_DIR" - tar -czvf "/tmp/$BUNDLE_NAME" . - - echo "Created bundle: /tmp/$BUNDLE_NAME" - ls -la "/tmp/$BUNDLE_NAME" - - # Calculate SHA256 - if command -v sha256sum &> /dev/null; then - SHA256=$(sha256sum "/tmp/$BUNDLE_NAME" | cut -d' ' -f1) - else - SHA256=$(shasum -a 256 "/tmp/$BUNDLE_NAME" | cut -d' ' -f1) - fi - echo "SHA256: $SHA256" - echo "$SHA256" > "/tmp/${BUNDLE_NAME}.sha256" - - # Store for upload step - echo "bundle_name=$BUNDLE_NAME" >> $GITHUB_ENV - echo "bundle_path=/tmp/$BUNDLE_NAME" >> $GITHUB_ENV - echo "sha256_path=/tmp/${BUNDLE_NAME}.sha256" >> $GITHUB_ENV - - - name: Upload bundle to release - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - TAG="${{ steps.version.outputs.tag }}" - - echo "Uploading ${{ env.bundle_name }} to release $TAG..." - gh release upload "$TAG" "${{ env.bundle_path }}" --clobber - gh release upload "$TAG" "${{ env.sha256_path }}" --clobber - echo "Uploaded successfully!" + - uses: NimbleBrainInc/mcpb-pack@v2 + with: + output: "{name}-{version}-${{ matrix.os }}-${{ matrix.arch }}.mcpb" From feeb9addda081dec445f3956aa2b356e1fa3dbe3 Mon Sep 17 00:00:00 2001 From: Mathew Goldsborough <1759329+mgoldsborough@users.noreply.github.com> Date: Sun, 4 Jan 2026 21:52:10 -1000 Subject: [PATCH 4/9] Fix manifest: remove invalid repository field --- manifest.json | 1 - 1 file changed, 1 deletion(-) diff --git a/manifest.json b/manifest.json index 2d5efe209e..eff3a77a0f 100644 --- a/manifest.json +++ b/manifest.json @@ -6,7 +6,6 @@ "author": { "name": "GitHub (packaged by NimbleBrain Inc)" }, - "repository": "https://github.com/github/github-mcp-server", "user_config": { "personal_access_token": { "type": "string", From 815ed268fa87ac15af62adbea4711982554f5910 Mon Sep 17 00:00:00 2001 From: Mathew Goldsborough <1759329+mgoldsborough@users.noreply.github.com> Date: Sun, 4 Jan 2026 21:54:12 -1000 Subject: [PATCH 5/9] Skip upload/announce for workflow_dispatch --- .github/workflows/mcpb-bundle.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/mcpb-bundle.yml b/.github/workflows/mcpb-bundle.yml index 7a72cd2712..5fec29df26 100644 --- a/.github/workflows/mcpb-bundle.yml +++ b/.github/workflows/mcpb-bundle.yml @@ -72,3 +72,5 @@ jobs: - uses: NimbleBrainInc/mcpb-pack@v2 with: output: "{name}-{version}-${{ matrix.os }}-${{ matrix.arch }}.mcpb" + upload: ${{ github.event_name == 'release' }} + announce: ${{ github.event_name == 'release' }} From 905e62415934524a147626106ea8f658433cdf37 Mon Sep 17 00:00:00 2001 From: Mathew Goldsborough Date: Wed, 18 Feb 2026 12:52:24 -1000 Subject: [PATCH 6/9] Add CODEOWNERS requiring @NimbleBrainInc/core approval --- CODEOWNERS | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000000..3c452fe38e --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,5 @@ +# Default code owners for all NimbleBrainInc repositories. +# +# See: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners + +* @NimbleBrainInc/core From ca64d5c0d8f569eb980a0235734c0bec9a156f0f Mon Sep 17 00:00:00 2001 From: Mathew Goldsborough <1759329+mgoldsborough@users.noreply.github.com> Date: Thu, 7 May 2026 22:59:23 -1000 Subject: [PATCH 7/9] Bump version to 0.26.3-mcpb.2 (add icon URL) --- manifest.json | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/manifest.json b/manifest.json index eff3a77a0f..a1bc0f949f 100644 --- a/manifest.json +++ b/manifest.json @@ -1,11 +1,12 @@ { "manifest_version": "0.3", "name": "@nimblebraininc/github", - "version": "0.26.3-mcpb.1", + "version": "0.26.3-mcpb.2", "description": "GitHub MCP server for repository management, issues, PRs, and workflow automation", "author": { "name": "GitHub (packaged by NimbleBrain Inc)" }, + "icon": "https://static.nimblebrain.ai/icons/github.png", "user_config": { "personal_access_token": { "type": "string", @@ -20,7 +21,9 @@ "entry_point": "bin/github-mcp-server", "mcp_config": { "command": "${__dirname}/bin/github-mcp-server", - "args": ["stdio"], + "args": [ + "stdio" + ], "env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "${user_config.personal_access_token}" } From 18766bfa2643d7d0fb47a9de9a2c463b91f52bd5 Mon Sep 17 00:00:00 2001 From: Mathew Goldsborough <1759329+mgoldsborough@users.noreply.github.com> Date: Thu, 7 May 2026 22:59:53 -1000 Subject: [PATCH 8/9] Bump version to 0.26.3-mcpb.3 --- manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index a1bc0f949f..e3c90eb886 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": "0.3", "name": "@nimblebraininc/github", - "version": "0.26.3-mcpb.2", + "version": "0.26.3-mcpb.3", "description": "GitHub MCP server for repository management, issues, PRs, and workflow automation", "author": { "name": "GitHub (packaged by NimbleBrain Inc)" From 581db04f8fc6309c6144532f5dc8672d6fbda174 Mon Sep 17 00:00:00 2001 From: Mathew Goldsborough <1759329+mgoldsborough@users.noreply.github.com> Date: Fri, 8 May 2026 17:21:14 -1000 Subject: [PATCH 9/9] chore: migrate to mcpb-pack@v3 (drop server.json) mpak now composes the MCP registry's `ServerDetail` discovery shape from `manifest.json` server-side, so the per-bundle `server.json` file is no longer used. Drops it, removes the version-bump step that synced its `version` from `manifest.json`, and bumps the build workflow to `mcpb-pack@v3`. Optional follow-up: add a branded reverse-DNS name override in `manifest.json` under `_meta["dev.mpak/registry"].name` (e.g. `ai.nimblebrain/` for `@nimblebraininc/*` bundles). Without an override the registry uses the mechanical default `dev.mpak./`. --- .github/workflows/mcpb-bundle.yml | 2 +- server.json | 25 ------------------------- 2 files changed, 1 insertion(+), 26 deletions(-) delete mode 100644 server.json diff --git a/.github/workflows/mcpb-bundle.yml b/.github/workflows/mcpb-bundle.yml index 5fec29df26..98ce93efae 100644 --- a/.github/workflows/mcpb-bundle.yml +++ b/.github/workflows/mcpb-bundle.yml @@ -69,7 +69,7 @@ jobs: jq --arg v "$VERSION" '.version = $v' manifest.json > manifest.tmp.json mv manifest.tmp.json manifest.json - - uses: NimbleBrainInc/mcpb-pack@v2 + - uses: NimbleBrainInc/mcpb-pack@v3 with: output: "{name}-{version}-${{ matrix.os }}-${{ matrix.arch }}.mcpb" upload: ${{ github.event_name == 'release' }} diff --git a/server.json b/server.json deleted file mode 100644 index 83b4e06bec..0000000000 --- a/server.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", - "name": "io.github.github/github-mcp-server", - "description": "Connect AI assistants to GitHub - manage repos, issues, PRs, and workflows through natural language.", - "title": "GitHub", - "repository": { - "url": "https://github.com/github/github-mcp-server", - "source": "github" - }, - "version": "${VERSION}", - "remotes": [ - { - "type": "streamable-http", - "url": "https://api.githubcopilot.com/mcp/", - "headers": [ - { - "name": "Authorization", - "description": "Authentication token (PAT or App token)", - "isRequired": true, - "isSecret": true - } - ] - } - ] -}