Skip to content

Add Google Groups (Cloud Identity API) service#566

Draft
darkdreamingdan wants to merge 2 commits into
taylorwilsdon:mainfrom
darkdreamingdan:feat/add_ggroups
Draft

Add Google Groups (Cloud Identity API) service#566
darkdreamingdan wants to merge 2 commits into
taylorwilsdon:mainfrom
darkdreamingdan:feat/add_ggroups

Conversation

@darkdreamingdan
Copy link
Copy Markdown

@darkdreamingdan darkdreamingdan commented Mar 13, 2026

Summary

  • Adds a new Google Groups MCP service module using the Cloud Identity Groups API (cloudidentity v1)
  • Implements 7 tools across core/extended/complete tiers: search_groups, get_group, list_group_members, manage_group, manage_group_members, list_groups, lookup_group
  • Wires up scopes (cloud-identity.groups, cloud-identity.groups.readonly), service config, permissions, tool tiers, and both entry points (main.py, fastmcp_server.py)
  • Adds 20 unit tests covering formatting helpers, imports, and constants
  • Updates README with tool documentation

Changes

File Change
ggroups/__init__.py New module init
ggroups/groups_tools.py 7 MCP tools + formatting helpers
auth/scopes.py GROUPS_SCOPE, GROUPS_READONLY_SCOPE, hierarchy, scope maps
auth/service_decorator.py cloudidentity service config + scope group aliases
auth/permissions.py groups permission levels (readonly/full)
core/tool_tiers.yaml groups tier definitions
main.py Tool import + icon registration
fastmcp_server.py Module import + service registration
README.md Google Groups tools documentation
tests/ggroups/test_groups_tools.py 20 unit tests

Test plan

  • All 284 tests pass (including 20 new ones)
  • lookup_group verified working with groupKey_id parameter naming (googleapiclient converts dotted REST params to underscored Python kwargs)
  • Manual testing of remaining tools against a live Cloud Identity environment

AI Usage

I am a professional dev, am totally anti-AI Slop. This PR is mostly coded using AI with detailed Plan function and considered prompting. I have tested the MCP for read-only use cases and it has been great. I will move out of Draft when i have reviewed the code, and tested other use cases.

Made with Cursor

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 13, 2026

📝 Walkthrough

Walkthrough

This PR introduces comprehensive Google Groups (Cloud Identity API) support to the MCP toolset. Changes include new OAuth scopes and permissions, tool tier definitions, a complete new Google Groups tool module with seven public functions, and integration into the service registry and main application entry points.

Changes

Cohort / File(s) Summary
Authentication & Authorization
auth/scopes.py, auth/permissions.py, auth/service_decorator.py
Added Google Groups OAuth scopes (GROUPS_SCOPE, GROUPS_READONLY_SCOPE), updated scope hierarchy and tool-to-scope mappings, added "groups" permission levels, and registered Cloud Identity service configuration with scope group aliases.
Tool Configuration
core/tool_tiers.yaml, README.md
Introduced new "groups" tool tier with core (search, get, list, manage), extended (manage_members), and complete (list_all, lookup) action tiers; documented in README alongside existing tools.
Groups Tool Implementation
ggroups/groups_tools.py, ggroups/__init__.py
Created new module providing seven public functions for group search, retrieval, membership management, and lifecycle operations with error handling, input validation, and formatted output helpers.
Service Integration
main.py, fastmcp_server.py
Registered "groups" tool in the service registry and import resolver to enable the tool alongside existing services.
Testing
tests/ggroups/__init__.py, tests/ggroups/test_groups_tools.py
Added test package and comprehensive unit tests for formatting helpers with coverage of basic, extended, and edge-case scenarios.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • #510: Modifies overlapping auth.scopes and auth.permissions structures along with SERVICE_PERMISSION_LEVELS and service mappings that are also updated in this PR.

Suggested labels

enhancement

Poem

🐰 Hop skip and jump, new groups now spring,
Scopes aligned, permissions sing!
Search and manage, members too,
Cloud Identity dreams come true! 👥

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: adding Google Groups (Cloud Identity API) service as a new feature to the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Description check ✅ Passed The PR description covers most required sections with clear summary, changes table, test plan, and implementation notes. However, the Testing checklist is incomplete—manual testing is pending.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@darkdreamingdan darkdreamingdan marked this pull request as draft March 13, 2026 02:59
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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
main.py (1)

146-159: ⚠️ Potential issue | 🟡 Minor

Missing "groups" in --tools argument choices.

The "groups" service is added to tool_imports and tool_icons but is missing from the --tools choices list. Users won't be able to selectively load the groups tool via --tools groups.

