@@ -34,7 +34,7 @@ func ListProjects(getClient GetClientFn, t translations.TranslationHelperFunc) (
3434 if err != nil {
3535 return mcp .NewToolResultError (err .Error ()), nil
3636 }
37- queryStr , err := OptionalParam [string ](req , "query " )
37+ queryStr , err := OptionalParam [string ](req , "q " )
3838 if err != nil {
3939 return mcp .NewToolResultError (err .Error ()), nil
4040 }
@@ -329,6 +329,92 @@ func GetProjectField(getClient GetClientFn, t translations.TranslationHelperFunc
329329 }
330330}
331331
332+ func ListProjectItems (getClient GetClientFn , t translations.TranslationHelperFunc ) (tool mcp.Tool , handler server.ToolHandlerFunc ) {
333+ return mcp .NewTool ("list_project_items" ,
334+ mcp .WithDescription (t ("TOOL_LIST_PROJECT_ITEMS_DESCRIPTION" , "List Project items for a user or org" )),
335+ mcp .WithToolAnnotation (mcp.ToolAnnotation {Title : t ("TOOL_LIST_PROJECT_ITEMS_USER_TITLE" , "List project items" ), ReadOnlyHint : ToBoolPtr (true )}),
336+ mcp .WithString ("owner_type" , mcp .Required (), mcp .Description ("Owner type" ), mcp .Enum ("user" , "org" )),
337+ 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." )),
338+ mcp .WithNumber ("project_number" , mcp .Required (), mcp .Description ("The project's number." )),
339+ mcp .WithString ("query" , mcp .Description ("Search query to filter items" )),
340+ mcp .WithNumber ("per_page" , mcp .Description ("Number of results per page (max 100, default: 30)" )),
341+ ), func (ctx context.Context , req mcp.CallToolRequest ) (* mcp.CallToolResult , error ) {
342+ owner , err := RequiredParam [string ](req , "owner" )
343+ if err != nil {
344+ return mcp .NewToolResultError (err .Error ()), nil
345+ }
346+ ownerType , err := RequiredParam [string ](req , "owner_type" )
347+ if err != nil {
348+ return mcp .NewToolResultError (err .Error ()), nil
349+ }
350+ projectNumber , err := RequiredInt (req , "project_number" )
351+ if err != nil {
352+ return mcp .NewToolResultError (err .Error ()), nil
353+ }
354+ perPage , err := OptionalIntParamWithDefault (req , "per_page" , 30 )
355+ if err != nil {
356+ return mcp .NewToolResultError (err .Error ()), nil
357+ }
358+ queryStr , err := OptionalParam [string ](req , "q" )
359+ if err != nil {
360+ return mcp .NewToolResultError (err .Error ()), nil
361+ }
362+ client , err := getClient (ctx )
363+ if err != nil {
364+ return mcp .NewToolResultError (err .Error ()), nil
365+ }
366+
367+ var url string
368+ if ownerType == "org" {
369+ url = fmt .Sprintf ("orgs/%s/projectsV2/%d/items" , owner , projectNumber )
370+ } else {
371+ url = fmt .Sprintf ("users/%s/projectsV2/%d/items" , owner , projectNumber )
372+ }
373+ projectItems := []projectV2Item {}
374+
375+ opts := listProjectsOptions {PerPage : perPage }
376+ if queryStr != "" {
377+ opts .Query = queryStr
378+ }
379+ if perPage > 0 {
380+ opts .PerPage = perPage
381+ }
382+ url , err = addOptions (url , opts )
383+ if err != nil {
384+ return nil , fmt .Errorf ("failed to add options to request: %w" , err )
385+ }
386+
387+ httpRequest , err := client .NewRequest ("GET" , url , nil )
388+ if err != nil {
389+ return nil , fmt .Errorf ("failed to create request: %w" , err )
390+ }
391+
392+ resp , err := client .Do (ctx , httpRequest , & projectItems )
393+ if err != nil {
394+ return ghErrors .NewGitHubAPIErrorResponse (ctx ,
395+ "failed to list project items" ,
396+ resp ,
397+ err ,
398+ ), nil
399+ }
400+ defer func () { _ = resp .Body .Close () }()
401+
402+ if resp .StatusCode != http .StatusOK {
403+ body , err := io .ReadAll (resp .Body )
404+ if err != nil {
405+ return nil , fmt .Errorf ("failed to read response body: %w" , err )
406+ }
407+ return mcp .NewToolResultError (fmt .Sprintf ("failed to list projects: %s" , string (body ))), nil
408+ }
409+ r , err := json .Marshal (projectItems )
410+ if err != nil {
411+ return nil , fmt .Errorf ("failed to marshal response: %w" , err )
412+ }
413+
414+ return mcp .NewToolResultText (string (r )), nil
415+ }
416+ }
417+
332418type projectV2Field struct {
333419 ID * int64 `json:"id,omitempty"` // The unique identifier for this field.
334420 NodeID string `json:"node_id,omitempty"` // The GraphQL node ID for this field.
@@ -340,6 +426,21 @@ type projectV2Field struct {
340426 UpdatedAt * github.Timestamp `json:"updated_at,omitempty"` // The time when this field was last updated.
341427}
342428
429+ type projectV2Item struct {
430+ ID * int64 `json:"id,omitempty"`
431+ NodeID * string `json:"node_id,omitempty"`
432+ ProjectNodeID * string `json:"project_node_id,omitempty"`
433+ ContentNodeID * string `json:"content_node_id,omitempty"`
434+ ProjectURL * string `json:"project_url,omitempty"`
435+ ContentType * string `json:"content_type,omitempty"`
436+ Creator * github.User `json:"creator,omitempty"`
437+ CreatedAt * github.Timestamp `json:"created_at,omitempty"`
438+ UpdatedAt * github.Timestamp `json:"updated_at,omitempty"`
439+ ArchivedAt * github.Timestamp `json:"archived_at,omitempty"`
440+ ItemURL * string `json:"item_url,omitempty"`
441+ Fields []* projectV2Field `json:"fields,omitempty"`
442+ }
443+
343444type listProjectsOptions struct {
344445 // For paginated result sets, the number of results to include per page.
345446 PerPage int `url:"per_page,omitempty"`
0 commit comments