Skip to content

Commit 7cf8e7b

Browse files
javrudskyHarness
authored andcommitted
feat: [FME-4269]: Added CCM Get Cost Category detail API integration (#21)
* Removing account identifier param from definition * Removing format issues * Fixing issues from rebase * Added required indicator to parameters * Adding Cloud Cost Management get cost category detail to documentation * Rebase with master * Added Get Cost Category by Id * Added Claud Cost Managment - Categories detail list * Added List Cloud Cost Management tool * [CCM-tools] CCM Overview * Added List Cloud Cost Management tool * Added Claud Cost Managment - Categories detail list * Added List Cloud Cost Management tool * [CCM-tools] CCM Overview * Fixing account id issue * Fixed account id field name * Added List Cloud Cost Management tool * [CCM-tools] CCM Overview
1 parent 640aa65 commit 7cf8e7b

File tree

5 files changed

+102
-31
lines changed

5 files changed

+102
-31
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ Toolset Name: `cloudcostmanagement`
5252
- `get_ccm_overview`: Retrieve the cost overview for a specific account.
5353
- `list_ccm_cost_categories`: List all cost categories names for a specified account.
5454
- `list_ccm_cost_categories_detail`: List all cost categories details for a specified account.
55+
- `get_ccm_cost_category`: Retrieve a cost category detail by Id for a specified account.
5556

5657
#### Logs Toolset
5758

client/cloudcostmanagement.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package client
33
import (
44
"context"
55
"fmt"
6+
"log/slog"
67
"github.com/harness/harness-mcp/client/dto"
78
"github.com/harness/harness-mcp/pkg/utils"
89
)
@@ -12,6 +13,7 @@ const (
1213
ccmGetOverviewPath = ccmBasePath + "/overview?accountIdentifier=%s&startTime=%d&endTime=%d&groupBy=%s"
1314
ccmCostCategoryListPath = ccmBasePath + "/business-mapping/filter-panel?accountIdentifier=%s"
1415
ccmCostCategoryDetailListPath = ccmBasePath + "/business-mapping?accountIdentifier=%s" // This endpoint lists cost categories
16+
ccmGetCostCategoryPath = ccmBasePath + "/business-mapping/%s?accountIdentifier=%s" // This endpoint lists cost categories
1517
)
1618

1719
type CloudCostManagementService struct {
@@ -20,6 +22,8 @@ type CloudCostManagementService struct {
2022

2123
func (c *CloudCostManagementService) GetOverview(ctx context.Context, accID string, startTime int64, endTime int64, groupBy string) (*dto.CEView, error) {
2224
path := fmt.Sprintf(ccmGetOverviewPath, accID, startTime, endTime, groupBy)
25+
26+
slog.Debug("GetOverView", "Path", path)
2327
params := make(map[string]string)
2428

2529
ccmOverview := new(dto.CEView)
@@ -92,6 +96,26 @@ func (r *CloudCostManagementService) ListCostCategoriesDetail(ctx context.Contex
9296
return costCategories, nil
9397
}
9498

99+
func (r *CloudCostManagementService) GetCostCategory(ctx context.Context, scope dto.Scope, opts *dto.CCMGetCostCategoryOptions) (*dto.CCMCostCategory, error) {
100+
// Opts shouuldn't be nil
101+
if opts == nil {
102+
return nil, fmt.Errorf("Missing parameters for Get CCM Cost categories.")
103+
}
104+
105+
path := fmt.Sprintf(ccmGetCostCategoryPath, opts.CostCategoryId, opts.AccountIdentifier)
106+
params := make(map[string]string)
107+
108+
// Temporary slice to hold the strings
109+
costCategory := new(dto.CCMCostCategory)
110+
111+
err := r.Client.Get(ctx, path, params, nil, costCategory)
112+
if err != nil {
113+
return nil, fmt.Errorf("failed to Get cloud cost managment cost category by Id: %w", err)
114+
}
115+
116+
return costCategory, nil
117+
}
118+
95119
func setCCMPaginationDefault(opts *dto.CCMPaginationOptions) {
96120
if opts == nil {
97121
return

client/dto/cloudcostmanagement.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ const (
1919
SortOrderDesc string = "DESCENDING"
2020
)
2121

22-
// CEView represents a basic ccm response.
23-
// The `data` field contains the response data.
22+
// CCMBaseResponse represents a basic ccm response.
2423
type CCMBaseResponse struct {
2524
Status string `json:"state,omitempty"`
2625
Message string `json:"message,omitempty"`
@@ -34,6 +33,8 @@ type CCMError struct {
3433
Error string `json:"error,omitempty"`
3534
}
3635

36+
// CEView represents a basic Cost Overview response.
37+
// The `data` field contains the response data.
3738
type CEView struct {
3839
CCMBaseResponse
3940
Data CCMOverview `json:"data,omitempty"`
@@ -192,3 +193,16 @@ type CCMSuppressed struct {
192193
Message string `json:"message"`
193194
LocalizedMessage string `json:"localizedMessage"`
194195
}
196+
197+
// CCMCostCategory represents the details of a cost category in CCM
198+
type CCMCostCategory struct {
199+
MetaData map[string]interface{} `json:"metaData"`
200+
Resource CCMBusinessMapping `json:"resource"`
201+
ResponseMessages []CCMResponseMessage `json:"responseMessages"`
202+
}
203+
204+
// CCMGetCostCategoryOptions represents options for listing cost categories
205+
type CCMGetCostCategoryOptions struct {
206+
AccountIdentifier string `json:"accountIdentifier,omitempty"`
207+
CostCategoryId string `json:"id,omitempty"`
208+
}

pkg/harness/cloudcostmanagement.go

Lines changed: 60 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -20,29 +20,26 @@ func GetCcmOverviewTool(config *config.Config, client *client.CloudCostManagemen
2020
defaultEndTime:= utils.CurrentMMDDYYYY();
2121
return mcp.NewTool("get_ccm_overview",
2222
mcp.WithDescription("Get an overview for an specific account in Harness Cloud Cost Management"),
23-
mcp.WithString("accountIdentifier",
24-
mcp.Description("The account identifier"),
25-
),
2623
mcp.WithString("startTime",
24+
mcp.Required(),
2725
mcp.DefaultString(defaultStartTime),
2826
mcp.Description("Start time of the period in format MM/DD/YYYY. (e.g. 10/30/2025)"),
2927
),
3028
mcp.WithString("endTime",
29+
mcp.Required(),
3130
mcp.DefaultString(defaultEndTime),
3231
mcp.Description("End time of the period in format MM/DD/YYYY. (e.g. 10/30/2025)"),
3332
),
3433
mcp.WithString("groupBy",
35-
mcp.Description("Optional type to group by period"),
34+
mcp.Required(),
35+
mcp.Description("Type to group by period"),
3636
mcp.DefaultString(dto.PeriodTypeHour),
3737
mcp.Enum(dto.PeriodTypeHour, dto.PeriodTypeDay, dto.PeriodTypeMonth, dto.PeriodTypeWeek, dto.PeriodTypeQuarter, dto.PeriodTypeYear),
3838
),
3939
WithScope(config, false),
4040
),
4141
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
42-
accID, err := OptionalParam[string](request, "accountIdentifier")
43-
if accID == "" {
44-
accID, err = getAccountID(config, request)
45-
}
42+
accountId, err := getAccountID(config, request)
4643
if err != nil {
4744
return mcp.NewToolResultError(err.Error()), nil
4845
}
@@ -64,7 +61,7 @@ func GetCcmOverviewTool(config *config.Config, client *client.CloudCostManagemen
6461
return mcp.NewToolResultError(err.Error()), nil
6562
}
6663

67-
data, err := client.GetOverview(ctx, accID, startTime, endTime, groupBy)
64+
data, err := client.GetOverview(ctx, accountId, startTime, endTime, groupBy)
6865
if err != nil {
6966
return nil, fmt.Errorf("failed to get CCM Overview: %w", err)
7067
}
@@ -80,9 +77,6 @@ func GetCcmOverviewTool(config *config.Config, client *client.CloudCostManagemen
8077
func ListCcmCostCategoriesTool(config *config.Config, client *client.CloudCostManagementService) (tool mcp.Tool, handler server.ToolHandlerFunc) {
8178
return mcp.NewTool("list_ccm_cost_categories",
8279
mcp.WithDescription("List the cost categories for an account in Harness Cloud Cost Management"),
83-
mcp.WithString("account_id",
84-
mcp.Description("The account identifier"),
85-
),
8680
mcp.WithString("cost_category",
8781
mcp.Description("Optional to search for specific category"),
8882
),
@@ -92,10 +86,7 @@ func ListCcmCostCategoriesTool(config *config.Config, client *client.CloudCostMa
9286
WithScope(config, false),
9387
),
9488
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
95-
accountId, err := OptionalParam[string](request, "accountIdentifier")
96-
if accountId == "" {
97-
accountId, err = getAccountID(config, request)
98-
}
89+
accountId, err := getAccountID(config, request)
9990
if err != nil {
10091
return mcp.NewToolResultError(err.Error()), nil
10192
}
@@ -104,7 +95,7 @@ func ListCcmCostCategoriesTool(config *config.Config, client *client.CloudCostMa
10495
params.AccountIdentifier = accountId
10596

10697
// Handle cost category parameter
107-
costCategory, ok, err := OptionalParamOK[string](request, "costCategory")
98+
costCategory, ok, err := OptionalParamOK[string](request, "cost_category")
10899
if err != nil {
109100
return mcp.NewToolResultError(err.Error()), nil
110101
}
@@ -113,7 +104,7 @@ func ListCcmCostCategoriesTool(config *config.Config, client *client.CloudCostMa
113104
}
114105

115106
// Handle search parameter
116-
searchTerm, ok, err := OptionalParamOK[string](request, "search")
107+
searchTerm, ok, err := OptionalParamOK[string](request, "search_term")
117108
if err != nil {
118109
return mcp.NewToolResultError(err.Error()), nil
119110
}
@@ -143,9 +134,6 @@ func ListCcmCostCategoriesTool(config *config.Config, client *client.CloudCostMa
143134
func ListCcmCostCategoriesDetailTool(config *config.Config, client *client.CloudCostManagementService) (tool mcp.Tool, handler server.ToolHandlerFunc) {
144135
return mcp.NewTool("list_ccm_cost_categories_detail",
145136
mcp.WithDescription("List the cost categories with advanced options in Harness Cloud Cost Management"),
146-
mcp.WithString("account_id",
147-
mcp.Description("The account identifier"),
148-
),
149137
mcp.WithString("search_key",
150138
mcp.Description("Optional search key to filter cost categories"),
151139
),
@@ -169,10 +157,7 @@ func ListCcmCostCategoriesDetailTool(config *config.Config, client *client.Cloud
169157
WithScope(config, false),
170158
),
171159
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
172-
accountId, err := OptionalParam[string](request, "account_id")
173-
if accountId == "" {
174-
accountId, err = getAccountID(config, request)
175-
}
160+
accountId, err := getAccountID(config, request)
176161
if err != nil {
177162
return mcp.NewToolResultError(err.Error()), nil
178163
}
@@ -244,11 +229,57 @@ func ListCcmCostCategoriesDetailTool(config *config.Config, client *client.Cloud
244229
}
245230
}
246231

232+
func GetCcmCostCategoryTool(config *config.Config, client *client.CloudCostManagementService) (tool mcp.Tool, handler server.ToolHandlerFunc) {
233+
return mcp.NewTool("get_ccm_cost_category",
234+
mcp.WithDescription("Retrieve the details of a cost category by its ID from a specific account in Harness Cloud Cost Management."),
235+
mcp.WithString("id",
236+
mcp.Description("Required Cost Category ID to retrieve a specific cost category"),
237+
mcp.Required(),
238+
),
239+
WithScope(config, false),
240+
),
241+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
242+
accountId, err := getAccountID(config, request)
243+
if err != nil {
244+
return mcp.NewToolResultError(err.Error()), nil
245+
}
246+
247+
params := &dto.CCMGetCostCategoryOptions{}
248+
params.AccountIdentifier = accountId
249+
// Handle cost category parameter
250+
costCategoryId, ok, err := OptionalParamOK[string](request, "id")
251+
if err != nil {
252+
return mcp.NewToolResultError(err.Error()), nil
253+
}
254+
if ok && costCategoryId != "" {
255+
params.CostCategoryId = costCategoryId
256+
}
257+
scope, err := fetchScope(config, request, false)
258+
if err != nil {
259+
return mcp.NewToolResultError(err.Error()), nil
260+
}
261+
262+
data, err := client.GetCostCategory(ctx, scope, params)
263+
if err != nil {
264+
return nil, fmt.Errorf("failed to get CCM Cost Categories: %w", err)
265+
}
266+
267+
r, err := json.Marshal(data)
268+
if err != nil {
269+
return nil, fmt.Errorf("failed to marshal CCM Cost Category: %w", err)
270+
}
271+
272+
return mcp.NewToolResultText(string(r)), nil
273+
}
274+
}
275+
247276
// getAccountID retrieves AccountID from the config file
248277
func getAccountID(config *config.Config, request mcp.CallToolRequest) (string, error) {
249-
scope, scopeErr := fetchScope(config, request, true)
250-
if scopeErr != nil {
251-
return "", nil
278+
scope, _ := fetchScope(config, request, true)
279+
// Error ignored because it can be related to project or org id
280+
// which are not required for CCM
281+
if scope.AccountID != "" {
282+
return scope.AccountID, nil
252283
}
253-
return scope.AccountID, nil
284+
return "", fmt.Errorf("Account ID is required")
254285
}

pkg/harness/tools.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,7 @@ func registerCloudCostManagement(config *config.Config, tsg *toolsets.ToolsetGro
503503
toolsets.NewServerTool(GetCcmOverviewTool(config, ccmClient)),
504504
toolsets.NewServerTool(ListCcmCostCategoriesTool(config, ccmClient)),
505505
toolsets.NewServerTool(ListCcmCostCategoriesDetailTool(config, ccmClient)),
506+
toolsets.NewServerTool(GetCcmCostCategoryTool(config, ccmClient)),
506507
)
507508

508509
// Add toolset to the group

0 commit comments

Comments
 (0)