Skip to content

Commit 9e099a7

Browse files
committed
add support for logs URLs, fix pagination
1 parent 3c17c21 commit 9e099a7

File tree

7 files changed

+131
-20
lines changed

7 files changed

+131
-20
lines changed

client/client.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ type Client struct {
5151
PullRequests *PullRequestService
5252
Pipelines *PipelineService
5353
Repositories *RepositoryService
54+
Logs *LogService
5455
}
5556

5657
type service struct {
@@ -94,6 +95,7 @@ func (c *Client) initialize() error {
9495
c.PullRequests = &PullRequestService{client: c}
9596
c.Pipelines = &PipelineService{client: c}
9697
c.Repositories = &RepositoryService{client: c}
98+
c.Logs = &LogService{client: c}
9799

98100
return nil
99101
}

client/dto/logs.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package dto
2+
3+
import (
4+
"time"
5+
)
6+
7+
// LogDownloadResponse represents the response from the log download API
8+
type LogDownloadResponse struct {
9+
Link string `json:"link"`
10+
Status string `json:"status"`
11+
Expires time.Time `json:"expires"`
12+
}

client/logs.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package client
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/harness/harness-mcp/client/dto"
8+
)
9+
10+
const (
11+
logDownloadPath = "log-service/blob/download"
12+
)
13+
14+
// LogService handles operations related to pipeline logs
15+
type LogService struct {
16+
client *Client
17+
}
18+
19+
// DownloadLogs fetches a download URL for pipeline execution logs
20+
func (l *LogService) DownloadLogs(ctx context.Context, scope dto.Scope, planExecutionID string) (string, error) {
21+
// First, get the pipeline execution details to determine the prefix format
22+
pipelineService := &PipelineService{client: l.client}
23+
execution, err := pipelineService.GetExecution(ctx, scope, planExecutionID)
24+
if err != nil {
25+
return "", fmt.Errorf("failed to get execution details: %w", err)
26+
}
27+
28+
// Build the prefix based on the execution details
29+
var prefix string
30+
if execution.Data.ShouldUseSimplifiedBaseKey {
31+
// Simplified key format
32+
prefix = fmt.Sprintf("%s/pipeline/%s/%d/-%s",
33+
scope.AccountID,
34+
execution.Data.PipelineIdentifier,
35+
execution.Data.RunSequence,
36+
planExecutionID)
37+
} else {
38+
// Standard key format
39+
prefix = fmt.Sprintf("accountId:%s/orgId:%s/projectId:%s/pipelineId:%s/runSequence:%d/level0:pipeline",
40+
scope.AccountID,
41+
execution.Data.OrgIdentifier,
42+
execution.Data.ProjectIdentifier,
43+
execution.Data.PipelineIdentifier,
44+
execution.Data.RunSequence)
45+
}
46+
47+
// Prepare query parameters
48+
params := make(map[string]string)
49+
params["accountID"] = scope.AccountID
50+
params["prefix"] = prefix
51+
52+
// Initialize the response object
53+
response := &dto.LogDownloadResponse{}
54+
55+
// Make the POST request
56+
err = l.client.Post(ctx, logDownloadPath, params, nil, response)
57+
if err != nil {
58+
return "", fmt.Errorf("failed to fetch log download URL: %w", err)
59+
}
60+
61+
return response.Link, nil
62+
}

