msgraph-mcp
Server Configuration
Describes the environment variables required to run the server.
| Name | Required | Description | Default |
|---|---|---|---|
| MSGRAPH_MCP_CLIENT_ID | Yes | Your Entra app's client ID | |
| MSGRAPH_MCP_TENANT_ID | Yes | Your tenant ID, or one of common / organizations / consumers | |
| MSGRAPH_MCP_TOKEN_CACHE_PATH | No | Optional path to override the token cache location |
Instructions
Guidance the server publishes about itself, which clients place ahead of the tool catalog so the model reads it before choosing anything.
This server publishes no instructions, or was last inspected before Glama recorded them.
Capabilities
Features and capabilities supported by this server
Protocol revision2025-11-25
| Capability | Details |
|---|---|
| tools | {
"listChanged": false
} |
| prompts | {
"listChanged": false
} |
| resources | {
"subscribe": false,
"listChanged": false
} |
| experimental | {} |
Tools
Functions exposed to the LLM to take actions
| Name | Description |
|---|---|
| whoamiA | Return the signed-in user's profile. Also a smoke test that authentication is working. Args: include_raw: when True, include the raw Graph payload under "raw". Returns: Trimmed user object: {id, display_name, user_principal_name, mail, job_title} |
| list_messagesA | List messages from a mail folder, newest first. Args:
folder_id: Folder id, or a well-known name (inbox, sentitems, drafts,
deleteditems, archive, junkemail). Default: inbox.
mailbox: Optional mailbox (email or user ID). Default: signed-in user.
limit: 1-100. Default 25.
page_token: Pass next_page_token from a previous result to continue.
include_raw: Include the raw Graph payload under "raw" on each item.
unread_only: When True, only return messages where isRead is false.
Adds Returns: {"items": [trimmed_message, ...], "next_page_token": str | None} |
| search_messagesA | Search messages across all folders using Graph $search. The query is passed to Graph as-is — supply quoting yourself if you need a literal phrase (e.g. '"weekly report"') or KQL fielded predicates (e.g. 'from:alice subject:"report"'). Graph does not allow combining $search with $filter or $orderby, and KQL
has no Args:
query: Graph $search expression. Plain tokens match across common
mail fields; quoted phrases match literally; KQL |
| get_messageA | Fetch a single message by id. Args: message_id: Graph message id. mailbox: Optional mailbox. include_body: When True, the full body is returned. Default False (snippet only). include_raw: Include the raw Graph payload under "raw". Returns: Trimmed message object. |
| list_attachmentsA | List attachments on a message (metadata only — no content). |
| download_attachmentB | Download a single attachment. Args: save_path: Write the bytes to this file path (or into this existing directory, using the attachment's name) instead of returning content. Returns {"path", "name", "content_type", "size_bytes"}. Returns: - Image attachments (PNG/JPEG/GIF/WebP, no save_path): metadata plus the image itself as a native MCP image block, viewable directly. - With save_path: {"path", "name", "content_type", "size_bytes"}. - Otherwise: {name, content_type, size_bytes, content_base64}. |
| send_messageA | Send a new message immediately. Args: to: List of recipient email addresses (required, non-empty). subject: Subject line. body: Message body. body_type: "text" or "html". Default "text". cc: Optional CC list. bcc: Optional BCC list. attachments: Optional list of {name, content_b64, content_type}. Total raw size must be <= 3 MB. save_to_sent_items: Default True. mailbox: Optional mailbox (defaults to signed-in user). Returns: {"status": "sent"} |
| create_draftB | Create a draft message (not sent). Returns the trimmed draft. |
| reply_messageB | Reply to the sender of a message. |
| reply_all_messageB | Reply-all to a message (sender + everyone on the to/cc lines). |
| forward_messageC | Forward a message to new recipients. |
| update_messageA | Patch a message's read state / flag / importance / categories. For moving a message between folders, prefer Returns the trimmed updated message. |
| delete_messageA | Soft-delete a message (moves it to Deleted Items). |
| list_foldersB | List the user's mail folders (top level). Returns: {"items": [trimmed_folder, ...], "next_page_token": None} |
| move_messageA | Move a message to another folder. Args: message_id: Graph message id. destination: Target folder id, OR one of the well-known names: 'archive', 'inbox', 'junkemail', 'deleteditems', 'sentitems', 'drafts'. mailbox: Optional mailbox. include_raw: Include the raw Graph payload. Returns: The trimmed moved message (which lives in the destination folder). |
| create_folderA | Create a new mail folder (top-level or child). Args: display_name: Folder name. Required, non-empty. parent_folder_id: If set, create as a child of this folder; else create at the top level. Accepts a folder id or a well-known name ('inbox', 'archive', etc.). mailbox: Optional mailbox (email or user id) for shared mailboxes. include_raw: Include the raw Graph payload. Returns: The trimmed new folder. |
| update_folderA | Rename and/or reparent a mail folder. At least one of Args: folder_id: Graph folder id. Required. display_name: New folder name. None = unchanged. parent_folder_id: Move under this folder. Accepts a folder id or a well-known name ('inbox', 'archive', etc.). None = unchanged. mailbox: Optional mailbox (email or user id) for shared mailboxes. include_raw: Include the raw Graph payload. Returns: The trimmed updated folder. |
| delete_folderA | Delete a mail folder. Refuses to delete folders that aren't empty. Reads the folder first and aborts if Args: folder_id: Graph folder id. Required. mailbox: Optional mailbox (email or user id) for shared mailboxes. Returns: {"deleted": True, "id": } |
| archive_messageC | Archive a message (move it to the Archive folder). |
| mark_readC | Mark a message as read. |
| mark_unreadC | Mark a message as unread. |
| flag_messageC | Set the follow-up flag on a message. |
| unflag_messageC | Clear the follow-up flag on a message. |
| batch_archive_messagesA | Archive a list of messages (bulk move to the Archive well-known folder). |
| batch_move_messagesA | Move a list of messages to the given folder.
Spec deviation: Spec §6.3 calls for pre-resolving well-known names to folder ids. In practice Graph's /move endpoint accepts well-known names directly (this is how the existing single-message move_message already works — see src/msgraph_mcp/tools/mail_folders.py). We pass through unchanged. If a real-world test surfaces a case where Graph rejects a well-known name in $batch but accepts it in single calls, add resolution then. |
| batch_mark_readB | Mark a list of messages as read. |
| batch_mark_unreadC | Mark a list of messages as unread. |
| batch_flag_messagesA | Flag a list of messages (set followup flag to 'flagged'). |
| batch_unflag_messagesA | Unflag a list of messages (set followup flag to 'notFlagged'). |
| list_rulesB | List inbox mail rules. Returns: {"items": [trimmed_rule, ...], "next_page_token": None} |
| get_ruleA | Get a single inbox mail rule by id. Args: rule_id: Graph rule id. Required. mailbox: Optional mailbox (email or user id) for shared mailboxes. include_raw: Include the raw Graph payload. Returns: Trimmed rule dict. |
| create_ruleA | Create an inbox mail rule. At least one condition AND one action are required. Conditions
within a rule are AND-ed by Outlook. Pass multiple values in a list
(e.g. Common condition args: sender_contains: Substrings to match against the sender display name or address (e.g. ["example.com"]). subject_contains: Substrings to match against the subject. body_contains: Substrings to match against the message body. body_or_subject_contains: Match either subject or body. from_addresses: Exact email addresses to match (e.g. ["noreply@example.com"]). has_attachments: True/False to require/exclude attachments. Common action args: move_to_folder: Destination folder id (use list_folders or create_folder to get one). mark_as_read: Mark matching messages as read. delete: Move matching messages to Deleted Items. stop_processing_rules: If True, no further rules run after this one matches. Recommended for routing-to-folder rules. Other args: sequence: Optional rule order (lower runs first). is_enabled: Whether the rule is active. Default True. mailbox: Optional mailbox for shared mailboxes. include_raw: Include the raw Graph payload. Returns: The trimmed new rule. |
| update_ruleA | Patch fields of an existing inbox mail rule. Pass None to leave a field unchanged. Replacement semantics for nested blocks:
Graph PATCH on messageRules replaces the entire Args: rule_id: Graph rule id. Required. display_name: New display name. None = unchanged. is_enabled: Enable/disable the rule. None = unchanged. sequence: New order (lower runs first). None = unchanged. sender_contains / subject_contains / body_contains / body_or_subject_contains / from_addresses / has_attachments: Passing any of these REPLACES the conditions block. move_to_folder / mark_as_read / delete / stop_processing_rules: Passing any of these REPLACES the actions block. mailbox: Optional mailbox for shared mailboxes. include_raw: Include the raw Graph payload. Returns: The trimmed updated rule. |
| delete_ruleA | Delete an inbox mail rule by id. Args: rule_id: Graph rule id. Required. mailbox: Optional mailbox (email or user id) for shared mailboxes. Returns: {"deleted": True, "id": } |
| list_calendarsB | List the user's calendars. |
| list_eventsA | List events in a calendar within a date range. Uses Graph's calendarView, which expands recurring events into instances. Args: calendar_id: Calendar ID. (Use list_calendars to discover.) start_datetime: ISO 8601 datetime, e.g. "2026-05-19T00:00:00Z". Required. end_datetime: ISO 8601 datetime. Required. mailbox: Optional mailbox. limit: 1-100. Default 25. page_token: Continuation token. include_raw: Include raw payloads. Returns: {"items": [trimmed_event, ...], "next_page_token": str | None} |
| get_eventB | Fetch a single event by id. |
| create_eventA | Create a calendar event. Args: subject: Event subject. start_datetime: ISO 8601 datetime (no offset; pair with time_zone). end_datetime: ISO 8601 datetime. time_zone: IANA tz id (e.g. "America/Los_Angeles") or "UTC". Default "UTC". body, body_type: Optional body and "text"|"html". location: Optional location string. attendees: Optional list of attendee email addresses (treated as required). is_online_meeting: When True, Outlook adds a Teams meeting link. is_all_day: All-day event flag. calendar_id: Optional calendar id; default is the user's primary calendar. mailbox: Optional mailbox. Returns: Trimmed created event. |
| update_eventB | Patch fields of an existing event. Pass None to leave a field unchanged. If you change start_datetime or end_datetime, you must also pass time_zone. |
| delete_eventA | Delete an event without sending a cancellation to attendees. |
| cancel_eventC | Cancel an event and send a cancellation notice to attendees. |
| respond_to_eventA | Respond to a meeting invite. Args: response: One of "accept", "tentativelyAccept", "decline". comment: Optional comment included with the response. send_response: When False, your response status is recorded without emailing the organizer. |
| find_meeting_timesA | Suggest meeting times that work for the given attendees. Args: attendees: List of attendee email addresses. duration_minutes: Meeting duration in whole minutes (e.g. 30). start_window: ISO 8601 start of the candidate window. end_window: ISO 8601 end of the candidate window. max_candidates: Cap on returned suggestions (default 20). Returns: { "suggestions": [ {"start": {date_time, time_zone}, "end": {...}, "confidence": float, "order_hint": int}, ... ], "empty_reason": str | None, } |
| list_chatsA | List the signed-in user's Teams chats (1:1, group, meeting). Members are expanded so 1:1 chats (which have no topic) are identifiable by participant name. Results come back in Graph's default order (most recent message activity first). Each item carries two timestamps; use the right one:
Args: limit: 1-100. Default 25. page_token: Pass next_page_token from a previous result to continue. include_raw: Include the raw Graph payload under "raw" on each item. Returns: {"items": [trimmed_chat, ...], "next_page_token": str | None} |
| list_chat_messagesA | List messages in a chat, newest first. Args: chat_id: Graph chat id (from list_chats). limit: 1-100. Default 25. page_token: Continuation token from a previous result. include_body: When True, include each message's full body and any attachment card payloads (e.g. Adaptive Card JSON for bot posts). Default False (snippet only; card text still feeds the snippet). include_raw: Include the raw Graph payload under "raw" on each item. Returns: {"items": [trimmed_chat_message, ...], "next_page_token": str | None} |
| list_joined_teamsA | List the teams the signed-in user is a member of. Args: limit: 1-100. Default 25. Applied client-side (see below); teams beyond the limit are dropped. page_token: Continuation token from a previous result. include_raw: Include the raw Graph payload under "raw" on each item. Returns: {"items": [trimmed_team, ...], "next_page_token": str | None} |
| list_channelsA | List channels in a team. Args: team_id: Graph team id (from list_joined_teams). limit: 1-100. Default 25. Applied client-side (see below); channels beyond the limit are dropped. page_token: Continuation token from a previous result. include_raw: Include the raw Graph payload under "raw" on each item. Returns: {"items": [trimmed_channel, ...], "next_page_token": str | None} |
| list_channel_messagesA | List root messages in a channel, newest first. Replies are not included here; fetch them with list_message_replies. Args: team_id: Graph team id. channel_id: Graph channel id (from list_channels). limit: 1-50 (Graph caps channel message pages at 50). Default 25. page_token: Continuation token from a previous result. include_body: When True, include each message's full body and any attachment card payloads (e.g. Adaptive Card JSON for bot posts). Default False (snippet only; card text still feeds the snippet). include_raw: Include the raw Graph payload under "raw" on each item. Returns: {"items": [trimmed_chat_message, ...], "next_page_token": str | None} |
| list_message_repliesA | List replies to a channel message (the thread under a root post), newest first. Args: team_id: Graph team id. channel_id: Graph channel id. message_id: Graph id of the root channel message (from list_channel_messages). limit: 1-50 (Graph caps reply pages at 50). Default 25. page_token: Continuation token from a previous result. include_body: When True, include each reply's full body and any attachment card payloads. Default False (snippet only). include_raw: Include the raw Graph payload under "raw" on each item. Returns: {"items": [trimmed_chat_message, ...], "next_page_token": str | None} |
| download_hosted_contentA | Download one inline hosted-content item (usually an image). Specify the message location with exactly one of:
Get message_id and hosted_content_id from a message's hosted_content_refs (returned by list_chat_messages / list_channel_messages / list_message_replies). Args: message_id: Graph id of the message the content is attached to. hosted_content_id: Graph hosted-content id (from hosted_content_refs). chat_id: Chat id, if the message is in a chat. team_id: Team id, if the message is in a channel. channel_id: Channel id, if the message is in a channel. save_path: Write the bytes to this file path (or into this existing directory) instead of returning content. Returns {"path", "content_type", "size_bytes"} with no content payload. include_raw: Include the raw payload under "raw" (ignored when the result is an image block or a saved file). Returns: - Image content (PNG/JPEG/GIF/WebP, no save_path): metadata plus the image itself as a native MCP image block, viewable directly. - With save_path: {"path": str, "content_type": str | None, "size_bytes": int}. - Otherwise: {"content_type": str | None, "size_bytes": int, "content_base64": str | None}. content_type is sniffed from the bytes (Graph does not return it on the $value endpoint); it may be None for unrecognized formats. |
Prompts
Interactive templates invoked by user choice
| Name | Description |
|---|---|
No prompts | |
Resources
Contextual data attached and managed by the client
| Name | Description |
|---|---|
No resources | |
TDQS
Scored across 50 tools
Most tools map cleanly to a resource-action pair (messages, folders, rules, events, chats, channels), and shortcuts like archive_message and the batch_* tools are clearly specialized versions of move_message. The main overlap is update_message duplicating the read-state/flag conveniences (mark_read, flag_message, etc.), but the descriptions resolve it.
Tool names follow a consistent snake_case verb_noun pattern with list_/get_/create_/update_/delete_ prefixes reused across resources. Exceptions like whoami and reply_all_message are still predictable and fit the overall style.
50 tools is well above the typical well-scoped range, and even for a multi-domain Microsoft Graph server it feels heavy. The broad mail/calendar/Teams coverage gives the count some rationale, so it is 'too many' rather than an extreme mismatch.
Mail coverage is very complete with CRUD for messages, folders, rules, attachments, and bulk operations, and calendar events have full lifecycle support. However, Teams is effectively read-only with no send/create/update/delete for chats or channels, and calendar resources themselves cannot be created or deleted, leaving notable dead ends.