Skip to content

Commit 3521c35

Browse files
committed
Add get_project_item tool
1 parent ad5f75c commit 3521c35

File tree

6 files changed

+503
-5
lines changed

6 files changed

+503
-5
lines changed

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -670,12 +670,25 @@ The following sets of tools are available (all are on by default):
670670
- `per_page`: Number of results per page (max 100, default: 30) (number, optional)
671671
- `project_number`: The project's number. (number, required)
672672

673+
- **get_project_item** - Get project item
674+
- `item_id`: The item's ID. (number, required)
675+
- `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required)
676+
- `owner_type`: Owner type (string, required)
677+
- `project_number`: The project's number. (number, required)
678+
673679
- **list_project_fields** - List project fields
674680
- `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required)
675681
- `owner_type`: Owner type (string, required)
676682
- `per_page`: Number of results per page (max 100, default: 30) (number, optional)
677683
- `project_number`: The project's number. (number, required)
678684

685+
- **list_project_items** - List project items
686+
- `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required)
687+
- `owner_type`: Owner type (string, required)
688+
- `per_page`: Number of results per page (max 100, default: 30) (number, optional)
689+
- `project_number`: The project's number. (number, required)
690+
- `query`: Search query to filter items (string, optional)
691+
679692
- **list_projects** - List projects
680693
- `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required)
681694
- `owner_type`: Owner type (string, required)
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"annotations": {
3+
"title": "Get project item",
4+
"readOnlyHint": true
5+
},
6+
"description": "Get a specific Project item for a user or org",
7+
"inputSchema": {
8+
"properties": {
9+
"item_id": {
10+
"description": "The item's ID.",
11+
"type": "number"
12+
},
13+
"owner": {
14+
"description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive.",
15+
"type": "string"
16+
},
17+
"owner_type": {
18+
"description": "Owner type",
19+
"enum": [
20+
"user",
21+
"org"
22+
],
23+
"type": "string"
24+
},
25+
"project_number": {
26+
"description": "The project's number.",
27+
"type": "number"
28+
}
29+
},
30+
"required": [
31+
"owner_type",
32+
"owner",
33+
"project_number",
34+
"item_id"
35+
],
36+
"type": "object"
37+
},
38+
"name": "get_project_item"
39+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{
2+
"annotations": {
3+
"title": "List project items",
4+
"readOnlyHint": true
5+
},
6+
"description": "List Project items for a user or org",
7+
"inputSchema": {
8+
"properties": {
9+
"owner": {
10+
"description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive.",
11+
"type": "string"
12+
},
13+
"owner_type": {
14+
"description": "Owner type",
15+
"enum": [
16+
"user",
17+
"org"
18+
],
19+
"type": "string"
20+
},
21+
"per_page": {
22+
"description": "Number of results per page (max 100, default: 30)",
23+
"type": "number"
24+
},
25+
"project_number": {
26+
"description": "The project's number.",
27+
"type": "number"
28+
},
29+
"query": {
30+
"description": "Search query to filter items",
31+
"type": "string"
32+
}
33+
},
34+
"required": [
35+
"owner_type",
36+
"owner",
37+
"project_number"
38+
],
39+
"type": "object"
40+
},
41+
"name": "list_project_items"
42+
}

