diff --git a/.dockerignore b/.dockerignore index 8f302e7c0..c5248351d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,5 @@ .github .vscode -script third-party .dockerignore .gitignore diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index e3ef25022..38e8904a3 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -1,5 +1,5 @@ name: Build and Test Go Project -on: [push, pull_request] +on: [ push, pull_request ] permissions: contents: read @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, windows-latest, macos-latest] + os: [ ubuntu-latest, windows-latest, macos-latest ] runs-on: ${{ matrix.os }} @@ -28,5 +28,8 @@ jobs: - name: Run unit tests run: script/test + - name: Prepare build_info files + run: script/prepare-build-info + - name: Build run: go build -v ./cmd/github-mcp-server diff --git a/.gitignore b/.gitignore index 0ad709cbf..e11e0d2c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .idea cmd/github-mcp-server/github-mcp-server +cmd/github-mcp-server/build_info/*.txt # VSCode .vscode/* diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 54f6b9f40..0cbbd1439 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -4,12 +4,16 @@ before: hooks: - go mod tidy - go generate ./... + - mkdir -p cmd/github-mcp-server/build_info + - echo "{{.Commit}}" > cmd/github-mcp-server/build_info/commit.txt + - echo "{{.Date}}" > cmd/github-mcp-server/build_info/date.txt + - echo "{{.Version}}" > cmd/github-mcp-server/build_info/version.txt builds: - env: - CGO_ENABLED=0 ldflags: - - -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} + - -s -w goos: - linux - windows diff --git a/.vscode/launch.json b/.vscode/launch.json index cea7fd917..2941bf846 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -10,7 +10,7 @@ "request": "launch", "mode": "auto", "cwd": "${workspaceFolder}", - "program": "cmd/github-mcp-server/main.go", + "program": "./cmd/github-mcp-server", "args": ["stdio"], "console": "integratedTerminal", }, @@ -20,7 +20,7 @@ "request": "launch", "mode": "auto", "cwd": "${workspaceFolder}", - "program": "cmd/github-mcp-server/main.go", + "program": "./cmd/github-mcp-server", "args": ["stdio", "--read-only"], "console": "integratedTerminal", } diff --git a/Dockerfile b/Dockerfile index a26f19a81..0384df249 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,4 @@ FROM golang:1.24.4-alpine AS build -ARG VERSION="dev" # Set the working directory WORKDIR /build @@ -12,9 +11,10 @@ RUN --mount=type=cache,target=/var/cache/apk \ # 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/main.go + --mount=type=bind,target=.,rw \ + script/prepare-build-info && \ + CGO_ENABLED=0 go build -ldflags="-s -w" \ + -o /bin/github-mcp-server ./cmd/github-mcp-server # Make a stage to run the app FROM gcr.io/distroless/base-debian12 diff --git a/cmd/github-mcp-server/build_info.go b/cmd/github-mcp-server/build_info.go new file mode 100644 index 000000000..32bbed3bc --- /dev/null +++ b/cmd/github-mcp-server/build_info.go @@ -0,0 +1,35 @@ +package main + +import ( + "embed" + "fmt" + "strings" +) + +//go:embed build_info/commit.txt build_info/date.txt build_info/version.txt +var versionFS embed.FS + +type buildInfoStruct struct { + commit string + date string + version string +} + +func (b buildInfoStruct) String() string { + return fmt.Sprintf("Commit: %s\nBuild Date: %s\nVersion: %s", b.commit, b.date, b.version) +} + +var buildInfo = func() buildInfoStruct { + readFile := func(path, fallback string) string { + if content, err := versionFS.ReadFile(path); err == nil { + return strings.TrimSpace(string(content)) + } + return fallback + } + + return buildInfoStruct{ + commit: readFile("build_info/commit.txt", "unknown commit"), + date: readFile("build_info/date.txt", "unknown date"), + version: readFile("build_info/version.txt", "unknown version"), + } +}() diff --git a/cmd/github-mcp-server/build_info/commit.txt b/cmd/github-mcp-server/build_info/commit.txt new file mode 100644 index 000000000..fcad76582 --- /dev/null +++ b/cmd/github-mcp-server/build_info/commit.txt @@ -0,0 +1 @@ +commit \ No newline at end of file diff --git a/cmd/github-mcp-server/build_info/date.txt b/cmd/github-mcp-server/build_info/date.txt new file mode 100644 index 000000000..81229b4a8 --- /dev/null +++ b/cmd/github-mcp-server/build_info/date.txt @@ -0,0 +1 @@ +date \ No newline at end of file diff --git a/cmd/github-mcp-server/build_info/version.txt b/cmd/github-mcp-server/build_info/version.txt new file mode 100644 index 000000000..ca291e965 --- /dev/null +++ b/cmd/github-mcp-server/build_info/version.txt @@ -0,0 +1 @@ +version \ No newline at end of file diff --git a/cmd/github-mcp-server/build_info_test.go b/cmd/github-mcp-server/build_info_test.go new file mode 100644 index 000000000..7d3ac522e --- /dev/null +++ b/cmd/github-mcp-server/build_info_test.go @@ -0,0 +1,43 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestBuildInfoStruct_String(t *testing.T) { + tests := []struct { + name string + info buildInfoStruct + expected string + }{ + { + name: "all fields populated", + info: buildInfoStruct{ + commit: "abc123", + date: "2024-01-01", + version: "1.0.0", + }, + expected: "Commit: abc123\nBuild Date: 2024-01-01\nVersion: 1.0.0", + }, + { + name: "initialized struct", + info: buildInfoStruct{}, + expected: "Commit: \nBuild Date: \nVersion: ", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := tt.info.String() + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestBuildInfo_Initialization(t *testing.T) { + assert.Equal(t, "commit", buildInfo.commit) + assert.Equal(t, "date", buildInfo.date) + assert.Equal(t, "version", buildInfo.version) +} diff --git a/cmd/github-mcp-server/main.go b/cmd/github-mcp-server/main.go index cad002666..9210926ba 100644 --- a/cmd/github-mcp-server/main.go +++ b/cmd/github-mcp-server/main.go @@ -13,17 +13,12 @@ import ( "github.com/spf13/viper" ) -// These variables are set by the build process using ldflags. -var version = "version" -var commit = "commit" -var date = "date" - var ( rootCmd = &cobra.Command{ Use: "server", Short: "GitHub MCP Server", Long: `A GitHub MCP server that handles various tools and resources.`, - Version: fmt.Sprintf("Version: %s\nCommit: %s\nBuild Date: %s", version, commit, date), + Version: buildInfo.String(), } stdioCmd = &cobra.Command{ @@ -46,7 +41,7 @@ var ( } stdioServerConfig := ghmcp.StdioServerConfig{ - Version: version, + Version: buildInfo.version, Host: viper.GetString("host"), Token: token, EnabledToolsets: enabledToolsets, diff --git a/cmd/github-mcp-server/main_test.go b/cmd/github-mcp-server/main_test.go new file mode 100644 index 000000000..80f2fe231 --- /dev/null +++ b/cmd/github-mcp-server/main_test.go @@ -0,0 +1,14 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_RootCmdVersion(t *testing.T) { + expectedVersion := buildInfo.String() + actualVersion := rootCmd.Version + + assert.Equal(t, expectedVersion, actualVersion) +} diff --git a/script/get-discussions b/script/get-discussions index 3e68abf24..5183c8b24 100755 --- a/script/get-discussions +++ b/script/get-discussions @@ -1,5 +1,4 @@ #!/bin/bash -# echo '{"jsonrpc":"2.0","id":3,"params":{"name":"list_discussions","arguments": {"owner": "github", "repo": "securitylab", "first": 10, "since": "2025-04-01T00:00:00Z"}},"method":"tools/call"}' | go run cmd/github-mcp-server/main.go stdio | jq . -echo '{"jsonrpc":"2.0","id":3,"params":{"name":"list_discussions","arguments": {"owner": "github", "repo": "securitylab", "first": 10, "since": "2025-04-01T00:00:00Z", "sort": "CREATED_AT", "direction": "DESC"}},"method":"tools/call"}' | go run cmd/github-mcp-server/main.go stdio | jq . - +# echo '{"jsonrpc":"2.0","id":3,"params":{"name":"list_discussions","arguments": {"owner": "github", "repo": "securitylab", "first": 10, "since": "2025-04-01T00:00:00Z"}},"method":"tools/call"}' | go run ./cmd/github-mcp-server stdio | jq . +echo '{"jsonrpc":"2.0","id":3,"params":{"name":"list_discussions","arguments": {"owner": "github", "repo": "securitylab", "first": 10, "since": "2025-04-01T00:00:00Z", "sort": "CREATED_AT", "direction": "DESC"}},"method":"tools/call"}' | go run ./cmd/github-mcp-server stdio | jq . diff --git a/script/get-me b/script/get-me index 46339ae53..425713af6 100755 --- a/script/get-me +++ b/script/get-me @@ -1,3 +1,3 @@ #!/bin/bash -echo '{"jsonrpc":"2.0","id":3,"params":{"name":"get_me"},"method":"tools/call"}' | go run cmd/github-mcp-server/main.go stdio | jq . +echo '{"jsonrpc":"2.0","id":3,"params":{"name":"get_me"},"method":"tools/call"}' | go run ./cmd/github-mcp-server stdio | jq . diff --git a/script/prepare-build-info b/script/prepare-build-info new file mode 100755 index 000000000..d82b65f1c --- /dev/null +++ b/script/prepare-build-info @@ -0,0 +1,29 @@ + +#!/bin/sh +set -eu + +mkdir -p cmd/github-mcp-server/build_info + +BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) + +if [ -n "${GITHUB_ACTIONS:-}" ]; then + # GitHub Actions context + COMMIT="${GITHUB_SHA}" + + if [ "${GITHUB_EVENT_NAME}" = "push" ] && [ "${GITHUB_REF_TYPE}" = "tag" ]; then + VERSION="${GITHUB_REF_NAME}" + elif [ "${GITHUB_EVENT_NAME}" = "push" ]; then + VERSION="${GITHUB_REF_NAME}-${GITHUB_SHA}" + else + VERSION="pr-${GITHUB_EVENT_PULL_REQUEST_NUMBER}" + fi +else + # Local/Docker context + COMMIT=$(git rev-parse HEAD 2>/dev/null || echo 'unknown commit') + VERSION=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo 'unknown version') +fi + +# Write build info files +echo "${COMMIT}" > cmd/github-mcp-server/build_info/commit.txt +echo "${BUILD_DATE}" > cmd/github-mcp-server/build_info/date.txt +echo "${VERSION}" > cmd/github-mcp-server/build_info/version.txt \ No newline at end of file