🔧 Proposed fix
     parser.add_argument(
         "--tools",
         nargs="*",
         choices=[
             "gmail",
             "drive",
             "calendar",
             "docs",
             "sheets",
             "chat",
             "forms",
             "slides",
             "tasks",
             "contacts",
+            "groups",
             "search",
             "appscript",
         ],
         help="Specify which tools to register. If not provided, all tools are registered.",
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@main.py` around lines 146 - 159, The choices array for the --tools CLI option
in main.py is missing "groups", so add "groups" to the choices list (the same
place where the current array contains "gmail","drive",...,"appscript") so it
matches the entries in tool_imports and tool_icons and allows users to enable
the groups tool via --tools groups.
🧹 Nitpick comments (3)
tests/ggroups/test_groups_tools.py (2)

7-10: Consider using pytest's conftest.py for path setup instead of sys.path manipulation.

Direct sys.path manipulation works but is brittle. A conftest.py at the tests root with proper path configuration would be more maintainable and consistent with pytest best practices.

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

In `@tests/ggroups/test_groups_tools.py` around lines 7 - 10, Replace the ad-hoc
sys.path manipulation in test_groups_tools.py (the sys.path.insert(0,
os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) / import os /
import sys lines) with a pytest conftest.py at the tests root that performs the
project-root path setup (e.g., in pytest_configure or by adding to sys.path
there) so individual tests don't modify sys.path; then remove the
sys.path/os.path import and insertion from test_groups_tools.py and verify
imports still resolve under pytest.

223-257: Tests verify module structure and constants correctly.

The import and constant tests ensure the module exports expected functions and maintains the expected constant values.

Consider adding integration tests with mocked Google APIs (using httpretty or similar) for the actual tool functions (search_groups, get_group, etc.) to ensure end-to-end behavior. As per coding guidelines: "Write unit tests for every FastMCP tool and integration tests for high-risk flows."

Do you want me to generate example test stubs for the tool functions with mocked Cloud Identity API responses?

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

In `@tests/ggroups/test_groups_tools.py` around lines 223 - 257, Add
integration-style test stubs that mock the Cloud Identity/Google Groups HTTP
responses for the tool functions (search_groups, get_group, list_group_members,
manage_group_members, list_groups, lookup_group) and assert expected outputs and
error handling; use a HTTP mocking library (httpretty or responses) to simulate
successful and failing API responses, import and exercise the actual functions
from groups_tools, and include a test that verifies the GOOGLE_GROUP_LABEL
constant is used correctly in requests or returned values; ensure mocks are torn
down between tests and cover at least one success path and one error/edge path
per function.
ggroups/groups_tools.py (1)

12-13: Incorrect Resource type import.

The Resource imported from mcp is the MCP Resource type, not the Google API client resource type. The service parameter in all tools is actually a googleapiclient.discovery.Resource object returned by build(). While the type hint won't cause runtime errors (Python's duck typing), it's semantically incorrect.

Consider either:

  1. Using Any for the service type hint (common pattern for dynamically built services)
  2. Importing from googleapiclient.discovery if a proper type is needed
♻️ Suggested fix
 from googleapiclient.errors import HttpError
-from mcp import Resource
+from typing import Any, Dict, List, Optional

Then update all function signatures to use service: Any instead of service: Resource.

Alternatively, keep Resource unused and use Any:

-from mcp import Resource
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ggroups/groups_tools.py` around lines 12 - 13, The file currently imports
Resource from mcp which is the wrong type for the Google API client; replace
that import with a correct typing approach and update all function signatures
that use service: Resource to use service: Any (import Any from typing) OR
alternately import Resource from googleapiclient.discovery and use that type;
specifically change the top-level import (remove "from mcp import Resource") and
update every function that declares service: Resource to service: Any (or
service: googleapiclient.discovery.Resource if you choose the latter) so the
type hints match the object returned by googleapiclient.discovery.build().
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ggroups/groups_tools.py`:
- Around line 479-494: The request body passed to
service.groups().memberships().modifyMembershipRoles (variable body) is invalid:
modifyMembershipRoles must not mix addRoles and removeRoles in one call and both
fields expect arrays of strings (e.g., "MANAGER"), not objects; update the logic
so either (A) only set removeRoles (delete addRoles and send removeRoles as
["MEMBER","OWNER",...]) or (B) perform two calls to modifyMembershipRoles
sequentially — first with addRoles as a list of role strings [role] and then a
second call with removeRoles as a list of role strings (or vice versa); ensure
you call .execute() on each request and keep membership_name and
service.groups().memberships().modifyMembershipRoles as the target for these
corrected calls.

---

Outside diff comments:
In `@main.py`:
- Around line 146-159: The choices array for the --tools CLI option in main.py
is missing "groups", so add "groups" to the choices list (the same place where
the current array contains "gmail","drive",...,"appscript") so it matches the
entries in tool_imports and tool_icons and allows users to enable the groups
tool via --tools groups.

---

Nitpick comments:
In `@ggroups/groups_tools.py`:
- Around line 12-13: The file currently imports Resource from mcp which is the
wrong type for the Google API client; replace that import with a correct typing
approach and update all function signatures that use service: Resource to use
service: Any (import Any from typing) OR alternately import Resource from
googleapiclient.discovery and use that type; specifically change the top-level
import (remove "from mcp import Resource") and update every function that
declares service: Resource to service: Any (or service:
googleapiclient.discovery.Resource if you choose the latter) so the type hints
match the object returned by googleapiclient.discovery.build().

In `@tests/ggroups/test_groups_tools.py`:
- Around line 7-10: Replace the ad-hoc sys.path manipulation in
test_groups_tools.py (the sys.path.insert(0,
os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))) / import os /
import sys lines) with a pytest conftest.py at the tests root that performs the
project-root path setup (e.g., in pytest_configure or by adding to sys.path
there) so individual tests don't modify sys.path; then remove the
sys.path/os.path import and insertion from test_groups_tools.py and verify
imports still resolve under pytest.
- Around line 223-257: Add integration-style test stubs that mock the Cloud
Identity/Google Groups HTTP responses for the tool functions (search_groups,
get_group, list_group_members, manage_group_members, list_groups, lookup_group)
and assert expected outputs and error handling; use a HTTP mocking library
(httpretty or responses) to simulate successful and failing API responses,
import and exercise the actual functions from groups_tools, and include a test
that verifies the GOOGLE_GROUP_LABEL constant is used correctly in requests or
returned values; ensure mocks are torn down between tests and cover at least one
success path and one error/edge path per function.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fd40e63b-d121-4ec7-9d06-69835ea311ca

📥 Commits

Reviewing files that changed from the base of the PR and between 5a2e1f3 and 9dc4620.

📒 Files selected for processing (11)
  • README.md
  • auth/permissions.py
  • auth/scopes.py
  • auth/service_decorator.py
  • core/tool_tiers.yaml
  • fastmcp_server.py
  • ggroups/__init__.py
  • ggroups/groups_tools.py
  • main.py
  • tests/ggroups/__init__.py
  • tests/ggroups/test_groups_tools.py

Comment thread ggroups/groups_tools.py
Comment on lines +479 to +494
# action == "modify_role"
body = {
"addRoles": [{"name": role}],
"removeRoles": [],
}

current_roles = {"MEMBER", "MANAGER", "OWNER"}
current_roles.discard(role)
body["removeRoles"] = [r for r in current_roles]

result = await asyncio.to_thread(
service.groups()
.memberships()
.modifyMembershipRoles(name=membership_name, body=body)
.execute
)
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

Cloud Identity API modifyMembershipRoles removeRoles format

💡 Result:

For Cloud Identity API groups.memberships.modifyMembershipRoles, the request body field removeRoles is an array of strings, where each string is the name of a MembershipRole you want removed (role names are OWNER, MANAGER, MEMBER). You cannot remove MEMBER; delete the membership instead. [1][2]

Example (remove MANAGER from a membership):

POST https://cloudidentity.googleapis.com/v1/groups/{group}/memberships/{membership}:modifyMembershipRoles
Content-Type: application/json

{
  "removeRoles": ["MANAGER"]
}

[1][2]

Note: Don’t mix removeRoles with addRoles or updateRolesParams in the same request. [1]

Sources:
[1] Cloud Identity API – groups.memberships.modifyMembershipRoles (REST)
[2] Cloud Identity API – MembershipRole type (role name values)


Remove addRoles or use separate API calls—modifyMembershipRoles does not allow mixing role modifications in a single request.

The Cloud Identity API modifyMembershipRoles endpoint requires that you either add roles OR remove roles in a single request, not both. The current implementation violates this constraint by including both addRoles (line 480) and removeRoles (line 487) in the same request body.

Additionally, both addRoles and removeRoles expect arrays of strings (role names like "MANAGER"), not objects. The current addRoles uses the wrong format [{"name": role}] instead of [role].

To fix, either:

  1. Only remove roles (delete the addRoles logic), or
  2. Make separate sequential calls—one to add the role, then one to remove others
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ggroups/groups_tools.py` around lines 479 - 494, The request body passed to
service.groups().memberships().modifyMembershipRoles (variable body) is invalid:
modifyMembershipRoles must not mix addRoles and removeRoles in one call and both
fields expect arrays of strings (e.g., "MANAGER"), not objects; update the logic
so either (A) only set removeRoles (delete addRoles and send removeRoles as
["MEMBER","OWNER",...]) or (B) perform two calls to modifyMembershipRoles
sequentially — first with addRoles as a list of role strings [role] and then a
second call with removeRoles as a list of role strings (or vice versa); ensure
you call .execute() on each request and keep membership_name and
service.groups().memberships().modifyMembershipRoles as the target for these
corrected calls.

lookup_group is the simplest path from a group email to a resource name
needed by other tools — it belongs in the default tool set.

Made-with: Cursor
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