Skip to content

Commit 6dcd958

Browse files
sribalijHarness
authored andcommitted
feat:[CCM-24222]: MCP integration for Savings Data for Commitment (#48)
* feat:[CCM-24222]: MCP integration for Savings Data for Commitment * feat:[CCM-24222]: MCP integration for Savings Data for Commitment * feat:[CCM-24222]: MCP integration for Savings Data for Commitment * feat:[CCM-24222]: MCP integration for Savings Data for Commitment * feat:[CCM-24222]: MCP integration for Savings Data for Commitment - README * feat:[CCM-24222]: MCP integration for Savings Data for Commitment
1 parent 10f1ef8 commit 6dcd958

File tree

5 files changed

+156
-3
lines changed

5 files changed

+156
-3
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ Toolset Name: `cloudcostmanagement`
101101
- `get_ccm_metadata`: Retrieves metadata about available cloud connectors, cost data sources, default perspectives, and currency preferences in Harness Cloud Cost Management.
102102
- `ccm_perspective_recommendations`: PerspectiveRecommendations: Returns monthly cost, savings, and a list of open recommendations for a perspective in Harness Cloud Cost Management.
103103
- `get_ccm_commitment_coverage`: Get commitment coverage information for an account in Harness Cloud Cost Management
104+
- `get_ccm_commitment_savings`: Get commitment savings information for an account in Harness Cloud Cost Management
104105

105106
#### Chaos Engineering Toolset
106107

client/ccmcosts.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const (
1818
ccmCostCategoryDetailListPath = ccmBasePath + "/business-mapping?accountIdentifier=%s" // This endpoint lists cost categories
1919
ccmGetCostCategoryPath = ccmBasePath + "/business-mapping/%s?accountIdentifier=%s" // This endpoint lists cost categories
2020
ccmCommitmentCoverageDetailsPath = ccmCommitmentBasePath + "/accounts/%s/v1/detail/compute_coverage?accountIdentifier=%s"
21+
ccmCommitmentSavingsDetailsPath = ccmCommitmentBasePath + "/accounts/%s/v1/detail/savings?accountIdentifier=%s"
2122

2223
ccmCommitmentComputeService string = "Amazon Elastic Compute Cloud - Compute"
2324
)
@@ -185,3 +186,54 @@ func (r *CloudCostManagementService) GetComputeCoverage(ctx context.Context, sco
185186

186187
return coverageRespone, nil
187188
}
189+
190+
func (r *CloudCostManagementService) GetCommitmentSavings(ctx context.Context, scope dto.Scope, opts *dto.CCMCommitmentOptions) (*dto.CCMCommitmentBaseResponse, error) {
191+
path := fmt.Sprintf(ccmCommitmentSavingsDetailsPath, scope.AccountID, scope.AccountID)
192+
params := make(map[string]string)
193+
addScope(scope, params)
194+
195+
// Handle nil options by creating default options
196+
if opts == nil {
197+
opts = &dto.CCMCommitmentOptions{}
198+
}
199+
200+
if opts.StartDate != nil && *opts.StartDate != "" {
201+
params["start_date"] = *opts.StartDate
202+
} else {
203+
// Default to last 30 days
204+
params["start_date"] = utils.FormatUnixToMMDDYYYY(time.Now().AddDate(0, 0, -30).Unix())
205+
}
206+
if opts.EndDate != nil && *opts.EndDate != "" {
207+
params["end_date"] = *opts.EndDate
208+
} else {
209+
// Default to last 30 days
210+
params["end_date"] = utils.CurrentMMDDYYYY()
211+
}
212+
213+
var requestPayload = dto.CCMCommitmentAPIFilter{
214+
Service: ccmCommitmentComputeService, // Default value
215+
216+
}
217+
218+
if opts.Service != nil && *opts.Service != "" {
219+
requestPayload.Service = *opts.Service
220+
}
221+
222+
if len(opts.CloudAccountIDs) > 0 {
223+
requestPayload.CloudAccounts = opts.CloudAccountIDs
224+
}
225+
226+
// Handle is_harness_managed parameter
227+
if opts.IsHarnessManaged != nil {
228+
requestPayload.IsHarnessManaged = opts.IsHarnessManaged
229+
}
230+
231+
savingsResponse := new(dto.CCMCommitmentBaseResponse)
232+
233+
err := r.Client.Post(ctx, path, params, requestPayload, savingsResponse)
234+
if err != nil {
235+
return nil, fmt.Errorf("failed to get cloud cost management compute savings with path %s: %w", path, err)
236+
}
237+
238+
return savingsResponse, nil
239+
}

client/dto/ccmcosts.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ type CCMCommitmentOptions struct {
8686
Service *string `json:"service,omitempty"`
8787
StartDate *string `json:"startDate,omitempty"`
8888
EndDate *string `json:"endDate,omitempty"`
89+
IsHarnessManaged *bool `json:"isHarnessManaged,omitempty"`
8990
}
9091

9192
// CcmCostCategoryList represents a list of cost categories in CCM
@@ -227,6 +228,7 @@ type CCMCommitmentBaseResponse struct {
227228
}
228229

229230
type CCMCommitmentAPIFilter struct {
230-
CloudAccounts []string `json:"cloud_account_ids,omitempty"`
231-
Service string `json:"service,omitempty"`
231+
CloudAccounts []string `json:"cloud_account_ids,omitempty"`
232+
Service string `json:"service,omitempty"`
233+
IsHarnessManaged *bool `json:"is_harness_managed,omitempty"`
232234
}

pkg/harness/ccmcosts.go

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7-
"time"
87
"github.com/harness/harness-mcp/client"
98
"github.com/harness/harness-mcp/client/dto"
109
"github.com/harness/harness-mcp/cmd/harness-mcp-server/config"
1110
"github.com/harness/harness-mcp/pkg/utils"
1211
"github.com/mark3labs/mcp-go/mcp"
1312
"github.com/mark3labs/mcp-go/server"
13+
"time"
1414
)
1515

