You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
improvement(microsoft-ad): add pagination support and fill BlockMeta gaps (#5442)
* improvement(microsoft-ad): add pagination support and fill BlockMeta gaps
Full validate-integration pass against live Microsoft Graph API docs. No
critical bugs found (endpoints, methods, params, and OAuth scopes were
already correct). Fixed the real gaps:
- List Users/Groups/Group Members had no way to page past the default
Graph page size (100, max 999 via $top), which silently truncated
results for the block's own audit/sweep templates. Added a nextLink
input/output following the same convention as microsoft_dataverse.
- Create Group's visibility dropdown was missing HiddenMembership, a
valid Microsoft 365-only value that can only be set at creation time.
- Rounded out BlockMeta skills (3 -> 5) with two more real, tool-grounded
use cases: directory search and ad hoc group membership changes.
* fix(microsoft-ad): correct $search syntax and create-user response fields
Independent 4-agent re-audit against live Graph docs surfaced two real bugs
predating this PR:
- list_users/list_groups sent $search="<term>" with no property prefix.
Graph requires the "property:value" form for directory-object search
(e.g. "displayName:term" OR "mail:term") and 400s on a bare string.
- create_user's POST had no $select, so Graph's default create response
omits department/accountEnabled even when submitted, making
transformResponse report them back as null. Added the same $select used
by list/get so the response reflects what was actually set.
Also tightened create_group's visibility description: only HiddenMembership
is create-only: Private/Public can still be changed after creation via
Update Group.
* fix(microsoft-ad): validate nextLink origin, allow nextLink-only pagination
Greptile and Cursor both flagged the same real issue: nextLink was passed
straight to fetch() as the request URL with no origin check, while the
OAuth bearer token was always attached. A crafted or prompt-injected
nextLink pointing outside graph.microsoft.com would exfiltrate the token.
Fix: reuse the existing assertGraphNextPageUrl/getGraphNextPageUrl helpers
from tools/sharepoint/utils (already the shared pattern for SharePoint,
OneDrive, Teams, Planner, Outlook, Excel Graph pagination) instead of a
bespoke unvalidated pass-through.
Cursor also caught that list_group_members required groupId even when
only nextLink was supplied for a later page. Relaxed groupId to optional
at the tool level, matching how sharepoint_get_list treats its analogous
listId param — the URL builder still throws a clear error if neither
groupId nor nextLink is given.
* fix(microsoft-ad): allow $search+$filter combo, escape backslashes, fix pagination UX
Second independent 4-agent re-audit of the final state (post security fix)
surfaced 3 more real issues:
- list_users/list_groups threw an error whenever $search and $filter were
both supplied, claiming Graph doesn't support combining them. It does
(AND semantics, documented) — the check was blocking valid, documented
usage for no reason. Removed it.
- The $search term escaping only handled embedded double quotes, not
backslashes, which Graph's own escaping rule also requires. Fixed the
replace order (backslashes first, then quotes).
- list_group_members's Group ID field was still hard-required in the
block UI for every operation including list_group_members, undermining
the nextLink-only pagination path added earlier (the tool itself no
longer requires it). Dropped list_group_members from the UI-required
list, matching the tool's own conditional requirement — the runtime
"Group ID is required" check still catches a genuinely empty call.
'List the members of an Azure AD group for an access review. Use for periodic attestation of privileged or sensitive groups.',
489
502
content:
490
-
'# Audit Group Membership\n\nProduce a current membership snapshot for a group.\n\n## Steps\n1. Resolve the target group with Get Group or List Groups (filter or search by name).\n2. Call List Group Members for the group id, raising Max Results if the group is large.\n3. For each member, optionally call Get User to enrich with job title, department, and account-enabled status.\n\n## Output\nReturn a table of members with id, display name, email, department, and whether the account is enabled. Highlight disabled or stale accounts that still hold membership and should be reviewed for removal.',
503
+
'# Audit Group Membership\n\nProduce a current membership snapshot for a group.\n\n## Steps\n1. Resolve the target group with Get Group or List Groups (filter or search by name).\n2. Call List Group Members for the group id, raising Max Results if the group is large. If the response includes a Next Page link, keep calling List Group Members with that link until it comes back empty to capture every member.\n3. For each member, optionally call Get User to enrich with job title, department, and account-enabled status.\n\n## Output\nReturn a table of members with id, display name, email, department, and whether the account is enabled. Highlight disabled or stale accounts that still hold membership and should be reviewed for removal.',
504
+
},
505
+
{
506
+
name: 'search-directory-users',
507
+
description:
508
+
'Search Azure AD (Entra ID) for users matching a name, department, or other attribute. Use for directory lookups and reporting.',
509
+
content:
510
+
"# Search Directory Users\n\nFind users in the directory by attribute instead of enumerating everyone.\n\n## Steps\n1. Use List Users with Search set to the name or email fragment, or Filter set to an OData expression (e.g. `department eq 'Sales'`) for attribute-based lookups. Search and Filter cannot be combined in one call.\n2. If the result set is large, follow the Next Page link returned in the response to page through additional results.\n3. Optionally call Get User for a specific match to retrieve full profile detail.\n\n## Output\nReturn the matching users with id, display name, user principal name, department, and account-enabled status. State clearly when Max Results or pagination limits mean the list may be incomplete.",
511
+
},
512
+
{
513
+
name: 'manage-group-membership',
514
+
description:
515
+
'Add or remove specific users from an Azure AD (Entra ID) group on demand, outside of onboarding/offboarding flows. Use for ad hoc access changes and team restructuring.',
516
+
content:
517
+
"# Manage Group Membership\n\nApply a one-off membership change to a group.\n\n## Steps\n1. Resolve the group with Get Group or List Groups, and resolve each affected user with Get User or List Users.\n2. Call Add Group Member or Remove Group Member with the group id and each user id.\n3. Confirm the change with List Group Members.\n\n## Output\nReturn which users were added or removed and the group's current member count. Report any member that failed to add or remove (e.g. already a member, or not found) instead of silently skipping it.",
description: 'Group visibility: "Private" or "Public"',
68
+
description:
69
+
'Group visibility: "Private" or "Public" (can be changed later), or "HiddenMembership" (Microsoft 365 groups only; can only be set at creation and never changed afterward)',
0 commit comments