Skip to content

Commit afeddbb

Browse files
committed
add get/list repos, fix scope and add defaults in the tool itself
1 parent b0827a7 commit afeddbb

File tree

8 files changed

+283
-12
lines changed

8 files changed

+283
-12
lines changed

client/client.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ type Client struct {
5050
Connectors *ConnectorService
5151
PullRequests *PullRequestService
5252
Pipelines *PipelineService
53+
Repositories *RepositoryService
5354
}
5455

5556
type service struct {
@@ -92,6 +93,7 @@ func (c *Client) initialize() error {
9293
c.Connectors = &ConnectorService{client: c}
9394
c.PullRequests = &PullRequestService{client: c}
9495
c.Pipelines = &PipelineService{client: c}
96+
c.Repositories = &RepositoryService{client: c}
9597

9698
return nil
9799
}

client/dto/repositories.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package dto
2+
3+
// Repository represents a repository in the system
4+
type Repository struct {
5+
Archived bool `json:"archived,omitempty"`
6+
Created int64 `json:"created,omitempty"`
7+
CreatedBy int `json:"created_by,omitempty"`
8+
DefaultBranch string `json:"default_branch,omitempty"`
9+
Deleted int64 `json:"deleted,omitempty"`
10+
Description string `json:"description,omitempty"`
11+
ForkID int `json:"fork_id,omitempty"`
12+
GitSSHURL string `json:"git_ssh_url,omitempty"`
13+
GitURL string `json:"git_url,omitempty"`
14+
ID int `json:"id,omitempty"`
15+
Identifier string `json:"identifier,omitempty"`
16+
Importing bool `json:"importing,omitempty"`
17+
IsEmpty bool `json:"is_empty,omitempty"`
18+
IsPublic bool `json:"is_public,omitempty"`
19+
LastGitPush int64 `json:"last_git_push,omitempty"`
20+
NumClosedPulls int `json:"num_closed_pulls,omitempty"`
21+
NumForks int `json:"num_forks,omitempty"`
22+
NumMergedPulls int `json:"num_merged_pulls,omitempty"`
23+
NumOpenPulls int `json:"num_open_pulls,omitempty"`
24+
NumPulls int `json:"num_pulls,omitempty"`
25+
ParentID int `json:"parent_id,omitempty"`
26+
Path string `json:"path,omitempty"`
27+
Size int64 `json:"size,omitempty"`
28+
SizeUpdated int64 `json:"size_updated,omitempty"`
29+
State int `json:"state,omitempty"`
30+
Updated int64 `json:"updated,omitempty"`
31+
}
32+
33+
// RepositoryOptions represents the options for listing repositories
34+
type RepositoryOptions struct {
35+
Query string `json:"query,omitempty"`
36+
Sort string `json:"sort,omitempty"`
37+
Order string `json:"order,omitempty"`
38+
Page int `json:"page,omitempty"`
39+
Limit int `json:"limit,omitempty"`
40+
}

client/repositories.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package client
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/harness/harness-mcp/client/dto"
8+
)
9+
10+
const (
11+
repositoryBasePath = "code/api/v1/repos"
12+
repositoryGetPath = repositoryBasePath + "/%s"
13+
repositoryListPath = repositoryBasePath
14+
)
15+
16+
type RepositoryService struct {
17+
client *Client
18+
}
19+
20+
func (r *RepositoryService) Get(ctx context.Context, scope dto.Scope, repoIdentifier string) (*dto.Repository, error) {
21+
path := fmt.Sprintf(repositoryGetPath, repoIdentifier)
22+
params := make(map[string]string)
23+
addScope(scope, params)
24+
25+
repo := new(dto.Repository)
26+
err := r.client.Get(ctx, path, params, nil, repo)
27+
if err != nil {
28+
return nil, fmt.Errorf("failed to get repository: %w", err)
29+
}
30+
31+
return repo, nil
32+
}
33+
34+
// setDefaultPaginationForRepo sets default pagination values for RepositoryOptions
35+
func setDefaultPaginationForRepo(opts *dto.RepositoryOptions) {
36+
if opts == nil {
37+
return
38+
}
39+
if opts.Page <= 0 {
40+
opts.Page = 1
41+
}
42+
43+
if opts.Limit <= 0 {
44+
opts.Limit = defaultPageSize
45+
} else if opts.Limit > maxPageSize {
46+
opts.Limit = maxPageSize
47+
}
48+
}
49+
50+
func (r *RepositoryService) List(ctx context.Context, scope dto.Scope, opts *dto.RepositoryOptions) ([]*dto.Repository, error) {
51+
path := repositoryListPath
52+
params := make(map[string]string)
53+
addScope(scope, params)
54+
55+
// Handle nil options by creating default options
56+
if opts == nil {
57+
opts = &dto.RepositoryOptions{}
58+
}
59+
60+
setDefaultPaginationForRepo(opts)
61+
62+
params["page"] = fmt.Sprintf("%d", opts.Page)
63+
params["limit"] = fmt.Sprintf("%d", opts.Limit)
64+
65+
if opts.Query != "" {
66+
params["query"] = opts.Query
67+
}
68+
if opts.Sort != "" {
69+
params["sort"] = opts.Sort
70+
}
71+
if opts.Order != "" {
72+
params["order"] = opts.Order
73+
}
74+
75+
var repos []*dto.Repository
76+
err := r.client.Get(ctx, path, params, nil, &repos)
77+
if err != nil {
78+
return nil, fmt.Errorf("failed to list repositories: %w", err)
79+
}
80+
81+
return repos, nil
82+
}

