Skip to main content
Glama
saidsef

GitHub PR Issue Analyser

by saidsef

get_user_org_activity

Analyze a user's GitHub activity across an organization's repositories, tracking commits, pull requests, and issues with paginated results for efficient data handling.

Instructions

Gets comprehensive activity for a SPECIFIC USER across ALL repositories in an organization.

PAGINATED RESULTS - Returns a manageable subset of data to prevent context overflow.

Efficiently filters by user at the GraphQL level - does NOT scan entire repos. Captures ALL branches, not just main/default branch.

Includes:

  • Commits by the user (paginated)

  • PRs where user was: author, reviewer, merger, commenter, or assigned (paginated)

  • Issues where user was: author, assigned, commenter, or participant (paginated)

  • Handles reviewed, open, merged, closed, and approved PRs

Args: org_name (str): GitHub organization name username (str): GitHub username to query from_date (str): Start date ISO 8601 (e.g., "2024-01-01T00:00:00Z") to_date (str): End date ISO 8601 (e.g., "2024-12-31T23:59:59Z") page (int): Page number (1-indexed, default: 1) per_page (int): Items per page (default: 50, max: 100)

Returns: Dict containing: - status: success/error - summary: aggregate statistics - commits[]: paginated commits (most recent first) - prs[]: paginated PRs (most recent first) - issues[]: paginated issues (most recent first) - pagination: current_page, per_page, total_items, total_pages, has_next_page

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
org_nameYes
usernameYes
from_dateYes
to_dateYes
pageNo
per_pageNo

