From 72e51b0e0a9448a047193c696b219e57ced3230b Mon Sep 17 00:00:00 2001 From: aliciatang07 Date: Wed, 4 Feb 2026 21:43:09 +0000 Subject: [PATCH 01/14] add new mcp tools for cloud build --- .../cloudbuild/client/cloudbuildclient.go | 78 ++++++++++++++++++- devops-mcp-server/cloudbuild/cloudbuild.go | 65 ++++++++++++++++ 2 files changed, 142 insertions(+), 1 deletion(-) diff --git a/devops-mcp-server/cloudbuild/client/cloudbuildclient.go b/devops-mcp-server/cloudbuild/client/cloudbuildclient.go index 7296ef9..c288c24 100644 --- a/devops-mcp-server/cloudbuild/client/cloudbuildclient.go +++ b/devops-mcp-server/cloudbuild/client/cloudbuildclient.go @@ -51,8 +51,32 @@ type CloudBuildClient interface { GetLatestBuildForTrigger(ctx context.Context, projectID, location, triggerID string) (*cloudbuildpb.Build, error) ListBuildTriggers(ctx context.Context, projectID, location string) ([]*cloudbuildpb.BuildTrigger, error) RunBuildTrigger(ctx context.Context, projectID, location, triggerID, branch, tag, commitSha string) (*cloudbuild.RunBuildTriggerOperation, error) + ListBuilds(ctx context.Context, projectID, location string) ([]*cloudbuildpb.Build, error) + GetBuildInfo(ctx context.Context, projectID, location, buildID string) (*cloudbuildpb.Build, error) + StartBuild(ctx context.Context, projectID, location string, source *cloudbuildpb.RepoSource) (*cloudbuildpb.Build, error) } +// Exec interface for running commands. +type Exec interface { + Command(name string, arg ...string) *exec.Cmd +} + +type execer struct{} + +func (e *execer) Command(name string, arg ...string) *exec.Cmd { + return exec.Command(name, arg...) +} + +var defaultExecer Exec = &execer{} + + +type BuildInfo struct { + BuildDetails *cloudbuildpb.Build + Logs string +} + + + // NewCloudBuildClient creates a new Cloud Build client. func NewCloudBuildClient(ctx context.Context) (CloudBuildClient, error) { c, err := cloudbuild.NewClient(ctx) @@ -65,13 +89,14 @@ func NewCloudBuildClient(ctx context.Context) (CloudBuildClient, error) { return nil, fmt.Errorf("failed to create Cloud Build service: %v", err) } - return &CloudBuildClientImpl{v1client: c, legacyClient: c2}, nil + return &CloudBuildClientImpl{v1client: c, legacyClient: c2, execer: defaultExecer}, nil } // CloudBuildClientImpl is an implementation of the CloudBuildClient interface. type CloudBuildClientImpl struct { v1client *cloudbuild.Client legacyClient *build.Service + execer Exec } // CreateCloudBuildTrigger creates a new build trigger. @@ -181,3 +206,54 @@ func (c *CloudBuildClientImpl) RunBuildTrigger(ctx context.Context, projectID, l } return op, nil } + + +func (c *CloudBuildClientImpl) ListBuilds(ctx context.Context, projectID, location string) ([]*cloudbuildpb.Build, error) { + req := &cloudbuildpb.ListBuildsRequest{ + Parent: fmt.Sprintf("projects/%s/locations/%s", projectID, location), + } + it := c.v1client.ListBuilds(ctx, req) + var builds []*cloudbuildpb.Build + for { + build, err := it.Next() + if err == iterator.Done { + break + } + if err != nil { + return nil, fmt.Errorf("failed to list builds: %v", err) + } + builds = append(builds, build) + } + return builds, nil +} + +func (c *CloudBuildClientImpl) GetBuildInfo(ctx context.Context, projectID, location, buildID string) (BuildInfo, error) { + req := &cloudbuildpb.GetBuildRequest{ + Name: fmt.Sprintf("projects/%s/locations/%s/builds/%s", projectID, location, buildID), + } + build, err := c.v1client.GetBuild(ctx, req) + if err != nil { + return BuildInfo{}, fmt.Errorf("failed to get build info: %w", err) + } + info := BuildInfo{BuildDetails: build} + args2:= []string{"builds", "log", buildID, "--project", projectID, "--region", location, "--format", "text"} + cmd = c.execer.Command("gcloud", args2...) + out, err = cmd.CombinedOutput() + if err != nil { + return BuildInfo{}, fmt.Errorf("failed to get build logs: %w, output: %s", err, out) + } + info.Logs = string(out) + return info, nil +} + +func (c *CloudBuildClientImpl) StartBuild(ctx context.Context, projectID string, source *cloudbuildpb.Source) (*cloudbuildpb.Build, error) { + req := &cloudbuildpb.CreateBuildRequest{ + Parent: fmt.Sprintf("projects/%s/locations/global", projectID), + Build: &cloudbuildpb.Build{Source: source}, + } + createdBuild, err := c.v1client.CreateBuild(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to start build: %v", err) + } + return createdBuild, nil +} diff --git a/devops-mcp-server/cloudbuild/cloudbuild.go b/devops-mcp-server/cloudbuild/cloudbuild.go index 4e35624..587f3b3 100644 --- a/devops-mcp-server/cloudbuild/cloudbuild.go +++ b/devops-mcp-server/cloudbuild/cloudbuild.go @@ -39,6 +39,9 @@ func (h *Handler) Register(server *mcp.Server) { addCreateTriggerTool(server, h.CbClient, h.IClient, h.RClient) addRunTriggerTool(server, h.CbClient) addListTriggersTool(server, h.CbClient) + addListBuildsTool(server, h.CbClient) + addGetBuildInfoTool(server, h.CbClient) + addStartBuildTool(server, h.CbClient) } type RunTriggerArgs struct { @@ -146,3 +149,65 @@ func IsValidServiceAccount(sa string) bool { var saRegex = regexp.MustCompile(`^serviceAccount:[a-z0-9-]+@[a-z0-9-]+\.iam\.gserviceaccount\.com$`) return saRegex.MatchString(sa) } + +type ListBuildsArgs struct { + ProjectID string `json:"project_id" jsonschema:"The Google Cloud project ID."` + Location string `json:"location" jsonschema:"The Google Cloud location for the builds."` +} + +type GetBuildInfoArgs struct { + ProjectID string `json:"project_id" jsonschema:"The Google Cloud project ID."` + Location string `json:"location" jsonschema:"The Google Cloud location for the build."` + BuildID string `json:"build_id" jsonschema:"The ID of the build."` +} + +type StartBuildArgs struct { + ProjectID string `json:"project_id" jsonschema:"The Google Cloud project ID."` + Location string `json:"location" jsonschema:"The Google Cloud location for the build."` + source map[string]interface{} `json:"source" jsonschema:"The Cloud Build source configuration as a map."` +} + +func addListBuildsTool(server *mcp.Server, cbClient cloudbuildclient.CloudBuildClient) { + listBuildsToolFunc := func(ctx context.Context, req *mcp.CallToolRequest, args ListBuildsArgs) (*mcp.CallToolResult, any, error) { + res, err := cbClient.ListBuilds(ctx, args.ProjectID, args.Location) + if err != nil { + return &mcp.CallToolResult{}, nil, fmt.Errorf("failed to list builds: %w", err) + } + return &mcp.CallToolResult{}, map[string]any{"builds": res}, nil + } + mcp.AddTool(server, &mcp.Tool{Name: "cloudbuild.list_builds", Description: "Lists all Cloud Builds in a given location."}, listBuildsToolFunc) +} + +func addGetBuildInfoTool(server *mcp.Server, cbClient cloudbuildclient.CloudBuildClient) { + getBuildInfoToolFunc := func(ctx context.Context, req *mcp.CallToolRequest, args GetBuildInfoArgs) (*mcp.CallToolResult, any, error) { + res, err := cbClient.GetBuildInfo(ctx, args.ProjectID, args.Location, args.BuildID) + if err != nil { + return &mcp.CallToolResult{}, nil, fmt.Errorf("failed to get build info: %w", err) + } + return &mcp.CallToolResult{}, res, nil + } + mcp.AddTool(server, &mcp.Tool{Name: "cloudbuild.get_build_info", Description: "Gets information about a specific Cloud Build."}, getBuildInfoToolFunc) +} + +func addStartBuildTool(server *mcp.Server, cbClient cloudbuildclient.CloudBuildClient) { + type StartBuildArgs struct { + ProjectID string `json:"project_id" jsonschema:"The Google Cloud project ID."` + Location string `json:"location" jsonschema:"The Google Cloud location for the build."` + Build map[string]interface{} `json:"build" jsonschema:"The Cloud Build configuration as a map."` + } + startBuildToolFunc := func(ctx context.Context, req *mcp.CallToolRequest, args StartBuildArgs) (*mcp.CallToolResult, any, error) { + // Convert map to cloudbuildpb.Build + source := &cloudbuildpb.RepoSource{} + err := mcp.DecodeMapToProto(args.Source, source) + if err != nil { + return &mcp.CallToolResult{}, nil, fmt.Errorf("failed to decode source map to proto: %w", err) + } + + res, err := cbClient.StartBuild(ctx, args.ProjectID, args.Location, source) + if err != nil { + return &mcp.CallToolResult{}, nil, fmt.Errorf("failed to start build: %w", err) + } + return &mcp.CallToolResult{}, res, nil + } + mcp.AddTool(server, &mcp.Tool{Name: "cloudbuild.start_build", Description: "Starts a new Cloud Build with the given configuration."}, startBuildToolFunc) +} \ No newline at end of file From 60edcbef40084dd12184b005c7dd26fe4c241585 Mon Sep 17 00:00:00 2001 From: aliciatang07 Date: Wed, 4 Feb 2026 22:07:05 +0000 Subject: [PATCH 02/14] replace logging with api --- .../cloudbuild/client/cloudbuildclient.go | 38 +++++++++++++++---- devops-mcp-server/cloudbuild/cloudbuild.go | 4 +- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/devops-mcp-server/cloudbuild/client/cloudbuildclient.go b/devops-mcp-server/cloudbuild/client/cloudbuildclient.go index c288c24..228d0fd 100644 --- a/devops-mcp-server/cloudbuild/client/cloudbuildclient.go +++ b/devops-mcp-server/cloudbuild/client/cloudbuildclient.go @@ -20,9 +20,10 @@ import ( "strings" cloudbuild "cloud.google.com/go/cloudbuild/apiv1/v2" + logging "cloud.google.com/go/logging/apiv2" cloudbuildpb "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb" - build "google.golang.org/api/cloudbuild/v1" + build "google.golang.org/api/cloudbuilsd/v1" "google.golang.org/api/iterator" "google.golang.org/protobuf/types/known/timestamppb" ) @@ -89,7 +90,17 @@ func NewCloudBuildClient(ctx context.Context) (CloudBuildClient, error) { return nil, fmt.Errorf("failed to create Cloud Build service: %v", err) } - return &CloudBuildClientImpl{v1client: c, legacyClient: c2, execer: defaultExecer}, nil + loggingClient, err := logging.NewClient(ctx) + if err != nil { + return nil, fmt.Errorf("failed to create Logging client: %v", err) + } + + return &CloudBuildClientImpl{ + v1client: c, + legacyClient: c2, + execer: defaultExecer, + loggingClient: loggingClient, + }, nil } // CloudBuildClientImpl is an implementation of the CloudBuildClient interface. @@ -97,6 +108,7 @@ type CloudBuildClientImpl struct { v1client *cloudbuild.Client legacyClient *build.Service execer Exec + loggingClient *logging.Client } // CreateCloudBuildTrigger creates a new build trigger. @@ -236,13 +248,23 @@ func (c *CloudBuildClientImpl) GetBuildInfo(ctx context.Context, projectID, loca return BuildInfo{}, fmt.Errorf("failed to get build info: %w", err) } info := BuildInfo{BuildDetails: build} - args2:= []string{"builds", "log", buildID, "--project", projectID, "--region", location, "--format", "text"} - cmd = c.execer.Command("gcloud", args2...) - out, err = cmd.CombinedOutput() - if err != nil { - return BuildInfo{}, fmt.Errorf("failed to get build logs: %w, output: %s", err, out) + logReq := &logging.ListLogEntriesRequest{ + ResourceNames: []string{fmt.Sprintf("projects/%s", projectID)}, + Filter: fmt.Sprintf(`resource.type="build" AND resource.labels.build_id="%s" AND logName="projects/%s/logs/cloudbuild"`, buildID, projectID), + } + it := c.loggingClient.ListLogEntries(ctx, logReq) + var logs []string + for { + entry, err := it.Next() + if err == iterator.Done { + break + } + if err != nil { + return BuildInfo{}, fmt.Errorf("failed to list log entries: %w", err) + } + logs = append(logs, entry.TextPayload) } - info.Logs = string(out) + info.Logs = strings.Join(logs, "\n") return info, nil } diff --git a/devops-mcp-server/cloudbuild/cloudbuild.go b/devops-mcp-server/cloudbuild/cloudbuild.go index 587f3b3..b5a867b 100644 --- a/devops-mcp-server/cloudbuild/cloudbuild.go +++ b/devops-mcp-server/cloudbuild/cloudbuild.go @@ -175,7 +175,7 @@ func addListBuildsTool(server *mcp.Server, cbClient cloudbuildclient.CloudBuildC } return &mcp.CallToolResult{}, map[string]any{"builds": res}, nil } - mcp.AddTool(server, &mcp.Tool{Name: "cloudbuild.list_builds", Description: "Lists all Cloud Builds in a given location."}, listBuildsToolFunc) + mcp.AddTool(server, &mcp.Tool{Name: "cloudbuild.list_builds", Description: "Lists all Cloud Builds in a given location and project."}, listBuildsToolFunc) } func addGetBuildInfoTool(server *mcp.Server, cbClient cloudbuildclient.CloudBuildClient) { @@ -209,5 +209,5 @@ func addStartBuildTool(server *mcp.Server, cbClient cloudbuildclient.CloudBuildC } return &mcp.CallToolResult{}, res, nil } - mcp.AddTool(server, &mcp.Tool{Name: "cloudbuild.start_build", Description: "Starts a new Cloud Build with the given configuration."}, startBuildToolFunc) + mcp.AddTool(server, &mcp.Tool{Name: "cloudbuild.start_build", Description: "Starts a new Cloud Build with the given source locally."}, startBuildToolFunc) } \ No newline at end of file From 62dd5def7ce99ce4560d6947fbb5ee864cf18c55 Mon Sep 17 00:00:00 2001 From: aliciatang07 Date: Wed, 4 Feb 2026 22:41:44 +0000 Subject: [PATCH 03/14] debug --- .../cloudbuild/client/cloudbuildclient.go | 24 ++++--------------- devops-mcp-server/cloudbuild/cloudbuild.go | 24 +++++++++---------- 2 files changed, 16 insertions(+), 32 deletions(-) diff --git a/devops-mcp-server/cloudbuild/client/cloudbuildclient.go b/devops-mcp-server/cloudbuild/client/cloudbuildclient.go index 228d0fd..cca073c 100644 --- a/devops-mcp-server/cloudbuild/client/cloudbuildclient.go +++ b/devops-mcp-server/cloudbuild/client/cloudbuildclient.go @@ -54,23 +54,9 @@ type CloudBuildClient interface { RunBuildTrigger(ctx context.Context, projectID, location, triggerID, branch, tag, commitSha string) (*cloudbuild.RunBuildTriggerOperation, error) ListBuilds(ctx context.Context, projectID, location string) ([]*cloudbuildpb.Build, error) GetBuildInfo(ctx context.Context, projectID, location, buildID string) (*cloudbuildpb.Build, error) - StartBuild(ctx context.Context, projectID, location string, source *cloudbuildpb.RepoSource) (*cloudbuildpb.Build, error) + StartBuild(ctx context.Context, projectID string, source *cloudbuildpb.Source) (*cloudbuildpb.CreateBuildOperation, error) } -// Exec interface for running commands. -type Exec interface { - Command(name string, arg ...string) *exec.Cmd -} - -type execer struct{} - -func (e *execer) Command(name string, arg ...string) *exec.Cmd { - return exec.Command(name, arg...) -} - -var defaultExecer Exec = &execer{} - - type BuildInfo struct { BuildDetails *cloudbuildpb.Build Logs string @@ -98,7 +84,6 @@ func NewCloudBuildClient(ctx context.Context) (CloudBuildClient, error) { return &CloudBuildClientImpl{ v1client: c, legacyClient: c2, - execer: defaultExecer, loggingClient: loggingClient, }, nil } @@ -107,7 +92,6 @@ func NewCloudBuildClient(ctx context.Context) (CloudBuildClient, error) { type CloudBuildClientImpl struct { v1client *cloudbuild.Client legacyClient *build.Service - execer Exec loggingClient *logging.Client } @@ -268,14 +252,14 @@ func (c *CloudBuildClientImpl) GetBuildInfo(ctx context.Context, projectID, loca return info, nil } -func (c *CloudBuildClientImpl) StartBuild(ctx context.Context, projectID string, source *cloudbuildpb.Source) (*cloudbuildpb.Build, error) { +func (c *CloudBuildClientImpl) StartBuild(ctx context.Context, projectID string, source *cloudbuildpb.Source) (*cloudbuildpb.CreateBuildOperation, error) { req := &cloudbuildpb.CreateBuildRequest{ Parent: fmt.Sprintf("projects/%s/locations/global", projectID), Build: &cloudbuildpb.Build{Source: source}, } - createdBuild, err := c.v1client.CreateBuild(ctx, req) + ops, err := c.v1client.CreateBuild(ctx, req) if err != nil { return nil, fmt.Errorf("failed to start build: %v", err) } - return createdBuild, nil + return ops, nil } diff --git a/devops-mcp-server/cloudbuild/cloudbuild.go b/devops-mcp-server/cloudbuild/cloudbuild.go index b5a867b..6ae805d 100644 --- a/devops-mcp-server/cloudbuild/cloudbuild.go +++ b/devops-mcp-server/cloudbuild/cloudbuild.go @@ -16,6 +16,7 @@ package cloudbuild import ( "context" + "encoding/json" "fmt" "regexp" "strings" @@ -25,6 +26,8 @@ import ( cloudbuildclient "devops-mcp-server/cloudbuild/client" iamclient "devops-mcp-server/iam/client" resourcemanagerclient "devops-mcp-server/resourcemanager/client" + + cloudbuildpb "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb" ) // Handler holds the clients for the cloudbuild service. @@ -164,7 +167,8 @@ type GetBuildInfoArgs struct { type StartBuildArgs struct { ProjectID string `json:"project_id" jsonschema:"The Google Cloud project ID."` Location string `json:"location" jsonschema:"The Google Cloud location for the build."` - source map[string]interface{} `json:"source" jsonschema:"The Cloud Build source configuration as a map."` + Bucket string `json:"bucket" jsonschema:"The Cloud Storage bucket where the source is located."` + Object string `json:"object" jsonschema:"The Cloud Storage object (file) where the source is located."` } func addListBuildsTool(server *mcp.Server, cbClient cloudbuildclient.CloudBuildClient) { @@ -190,20 +194,16 @@ func addGetBuildInfoTool(server *mcp.Server, cbClient cloudbuildclient.CloudBuil } func addStartBuildTool(server *mcp.Server, cbClient cloudbuildclient.CloudBuildClient) { - type StartBuildArgs struct { - ProjectID string `json:"project_id" jsonschema:"The Google Cloud project ID."` - Location string `json:"location" jsonschema:"The Google Cloud location for the build."` - Build map[string]interface{} `json:"build" jsonschema:"The Cloud Build configuration as a map."` - } startBuildToolFunc := func(ctx context.Context, req *mcp.CallToolRequest, args StartBuildArgs) (*mcp.CallToolResult, any, error) { // Convert map to cloudbuildpb.Build - source := &cloudbuildpb.RepoSource{} - err := mcp.DecodeMapToProto(args.Source, source) - if err != nil { - return &mcp.CallToolResult{}, nil, fmt.Errorf("failed to decode source map to proto: %w", err) + source:= &cloudbuildpb.Source{ + StorageSource: &cloudbuildpb.StorageSource{ + Bucket: args.Bucket, + Object: args.Object, + }, } - - res, err := cbClient.StartBuild(ctx, args.ProjectID, args.Location, source) + + res, err := cbClient.StartBuild(ctx, args.ProjectID, source) if err != nil { return &mcp.CallToolResult{}, nil, fmt.Errorf("failed to start build: %w", err) } From b49e30edff8b7b7e0c9b268d28800d71af44b71a Mon Sep 17 00:00:00 2001 From: aliciatang07 Date: Wed, 4 Feb 2026 22:49:07 +0000 Subject: [PATCH 04/14] fix wrong import --- devops-mcp-server/cloudbuild/client/cloudbuildclient.go | 2 +- devops-mcp-server/cloudbuild/cloudbuild.go | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/devops-mcp-server/cloudbuild/client/cloudbuildclient.go b/devops-mcp-server/cloudbuild/client/cloudbuildclient.go index cca073c..07de16a 100644 --- a/devops-mcp-server/cloudbuild/client/cloudbuildclient.go +++ b/devops-mcp-server/cloudbuild/client/cloudbuildclient.go @@ -23,7 +23,7 @@ import ( logging "cloud.google.com/go/logging/apiv2" cloudbuildpb "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb" - build "google.golang.org/api/cloudbuilsd/v1" + build "google.golang.org/api/cloudbuild/v1" "google.golang.org/api/iterator" "google.golang.org/protobuf/types/known/timestamppb" ) diff --git a/devops-mcp-server/cloudbuild/cloudbuild.go b/devops-mcp-server/cloudbuild/cloudbuild.go index 6ae805d..801c54e 100644 --- a/devops-mcp-server/cloudbuild/cloudbuild.go +++ b/devops-mcp-server/cloudbuild/cloudbuild.go @@ -16,7 +16,6 @@ package cloudbuild import ( "context" - "encoding/json" "fmt" "regexp" "strings" From 5808353899b4eff277609fccc5137717310998d7 Mon Sep 17 00:00:00 2001 From: aliciatang07 Date: Wed, 4 Feb 2026 22:50:10 +0000 Subject: [PATCH 05/14] add new line --- devops-mcp-server/cloudbuild/cloudbuild.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devops-mcp-server/cloudbuild/cloudbuild.go b/devops-mcp-server/cloudbuild/cloudbuild.go index 801c54e..f617a6d 100644 --- a/devops-mcp-server/cloudbuild/cloudbuild.go +++ b/devops-mcp-server/cloudbuild/cloudbuild.go @@ -209,4 +209,4 @@ func addStartBuildTool(server *mcp.Server, cbClient cloudbuildclient.CloudBuildC return &mcp.CallToolResult{}, res, nil } mcp.AddTool(server, &mcp.Tool{Name: "cloudbuild.start_build", Description: "Starts a new Cloud Build with the given source locally."}, startBuildToolFunc) -} \ No newline at end of file +} From 09b6e1ecd3e2f235a9c3dec6c6f40cc5b21845c9 Mon Sep 17 00:00:00 2001 From: aliciatang07 Date: Wed, 4 Feb 2026 23:30:32 +0000 Subject: [PATCH 06/14] debug --- .../cloudbuild/client/cloudbuildclient.go | 13 +++++++------ devops-mcp-server/cloudbuild/cloudbuild.go | 5 ++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/devops-mcp-server/cloudbuild/client/cloudbuildclient.go b/devops-mcp-server/cloudbuild/client/cloudbuildclient.go index 07de16a..e3049e9 100644 --- a/devops-mcp-server/cloudbuild/client/cloudbuildclient.go +++ b/devops-mcp-server/cloudbuild/client/cloudbuildclient.go @@ -21,11 +21,12 @@ import ( cloudbuild "cloud.google.com/go/cloudbuild/apiv1/v2" logging "cloud.google.com/go/logging/apiv2" - cloudbuildpb "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb" - build "google.golang.org/api/cloudbuild/v1" "google.golang.org/api/iterator" "google.golang.org/protobuf/types/known/timestamppb" + + cloudbuildpb "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb" + loggingpb "cloud.google.com/go/logging/apiv2/loggingpb" ) // contextKey is a private type to use as a key for context values. @@ -53,8 +54,8 @@ type CloudBuildClient interface { ListBuildTriggers(ctx context.Context, projectID, location string) ([]*cloudbuildpb.BuildTrigger, error) RunBuildTrigger(ctx context.Context, projectID, location, triggerID, branch, tag, commitSha string) (*cloudbuild.RunBuildTriggerOperation, error) ListBuilds(ctx context.Context, projectID, location string) ([]*cloudbuildpb.Build, error) - GetBuildInfo(ctx context.Context, projectID, location, buildID string) (*cloudbuildpb.Build, error) - StartBuild(ctx context.Context, projectID string, source *cloudbuildpb.Source) (*cloudbuildpb.CreateBuildOperation, error) + GetBuildInfo(ctx context.Context, projectID, location, buildID string) (BuildInfo, error) + StartBuild(ctx context.Context, projectID string, source *cloudbuildpb.Source) (*cloudbuild.CreateBuildOperation, error) } type BuildInfo struct { @@ -232,7 +233,7 @@ func (c *CloudBuildClientImpl) GetBuildInfo(ctx context.Context, projectID, loca return BuildInfo{}, fmt.Errorf("failed to get build info: %w", err) } info := BuildInfo{BuildDetails: build} - logReq := &logging.ListLogEntriesRequest{ + logReq := &loggingpb.ListLogEntriesRequest{ ResourceNames: []string{fmt.Sprintf("projects/%s", projectID)}, Filter: fmt.Sprintf(`resource.type="build" AND resource.labels.build_id="%s" AND logName="projects/%s/logs/cloudbuild"`, buildID, projectID), } @@ -252,7 +253,7 @@ func (c *CloudBuildClientImpl) GetBuildInfo(ctx context.Context, projectID, loca return info, nil } -func (c *CloudBuildClientImpl) StartBuild(ctx context.Context, projectID string, source *cloudbuildpb.Source) (*cloudbuildpb.CreateBuildOperation, error) { +func (c *CloudBuildClientImpl) StartBuild(ctx context.Context, projectID string, source *cloudbuildpb.Source) (*cloudbuild.CreateBuildOperation, error) { req := &cloudbuildpb.CreateBuildRequest{ Parent: fmt.Sprintf("projects/%s/locations/global", projectID), Build: &cloudbuildpb.Build{Source: source}, diff --git a/devops-mcp-server/cloudbuild/cloudbuild.go b/devops-mcp-server/cloudbuild/cloudbuild.go index f617a6d..0e1c73d 100644 --- a/devops-mcp-server/cloudbuild/cloudbuild.go +++ b/devops-mcp-server/cloudbuild/cloudbuild.go @@ -194,9 +194,8 @@ func addGetBuildInfoTool(server *mcp.Server, cbClient cloudbuildclient.CloudBuil func addStartBuildTool(server *mcp.Server, cbClient cloudbuildclient.CloudBuildClient) { startBuildToolFunc := func(ctx context.Context, req *mcp.CallToolRequest, args StartBuildArgs) (*mcp.CallToolResult, any, error) { - // Convert map to cloudbuildpb.Build - source:= &cloudbuildpb.Source{ - StorageSource: &cloudbuildpb.StorageSource{ + source:= &cloudbuild.Source{ + StorageSource: &cloudbuild.StorageSource{ Bucket: args.Bucket, Object: args.Object, }, From 4ed3a6a7648916263817719d8a85d9efd6738207 Mon Sep 17 00:00:00 2001 From: aliciatang07 Date: Thu, 5 Feb 2026 14:00:07 +0000 Subject: [PATCH 07/14] fix typo --- devops-mcp-server/cloudbuild/client/cloudbuildclient.go | 2 +- devops-mcp-server/cloudbuild/cloudbuild.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/devops-mcp-server/cloudbuild/client/cloudbuildclient.go b/devops-mcp-server/cloudbuild/client/cloudbuildclient.go index e3049e9..144be8d 100644 --- a/devops-mcp-server/cloudbuild/client/cloudbuildclient.go +++ b/devops-mcp-server/cloudbuild/client/cloudbuildclient.go @@ -247,7 +247,7 @@ func (c *CloudBuildClientImpl) GetBuildInfo(ctx context.Context, projectID, loca if err != nil { return BuildInfo{}, fmt.Errorf("failed to list log entries: %w", err) } - logs = append(logs, entry.TextPayload) + logs = append(logs, entry.Payload.(string)) } info.Logs = strings.Join(logs, "\n") return info, nil diff --git a/devops-mcp-server/cloudbuild/cloudbuild.go b/devops-mcp-server/cloudbuild/cloudbuild.go index 0e1c73d..a723898 100644 --- a/devops-mcp-server/cloudbuild/cloudbuild.go +++ b/devops-mcp-server/cloudbuild/cloudbuild.go @@ -22,11 +22,11 @@ import ( "github.com/modelcontextprotocol/go-sdk/mcp" + cloudbuild "cloud.google.com/go/cloudbuild/apiv1/v2" + cloudbuildclient "devops-mcp-server/cloudbuild/client" iamclient "devops-mcp-server/iam/client" resourcemanagerclient "devops-mcp-server/resourcemanager/client" - - cloudbuildpb "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb" ) // Handler holds the clients for the cloudbuild service. From 2ead99b33fdc93e674d6a4f4005b6a1e6330002c Mon Sep 17 00:00:00 2001 From: aliciatang07 Date: Thu, 5 Feb 2026 14:35:57 +0000 Subject: [PATCH 08/14] fix payload error --- .../cloudbuild/client/cloudbuildclient.go | 13 ++++++++++++- devops-mcp-server/cloudbuild/cloudbuild.go | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/devops-mcp-server/cloudbuild/client/cloudbuildclient.go b/devops-mcp-server/cloudbuild/client/cloudbuildclient.go index 144be8d..04ae564 100644 --- a/devops-mcp-server/cloudbuild/client/cloudbuildclient.go +++ b/devops-mcp-server/cloudbuild/client/cloudbuildclient.go @@ -247,7 +247,18 @@ func (c *CloudBuildClientImpl) GetBuildInfo(ctx context.Context, projectID, loca if err != nil { return BuildInfo{}, fmt.Errorf("failed to list log entries: %w", err) } - logs = append(logs, entry.Payload.(string)) + var logMessage string + switch payload := entry.Payload.(type) { + case *loggingpb.LogEntry_TextPayload: + logMessage = payload.TextPayload + case *loggingpb.LogEntry_JsonPayload: + logMessage = fmt.Sprintf("%v", payload.JsonPayload) + case *loggingpb.LogEntry_ProtoPayload: + logMessage = fmt.Sprintf("%v", payload.ProtoPayload) + default: + return BuildInfo{}, fmt.Errorf("unknown log entry payload type") + } + logs = append(logs, logMessage) } info.Logs = strings.Join(logs, "\n") return info, nil diff --git a/devops-mcp-server/cloudbuild/cloudbuild.go b/devops-mcp-server/cloudbuild/cloudbuild.go index a723898..04ee7f5 100644 --- a/devops-mcp-server/cloudbuild/cloudbuild.go +++ b/devops-mcp-server/cloudbuild/cloudbuild.go @@ -22,7 +22,7 @@ import ( "github.com/modelcontextprotocol/go-sdk/mcp" - cloudbuild "cloud.google.com/go/cloudbuild/apiv1/v2" + cloudbuild "google.golang.org/api/cloudbuild/v1" cloudbuildclient "devops-mcp-server/cloudbuild/client" iamclient "devops-mcp-server/iam/client" From e188839f36fb1a5586007725b18b06a176208ce8 Mon Sep 17 00:00:00 2001 From: aliciatang07 Date: Thu, 5 Feb 2026 15:18:37 +0000 Subject: [PATCH 09/14] debug --- devops-mcp-server/cloudbuild/cloudbuild.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/devops-mcp-server/cloudbuild/cloudbuild.go b/devops-mcp-server/cloudbuild/cloudbuild.go index 04ee7f5..4939541 100644 --- a/devops-mcp-server/cloudbuild/cloudbuild.go +++ b/devops-mcp-server/cloudbuild/cloudbuild.go @@ -22,11 +22,11 @@ import ( "github.com/modelcontextprotocol/go-sdk/mcp" - cloudbuild "google.golang.org/api/cloudbuild/v1" - cloudbuildclient "devops-mcp-server/cloudbuild/client" iamclient "devops-mcp-server/iam/client" resourcemanagerclient "devops-mcp-server/resourcemanager/client" + + cloudbuildpb "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb" ) // Handler holds the clients for the cloudbuild service. @@ -194,8 +194,8 @@ func addGetBuildInfoTool(server *mcp.Server, cbClient cloudbuildclient.CloudBuil func addStartBuildTool(server *mcp.Server, cbClient cloudbuildclient.CloudBuildClient) { startBuildToolFunc := func(ctx context.Context, req *mcp.CallToolRequest, args StartBuildArgs) (*mcp.CallToolResult, any, error) { - source:= &cloudbuild.Source{ - StorageSource: &cloudbuild.StorageSource{ + source:= &cloudbuildpb.Source{ + StorageSource: &cloudbuildpb.StorageSource{ Bucket: args.Bucket, Object: args.Object, }, From b8d5e425becc6e8a04e9f329ff2f6c661740e48b Mon Sep 17 00:00:00 2001 From: aliciatang07 Date: Thu, 5 Feb 2026 15:30:26 +0000 Subject: [PATCH 10/14] debug wrong type --- devops-mcp-server/cloudbuild/cloudbuild.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devops-mcp-server/cloudbuild/cloudbuild.go b/devops-mcp-server/cloudbuild/cloudbuild.go index 4939541..7c36b6e 100644 --- a/devops-mcp-server/cloudbuild/cloudbuild.go +++ b/devops-mcp-server/cloudbuild/cloudbuild.go @@ -194,7 +194,7 @@ func addGetBuildInfoTool(server *mcp.Server, cbClient cloudbuildclient.CloudBuil func addStartBuildTool(server *mcp.Server, cbClient cloudbuildclient.CloudBuildClient) { startBuildToolFunc := func(ctx context.Context, req *mcp.CallToolRequest, args StartBuildArgs) (*mcp.CallToolResult, any, error) { - source:= &cloudbuildpb.Source{ + source:= &cloudbuildpb.Source_StorageSource{ StorageSource: &cloudbuildpb.StorageSource{ Bucket: args.Bucket, Object: args.Object, From 49e2bf6bc8fdf634a9d4d3e1957c96e8bfcf1616 Mon Sep 17 00:00:00 2001 From: aliciatang07 Date: Thu, 5 Feb 2026 18:17:19 +0000 Subject: [PATCH 11/14] debug --- devops-mcp-server/cloudbuild/cloudbuild.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/devops-mcp-server/cloudbuild/cloudbuild.go b/devops-mcp-server/cloudbuild/cloudbuild.go index 7c36b6e..2244f5e 100644 --- a/devops-mcp-server/cloudbuild/cloudbuild.go +++ b/devops-mcp-server/cloudbuild/cloudbuild.go @@ -194,13 +194,14 @@ func addGetBuildInfoTool(server *mcp.Server, cbClient cloudbuildclient.CloudBuil func addStartBuildTool(server *mcp.Server, cbClient cloudbuildclient.CloudBuildClient) { startBuildToolFunc := func(ctx context.Context, req *mcp.CallToolRequest, args StartBuildArgs) (*mcp.CallToolResult, any, error) { - source:= &cloudbuildpb.Source_StorageSource{ - StorageSource: &cloudbuildpb.StorageSource{ - Bucket: args.Bucket, - Object: args.Object, + source:= &cloudbuildpb.Source{ + Source: &cloudbuildpb.Source_StorageSource{ + StorageSource: &cloudbuildpb.StorageSource{ + Bucket: args.Bucket, + Object: args.Object, + }, }, } - res, err := cbClient.StartBuild(ctx, args.ProjectID, source) if err != nil { return &mcp.CallToolResult{}, nil, fmt.Errorf("failed to start build: %w", err) From 76dbe7b2a49074ddffbb5d1b8eeb145200a609e6 Mon Sep 17 00:00:00 2001 From: aliciatang07 Date: Mon, 9 Feb 2026 16:08:15 +0000 Subject: [PATCH 12/14] reslove CRA suggestions --- .../cloudbuild/client/cloudbuildclient.go | 21 +++++++++++-------- devops-mcp-server/cloudbuild/cloudbuild.go | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/devops-mcp-server/cloudbuild/client/cloudbuildclient.go b/devops-mcp-server/cloudbuild/client/cloudbuildclient.go index 04ae564..45a2879 100644 --- a/devops-mcp-server/cloudbuild/client/cloudbuildclient.go +++ b/devops-mcp-server/cloudbuild/client/cloudbuildclient.go @@ -55,7 +55,7 @@ type CloudBuildClient interface { RunBuildTrigger(ctx context.Context, projectID, location, triggerID, branch, tag, commitSha string) (*cloudbuild.RunBuildTriggerOperation, error) ListBuilds(ctx context.Context, projectID, location string) ([]*cloudbuildpb.Build, error) GetBuildInfo(ctx context.Context, projectID, location, buildID string) (BuildInfo, error) - StartBuild(ctx context.Context, projectID string, source *cloudbuildpb.Source) (*cloudbuild.CreateBuildOperation, error) + StartBuild(ctx context.Context, projectID, location string, source *cloudbuildpb.Source) (*cloudbuild.CreateBuildOperation, error) } type BuildInfo struct { @@ -63,8 +63,6 @@ type BuildInfo struct { Logs string } - - // NewCloudBuildClient creates a new Cloud Build client. func NewCloudBuildClient(ctx context.Context) (CloudBuildClient, error) { c, err := cloudbuild.NewClient(ctx) @@ -83,10 +81,10 @@ func NewCloudBuildClient(ctx context.Context) (CloudBuildClient, error) { } return &CloudBuildClientImpl{ - v1client: c, - legacyClient: c2, + v1client: c, + legacyClient: c2, loggingClient: loggingClient, - }, nil + }, nil } // CloudBuildClientImpl is an implementation of the CloudBuildClient interface. @@ -252,7 +250,12 @@ func (c *CloudBuildClientImpl) GetBuildInfo(ctx context.Context, projectID, loca case *loggingpb.LogEntry_TextPayload: logMessage = payload.TextPayload case *loggingpb.LogEntry_JsonPayload: - logMessage = fmt.Sprintf("%v", payload.JsonPayload) + jsonBytes, err := protojson.Marshal(payload.JsonPayload) + if err != nil { + logMessage = fmt.Sprintf("failed to marshal json payload to string: %v", err) + } else { + logMessage = string(jsonBytes) + } case *loggingpb.LogEntry_ProtoPayload: logMessage = fmt.Sprintf("%v", payload.ProtoPayload) default: @@ -264,9 +267,9 @@ func (c *CloudBuildClientImpl) GetBuildInfo(ctx context.Context, projectID, loca return info, nil } -func (c *CloudBuildClientImpl) StartBuild(ctx context.Context, projectID string, source *cloudbuildpb.Source) (*cloudbuild.CreateBuildOperation, error) { +func (c *CloudBuildClientImpl) StartBuild(ctx context.Context, projectID, location string, source *cloudbuildpb.Source) (*cloudbuild.CreateBuildOperation, error) { req := &cloudbuildpb.CreateBuildRequest{ - Parent: fmt.Sprintf("projects/%s/locations/global", projectID), + Parent: fmt.Sprintf("projects/%s/locations/%s", projectID, location), Build: &cloudbuildpb.Build{Source: source}, } ops, err := c.v1client.CreateBuild(ctx, req) diff --git a/devops-mcp-server/cloudbuild/cloudbuild.go b/devops-mcp-server/cloudbuild/cloudbuild.go index 2244f5e..8830697 100644 --- a/devops-mcp-server/cloudbuild/cloudbuild.go +++ b/devops-mcp-server/cloudbuild/cloudbuild.go @@ -208,5 +208,5 @@ func addStartBuildTool(server *mcp.Server, cbClient cloudbuildclient.CloudBuildC } return &mcp.CallToolResult{}, res, nil } - mcp.AddTool(server, &mcp.Tool{Name: "cloudbuild.start_build", Description: "Starts a new Cloud Build with the given source locally."}, startBuildToolFunc) + mcp.AddTool(server, &mcp.Tool{Name: "cloudbuild.start_build", Description: "Starts a new Cloud Build from a source in Google Cloud Storage."}, startBuildToolFunc) } From ba4497a0cb31c7bcc3b7b7abb7634f9b4f1ac004 Mon Sep 17 00:00:00 2001 From: aliciatang07 Date: Mon, 9 Feb 2026 17:49:16 +0000 Subject: [PATCH 13/14] add protojson import --- devops-mcp-server/cloudbuild/client/cloudbuildclient.go | 1 + devops-mcp-server/go.mod | 1 + 2 files changed, 2 insertions(+) diff --git a/devops-mcp-server/cloudbuild/client/cloudbuildclient.go b/devops-mcp-server/cloudbuild/client/cloudbuildclient.go index 45a2879..6213fc2 100644 --- a/devops-mcp-server/cloudbuild/client/cloudbuildclient.go +++ b/devops-mcp-server/cloudbuild/client/cloudbuildclient.go @@ -23,6 +23,7 @@ import ( logging "cloud.google.com/go/logging/apiv2" build "google.golang.org/api/cloudbuild/v1" "google.golang.org/api/iterator" + "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/types/known/timestamppb" cloudbuildpb "cloud.google.com/go/cloudbuild/apiv1/v2/cloudbuildpb" diff --git a/devops-mcp-server/go.mod b/devops-mcp-server/go.mod index bdc2ff0..238f7f9 100644 --- a/devops-mcp-server/go.mod +++ b/devops-mcp-server/go.mod @@ -7,6 +7,7 @@ require ( cloud.google.com/go/auth v0.17.0 cloud.google.com/go/cloudbuild v1.23.1 cloud.google.com/go/iam v1.5.3 + cloud.google.com/go/logging v1.13.0 cloud.google.com/go/resourcemanager v1.10.7 cloud.google.com/go/run v1.12.1 cloud.google.com/go/storage v1.57.0 From 71e2c9b7237992b3a436d6c34fffd32971ebd722 Mon Sep 17 00:00:00 2001 From: aliciatang07 Date: Mon, 9 Feb 2026 18:34:57 +0000 Subject: [PATCH 14/14] fix wrong parameter --- devops-mcp-server/cloudbuild/cloudbuild.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devops-mcp-server/cloudbuild/cloudbuild.go b/devops-mcp-server/cloudbuild/cloudbuild.go index 8830697..0a4c63f 100644 --- a/devops-mcp-server/cloudbuild/cloudbuild.go +++ b/devops-mcp-server/cloudbuild/cloudbuild.go @@ -202,7 +202,7 @@ func addStartBuildTool(server *mcp.Server, cbClient cloudbuildclient.CloudBuildC }, }, } - res, err := cbClient.StartBuild(ctx, args.ProjectID, source) + res, err := cbClient.StartBuild(ctx, args.ProjectID, args.Location, source) if err != nil { return &mcp.CallToolResult{}, nil, fmt.Errorf("failed to start build: %w", err) }