pkg/harness/pipelines.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,11 @@ func ListPipelinesTool(config *config.Config, client *client.Client) (tool mcp.T
5353
),
5454
mcp.WithNumber("page",
5555
mcp.Description("Page number for pagination"),
56+
mcp.DefaultNumber(0),
5657
),
5758
mcp.WithNumber("size",
5859
mcp.Description("Number of items per page"),
60+
mcp.DefaultNumber(5),
5961
),
6062
WithScope(config, true),
6163
WithPagination(),
@@ -190,9 +192,11 @@ func ListExecutionsTool(config *config.Config, client *client.Client) (tool mcp.
190192
),
191193
mcp.WithNumber("page",
192194
mcp.Description("Page number for pagination"),
195+
mcp.DefaultNumber(0),
193196
),
194197
mcp.WithNumber("size",
195198
mcp.Description("Number of items per page"),
199+
mcp.DefaultNumber(5),
196200
),
197201
WithScope(config, true),
198202
WithPagination(),

pkg/harness/pullreq.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,11 @@ func ListPullRequestsTool(config *config.Config, client *client.Client) (tool mc
8484
mcp.Description("Optional flag to include CI check information for builds ran in the PR"),
8585
),
8686
mcp.WithNumber("page",
87+
mcp.DefaultNumber(1),
8788
mcp.Description("Page number for pagination"),
8889
),
8990
mcp.WithNumber("limit",
91+
mcp.DefaultNumber(5),
9092
mcp.Description("Number of items per page"),
9193
),
9294
WithScope(config, true),