pkg/github/projects.go

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,7 @@ func ListProjectFields(getClient GetClientFn, t translations.TranslationHelperFu
223223
resp, err := client.Do(ctx, httpRequest, &projectFields)
224224
if err != nil {
225225
return ghErrors.NewGitHubAPIErrorResponse(ctx,
226-
"failed to list projects",
226+
"failed to list project fields",
227227
resp,
228228
err,
229229
), nil
@@ -235,7 +235,7 @@ func ListProjectFields(getClient GetClientFn, t translations.TranslationHelperFu
235235
if err != nil {
236236
return nil, fmt.Errorf("failed to read response body: %w", err)
237237
}
238-
return mcp.NewToolResultError(fmt.Sprintf("failed to list projects: %s", string(body))), nil
238+
return mcp.NewToolResultError(fmt.Sprintf("failed to list project fields: %s", string(body))), nil
239239
}
240240
r, err := json.Marshal(projectFields)
241241
if err != nil {
@@ -404,7 +404,7 @@ func ListProjectItems(getClient GetClientFn, t translations.TranslationHelperFun
404404
if err != nil {
405405
return nil, fmt.Errorf("failed to read response body: %w", err)
406406
}
407-
return mcp.NewToolResultError(fmt.Sprintf("failed to list projects: %s", string(body))), nil
407+
return mcp.NewToolResultError(fmt.Sprintf("failed to list project items: %s", string(body))), nil
408408
}
409409
r, err := json.Marshal(projectItems)
410410
if err != nil {
@@ -415,6 +415,76 @@ func ListProjectItems(getClient GetClientFn, t translations.TranslationHelperFun
415415
}
416416
}
417417

418+
func GetProjectItem(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
419+
return mcp.NewTool("get_project_item",
420+
mcp.WithDescription(t("TOOL_GET_PROJECT_ITEM_DESCRIPTION", "Get a specific Project item for a user or org")),
421+
mcp.WithToolAnnotation(mcp.ToolAnnotation{Title: t("TOOL_GET_PROJECT_ITEM_USER_TITLE", "Get project item"), ReadOnlyHint: ToBoolPtr(true)}),
422+
mcp.WithString("owner_type", mcp.Required(), mcp.Description("Owner type"), mcp.Enum("user", "org")),
423+
mcp.WithString("owner", mcp.Required(), mcp.Description("If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive.")),
424+
mcp.WithNumber("project_number", mcp.Required(), mcp.Description("The project's number.")),
425+
mcp.WithNumber("item_id", mcp.Required(), mcp.Description("The item's ID.")),
426+
), func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
427+
owner, err := RequiredParam[string](req, "owner")
428+
if err != nil {
429+
return mcp.NewToolResultError(err.Error()), nil
430+
}
431+
ownerType, err := RequiredParam[string](req, "owner_type")
432+
if err != nil {
433+
return mcp.NewToolResultError(err.Error()), nil
434+
}
435+
projectNumber, err := RequiredInt(req, "project_number")
436+
if err != nil {
437+
return mcp.NewToolResultError(err.Error()), nil
438+
}
439+
itemID, err := RequiredInt(req, "item_id")
440+
if err != nil {
441+
return mcp.NewToolResultError(err.Error()), nil
442+
}
443+
444+
client, err := getClient(ctx)
445+
if err != nil {
446+
return mcp.NewToolResultError(err.Error()), nil
447+
}
448+
449+
var url string
450+
if ownerType == "org" {
451+
url = fmt.Sprintf("orgs/%s/projectsV2/%d/items/%d", owner, projectNumber, itemID)
452+
} else {
453+
url = fmt.Sprintf("users/%s/projectsV2/%d/items/%d", owner, projectNumber, itemID)
454+
}
455+
projectItem := projectV2Item{}
456+
457+
httpRequest, err := client.NewRequest("GET", url, nil)
458+
if err != nil {
459+
return nil, fmt.Errorf("failed to create request: %w", err)
460+
}
461+
462+
resp, err := client.Do(ctx, httpRequest, &projectItem)
463+
if err != nil {
464+
return ghErrors.NewGitHubAPIErrorResponse(ctx,
465+
"failed to get project item",
466+
resp,
467+
err,
468+
), nil
469+
}
470+
defer func() { _ = resp.Body.Close() }()
471+
472+
if resp.StatusCode != http.StatusOK {
473+
body, err := io.ReadAll(resp.Body)
474+
if err != nil {
475+
return nil, fmt.Errorf("failed to read response body: %w", err)
476+
}
477+
return mcp.NewToolResultError(fmt.Sprintf("failed to get project item: %s", string(body))), nil
478+
}
479+
r, err := json.Marshal(projectItem)
480+
if err != nil {
481+
return nil, fmt.Errorf("failed to marshal response: %w", err)
482+
}
483+
484+
return mcp.NewToolResultText(string(r)), nil
485+
}
486+
}
487+
418488
type projectV2Field struct {
419489
ID *int64 `json:"id,omitempty"` // The unique identifier for this field.
420490
NodeID string `json:"node_id,omitempty"` // The GraphQL node ID for this field.

0 commit comments

Comments
 (0)