pkg/harness/logs.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package harness
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/harness/harness-mcp/client"
8+
"github.com/harness/harness-mcp/cmd/harness-mcp-server/config"
9+
"github.com/mark3labs/mcp-go/mcp"
10+
"github.com/mark3labs/mcp-go/server"
11+
)
12+
13+
// FetchLogDownloadURLTool creates a tool for fetching log download URLs for pipeline executions
14+
func FetchLogDownloadURLTool(config *config.Config, client *client.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) {
15+
return mcp.NewTool("fetch_log_download_url",
16+
mcp.WithDescription("Fetch the download URL for pipeline execution logs in Harness."),
17+
mcp.WithString("plan_execution_id",
18+
mcp.Required(),
19+
mcp.Description("The ID of the plan execution"),
20+
),
21+
WithScope(config, true),
22+
),
23+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
24+
planExecutionID, err := requiredParam[string](request, "plan_execution_id")
25+
if err != nil {
26+
return mcp.NewToolResultError(err.Error()), nil
27+
}
28+
29+
scope, err := fetchScope(config, request, true)
30+
if err != nil {
31+
return mcp.NewToolResultError(err.Error()), nil
32+
}
33+
34+
logDownloadURL, err := client.Logs.DownloadLogs(ctx, scope, planExecutionID)
35+
if err != nil {
36+
return nil, fmt.Errorf("failed to fetch log download URL: %w", err)
37+
}
38+
39+
instruction := fmt.Sprintf("You can use the command \"curl -o logs.zip %s\" to download the zip for the logs.", logDownloadURL)
40+
41+
return mcp.NewToolResultText(instruction), nil
42+
}
43+
}

pkg/harness/pagination.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@ import "github.com/mark3labs/mcp-go/mcp"
66
func WithPagination() mcp.ToolOption {
77
return func(tool *mcp.Tool) {
88
mcp.WithNumber("page",
9-
mcp.Description("Page number for pagination"),
9+
mcp.Description("Page number for pagination - page 0 is the first page"),
10+
mcp.Min(0),
11+
mcp.DefaultNumber(0),
1012
)(tool)
1113
mcp.WithNumber("size",
1214
mcp.Description("Number of items per page"),
15+
mcp.DefaultNumber(5),
16+
mcp.Max(20),
1317
)(tool)
1418
}
1519
}
@@ -23,7 +27,6 @@ func fetchPagination(request mcp.CallToolRequest) (page, size int, err error) {
2327
if pageVal != 0 {
2428
page = int(pageVal)
2529
}
26-
2730
sizeVal, err := OptionalIntParamWithDefault(request, "size", 5)
2831
if err != nil {
2932
return 0, 0, err

pkg/harness/pipelines.go

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,6 @@ func ListPipelinesTool(config *config.Config, client *client.Client) (tool mcp.T
5151
mcp.WithString("search_term",
5252
mcp.Description("Optional search term to filter pipelines"),
5353
),
54-
mcp.WithNumber("page",
55-
mcp.Description("Page number for pagination"),
56-
mcp.DefaultNumber(0),
57-
),
58-
mcp.WithNumber("size",
59-
mcp.Description("Number of items per page"),
60-
mcp.DefaultNumber(5),
61-
mcp.Max(20),
62-
),
6354
WithScope(config, true),
6455
WithPagination(),
6556
),
@@ -191,15 +182,6 @@ func ListExecutionsTool(config *config.Config, client *client.Client) (tool mcp.
191182
mcp.WithBoolean("my_deployments",
192183
mcp.Description("Optional flag to show only my deployments"),
193184
),
194-
mcp.WithNumber("page",
195-
mcp.Description("Page number for pagination"),
196-
mcp.DefaultNumber(0),
197-
),
198-
mcp.WithNumber("size",
199-
mcp.Description("Number of items per page"),
200-
mcp.DefaultNumber(5),
201-
mcp.Max(20),
202-
),
203185
WithScope(config, true),
204186
WithPagination(),
205187
),

pkg/harness/tools.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,17 @@ func InitToolsets(client *client.Client, config *config.Config) (*toolsets.Tools
4141
toolsets.NewServerTool(ListRepositoriesTool(config, client)),
4242
)
4343

44+
// Create the logs toolset
45+
logs := toolsets.NewToolset("logs", "Harness Logs related tools").
46+
AddReadTools(
47+
toolsets.NewServerTool(FetchLogDownloadURLTool(config, client)),
48+
)
49+
4450
// Add toolsets to the group
4551
tsg.AddToolset(pullrequests)
4652
tsg.AddToolset(pipelines)
4753
tsg.AddToolset(repositories)
54+
tsg.AddToolset(logs)
4855

4956
// Enable requested toolsets
5057
if err := tsg.EnableToolsets(config.Toolsets); err != nil {

0 commit comments

Comments
 (0)