Skip to content

feat(contacts): add list/search Workspace domain directory people#834

Open
abedegno wants to merge 3 commits into
taylorwilsdon:mainfrom
abedegno:feat/contacts-directory-people
Open

feat(contacts): add list/search Workspace domain directory people#834
abedegno wants to merge 3 commits into
taylorwilsdon:mainfrom
abedegno:feat/contacts-directory-people

Conversation

@abedegno

@abedegno abedegno commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds two read-only tools backed by the People API endpoints
listDirectoryPeople and searchDirectoryPeople. These surface the Google
Workspace domain directory (colleagues and admin-managed shared domain
contacts), which is distinct from the user's personal contacts already
covered by list_contacts and search_contacts.

Domain directory access requires a Google Workspace account. Consumer
Google accounts cannot grant directory.readonly.

Changes

  • auth/scopes.py: add DIRECTORY_READONLY_SCOPE
    (https://www.googleapis.com/auth/directory.readonly) and include it in
    CONTACTS_SCOPES and the contacts read-only tool scope set.
  • auth/service_decorator.py: expose a "directory_read" scope key so the
    new tools require only the directory scope.
  • gcontacts/contacts_tools.py: add list_directory_people and
    search_directory_people, plus a helper that resolves friendly source
    aliases ('profile' / 'contact') to the API's DIRECTORY_SOURCE_TYPE_*
    values. The default readMask additionally requests locations and
    userDefined so admin-synced HR fields (such as start date or employee
    id) surface when configured.
  • tests/gcontacts/test_directory_tools.py: 18 unit tests covering source
    resolution, default and explicit params, page-size clamping,
    pagination, empty results, and input validation.

New tools

  • list_directory_people: list people in the authenticated user's
    Workspace domain directory. Supports page_size (max 1000), pagination,
    source selection (profile and/or contact), and optional merging of
    shared-contact entries into matching profiles.
  • search_directory_people: substring search across the domain directory
    by name, email, organization, and title. Supports page_size (max 500),
    pagination, source selection, and merge.

Both tools are read-only and request only directory.readonly.

Testing

All 18 unit tests pass. Each tool is invoked through its unwrapped async
function with a mocked People API client.

Notes

Adding DIRECTORY_READONLY_SCOPE to the shared contacts scope set means
existing contacts users will be prompted to re-consent on next auth, and
consumer (non-Workspace) accounts will see a Workspace-only scope they
cannot grant. Happy to scope this down to only the two new tools if
preferred.

Summary by CodeRabbit

  • New Features

    • List and search Google Workspace directory people with pagination and source filtering
    • Optional merge of shared contacts into profile results when querying directory sources
    • Contact permissions updated to include Google Workspace directory read-only access
  • Tests

    • Added unit tests covering directory listing/search behavior, input validation, pagination, source resolution, and user-facing messages

Adds two read-only tools backed by the People API endpoints
listDirectoryPeople and searchDirectoryPeople, which surface the
Workspace domain directory (colleagues and admin-managed shared
domain contacts) - distinct from the user's personal contacts already
covered by list_contacts and search_contacts.

- auth/scopes.py: add DIRECTORY_READONLY_SCOPE
  (https://www.googleapis.com/auth/directory.readonly) and include it in
  CONTACTS_SCOPES and the contacts read-only tool scope set.
- auth/service_decorator.py: expose a "directory_read" scope key so the
  new tools require only the directory scope.
- gcontacts/contacts_tools.py: add list_directory_people and
  search_directory_people, plus a small helper that resolves friendly
  source aliases ('profile' / 'contact') to the API's
  DIRECTORY_SOURCE_TYPE_* values. Default readMask additionally
  requests locations and userDefined so admin-synced HR fields (such as
  start date or employee id) surface when configured.
- tests/gcontacts/test_directory_tools.py: 17 unit tests covering source
  resolution, default and explicit params, page-size clamping,
  pagination, empty results and input validation.

Domain directory access requires a Google Workspace account; consumer
Google accounts cannot grant directory.readonly.
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 562a2706-c2e9-4827-a6e7-658b20dcd413

📥 Commits

Reviewing files that changed from the base of the PR and between 5f78b92 and c245e40.

📒 Files selected for processing (1)
  • tests/gcontacts/test_directory_tools.py

📝 Walkthrough

Walkthrough

This PR adds OAuth scope configuration and two new MCP tools to enable listing and searching people in a Google Workspace directory via the Google People API, backed by comprehensive tests.

Changes

Directory People Listing and Search

Layer / File(s) Summary
OAuth Scope Configuration
auth/scopes.py, auth/service_decorator.py
Introduces DIRECTORY_READONLY_SCOPE constant for directory API read-only access, adds it to full contacts scopes and read-only scope mappings, and registers a "directory_read" scope group.
Directory People Tools Implementation
gcontacts/contacts_tools.py
Adds directory-specific API constants (readMask fields, source identifiers, merge mode), implements _resolve_directory_sources() to normalize/validate source aliases, and implements list_directory_people() and search_directory_people() with input validation, page-size clamping, pagination, optional mergeSources, and formatted responses.
Directory Tools Test Suite
tests/gcontacts/test_directory_tools.py
Adds tests that unwrap auth-decorated async tools, cover source alias resolution and error cases, validate request parameter construction for listDirectoryPeople and searchDirectoryPeople, enforce page-size limits and query validation, and assert user-facing output including pagination and empty-result messages.

Sequence Diagram

sequenceDiagram
  participant Client
  participant list_directory_people
  participant _resolve_directory_sources
  participant PeopleAPI as People API
  participant ClientResponse as Client Response

  Client->>list_directory_people: page_size, sources, merge_contact_into_profile
  list_directory_people->>_resolve_directory_sources: sources aliases
  _resolve_directory_sources-->>list_directory_people: resolved sources
  list_directory_people->>PeopleAPI: listDirectoryPeople(readMask, sources, pageSize, mergeSources, pageToken)
  PeopleAPI-->>list_directory_people: people[], nextPageToken
  list_directory_people-->>ClientResponse: formatted text + nextPageToken?

  Client->>search_directory_people: query, page_size, sources, merge_contact_into_profile
  search_directory_people->>_resolve_directory_sources: sources aliases
  _resolve_directory_sources-->>search_directory_people: resolved sources
  search_directory_people->>PeopleAPI: searchDirectoryPeople(query, readMask, sources, pageSize, mergeSources, pageToken)
  PeopleAPI-->>search_directory_people: people[], nextPageToken
  search_directory_people-->>ClientResponse: formatted text + nextPageToken?
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

enhancement

Poem

🐰 I hopped through scopes and People API streams,
I stitched aliases and paged through dreams.
List or search, with tokens in tow,
Directory blooms where the contacts grow. ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description includes detailed information about changes, new tools, testing, and notes; however, the required template sections are not filled in. Complete the PR description template with sections for Type of Change, Testing checklist, and Checklist items to ensure consistency with repository standards.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely summarizes the main change: adding list/search tools for Workspace domain directory people.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/gcontacts/test_directory_tools.py (1)

203-267: ⚡ Quick win

Consider additional test coverage for search_directory_people to match list_directory_people.

The test suite for list_directory_people includes tests for merge_contact_into_profile=False (line 102), page_token passthrough (line 145), and negative page_size rejection (line 134). Adding equivalent tests for search_directory_people would provide symmetrical coverage and catch potential regressions in either tool.

💚 Suggested additional test cases
def test_merge_disabled_omits_param(self):
    svc = MagicMock()
    svc.people.return_value.searchDirectoryPeople.return_value.execute.return_value = {
        "people": []
    }
    
    run(
        search_directory_people(
            service=svc,
            user_google_email="me@example.com",
            query="test",
            merge_contact_into_profile=False,
        )
    )
    
    kwargs = svc.people.return_value.searchDirectoryPeople.call_args.kwargs
    assert "mergeSources" not in kwargs

def test_passes_page_token(self):
    svc = MagicMock()
    svc.people.return_value.searchDirectoryPeople.return_value.execute.return_value = {
        "people": []
    }
    
    run(
        search_directory_people(
            service=svc,
            user_google_email="me@example.com",
            query="test",
            page_token="tok123",
        )
    )
    
    kwargs = svc.people.return_value.searchDirectoryPeople.call_args.kwargs
    assert kwargs["pageToken"] == "tok123"

def test_rejects_negative_page_size(self):
    svc = MagicMock()
    with pytest.raises(UserInputError):
        run(
            search_directory_people(
                service=svc,
                user_google_email="me@example.com",
                query="test",
                page_size=-1,
            )
        )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/gcontacts/test_directory_tools.py` around lines 203 - 267, Add three
tests for search_directory_people to mirror list_directory_people: (1)
test_merge_disabled_omits_param — call search_directory_people(service=svc,
user_google_email="me@example.com", query="test",
merge_contact_into_profile=False) with svc mocked and assert "mergeSources" not
in svc.people.return_value.searchDirectoryPeople.call_args.kwargs; (2)
test_passes_page_token — call search_directory_people(..., page_token="tok123")
and assert kwargs["pageToken"] == "tok123"; (3) test_rejects_negative_page_size
— call search_directory_people(..., page_size=-1) inside
pytest.raises(UserInputError) to ensure negative sizes raise; use the same
MagicMock pattern and run(...) helper as other tests to locate where to add
them.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/gcontacts/test_directory_tools.py`:
- Around line 203-267: Add three tests for search_directory_people to mirror
list_directory_people: (1) test_merge_disabled_omits_param — call
search_directory_people(service=svc, user_google_email="me@example.com",
query="test", merge_contact_into_profile=False) with svc mocked and assert
"mergeSources" not in
svc.people.return_value.searchDirectoryPeople.call_args.kwargs; (2)
test_passes_page_token — call search_directory_people(..., page_token="tok123")
and assert kwargs["pageToken"] == "tok123"; (3) test_rejects_negative_page_size
— call search_directory_people(..., page_size=-1) inside
pytest.raises(UserInputError) to ensure negative sizes raise; use the same
MagicMock pattern and run(...) helper as other tests to locate where to add
them.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a0e01610-3053-4309-bcfa-bce48c6f78ec

📥 Commits

Reviewing files that changed from the base of the PR and between ae88c01 and 5f78b92.

📒 Files selected for processing (4)
  • auth/scopes.py
  • auth/service_decorator.py
  • gcontacts/contacts_tools.py
  • tests/gcontacts/test_directory_tools.py

Mirror list_directory_people tests for search_directory_people:
merge-disabled omits mergeSources, page_token passthrough, and
invalid page_size rejection.
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