Implementation Reference

  • The core handler function implementing the 'get_user_org_activity' MCP tool. Fetches paginated commits, PRs, and issues for a specific user across all repositories in a GitHub organization within a date range using GraphQL queries. Includes summary statistics and handles pagination.
    def get_user_org_activity(
        self, 
        org_name: str, 
        username: str, 
        from_date: str, 
        to_date: str,
        page: int = 1,
        per_page: int = 50
    ) -> Dict[str, Any]:
        """
        Gets comprehensive activity for a SPECIFIC USER across ALL repositories in an organization.
        
        **PAGINATED RESULTS** - Returns a manageable subset of data to prevent context overflow.
        
        Efficiently filters by user at the GraphQL level - does NOT scan entire repos.
        Captures ALL branches, not just main/default branch.
        
        Includes:
        - Commits by the user (paginated)
        - PRs where user was: author, reviewer, merger, commenter, or assigned (paginated)
        - Issues where user was: author, assigned, commenter, or participant (paginated)
        - Handles reviewed, open, merged, closed, and approved PRs
        
        Args:
            org_name (str): GitHub organization name
            username (str): GitHub username to query
            from_date (str): Start date ISO 8601 (e.g., "2024-01-01T00:00:00Z")
            to_date (str): End date ISO 8601 (e.g., "2024-12-31T23:59:59Z")
            page (int): Page number (1-indexed, default: 1)
            per_page (int): Items per page (default: 50, max: 100)
        
        Returns:
            Dict containing: 
            - status: success/error
            - summary: aggregate statistics
            - commits[]: paginated commits (most recent first)
            - prs[]: paginated PRs (most recent first)
            - issues[]: paginated issues (most recent first)
            - pagination: current_page, per_page, total_items, total_pages, has_next_page
        """
        logging.info(f"Fetching ALL activity for '{username}' in '{org_name}' from {from_date} to {to_date}")
        
        # Step 1: Get user's email addresses for efficient commit filtering
        user_emails = self._get_user_emails(username)
        logging.info(f"Found {len(user_emails)} email(s) for filtering commits")
        
        # Step 2: Get repositories where user actually contributed (optimized approach)
        # First try to get repos from user's contribution collection
        contributed_repos = self._get_user_contributed_repos(username, org_name, from_date, to_date)
        
        if contributed_repos:
            logging.info(f"Found {len(contributed_repos)} repos with user contributions via contributionsCollection")
            org_repos = contributed_repos
        else:
            # Fallback: Get ALL repositories in organization
            logging.info(f"Fallback: Scanning all org repos (contributionsCollection returned no results)")
            org_repos = self._get_all_org_repos(org_name)
            logging.info(f"Found {len(org_repos)} total repositories in {org_name}")
        
        if not org_repos:
            return self._empty_activity_response(username, org_name, from_date, to_date, page, per_page)
        
        # Step 3: Process each repo - filter by user at GraphQL level
        all_commits = []
        all_prs = []
        all_issues = []
        repos_with_activity = 0
        
        for repo_info in org_repos:
            repo_name = repo_info.get("name")
            repo_url = repo_info.get("url")
            
            logging.info(f"Scanning {org_name}/{repo_name} for {username}")
            
            # Fetch user-specific data from this repo
            repo_activity = self._fetch_repo_user_activity(
                org_name, repo_name, repo_url, username, user_emails, from_date, to_date
            )
            
            if repo_activity:
                all_commits.extend(repo_activity.get("commits", []))
                all_prs.extend(repo_activity.get("prs", []))
                all_issues.extend(repo_activity.get("issues", []))
                
                if repo_activity.get("has_activity"):
                    repos_with_activity += 1
        
        # Sort by date (most recent first)
        all_commits.sort(key=lambda x: x["date"], reverse=True)
        all_prs.sort(key=lambda x: x["updated_at"], reverse=True)
        all_issues.sort(key=lambda x: x["updated_at"], reverse=True)
        
        # Calculate pagination
        per_page = min(max(1, per_page), 100)  # Clamp between 1-100
        page = max(1, page)  # Must be at least 1
        
        total_commits = len(all_commits)
        total_prs = len(all_prs)
        total_issues = len(all_issues)
        
        # Calculate pages
        commits_total_pages = (total_commits + per_page - 1) // per_page if total_commits > 0 else 1
        prs_total_pages = (total_prs + per_page - 1) // per_page if total_prs > 0 else 1
        issues_total_pages = (total_issues + per_page - 1) // per_page if total_issues > 0 else 1
        
        # Slice data for current page
        start_idx = (page - 1) * per_page
        end_idx = start_idx + per_page
        
        paginated_commits = all_commits[start_idx:end_idx]
        paginated_prs = all_prs[start_idx:end_idx]
        paginated_issues = all_issues[start_idx:end_idx]
        
        # Generate summary (based on ALL data, not just current page)
        user_authored_prs = [pr for pr in all_prs if "Author" in pr["user_roles"]]
        
        summary = {
            "user": username,
            "organization": org_name,
            "date_range": f"{from_date} to {to_date}",
            "total_commits": total_commits,
            "total_prs_involved": total_prs,
            "prs_authored": len(user_authored_prs),
            "prs_reviewed": len([pr for pr in all_prs if any(r in pr["user_roles"] for r in ["Approved", "Requested Changes", "Reviewed"])]),
            "prs_merged": len([pr for pr in all_prs if "Merged" in pr["user_roles"]]),
            "prs_commented": len([pr for pr in all_prs if "Commented" in pr["user_roles"]]),
            "total_issues_involved": len(all_issues),
            "issues_authored": len([issue for issue in all_issues if "Author" in issue["user_roles"]]),
            "issues_assigned": len([issue for issue in all_issues if "Assigned" in issue["user_roles"]]),
            "issues_commented": len([issue for issue in all_issues if "Commented" in issue["user_roles"]]),
            "total_additions": sum(c["additions"] for c in all_commits),
            "total_deletions": sum(c["deletions"] for c in all_commits),
        }
        
        logging.info(f"Activity complete: Page {page}/{max(commits_total_pages, prs_total_pages, issues_total_pages)} - Returning {len(paginated_commits)} commits, {len(paginated_prs)} PRs, {len(paginated_issues)} issues from {repos_with_activity}/{len(org_repos)} repos")
        
        return {
            "status": "success",
            "summary": summary,
            "commits": paginated_commits,
            "prs": paginated_prs,
            "issues": paginated_issues,
            "pagination": {
                "current_page": page,
                "per_page": per_page,
                "commits": {
                    "total": total_commits,
                    "total_pages": commits_total_pages,
                    "has_next_page": page < commits_total_pages,
                    "returned": len(paginated_commits)
                },
                "prs": {
                    "total": total_prs,
                    "total_pages": prs_total_pages,
                    "has_next_page": page < prs_total_pages,
                    "returned": len(paginated_prs)
                },
                "issues": {
                    "total": total_issues,
                    "total_pages": issues_total_pages,
                    "has_next_page": page < issues_total_pages,
                    "returned": len(paginated_issues)
                },
                "repos": {
                    "total_in_org": len(org_repos),
                    "with_user_activity": repos_with_activity
                }
            }
        }
  • Dynamic registration of all public methods of GitHubIntegration (including get_user_org_activity) as MCP tools via FastMCP.add_tool().
    def register_tools(self, methods: Any = None) -> None:
        for name, method in inspect.getmembers(methods):
            if (inspect.isfunction(method) or inspect.ismethod(method)) and not name.startswith("_"):
                self.mcp.add_tool(method)
  • Key helper method for executing GitHub GraphQL queries, used extensively by get_user_org_activity and its sub-helpers for fetching repository, commit, PR, and issue data.
    def user_activity_query(self, variables: dict[str, Any], query: str) -> Dict[str, Any]:
        """
        Performs a user activity query using GitHub's GraphQL API with support for organization-specific 
        and cross-organization queries.
    
        **Query Modes**:
        
        1. **Organization-Specific Activity** (fastest, most comprehensive):
           - Query organization repositories directly
           - Access all private repos in the org (with proper token scopes)
           - Get detailed commit history, PRs, and issues
           - Variables: {"orgName": "Pelle-Tech", "from": "2024-10-01T00:00:00Z", "to": "2024-10-31T23:59:59Z"}
           - Variable types: `$orgName: String!`, `$from: GitTimestamp!`, `$to: GitTimestamp!`
        
        2. **Authenticated User Activity Across All Orgs** (slower, summary only):
           - Query viewer's contribution collection
           - Includes all orgs where user is a member
           - Summary counts only (no detailed commit messages)
           - Variables: {"from": "2024-10-01T00:00:00Z", "to": "2024-10-31T23:59:59Z"}
           - Variable types: `$from: DateTime!`, `$to: DateTime!`
        
        3. **User Activity in Specific Organization** (most restrictive):
           - Query organization repos filtered by user
           - Requires combining org query with author filtering
           - Variables: {"orgName": "Pelle-Tech", "username": "saidsef", "from": "2024-10-01T00:00:00Z", "to": "2024-10-31T23:59:59Z"}
           - Variable types: `$orgName: String!`, `$username: String!`, `$from: GitTimestamp!`, `$to: GitTimestamp!`
    
        **Performance Tips**:
        - Use pagination parameters to limit initial data: `first: 50` instead of `first: 100`
        - Query only required fields to reduce response size
        - Use org-specific queries when possible (faster than viewer queries)
        - For large date ranges, split into smaller queries
        - Cache results for repeated queries
    
        **Example Queries**:
    
        **Fast Org Query with Pagination**:
        ```graphql
        query($orgName: String!, $from: GitTimestamp!, $to: GitTimestamp!, $repoCount: Int = 50) {
          organization(login: $orgName) {
            login
            repositories(first: $repoCount, privacy: PRIVATE, orderBy: {field: PUSHED_AT, direction: DESC}) {
              pageInfo {
                hasNextPage
                endCursor
              }
              nodes {
                name
                isPrivate
                defaultBranchRef {
                  target {
                    ... on Commit {
                      history(since: $from, until: $to, first: 100) {
                        totalCount
                        pageInfo {
                          hasNextPage
                          endCursor
                        }
                        nodes {
                          author { 
                            user { login }
                            email
                          }
                          committedDate
                          message
                          additions
                          deletions
                        }
                      }
                    }
                  }
                }
                pullRequests(first: 50, states: [OPEN, CLOSED, MERGED], orderBy: {field: UPDATED_AT, direction: DESC}) {
                  totalCount
                  nodes {
                    number
                    title
                    author { login }
                    createdAt
                    state
                    additions
                    deletions
                  }
                }
              }
            }
          }
        }
        ```
    
        **User-Filtered Org Query**:
        ```graphql
        query($orgName: String!, $username: String!, $from: GitTimestamp!, $to: GitTimestamp!) {
          organization(login: $orgName) {
            login
            repositories(first: 100, privacy: PRIVATE) {
              nodes {
                name
                defaultBranchRef {
                  target {
                    ... on Commit {
                      history(since: $from, until: $to, author: {emails: [$username]}, first: 100) {
                        totalCount
                        nodes {
                          author { user { login } }
                          committedDate
                          message
                        }
                      }
                    }
                  }
                }
                pullRequests(first: 100, states: [OPEN, CLOSED, MERGED]) {
                  nodes {
                    author { login }
                    title
                    createdAt
                    state
                  }
                }
              }
            }
          }
        }
        ```
    
        **Cross-Org Viewer Query**:
        ```graphql
        query($from: DateTime!, $to: DateTime!) {
          viewer {
            login
            contributionsCollection(from: $from, to: $to) {
              commitContributionsByRepository(maxRepositories: 100) {
                repository { 
                  name 
                  isPrivate 
                  owner { login }
                }
                contributions { totalCount }
              }
              pullRequestContributionsByRepository(maxRepositories: 100) {
                repository { 
                  name 
                  isPrivate 
                  owner { login }
                }
                contributions { totalCount }
              }
              issueContributionsByRepository(maxRepositories: 100) {
                repository { 
                  name 
                  isPrivate 
                  owner { login }
                }
                contributions { totalCount }
              }
            }
            organizations(first: 100) {
              nodes { 
                login 
                viewerCanAdminister
              }
            }
          }
        }
        ```
    
        Args:
            variables (dict[str, Any]): Query variables. Supported combinations:
                - Org-specific: {"orgName": "Pelle-Tech", "from": "...", "to": "..."}
                - Cross-org: {"from": "...", "to": "..."}
                - User-filtered org: {"orgName": "Pelle-Tech", "username": "saidsef", "from": "...", "to": "..."}
                - With pagination: Add {"repoCount": 50, "prCount": 50} for custom limits
            query (str): GraphQL query string. Must declare correct variable types:
                - Organization queries: Use `GitTimestamp!` for $from/$to
                - Viewer queries: Use `DateTime!` for $from/$to
                - Both types accept ISO 8601 format: "YYYY-MM-DDTHH:MM:SSZ"
    
        Returns:
            Dict[str, Any]: GraphQL response with activity data or error information.
                - Success: {"data": {...}}
                - Errors: {"errors": [...], "data": null}
                - Network error: {"status": "error", "message": "..."}
    
        Error Handling:
            - Validates response status codes
            - Logs GraphQL errors with details
            - Returns structured error responses
            - Includes traceback for debugging
    
        Required Token Scopes:
            - `repo`: Full control of private repositories
            - `read:org`: Read org and team membership
            - `read:user`: Read user profile data
    
        Performance Notes:
            - Org queries are ~3x faster than viewer queries
            - Large date ranges (>1 year) may timeout
            - Use pagination for repos with >100 commits
            - Response size correlates with date range and repo count
        """
        # Validate inputs
        if not query or not isinstance(query, str):
            return {"status": "error", "message": "Query must be a non-empty string"}
        
        if not variables or not isinstance(variables, dict):
            return {"status": "error", "message": "Variables must be a non-empty dictionary"}
        
        # Determine query type for optimized logging
        query_type = "unknown"
        if "orgName" in variables and "username" in variables:
            query_type = "user-filtered-org"
        elif "orgName" in variables:
            query_type = "org-specific"
        elif "from" in variables and "to" in variables:
            query_type = "cross-org-viewer"
        
        logging.info(f"Performing GraphQL query [type: {query_type}] with variables: {variables}")
    
        try:
            # Make GraphQL request with optimized timeout
            response = requests.post(
                'https://api.github.com/graphql',
                json={'query': query, 'variables': variables},
                headers=self._get_headers(),
                timeout=TIMEOUT * 2  # Double timeout for GraphQL queries (can be complex)
            )
            response.raise_for_status()
            query_data = response.json()
    
            # Handle GraphQL errors (API accepts request but query has issues)
            if 'errors' in query_data:
                error_messages = [err.get('message', 'Unknown error') for err in query_data['errors']]
                logging.error(f"GraphQL query errors: {error_messages}")
                
                # Check for common errors and provide helpful messages
                for error in query_data['errors']:
                    error_type = error.get('extensions', {}).get('code')
                    if error_type == 'variableMismatch':
                        logging.error(f"Variable type mismatch: Use GitTimestamp for org queries, DateTime for viewer queries")
                    elif error_type == 'NOT_FOUND':
                        logging.error(f"Resource not found: Check org/user name is correct and case-sensitive")
                    elif error_type == 'FORBIDDEN':
                        logging.error(f"Access forbidden: Check token has required scopes (repo, read:org)")
                
                return query_data  # Return with errors for caller to handle
            
            # Log success with summary
            if 'data' in query_data:
                data_keys = list(query_data['data'].keys())
                logging.info(f"GraphQL query successful [type: {query_type}], returned data keys: {data_keys}")
            
            return query_data
    
        except requests.exceptions.Timeout:
            error_msg = f"GraphQL query timeout after {TIMEOUT * 2}s. Try reducing date range or repo count."
            logging.error(error_msg)
            return {"status": "error", "message": error_msg, "timeout": True}
        
        except requests.exceptions.RequestException as req_err:
            error_msg = f"Request error during GraphQL query: {str(req_err)}"
            logging.error(error_msg)
            traceback.print_exc()
            return {"status": "error", "message": error_msg, "request_exception": True}
        
        except Exception as e:
            error_msg = f"Unexpected error performing GraphQL query: {str(e)}"
            logging.error(error_msg)
            traceback.print_exc()
            return {"status": "error", "message": error_msg, "unexpected": True}
  • Primary helper that fetches user-specific commits, PRs, and issues from individual repositories, filters by involvement roles, and aggregates data. Called for each relevant repo.
    def _fetch_repo_user_activity(self, org_name: str, repo_name: str, repo_url: str, 
                                   username: str, user_emails: list, from_date: str, to_date: str) -> Dict:
        """Fetch user-specific activity from a single repo - FILTERED at GraphQL level."""
        
        # Build email filter for commits (server-side filtering)
        if user_emails:
            emails_json = str(user_emails).replace("'", '"')
            author_filter = f'author: {{emails: {emails_json}}}, '
        else:
            author_filter = ""
        
        # First, check if user has ANY activity in this repo within date range
        # This prevents fetching from repos where user has no contributions
        check_query = """
        query($orgName: String!, $repoName: String!, $from: GitTimestamp!, $to: GitTimestamp!) {
          repository(owner: $orgName, name: $repoName) {
            defaultBranchRef {
              target {
                ... on Commit {
                  history(since: $from, until: $to, """ + author_filter + """first: 1) {
                    totalCount
                  }
                }
              }
            }
          }
        }
        """
        
        check_result = self.user_activity_query({
            "orgName": org_name,
            "repoName": repo_name,
            "from": from_date,
            "to": to_date
        }, check_query)
        
        # Skip if no commits found (user has no activity in default branch)
        if "data" in check_result and check_result.get("data", {}).get("repository"):
            if check_result["data"]["repository"].get("defaultBranchRef"):
                commit_count = check_result["data"]["repository"]["defaultBranchRef"]["target"]["history"]["totalCount"]
                if commit_count == 0:
                    logging.info(f"  No commits by {username} in {repo_name}, checking PRs/Issues only")
        
        # Query with user filtering at GraphQL level
        query = """
        query($orgName: String!, $repoName: String!, $from: GitTimestamp!, $to: GitTimestamp!) {
          repository(owner: $orgName, name: $repoName) {
            refs(refPrefix: "refs/heads/", first: 100) {
              nodes {
                name
                target {
                  ... on Commit {
                    history(since: $from, until: $to, """ + author_filter + """first: 100) {
                      nodes {
                        oid
                        messageHeadline
                        author {
                          user { login }
                          email
                          name
                        }
                        committedDate
                        additions
                        deletions
                        url
                      }
                    }
                  }
                }
              }
            }
            pullRequests(first: 100, orderBy: {field: UPDATED_AT, direction: DESC}) {
              nodes {
                number
                title
                url
                state
                isDraft
                author { login }
                createdAt
                updatedAt
                mergedAt
                closedAt
                commits { totalCount }
                additions
                deletions
                changedFiles
                mergedBy { login }
                assignees(first: 10) { nodes { login } }
                reviews(first: 50) {
                  nodes {
                    author { login }
                    state
                    submittedAt
                  }
                }
                comments(first: 50) { nodes { author { login } } }
                labels(first: 10) { nodes { name } }
              }
            }
            issues(first: 100, orderBy: {field: UPDATED_AT, direction: DESC}) {
              nodes {
                number
                title
                url
                state
                author { login }
                createdAt
                updatedAt
                closedAt
                assignees(first: 10) { nodes { login } }
                participants(first: 50) { nodes { login } }
                comments(first: 50) { nodes { author { login } } }
                labels(first: 10) { nodes { name } }
              }
            }
          }
        }
        """
        
        variables = {
            "orgName": org_name,
            "repoName": repo_name,
            "from": from_date,
            "to": to_date
        }
        
        result = self.user_activity_query(variables, query)
        
        if "data" not in result or "errors" in result:
            return None
        
        repo_data = result.get("data", {}).get("repository", {})
        if not repo_data:
            return None
        
        # Parse commits (deduplicate by OID across branches)
        commits = []
        seen_oids = set()
        for ref in repo_data.get("refs", {}).get("nodes", []):
            branch = ref.get("name")
            for commit in ref.get("target", {}).get("history", {}).get("nodes", []):
                oid = commit.get("oid")
                if oid not in seen_oids:
                    seen_oids.add(oid)
                    commits.append({
                        "repo": repo_name,
                        "repo_url": repo_url,
                        "branch": branch,
                        "oid": oid[:7],
                        "full_oid": oid,
                        "message": commit.get("messageHeadline", ""),
                        "author": commit.get("author", {}).get("name", "Unknown"),
                        "date": commit.get("committedDate", ""),
                        "additions": commit.get("additions", 0),
                        "deletions": commit.get("deletions", 0),
                        "url": commit.get("url", "")
                    })
        
        # Parse PRs (filter by user involvement)
        prs = []
        for pr in repo_data.get("pullRequests", {}).get("nodes", []):
            pr_author = pr.get("author", {}).get("login", "") if pr.get("author") else ""
            merged_by = pr.get("mergedBy", {}).get("login", "") if pr.get("mergedBy") else ""
            assignees = [a.get("login") for a in pr.get("assignees", {}).get("nodes", [])]
            reviewers = [r.get("author", {}).get("login") for r in pr.get("reviews", {}).get("nodes", []) if r.get("author")]
            commenters = [c.get("author", {}).get("login") for c in pr.get("comments", {}).get("nodes", []) if c.get("author")]
            
            if username in [pr_author, merged_by] + assignees + reviewers + commenters:
                roles = []
                if pr_author == username: roles.append("Author")
                if merged_by == username: roles.append("Merged")
                if username in assignees: roles.append("Assigned")
                
                if username in reviewers:
                    user_reviews = [r for r in pr.get("reviews", {}).get("nodes", []) if r.get("author", {}).get("login") == username]
                    states = set(r.get("state") for r in user_reviews)
                    if "APPROVED" in states: roles.append("Approved")
                    elif "CHANGES_REQUESTED" in states: roles.append("Requested Changes")
                    elif "COMMENTED" in states: roles.append("Reviewed")
                
                if username in commenters and "Author" not in roles:
                    roles.append("Commented")
                
                prs.append({
                    "repo": repo_name,
                    "repo_url": repo_url,
                    "number": pr.get("number", 0),
                    "title": pr.get("title", ""),
                    "author": pr_author,
                    "state": pr.get("state", ""),
                    "is_draft": pr.get("isDraft", False),
                    "created_at": pr.get("createdAt", ""),
                    "updated_at": pr.get("updatedAt", ""),
                    "merged_at": pr.get("mergedAt", ""),
                    "merged_by": merged_by,
                    "additions": pr.get("additions", 0),
                    "deletions": pr.get("deletions", 0),
                    "changed_files": pr.get("changedFiles", 0),
                    "commits_count": pr.get("commits", {}).get("totalCount", 0),
                    "url": pr.get("url", ""),
                    "user_roles": ", ".join(roles),
                    "labels": [l.get("name") for l in pr.get("labels", {}).get("nodes", [])]
                })
        
        # Parse issues (filter by user involvement)
        issues = []
        for issue in repo_data.get("issues", {}).get("nodes", []):
            issue_author = issue.get("author", {}).get("login", "") if issue.get("author") else ""
            assignees = [a.get("login") for a in issue.get("assignees", {}).get("nodes", [])]
            participants = [p.get("login") for p in issue.get("participants", {}).get("nodes", [])]
            commenters = [c.get("author", {}).get("login") for c in issue.get("comments", {}).get("nodes", []) if c.get("author")]
            
            if username in [issue_author] + assignees + participants + commenters:
                roles = []
                if issue_author == username: roles.append("Author")
                if username in assignees: roles.append("Assigned")
                if username in commenters and "Author" not in roles:
                    count = len([c for c in issue.get("comments", {}).get("nodes", []) if c.get("author", {}).get("login") == username])
                    roles.append(f"Commented ({count})")
                if username in participants and not roles:
                    roles.append("Participant")
                
                issues.append({
                    "repo": repo_name,
                    "repo_url": repo_url,
                    "number": issue.get("number", 0),
                    "title": issue.get("title", ""),
                    "author": issue_author,
                    "state": issue.get("state", ""),
                    "created_at": issue.get("createdAt", ""),
                    "updated_at": issue.get("updatedAt", ""),
                    "closed_at": issue.get("closedAt", ""),
                    "url": issue.get("url", ""),
                    "user_roles": ", ".join(roles),
                    "labels": [l.get("name") for l in issue.get("labels", {}).get("nodes", [])]
                })
        
        return {
            "commits": commits,
            "prs": prs,
            "issues": issues,
            "has_activity": len(commits) > 0 or len(prs) > 0 or len(issues) > 0
        }
    
    def _empty_activity_response(self, username: str, org_name: str, from_date: str, to_date: str, page: int = 1, per_page: int = 50) -> Dict:
        """Return empty activity response with pagination info."""
        return {
            "status": "success",
            "summary": {
                "user": username,
                "organization": org_name,
                "date_range": f"{from_date} to {to_date}",
                "total_commits": 0,
                "total_prs_involved": 0,
                "prs_authored": 0,
                "prs_reviewed": 0,
                "prs_merged": 0,
                "total_additions": 0,
                "total_deletions": 0,
            },
            "commits": [],
            "prs": [],
            "issues": [],
            "pagination": {
                "current_page": page,
                "per_page": per_page,
                "commits": {"total": 0, "total_pages": 1, "has_next_page": False, "returned": 0},
                "prs": {"total": 0, "total_pages": 1, "has_next_page": False, "returned": 0},
                "issues": {"total": 0, "total_pages": 1, "has_next_page": False, "returned": 0},
                "repos": {"total_in_org": 0, "with_user_activity": 0}
            }
        }
  • Invocation that registers all GitHubIntegration tools, including get_user_org_activity, by passing the instance to register_tools.
    self.register_tools(self.gi)

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/saidsef/mcp-github-pr-issue-analyser'

If you have feedback or need assistance with the MCP directory API, please join our Discord server