Skip to content

Commit 76facd1

Browse files
committed
download logs completely instead of just returning the URL
1 parent 9e099a7 commit 76facd1

File tree

2 files changed

+73
-6
lines changed

2 files changed

+73
-6
lines changed

pkg/harness/logs.go

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,31 @@ package harness
33
import (
44
"context"
55
"fmt"
6+
"io"
7+
"net/http"
8+
"os"
9+
"path/filepath"
610

711
"github.com/harness/harness-mcp/client"
812
"github.com/harness/harness-mcp/cmd/harness-mcp-server/config"
913
"github.com/mark3labs/mcp-go/mcp"
1014
"github.com/mark3labs/mcp-go/server"
1115
)
1216

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+
// DownloadExecutionLogsTool creates a tool for downloading logs for a pipeline execution
18+
// TODO: to make this easy to use, we ask to pass in an output path and do the complete download of the logs.
19+
// This is less work for the user, but we may want to only return the download instruction instead in the future.
20+
func DownloadExecutionLogsTool(config *config.Config, client *client.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) {
21+
return mcp.NewTool("download_execution_logs",
22+
mcp.WithDescription("Downloads logs for an execution inside Harness"),
1723
mcp.WithString("plan_execution_id",
1824
mcp.Required(),
1925
mcp.Description("The ID of the plan execution"),
2026
),
27+
mcp.WithString("logs_directory",
28+
mcp.Required(),
29+
mcp.Description("The absolute path to the directory where the logs should get downloaded"),
30+
),
2131
WithScope(config, true),
2232
),
2333
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
@@ -36,7 +46,64 @@ func FetchLogDownloadURLTool(config *config.Config, client *client.Client) (tool
3646
return nil, fmt.Errorf("failed to fetch log download URL: %w", err)
3747
}
3848

39-
instruction := fmt.Sprintf("You can use the command \"curl -o logs.zip %s\" to download the zip for the logs.", logDownloadURL)
49+
logsDirectory, err := requiredParam[string](request, "logs_directory")
50+
if err != nil {
51+
return mcp.NewToolResultError(err.Error()), nil
52+
}
53+
54+
// Check if logs directory exists, if not create it
55+
_, err = os.Stat(logsDirectory)
56+
if err != nil {
57+
createErr := os.Mkdir(logsDirectory, 0755)
58+
if createErr != nil {
59+
return mcp.NewToolResultError(createErr.Error()), nil
60+
}
61+
}
62+
63+
// Create the logs folder with plan execution ID
64+
logsFolderName := fmt.Sprintf("logs-%s", planExecutionID)
65+
logsFolderPath := filepath.Join(logsDirectory, logsFolderName)
66+
67+
err = os.Mkdir(logsFolderPath, 0755)
68+
if err != nil {
69+
return mcp.NewToolResultError(fmt.Sprintf("failed to create logs folder: %v", err)), nil
70+
}
71+
72+
// Get the download URL
73+
logDownloadURL, err = client.Logs.DownloadLogs(ctx, scope, planExecutionID)
74+
if err != nil {
75+
return mcp.NewToolResultError(fmt.Sprintf("failed to fetch log download URL: %v", err)), nil
76+
}
77+
78+
// Download the logs into outputPath
79+
resp, err := http.Get(logDownloadURL)
80+
if err != nil {
81+
return mcp.NewToolResultError(fmt.Sprintf("failed to download logs: %v", err)), nil
82+
}
83+
defer resp.Body.Close()
84+
85+
if resp.StatusCode != http.StatusOK {
86+
return mcp.NewToolResultError(fmt.Sprintf("failed to download logs: unexpected status code %d", resp.StatusCode)), nil
87+
}
88+
89+
// Create the logs.zip file path
90+
logsZipPath := filepath.Join(logsFolderPath, "logs.zip")
91+
92+
// Create the output file
93+
outputFile, err := os.Create(logsZipPath)
94+
if err != nil {
95+
return mcp.NewToolResultError(fmt.Sprintf("failed to create output file: %v", err)), nil
96+
}
97+
defer outputFile.Close()
98+
99+
// Copy the response body to the output file
100+
bytesWritten, err := io.Copy(outputFile, resp.Body)
101+
if err != nil {
102+
return mcp.NewToolResultError(fmt.Sprintf("failed to write logs to file: %v", err)), nil
103+
}
104+
105+
// Success message with download details
106+
instruction := fmt.Sprintf("Successfully downloaded logs to %s (%d bytes)! You can unzip and analyze these logs.", logsZipPath, bytesWritten)
40107

41108
return mcp.NewToolResultText(instruction), nil
42109
}

pkg/harness/tools.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func InitToolsets(client *client.Client, config *config.Config) (*toolsets.Tools
4444
// Create the logs toolset
4545
logs := toolsets.NewToolset("logs", "Harness Logs related tools").
4646
AddReadTools(
47-
toolsets.NewServerTool(FetchLogDownloadURLTool(config, client)),
47+
toolsets.NewServerTool(DownloadExecutionLogsTool(config, client)),
4848
)
4949

5050
// Add toolsets to the group

0 commit comments

Comments
 (0)