Consolidated project tool - handles ALL GitHub Projects V2 operations.
(
action: str,
project_number: int = None,
issue_number: int = None,
status: str = None,
field_name: str = None,
field_value: str = None,
limit: int = 50,
_context: dict = None,
**kwargs,
)
| 1388 | |
| 1389 | |
| 1390 | async def tool_github_project( |
| 1391 | action: str, |
| 1392 | project_number: int = None, |
| 1393 | issue_number: int = None, |
| 1394 | status: str = None, |
| 1395 | field_name: str = None, |
| 1396 | field_value: str = None, |
| 1397 | limit: int = 50, |
| 1398 | _context: dict = None, |
| 1399 | **kwargs, |
| 1400 | ) -> dict: |
| 1401 | """ |
| 1402 | Consolidated project tool - handles ALL GitHub Projects V2 operations. |
| 1403 | """ |
| 1404 | # Extract admin status from context |
| 1405 | is_admin = False |
| 1406 | context_user_id = None |
| 1407 | context_user_name = None |
| 1408 | if _context: |
| 1409 | is_admin = _context.get("is_admin", False) |
| 1410 | context_user_id = _context.get("user_id") |
| 1411 | context_user_name = _context.get("user_name") |
| 1412 | |
| 1413 | action = action.lower() |
| 1414 | |
| 1415 | # ADMIN ACTIONS - require admin permission |
| 1416 | ADMIN_ACTIONS = {"add", "remove", "set_status", "set_field"} |
| 1417 | if action in ADMIN_ACTIONS: |
| 1418 | if not is_admin: |
| 1419 | # SECURITY: Log blocked admin action attempt |
| 1420 | logger.warning( |
| 1421 | f"SECURITY: Blocked project admin action '{action}' for non-admin user {context_user_name} (id={context_user_id})" |
| 1422 | ) |
| 1423 | return {"error": f"The '{action}' action requires admin permissions. Ask a team member with admin access!"} |
| 1424 | else: |
| 1425 | logger.info(f"Project admin action '{action}' authorized for {context_user_name} (id={context_user_id})") |
| 1426 | |
| 1427 | # LIST ALL PROJECTS (no project_number required) |
| 1428 | if action == "list": |
| 1429 | return await github_graphql.list_projects(limit=limit) |
| 1430 | |
| 1431 | # All other actions require project_number |
| 1432 | if not project_number: |
| 1433 | return {"error": f"project_number required for '{action}' action. Use action='list' to see all projects."} |
| 1434 | |
| 1435 | # READ ACTIONS |
| 1436 | if action == "view": |
| 1437 | result = await github_graphql.get_project_view(project_number) |
| 1438 | if result.get("error"): |
| 1439 | return { |
| 1440 | "error": result["error"], |
| 1441 | "not_found": True, |
| 1442 | "hint": "Project doesn't exist. Use action='list' to see available projects.", |
| 1443 | } |
| 1444 | return result |
| 1445 | |
| 1446 | elif action == "list_items": |
| 1447 | result = await github_graphql.list_project_items(project_number=project_number, status=status, limit=limit) |
nothing calls this directly
no test coverage detected