outlook-mcp/email/index.js
Seton Carmichael e70840552d feat(outlook-mcp): shared mailbox targeting, discovery, and scopes (v1.1.0)
Add optional mailbox (UPN/SMTP) routing on email/folder/thread tools via
users/{upn}/... and X-AnchorMailbox. New list-mailboxes probes primary,
OUTLOOK_SHARED_MAILBOXES seeds, cache, and candidates. Send supports
mailbox-rooted sendMail and onBehalfOf. MSAL requests Mail.*.Shared;
check-auth-status reports token scp gaps. Docs, env example, tests.
2026-08-24 08:41:05 -04:00

202 lines
7.1 KiB
JavaScript

/**
* Email module for Outlook MCP server
*/
const handleListEmails = require('./list');
const handleSearchEmails = require('./search');
const handleReadEmail = require('./read');
const handleReadMultipleEmails = require('./read-multiple');
const handleSendEmail = require('./send');
const mailboxProp = {
type: "string",
description: "Optional shared/delegated mailbox UPN or SMTP (e.g. 'helpdesk@contoso.com'). Omit for the signed-in user's primary mailbox. Message IDs are mailbox-scoped — pass the same mailbox on read/thread calls."
};
// Email tool definitions
const emailTools = [
{
name: "list-emails",
description: "Lists recent emails from a folder. Results include 'conversationId' which can be passed to 'get-email-thread' to retrieve a full thread. Optional mailbox targets a shared mailbox.",
inputSchema: {
type: "object",
properties: {
folder: {
type: "string",
description: "Email folder to list (e.g., 'inbox', 'sent', 'drafts', default: 'inbox')"
},
count: {
anyOf: [{ type: "number" }, { type: "string" }],
description: "Number of emails to retrieve (default: 10, max: 500). WARNING: Large counts may consume significant context tokens."
},
dateFrom: {
type: "string",
description: "Start date for email filtering (ISO format: 'YYYY-MM-DD' or relative: 'yesterday', 'last7days')"
},
dateTo: {
type: "string",
description: "End date for email filtering (ISO format: 'YYYY-MM-DD' or relative: 'today', 'tomorrow')"
},
dateRange: {
type: "string",
description: "Predefined date range ('today', 'yesterday', 'last7days', 'last30days', 'thisweek', 'lastweek', 'thismonth', 'lastmonth')"
},
mailbox: mailboxProp
},
required: []
},
handler: handleListEmails
},
{
name: "search-emails",
description: "Search for emails by sender, subject, keywords, or filters. Results include 'conversationId' — pass it to 'get-email-thread' to read the full thread. Optional mailbox targets a shared mailbox. If no matching emails are found, use 'list-emails' to browse recent mail instead.",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "Full-text search query (searches across subject, body, and sender)"
},
folder: {
type: "string",
description: "Email folder to search in (default: 'inbox'). Use 'list-folders' to discover folder names."
},
from: {
type: "string",
description: "Filter by sender — can be an email address or display name (e.g. 'john@example.com' or 'John')"
},
to: {
type: "string",
description: "Filter by recipient email address or display name"
},
subject: {
type: "string",
description: "Filter by subject line keywords (e.g. 'invoice', 'meeting notes')"
},
dateFrom: {
type: "string",
description: "Start date for email filtering (ISO format: 'YYYY-MM-DD' or relative: 'yesterday', 'last7days')"
},
dateTo: {
type: "string",
description: "End date for email filtering (ISO format: 'YYYY-MM-DD' or relative: 'today', 'tomorrow')"
},
dateRange: {
type: "string",
description: "Predefined date range ('today', 'yesterday', 'last7days', 'last30days', 'thisweek', 'lastweek', 'thismonth', 'lastmonth')"
},
hasAttachments: {
anyOf: [{ type: "boolean" }, { type: "string" }],
description: "Set true to return only emails that have attachments"
},
unreadOnly: {
anyOf: [{ type: "boolean" }, { type: "string" }],
description: "Set true to return only unread emails"
},
count: {
anyOf: [{ type: "number" }, { type: "string" }],
description: "Number of results to return (default: 10, max: 500). WARNING: Large counts may consume significant context tokens."
},
strict: {
anyOf: [{ type: "boolean" }, { type: "string" }],
description: "Set true to disable the fallback to recent emails when no exact search matches are found"
},
mailbox: mailboxProp
},
required: []
},
handler: handleSearchEmails
},
{
name: "read-email",
description: "Reads the content of a specific email. If the message came from a shared mailbox, pass the same mailbox value.",
inputSchema: {
type: "object",
properties: {
id: {
type: "string",
description: "ID of the email to read"
},
mailbox: mailboxProp
},
required: ["id"]
},
handler: handleReadEmail
},
{
name: "read-emails",
description: "Reads the content of multiple emails at once. If IDs came from a shared mailbox, pass the same mailbox value.",
inputSchema: {
type: "object",
properties: {
ids: {
type: "array",
items: {
type: "string"
},
description: "Array of email IDs to read (max: 10)"
},
mailbox: mailboxProp
},
required: ["ids"]
},
handler: handleReadMultipleEmails
},
{
name: "send-email",
description: "Composes and sends a new email. Optional mailbox sends as that shared mailbox (requires Exchange Send As + Mail.Send.Shared). Use onBehalfOf=true for Send on Behalf via me/sendMail with from set.",
inputSchema: {
type: "object",
properties: {
to: {
type: "string",
description: "Comma-separated list of recipient email addresses"
},
cc: {
type: "string",
description: "Comma-separated list of CC recipient email addresses"
},
bcc: {
type: "string",
description: "Comma-separated list of BCC recipient email addresses"
},
subject: {
type: "string",
description: "Email subject"
},
body: {
type: "string",
description: "Email body content (can be plain text or HTML)"
},
importance: {
type: "string",
description: "Email importance (normal, high, low)",
enum: ["normal", "high", "low"]
},
saveToSentItems: {
type: "boolean",
description: "Whether to save the email to sent items"
},
mailbox: mailboxProp,
from: {
type: "string",
description: "Optional From SMTP. Defaults to mailbox when mailbox is set."
},
onBehalfOf: {
anyOf: [{ type: "boolean" }, { type: "string" }],
description: "If true, send via me/sendMail with from=shared (Send on Behalf style). Default false uses users/{mailbox}/sendMail when mailbox is set."
}
},
required: ["to", "subject", "body"]
},
handler: handleSendEmail
}
];
module.exports = {
emailTools,
handleListEmails,
handleSearchEmails,
handleReadEmail,
handleReadMultipleEmails,
handleSendEmail
};