Skip to content

feat(gmail): add get_gmail_label_stats tool for unread/total message counts#698

Open
robelmes wants to merge 1 commit into
taylorwilsdon:mainfrom
robelmes:feat/1-get-gmail-label-stats
Open

feat(gmail): add get_gmail_label_stats tool for unread/total message counts#698
robelmes wants to merge 1 commit into
taylorwilsdon:mainfrom
robelmes:feat/1-get-gmail-label-stats

Conversation

@robelmes
Copy link
Copy Markdown

@robelmes robelmes commented Apr 17, 2026

Summary

Add a get_gmail_label_stats MCP tool that returns message and thread counts for a Gmail label using the labels.get endpoint.

Motivation

Currently, the only way to get unread message counts is to paginate through search_gmail_messages with is:unread — which is slow and token-heavy for accounts with many messages. The Gmail API's labels.get endpoint returns messagesTotal, messagesUnread, threadsTotal, and threadsUnread in a single API call.

What changed

  • gmail/gmail_tools.py: Added get_gmail_label_stats tool (placed after list_gmail_labels). Accepts an optional label_id parameter (defaults to INBOX), calls service.users().labels().get(), and returns formatted stats.
  • tests/gmail/test_get_gmail_label_stats.py: 4 unit tests covering default inbox, custom label ID, zero counts, and missing optional fields.

Implementation details

  • Follows the existing tool pattern: @server.tool() / @handle_http_errors / @require_google_service
  • Uses gmail_read scope (read-only, no extra permissions needed)
  • Tested live against a real Gmail account — API response matches expected format

Example output

📊 Label stats for 'INBOX' (ID: INBOX, type: system):

  Messages total:  1234
  Messages unread: 56
  Threads total:   800
  Threads unread:  30

Gmail API Reference

Summary by CodeRabbit

New Features

  • Added a new Gmail label statistics tool that retrieves detailed information including total messages, unread messages, total threads, and unread threads for any label (defaults to Inbox when not specified)

Tests

  • Added comprehensive test coverage for the label statistics feature, validating functionality across various scenarios including default behavior, custom labels, and edge cases

…loses #1)

Add a new MCP tool that returns message and thread counts for a Gmail
label using the labels.get endpoint — much faster than paginating
through search results to count unread messages.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 17, 2026

📝 Walkthrough

Walkthrough

A new MCP tool function get_gmail_label_stats was added to retrieve Gmail label statistics including message and thread counts. The function wraps a single Gmail API call and formats the results. Comprehensive test coverage with four scenarios was included to validate the functionality.

Changes

Cohort / File(s) Summary
Gmail Label Statistics Tool
gmail/gmail_tools.py
Added new get_gmail_label_stats function with read-only access guards that queries Gmail API for label metadata and message/thread counters, returning formatted summary text.
Tool Test Coverage
tests/gmail/test_get_gmail_label_stats.py
Added comprehensive async pytest suite covering default behavior, custom label IDs, zero count handling, and missing field edge cases with mocked Gmail API service.

Sequence Diagram

sequenceDiagram
    participant Client
    participant MCP as MCP Server
    participant Gmail as Gmail API
    
    Client->>MCP: Call get_gmail_label_stats(label_id)
    activate MCP
    MCP->>MCP: Verify read-only access & auth
    MCP->>Gmail: users().labels().get(id=label_id).execute()
    activate Gmail
    Gmail-->>MCP: Label metadata + counts
    deactivate Gmail
    MCP->>MCP: Extract & format stats
    MCP-->>Client: Formatted label statistics
    deactivate MCP
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A label's tale now told with care,
Stats hopping forth through the Gmail air,
Messages counted, threads all clear,
Five tests bounding, have no fear!
The inbox whispers, we now hear. 📧✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description includes Summary, Motivation, What changed, Implementation details, Example output, and API reference. However, the required Description template sections (Type of Change, Testing, Checklist) are not completed. Complete the required template sections: mark Type of Change as 'New feature', confirm Testing checkboxes, and complete the Checklist section including 'Allow edits from maintainers'.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: adding a new get_gmail_label_stats tool for retrieving unread and total message counts.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
tests/gmail/test_get_gmail_label_stats.py (1)

1-122: Test coverage looks good.

Four scenarios (default INBOX, custom label, zero counts, missing fields) cover the meaningful branches of the formatter. Mock wiring via shared return_value chains correctly models the service.users().labels().get(...).execute() call, and _unwrap appropriately bypasses the FastMCP/decorator layers.

Minor optional suggestions (non-blocking):

  • Line 51/78: assert_called_once_with(userId="me", id=...) is good; consider additionally asserting .execute was awaited/called once to catch regressions where someone re-invokes the builder chain.
  • test_inbox_default does not assert the label type ("system") is rendered in the header, whereas test_custom_label_id does. Adding a symmetric check would tighten coverage.
  • The sys.path.insert at line 7 works but is brittle; if this repo configures pytest roots via conftest.py/pyproject.toml, prefer relying on that instead.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/gmail/test_get_gmail_label_stats.py` around lines 1 - 122, Add
assertions to verify the final execute call and the label type rendering: in
test_inbox_default and test_custom_label_id, after calling fn(...) assert that
service.users().labels().get().execute was called once (to catch broken builder
invocation) and in test_inbox_default also assert the label "system" appears in
result (symmetry with test_custom_label_id which checks "user"); locate these in
the tests referencing _mock_service, test_inbox_default, and
test_custom_label_id and add the two assertions accordingly.
gmail/gmail_tools.py (1)

2576-2630: LGTM — clean, idiomatic addition.

The tool follows the established pattern in this file: decorator order matches other read-only tools, asyncio.to_thread is correctly passed the bound .execute method reference, the least-privilege gmail_read scope is used, and missing optional counters safely default to 0 via response.get(..., 0). Output format is consistent with list_gmail_labels.

One optional nit: for very large mailboxes the raw counts (e.g. 123456) may be harder to read. Consider formatting with thousands separators (f"{messages_total:,}") for nicer display. Non-blocking.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@gmail/gmail_tools.py` around lines 2576 - 2630, The numeric counters in
get_gmail_label_stats (messages_total, messages_unread, threads_total,
threads_unread) should be displayed with thousands separators for readability;
update the formatted strings in the lines list (the f-strings that currently
embed messages_total, messages_unread, threads_total, threads_unread) to use
comma-formatting (e.g. f"{messages_total:,}") so large counts render with
separators.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@gmail/gmail_tools.py`:
- Around line 2576-2630: The numeric counters in get_gmail_label_stats
(messages_total, messages_unread, threads_total, threads_unread) should be
displayed with thousands separators for readability; update the formatted
strings in the lines list (the f-strings that currently embed messages_total,
messages_unread, threads_total, threads_unread) to use comma-formatting (e.g.
f"{messages_total:,}") so large counts render with separators.

In `@tests/gmail/test_get_gmail_label_stats.py`:
- Around line 1-122: Add assertions to verify the final execute call and the
label type rendering: in test_inbox_default and test_custom_label_id, after
calling fn(...) assert that service.users().labels().get().execute was called
once (to catch broken builder invocation) and in test_inbox_default also assert
the label "system" appears in result (symmetry with test_custom_label_id which
checks "user"); locate these in the tests referencing _mock_service,
test_inbox_default, and test_custom_label_id and add the two assertions
accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a49a2cd7-b69c-4f31-af72-ae59ad0ae399

📥 Commits

Reviewing files that changed from the base of the PR and between 8294887 and 46a219d.

📒 Files selected for processing (2)
  • gmail/gmail_tools.py
  • tests/gmail/test_get_gmail_label_stats.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant