package buildkite
import (
"encoding/json"
"fmt"
"github.com/buildkite/buildkite-mcp-server/pkg/tokens"
"github.com/buildkite/go-buildkite/v4"
"github.com/mark3labs/mcp-go/mcp"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/trace"
)
type PaginatedResult[T any] struct {
Headers map[string]string `json:"headers"`
Items []T `json:"items"`
}
func optionalPaginationParams(r mcp.CallToolRequest) (buildkite.ListOptions, error) {
page := r.GetInt("page", 1)
perPage := r.GetInt("perPage", 100)
return buildkite.ListOptions{
Page: page,
PerPage: perPage,
}, nil
}
func withPagination() mcp.ToolOption {
return func(tool *mcp.Tool) {
mcp.WithNumber("page",
mcp.Description("Page number for pagination (min 1)"),
mcp.Min(1),
)(tool)
mcp.WithNumber("perPage",
mcp.Description("Results per page for pagination (min 1, max 100)"),
mcp.Min(1),
mcp.Max(100),
)(tool)
}
}
// ClientSidePaginationParams represents parameters for client-side pagination
type ClientSidePaginationParams struct {
Page int
PerPage int
}
// ClientSidePaginatedResult represents a paginated result for client-side pagination
type ClientSidePaginatedResult[T any] struct {
Items []T `json:"items"`
Page int `json:"page"`
PerPage int `json:"per_page"`
Total int `json:"total"`
TotalPages int `json:"total_pages"`
HasNext bool `json:"has_next"`
HasPrev bool `json:"has_prev"`
}
// withClientSidePagination adds client-side pagination options to a tool
func withClientSidePagination() mcp.ToolOption {
return func(tool *mcp.Tool) {
mcp.WithNumber("page",
mcp.Description("Page number for pagination (min 1)"),
mcp.Min(1),
)(tool)
mcp.WithNumber("perPage",
mcp.Description("Results per page for pagination (min 1, max 100)"),
mcp.Min(1),
mcp.Max(100),
)(tool)
}
}
// getClientSidePaginationParams extracts client-side pagination parameters from request
// Always returns pagination params with sensible defaults
func getClientSidePaginationParams(r mcp.CallToolRequest) ClientSidePaginationParams {
page := r.GetInt("page", 1)
perPage := r.GetInt("perPage", 25) // Default page size for client-side pagination
return ClientSidePaginationParams{
Page: page,
PerPage: perPage,
}
}
// applyClientSidePagination applies client-side pagination to a slice of items
func applyClientSidePagination[T any](items []T, params ClientSidePaginationParams) ClientSidePaginatedResult[T] {
total := len(items)
totalPages := (total + params.PerPage - 1) / params.PerPage
if totalPages == 0 {
totalPages = 1
}
startIndex := (params.Page - 1) * params.PerPage
endIndex := startIndex + params.PerPage
var paginatedItems []T
if startIndex >= total {
paginatedItems = []T{}
} else {
if endIndex > total {
endIndex = total
}
paginatedItems = items[startIndex:endIndex]
}
return ClientSidePaginatedResult[T]{
Items: paginatedItems,
Page: params.Page,
PerPage: params.PerPage,
Total: total,
TotalPages: totalPages,
HasNext: params.Page < totalPages,
HasPrev: params.Page > 1,
}
}
func mcpTextResult(span trace.Span, result any) (*mcp.CallToolResult, error) {
r, err := json.Marshal(result)
if err != nil {
return mcp.NewToolResultError(fmt.Sprintf("failed to marshal result: %v", err)), nil
}
span.SetAttributes(
attribute.Int("estimated_tokens", tokens.EstimateTokens(string(r))),
)
return mcp.NewToolResultText(string(r)), nil
}