MCP Server for Microsoft Entra ID user and group management
The server has 6 tools with moderate quality. Tool naming is clear and verb-based (find_user_by_name, add_user_to_group, list_group_members, remove_user_from_group, get_group_info, search_groups). Descriptions exist for all tools and most parameters, meeting baseline expectations. However, descriptions are inconsistent in detail, some are short (e.g., 'Add a user to an Entra ID group.' is only 36 chars) while others provide reasonable context (find_user_by_name description is 171 chars and explains the search behavior). Input schemas are present with type definitions (all params are 'string' type), but parameter descriptions vary in quality. Most critically, OUTPUT schemas are not documented, the code returns dict[str, Any] with ad-hoc structures (e.g., {"success": bool, "message": str, ...members: list}), making it impossible for an LLM to understand the response structure in advance. Error handling returns success/failure flags with messages, but does not provide recovery guidance (e.g., 'User not found. Try search_users() with a partial name.'). No tool annotations (readOnlyHint, destructiveHint) are present despite clear risk levels being specified in the metadata. Tool composition is sound, each tool has a single responsibility and naming chains logically (find_user -> add_user_to_group). Parameter naming is adequate but lacks type suffixes in some cases (e.g., user_id and group_id are explicit, but search_term and name could be clearer about what they accept).
Add a user to an Entra ID group.
Find users in Entra ID by searching their name. Searches in displayName, givenName (first name), and surname (last name). Returns users where any of these fields contain the search term.
Get information about an Entra ID group.
List all members of an Entra ID group.
Remove a user from an Entra ID group.
Search for groups in Entra ID by name.
Output schemas are not documented. Tools return dict[str, Any] with ad-hoc structures (success/message/data fields vary per tool). LLMs cannot infer response structure in advance, forcing runtime guessing and error-prone field extraction.
Tool annotations (readOnlyHint, destructiveHint, idempotentHint) are absent from all tool definitions. The metadata specifies risk levels (READ_ONLY, WRITE, DESTRUCTIVE), but these are not communicated in the MCP protocol via tool annotations. Agents cannot distinguish safe vs. destructive operations without manually parsing risk metadata.
Inferred effective spec: <=2025-11-25.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-22 | D | 55 | <=2025-11-25 | v2 |
| 2026-03-09 | D | 58 | - | v1 |
Error handling provides no recovery guidance. Tools return {"success": false, "message": str(e)} with raw exception messages (e.g., 'Error adding user to group: <exception>'). LLMs receive no actionable next steps, should indicate whether to retry, call a different tool, or ask the user.
Parameter descriptions are inconsistent in detail. add_user_to_group and remove_user_from_group have minimal descriptions ('The ID of the user to add/remove', 'The ID of the group'). find_user_by_name provides richer context ('The name to search for... case-insensitive partial matches'). Descriptions should consistently explain format, constraints, and use cases across all tools.
Tool descriptions vary significantly in length (35-171 chars).
search_groups lacks pagination support. The code hardcodes '$top': '50' but does not expose a limit parameter or return a total count or next_cursor. If results exceed 50, the LLM cannot access them. Should support limit and offset/cursor parameters.
list_group_members has no pagination parameters despite the code supporting OData queries. Large groups may have >50 members, but the tool provides no way to paginate. Should accept limit and offset/cursor parameters and return a count.
Destructive operations (remove_user_from_group) have no confirmation or dry-run step. An agent calling this tool immediately removes a user without preview or confirmation. Should implement a confirm_before_execute pattern or at least provide a dry_run parameter.