1616
// GetCcmOverview creates a tool for getting a ccm overview from an account
@@ -368,3 +368,100 @@ func FetchCommitmentCoverageTool(config *config.Config, client *client.CloudCost
368368
return mcp.NewToolResultText(string(r)), nil
369369
}
370370
}
371+
372+
func FetchCommitmentSavingsTool(config *config.Config, client *client.CloudCostManagementService) (tool mcp.Tool, handler server.ToolHandlerFunc) {
373+
return mcp.NewTool("get_ccm_commitment_savings",
374+
mcp.WithDescription("Get commitment savings information for an account in Harness Cloud Cost Management"),
375+
mcp.WithString("start_date",
376+
mcp.Required(),
377+
mcp.Description("Start date to filter commitment savings"),
378+
),
379+
mcp.WithString("end_date",
380+
mcp.Required(),
381+
mcp.Description("End date to filter commitment savings"),
382+
),
383+
mcp.WithBoolean("is_harness_managed",
384+
mcp.Description("Filter results to show only Harness-managed commitments when set to true. When false or omitted, shows all commitments including both Harness-managed and non-Harness-managed ones."),
385+
),
386+
mcp.WithString("service",
387+
mcp.Description("Optional service to filter commitment savings"),
388+
),
389+
mcp.WithArray("cloud_account_ids",
390+
mcp.Description("Optional cloud account IDs to filter commitment savings"),
391+
mcp.Items(map[string]any{
392+
"type": "string",
393+
}),
394+
),
395+
WithScope(config, false),
396+
),
397+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
398+
accountId, err := getAccountID(config, request)
399+
if err != nil {
400+
return mcp.NewToolResultError(err.Error()), nil
401+
}
402+
403+
params := &dto.CCMCommitmentOptions{}
404+
params.AccountIdentifier = &accountId
405+
406+
// Handle service parameter
407+
service, ok, err := OptionalParamOK[string](request, "service")
408+
if err != nil {
409+
return mcp.NewToolResultError(err.Error()), nil
410+
}
411+
if ok && service != "" {
412+
params.Service = &service
413+
}
414+
415+
// Handle cloud account IDs parameter
416+
cloudAccountIDs, ok, err := OptionalParamOK[[]string](request, "cloud_account_ids")
417+
if err != nil {
418+
return mcp.NewToolResultError(err.Error()), nil
419+
}
420+
if ok && len(cloudAccountIDs) > 0 {
421+
params.CloudAccountIDs = cloudAccountIDs
422+
}
423+
424+
// Handle start date parameter
425+
startDate, ok, err := OptionalParamOK[string](request, "start_date")
426+
if err != nil {
427+
return mcp.NewToolResultError(err.Error()), nil
428+
}
429+
if ok && startDate != "" {
430+
params.StartDate = &startDate
431+
}
432+
433+
// Handle end date parameter
434+
endDate, ok, err := OptionalParamOK[string](request, "end_date")
435+
if err != nil {
436+
return mcp.NewToolResultError(err.Error()), nil
437+
}
438+
if ok && endDate != "" {
439+
params.EndDate = &endDate
440+
}
441+
// Handle is_harness_managed parameter
442+
isHarnessManaged, ok, err := OptionalParamOK[bool](request, "is_harness_managed")
443+
if err != nil {
444+
return mcp.NewToolResultError(err.Error()), nil
445+
}
446+
if ok {
447+
params.IsHarnessManaged = &isHarnessManaged
448+
}
449+
450+
scope, err := fetchScope(config, request, false)
451+
if err != nil {
452+
return mcp.NewToolResultError(err.Error()), nil
453+
}
454+
455+
data, err := client.GetCommitmentSavings(ctx, scope, params)
456+
if err != nil {
457+
return nil, fmt.Errorf("failed to get commitment savings: %w", err)
458+
}
459+
460+
r, err := json.Marshal(data)
461+
if err != nil {
462+
return nil, fmt.Errorf("failed to marshal commitment savings: %w", err)
463+
}
464+
465+
return mcp.NewToolResultText(string(r)), nil
466+
}
467+
}

pkg/harness/tools.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,7 @@ func registerCloudCostManagement(config *config.Config, tsg *toolsets.ToolsetGro
584584
toolsets.NewServerTool(CcmMetadataTool(config, ccmClient)),
585585
toolsets.NewServerTool(CcmPerspectiveRecommendationsTool(config, ccmClient)),
586586
toolsets.NewServerTool(FetchCommitmentCoverageTool(config, ccmClient)),
587+
toolsets.NewServerTool(FetchCommitmentSavingsTool(config, ccmClient)),
587588
)
588589

589590
// Add toolset to the group

0 commit comments

Comments
 (0)