pkg/harness/repositories.go

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package harness
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
8+
"github.com/harness/harness-mcp/client"
9+
"github.com/harness/harness-mcp/client/dto"
10+
"github.com/harness/harness-mcp/cmd/harness-mcp-server/config"
11+
"github.com/mark3labs/mcp-go/mcp"
12+
"github.com/mark3labs/mcp-go/server"
13+
)
14+
15+
// GetRepositoryTool creates a tool for getting a specific repository
16+
func GetRepositoryTool(config *config.Config, client *client.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) {
17+
return mcp.NewTool("get_repository",
18+
mcp.WithDescription("Get details of a specific repository in Harness."),
19+
mcp.WithString("repo_identifier",
20+
mcp.Required(),
21+
mcp.Description("The identifier of the repository"),
22+
),
23+
WithScope(config, false),
24+
),
25+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
26+
repoIdentifier, err := requiredParam[string](request, "repo_identifier")
27+
if err != nil {
28+
return mcp.NewToolResultError(err.Error()), nil
29+
}
30+
31+
scope, err := fetchScope(config, request, false)
32+
if err != nil {
33+
return mcp.NewToolResultError(err.Error()), nil
34+
}
35+
36+
data, err := client.Repositories.Get(ctx, scope, repoIdentifier)
37+
if err != nil {
38+
return nil, fmt.Errorf("failed to get repository: %w", err)
39+
}
40+
41+
r, err := json.Marshal(data)
42+
if err != nil {
43+
return nil, fmt.Errorf("failed to marshal repository: %w", err)
44+
}
45+
46+
return mcp.NewToolResultText(string(r)), nil
47+
}
48+
}
49+
50+
// ListRepositoriesTool creates a tool for listing repositories
51+
func ListRepositoriesTool(config *config.Config, client *client.Client) (tool mcp.Tool, handler server.ToolHandlerFunc) {
52+
return mcp.NewTool("list_repositories",
53+
mcp.WithDescription("List repositories in Harness."),
54+
mcp.WithString("query",
55+
mcp.Description("Optional search term to filter repositories"),
56+
),
57+
mcp.WithString("sort",
58+
mcp.Description("Optional field to sort by (e.g., identifier)"),
59+
),
60+
mcp.WithString("order",
61+
mcp.Description("Optional sort order (asc or desc)"),
62+
),
63+
mcp.WithNumber("page",
64+
mcp.DefaultNumber(1),
65+
mcp.Description("Page number for pagination"),
66+
),
67+
mcp.WithNumber("limit",
68+
mcp.DefaultNumber(5),
69+
mcp.Description("Number of items per page"),
70+
),
71+
WithScope(config, false),
72+
),
73+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
74+
scope, err := fetchScope(config, request, false)
75+
if err != nil {
76+
return mcp.NewToolResultError(err.Error()), nil
77+
}
78+
79+
opts := &dto.RepositoryOptions{}
80+
81+
// Handle pagination
82+
page, err := OptionalParam[float64](request, "page")
83+
if err != nil {
84+
return mcp.NewToolResultError(err.Error()), nil
85+
}
86+
if page > 0 {
87+
opts.Page = int(page)
88+
}
89+
90+
limit, err := OptionalParam[float64](request, "limit")
91+
if err != nil {
92+
return mcp.NewToolResultError(err.Error()), nil
93+
}
94+
if limit > 0 {
95+
opts.Limit = int(limit)
96+
}
97+
98+
// Handle other optional parameters
99+
query, err := OptionalParam[string](request, "query")
100+
if err != nil {
101+
return mcp.NewToolResultError(err.Error()), nil
102+
}
103+
if query != "" {
104+
opts.Query = query
105+
}
106+
107+
sort, err := OptionalParam[string](request, "sort")
108+
if err != nil {
109+
return mcp.NewToolResultError(err.Error()), nil
110+
}
111+
if sort != "" {
112+
opts.Sort = sort
113+
}
114+
115+
order, err := OptionalParam[string](request, "order")
116+
if err != nil {
117+
return mcp.NewToolResultError(err.Error()), nil
118+
}
119+
if order != "" {
120+
opts.Order = order
121+
}
122+
123+
data, err := client.Repositories.List(ctx, scope, opts)
124+
if err != nil {
125+
return nil, fmt.Errorf("failed to list repositories: %w", err)
126+
}
127+
128+
r, err := json.Marshal(data)
129+
if err != nil {
130+
return nil, fmt.Errorf("failed to marshal repository list: %w", err)
131+
}
132+
133+
return mcp.NewToolResultText(string(r)), nil
134+
}
135+
}

pkg/harness/scope.go

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,16 @@ func WithScope(config *config.Config, required bool) mcp.ToolOption {
1616
opt = mcp.Required()
1717
}
1818
return func(tool *mcp.Tool) {
19-
if config.OrgID == "" {
20-
mcp.WithString("org_id",
21-
mcp.Description("The ID of the organization."),
22-
opt,
23-
)
24-
}
25-
if config.ProjectID == "" {
26-
mcp.WithString("project_id",
27-
mcp.Description("The ID of the project."),
28-
opt,
29-
)
30-
}
19+
mcp.WithString("org_id",
20+
mcp.Description("The ID of the organization."),
21+
mcp.DefaultString(config.OrgID),
22+
opt,
23+
)
24+
mcp.WithString("project_id",
25+
mcp.Description("The ID of the project."),
26+
mcp.DefaultString(config.ProjectID),
27+
opt,
28+
)
3129
}
3230
}
3331

pkg/harness/tools.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,17 @@ func InitToolsets(client *client.Client, config *config.Config) (*toolsets.Tools
3434
toolsets.NewServerTool(ListPullRequestsTool(config, client)),
3535
)
3636

37+
// Create the repositories toolset
38+
repositories := toolsets.NewToolset("repositories", "Harness Repository related tools").
39+
AddReadTools(
40+
toolsets.NewServerTool(GetRepositoryTool(config, client)),
41+
toolsets.NewServerTool(ListRepositoriesTool(config, client)),
42+
)
43+
3744
// Add toolsets to the group
3845
tsg.AddToolset(pullrequests)
3946
tsg.AddToolset(pipelines)
47+
tsg.AddToolset(repositories)
4048

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

0 commit comments

Comments
 (0)