@@ -3,21 +3,31 @@ package harness
3
3
import (
4
4
"context"
5
5
"fmt"
6
+ "io"
7
+ "net/http"
8
+ "os"
9
+ "path/filepath"
6
10
7
11
"github.com/harness/harness-mcp/client"
8
12
"github.com/harness/harness-mcp/cmd/harness-mcp-server/config"
9
13
"github.com/mark3labs/mcp-go/mcp"
10
14
"github.com/mark3labs/mcp-go/server"
11
15
)
12
16
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" ),
17
23
mcp .WithString ("plan_execution_id" ,
18
24
mcp .Required (),
19
25
mcp .Description ("The ID of the plan execution" ),
20
26
),
27
+ mcp .WithString ("logs_directory" ,
28
+ mcp .Required (),
29
+ mcp .Description ("The absolute path to the directory where the logs should get downloaded" ),
30
+ ),
21
31
WithScope (config , true ),
22
32
),
23
33
func (ctx context.Context , request mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
@@ -36,7 +46,64 @@ func FetchLogDownloadURLTool(config *config.Config, client *client.Client) (tool
36
46
return nil , fmt .Errorf ("failed to fetch log download URL: %w" , err )
37
47
}
38
48
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 )
40
107
41
108
return mcp .NewToolResultText (instruction ), nil
42
109
}
0 commit comments