@@ -12,6 +12,11 @@ import (
1212// AnnotationsClient describes the subset of the Buildkite client we need for annotations.
1313type AnnotationsClient interface {
1414 ListByBuild (ctx context.Context , org , pipelineSlug , buildNumber string , opts * buildkite.AnnotationListOptions ) ([]buildkite.Annotation , * buildkite.Response , error )
15+ Create (ctx context.Context , org , pipelineSlug , buildNumber string , ac buildkite.AnnotationCreate ) (buildkite.Annotation , * buildkite.Response , error )
16+ Delete (ctx context.Context , org , pipelineSlug , buildNumber , annotationID string ) (* buildkite.Response , error )
17+ ListByJob (ctx context.Context , org , pipelineSlug , buildNumber , jobID string , opts * buildkite.AnnotationListOptions ) ([]buildkite.Annotation , * buildkite.Response , error )
18+ CreateForJob (ctx context.Context , org , pipelineSlug , buildNumber , jobID string , ac buildkite.AnnotationCreate ) (buildkite.Annotation , * buildkite.Response , error )
19+ DeleteForJob (ctx context.Context , org , pipelineSlug , buildNumber , jobID , annotationID string ) (* buildkite.Response , error )
1520}
1621
1722type ListAnnotationsArgs struct {
@@ -22,13 +27,60 @@ type ListAnnotationsArgs struct {
2227 PerPage int `json:"per_page,omitempty" jsonschema:"Results per page for pagination (min 1\\, max 100)"`
2328}
2429
30+ type CreateAnnotationArgs struct {
31+ OrgSlug string `json:"org_slug"`
32+ PipelineSlug string `json:"pipeline_slug"`
33+ BuildNumber string `json:"build_number"`
34+ Body string `json:"body" jsonschema:"The annotation body as HTML or Markdown"`
35+ Style string `json:"style,omitempty" jsonschema:"Optional annotation style: success\, info\, warning\, or error"`
36+ Priority int `json:"priority,omitempty" jsonschema:"Optional annotation priority from 1 to 10"`
37+ Context string `json:"context,omitempty" jsonschema:"Optional annotation context used to identify or append to an annotation"`
38+ Append bool `json:"append,omitempty" jsonschema:"Append the body to an existing annotation with the same context"`
39+ }
40+
41+ type DeleteAnnotationArgs struct {
42+ OrgSlug string `json:"org_slug"`
43+ PipelineSlug string `json:"pipeline_slug"`
44+ BuildNumber string `json:"build_number"`
45+ AnnotationID string `json:"annotation_id"`
46+ }
47+
48+ type ListJobAnnotationsArgs struct {
49+ OrgSlug string `json:"org_slug"`
50+ PipelineSlug string `json:"pipeline_slug"`
51+ BuildNumber string `json:"build_number"`
52+ JobID string `json:"job_id"`
53+ Page int `json:"page,omitempty" jsonschema:"Page number for pagination (min 1)"`
54+ PerPage int `json:"per_page,omitempty" jsonschema:"Results per page for pagination (min 1\\, max 100)"`
55+ }
56+
57+ type CreateJobAnnotationArgs struct {
58+ OrgSlug string `json:"org_slug"`
59+ PipelineSlug string `json:"pipeline_slug"`
60+ BuildNumber string `json:"build_number"`
61+ JobID string `json:"job_id"`
62+ Body string `json:"body" jsonschema:"The annotation body as HTML or Markdown"`
63+ Style string `json:"style,omitempty" jsonschema:"Optional annotation style: success\, info\, warning\, or error"`
64+ Priority int `json:"priority,omitempty" jsonschema:"Optional annotation priority from 1 to 10"`
65+ Context string `json:"context,omitempty" jsonschema:"Optional annotation context used to identify or append to an annotation"`
66+ Append bool `json:"append,omitempty" jsonschema:"Append the body to an existing annotation with the same context"`
67+ }
68+
69+ type DeleteJobAnnotationArgs struct {
70+ OrgSlug string `json:"org_slug"`
71+ PipelineSlug string `json:"pipeline_slug"`
72+ BuildNumber string `json:"build_number"`
73+ JobID string `json:"job_id"`
74+ AnnotationID string `json:"annotation_id"`
75+ }
76+
2577// ListAnnotations returns an MCP tool + handler pair that lists annotations for a build.
2678func ListAnnotations () (mcp.Tool , mcp.ToolHandlerFor [ListAnnotationsArgs , any ], []string ) {
2779 return mcp.Tool {
2880 Name : "list_annotations" ,
29- Description : "List all annotations for a build, including their context, style (success/info/warning/error) , rendered HTML content, and creation timestamps" ,
81+ Description : "List all annotations for a build, including their context, scope, style , rendered HTML content, and timestamps" ,
3082 Annotations : & mcp.ToolAnnotations {
31- Title : "List Annotations" ,
83+ Title : "List Build Annotations" ,
3284 ReadOnlyHint : true ,
3385 },
3486 }, func (ctx context.Context , request * mcp.CallToolRequest , args ListAnnotationsArgs ) (* mcp.CallToolResult , any , error ) {
@@ -67,3 +119,192 @@ func ListAnnotations() (mcp.Tool, mcp.ToolHandlerFor[ListAnnotationsArgs, any],
67119 return mcpTextResult (span , & result )
68120 }, []string {"read_builds" }
69121}
122+
123+ func CreateAnnotation () (mcp.Tool , mcp.ToolHandlerFor [CreateAnnotationArgs , any ], []string ) {
124+ return mcp.Tool {
125+ Name : "create_annotation" ,
126+ Description : "Create a build-level annotation on a Buildkite build using HTML or Markdown content" ,
127+ Annotations : & mcp.ToolAnnotations {
128+ Title : "Create Build Annotation" ,
129+ },
130+ }, func (ctx context.Context , request * mcp.CallToolRequest , args CreateAnnotationArgs ) (* mcp.CallToolResult , any , error ) {
131+ ctx , span := trace .Start (ctx , "buildkite.CreateAnnotation" )
132+ defer span .End ()
133+
134+ span .SetAttributes (
135+ attribute .String ("org_slug" , args .OrgSlug ),
136+ attribute .String ("pipeline_slug" , args .PipelineSlug ),
137+ attribute .String ("build_number" , args .BuildNumber ),
138+ attribute .String ("context" , args .Context ),
139+ attribute .String ("style" , args .Style ),
140+ attribute .Int ("priority" , args .Priority ),
141+ attribute .Bool ("append" , args .Append ),
142+ )
143+
144+ deps := DepsFromContext (ctx )
145+ annotation , _ , err := deps .AnnotationsClient .Create (ctx , args .OrgSlug , args .PipelineSlug , args .BuildNumber , buildkite.AnnotationCreate {
146+ Body : args .Body ,
147+ Context : args .Context ,
148+ Style : args .Style ,
149+ Priority : args .Priority ,
150+ Append : args .Append ,
151+ })
152+ if err != nil {
153+ return handleBuildkiteError (err )
154+ }
155+
156+ return mcpTextResult (span , & annotation )
157+ }, []string {"write_builds" }
158+ }
159+
160+ func DeleteAnnotation () (mcp.Tool , mcp.ToolHandlerFor [DeleteAnnotationArgs , any ], []string ) {
161+ return mcp.Tool {
162+ Name : "delete_annotation" ,
163+ Description : "Delete a build-level annotation from a Buildkite build" ,
164+ Annotations : & mcp.ToolAnnotations {
165+ Title : "Delete Build Annotation" ,
166+ DestructiveHint : boolPtr (true ),
167+ },
168+ }, func (ctx context.Context , request * mcp.CallToolRequest , args DeleteAnnotationArgs ) (* mcp.CallToolResult , any , error ) {
169+ ctx , span := trace .Start (ctx , "buildkite.DeleteAnnotation" )
170+ defer span .End ()
171+
172+ span .SetAttributes (
173+ attribute .String ("org_slug" , args .OrgSlug ),
174+ attribute .String ("pipeline_slug" , args .PipelineSlug ),
175+ attribute .String ("build_number" , args .BuildNumber ),
176+ attribute .String ("annotation_id" , args .AnnotationID ),
177+ )
178+
179+ deps := DepsFromContext (ctx )
180+ _ , err := deps .AnnotationsClient .Delete (ctx , args .OrgSlug , args .PipelineSlug , args .BuildNumber , args .AnnotationID )
181+ if err != nil {
182+ return handleBuildkiteError (err )
183+ }
184+
185+ return mcpTextResult (span , map [string ]any {
186+ "deleted" : true ,
187+ "scope" : "build" ,
188+ "annotation_id" : args .AnnotationID ,
189+ })
190+ }, []string {"write_builds" }
191+ }
192+
193+ func ListJobAnnotations () (mcp.Tool , mcp.ToolHandlerFor [ListJobAnnotationsArgs , any ], []string ) {
194+ return mcp.Tool {
195+ Name : "list_job_annotations" ,
196+ Description : "List all annotations for a specific job, including their context, scope, style, rendered HTML content, and timestamps" ,
197+ Annotations : & mcp.ToolAnnotations {
198+ Title : "List Job Annotations" ,
199+ ReadOnlyHint : true ,
200+ },
201+ }, func (ctx context.Context , request * mcp.CallToolRequest , args ListJobAnnotationsArgs ) (* mcp.CallToolResult , any , error ) {
202+ ctx , span := trace .Start (ctx , "buildkite.ListJobAnnotations" )
203+ defer span .End ()
204+
205+ paginationParams := paginationFromArgs (args .Page , args .PerPage )
206+
207+ span .SetAttributes (
208+ attribute .String ("org_slug" , args .OrgSlug ),
209+ attribute .String ("pipeline_slug" , args .PipelineSlug ),
210+ attribute .String ("build_number" , args .BuildNumber ),
211+ attribute .String ("job_id" , args .JobID ),
212+ attribute .Int ("page" , paginationParams .Page ),
213+ attribute .Int ("per_page" , paginationParams .PerPage ),
214+ )
215+
216+ deps := DepsFromContext (ctx )
217+ annotations , resp , err := deps .AnnotationsClient .ListByJob (ctx , args .OrgSlug , args .PipelineSlug , args .BuildNumber , args .JobID , & buildkite.AnnotationListOptions {
218+ ListOptions : paginationParams ,
219+ })
220+ if err != nil {
221+ return handleBuildkiteError (err )
222+ }
223+
224+ result := PaginatedResult [buildkite.Annotation ]{
225+ Items : annotations ,
226+ Headers : map [string ]string {
227+ "Link" : resp .Header .Get ("Link" ),
228+ },
229+ }
230+
231+ span .SetAttributes (
232+ attribute .Int ("item_count" , len (annotations )),
233+ )
234+
235+ return mcpTextResult (span , & result )
236+ }, []string {"read_builds" }
237+ }
238+
239+ func CreateJobAnnotation () (mcp.Tool , mcp.ToolHandlerFor [CreateJobAnnotationArgs , any ], []string ) {
240+ return mcp.Tool {
241+ Name : "create_job_annotation" ,
242+ Description : "Create a job-level annotation on a specific Buildkite job using HTML or Markdown content" ,
243+ Annotations : & mcp.ToolAnnotations {
244+ Title : "Create Job Annotation" ,
245+ },
246+ }, func (ctx context.Context , request * mcp.CallToolRequest , args CreateJobAnnotationArgs ) (* mcp.CallToolResult , any , error ) {
247+ ctx , span := trace .Start (ctx , "buildkite.CreateJobAnnotation" )
248+ defer span .End ()
249+
250+ span .SetAttributes (
251+ attribute .String ("org_slug" , args .OrgSlug ),
252+ attribute .String ("pipeline_slug" , args .PipelineSlug ),
253+ attribute .String ("build_number" , args .BuildNumber ),
254+ attribute .String ("job_id" , args .JobID ),
255+ attribute .String ("context" , args .Context ),
256+ attribute .String ("style" , args .Style ),
257+ attribute .Int ("priority" , args .Priority ),
258+ attribute .Bool ("append" , args .Append ),
259+ )
260+
261+ deps := DepsFromContext (ctx )
262+ annotation , _ , err := deps .AnnotationsClient .CreateForJob (ctx , args .OrgSlug , args .PipelineSlug , args .BuildNumber , args .JobID , buildkite.AnnotationCreate {
263+ Body : args .Body ,
264+ Context : args .Context ,
265+ Style : args .Style ,
266+ Priority : args .Priority ,
267+ Append : args .Append ,
268+ })
269+ if err != nil {
270+ return handleBuildkiteError (err )
271+ }
272+
273+ return mcpTextResult (span , & annotation )
274+ }, []string {"write_builds" }
275+ }
276+
277+ func DeleteJobAnnotation () (mcp.Tool , mcp.ToolHandlerFor [DeleteJobAnnotationArgs , any ], []string ) {
278+ return mcp.Tool {
279+ Name : "delete_job_annotation" ,
280+ Description : "Delete a job-level annotation from a specific job in a Buildkite build" ,
281+ Annotations : & mcp.ToolAnnotations {
282+ Title : "Delete Job Annotation" ,
283+ DestructiveHint : boolPtr (true ),
284+ },
285+ }, func (ctx context.Context , request * mcp.CallToolRequest , args DeleteJobAnnotationArgs ) (* mcp.CallToolResult , any , error ) {
286+ ctx , span := trace .Start (ctx , "buildkite.DeleteJobAnnotation" )
287+ defer span .End ()
288+
289+ span .SetAttributes (
290+ attribute .String ("org_slug" , args .OrgSlug ),
291+ attribute .String ("pipeline_slug" , args .PipelineSlug ),
292+ attribute .String ("build_number" , args .BuildNumber ),
293+ attribute .String ("job_id" , args .JobID ),
294+ attribute .String ("annotation_id" , args .AnnotationID ),
295+ )
296+
297+ deps := DepsFromContext (ctx )
298+ _ , err := deps .AnnotationsClient .DeleteForJob (ctx , args .OrgSlug , args .PipelineSlug , args .BuildNumber , args .JobID , args .AnnotationID )
299+ if err != nil {
300+ return handleBuildkiteError (err )
301+ }
302+
303+ return mcpTextResult (span , map [string ]any {
304+ "deleted" : true ,
305+ "scope" : "job" ,
306+ "job_id" : args .JobID ,
307+ "annotation_id" : args .AnnotationID ,
308+ })
309+ }, []string {"write_builds" }
310+ }
0 commit comments