@teamnetwork/m365-mcp-server 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/graph/calendar.js
CHANGED
|
@@ -22,7 +22,7 @@ export async function listEvents(client, email, params) {
|
|
|
22
22
|
startDateTime: params.startDate,
|
|
23
23
|
endDateTime: params.endDate,
|
|
24
24
|
})
|
|
25
|
-
.select(
|
|
25
|
+
.select(params.select)
|
|
26
26
|
.top(params.maxResults)
|
|
27
27
|
.orderby('start/dateTime')
|
|
28
28
|
.get();
|
package/dist/graph/email.js
CHANGED
|
@@ -23,7 +23,7 @@ export async function listMessages(client, email, params) {
|
|
|
23
23
|
const folderId = params.folderId === 'Inbox' ? 'Inbox' : params.folderId;
|
|
24
24
|
let req = client
|
|
25
25
|
.api(`/users/${email}/mailFolders/${folderId}/messages`)
|
|
26
|
-
.select(
|
|
26
|
+
.select(params.select)
|
|
27
27
|
.top(params.maxResults)
|
|
28
28
|
.orderby('receivedDateTime desc');
|
|
29
29
|
if (params.bodySearch) {
|
|
@@ -2,19 +2,31 @@ import { ListEventsSchema } from '../schemas.js';
|
|
|
2
2
|
import { createGraphClient } from '../../graph/clientFactory.js';
|
|
3
3
|
import { listEvents } from '../../graph/calendar.js';
|
|
4
4
|
import { assertMailboxConfigured } from '../../middleware/readonlyGuard.js';
|
|
5
|
-
import { errorResponse } from '../../utils/errors.js';
|
|
5
|
+
import { errorResponse, ToolError } from '../../utils/errors.js';
|
|
6
6
|
import { logger } from '../../utils/logger.js';
|
|
7
|
+
import { resolveSelect, SelectValidationError, EVENT_SELECT_FIELDS, EVENT_SELECT_DEFAULT } from '../../utils/selectFields.js';
|
|
7
8
|
export function registerListEvents(server, config, auth) {
|
|
8
9
|
server.tool('list_events', 'List calendar events within a date range. Recurring events are expanded into individual instances.', ListEventsSchema.shape, async (params) => {
|
|
9
10
|
logger.info('list_events', { accountId: params.accountId, mailbox: params.mailbox });
|
|
10
11
|
try {
|
|
11
12
|
assertMailboxConfigured(config, params.accountId, params.mailbox);
|
|
13
|
+
let select;
|
|
14
|
+
try {
|
|
15
|
+
select = resolveSelect(params.select, EVENT_SELECT_FIELDS, EVENT_SELECT_DEFAULT);
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
if (err instanceof SelectValidationError) {
|
|
19
|
+
return errorResponse(new ToolError(err.message, 'INVALID_SELECT_FIELD'));
|
|
20
|
+
}
|
|
21
|
+
throw err;
|
|
22
|
+
}
|
|
12
23
|
const token = await auth.getAccessToken(params.accountId);
|
|
13
24
|
const client = createGraphClient(token);
|
|
14
25
|
const result = await listEvents(client, params.mailbox, {
|
|
15
26
|
startDate: params.startDate,
|
|
16
27
|
endDate: params.endDate,
|
|
17
28
|
maxResults: params.maxResults,
|
|
29
|
+
select,
|
|
18
30
|
...(params.calendarId !== undefined && { calendarId: params.calendarId }),
|
|
19
31
|
});
|
|
20
32
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
@@ -2,8 +2,9 @@ import { ListMessagesSchema } from '../schemas.js';
|
|
|
2
2
|
import { createGraphClient } from '../../graph/clientFactory.js';
|
|
3
3
|
import { listMessages } from '../../graph/email.js';
|
|
4
4
|
import { assertMailboxConfigured } from '../../middleware/readonlyGuard.js';
|
|
5
|
-
import { errorResponse } from '../../utils/errors.js';
|
|
5
|
+
import { errorResponse, ToolError } from '../../utils/errors.js';
|
|
6
6
|
import { logger } from '../../utils/logger.js';
|
|
7
|
+
import { resolveSelect, SelectValidationError, MESSAGE_SELECT_FIELDS, MESSAGE_SELECT_DEFAULT } from '../../utils/selectFields.js';
|
|
7
8
|
export function registerListMessages(server, config, auth) {
|
|
8
9
|
server.tool('list_messages', 'List email messages in a mailbox folder with optional filters for date range, subject, body content, and sender. Returns message metadata (not full body) to keep response size small.', ListMessagesSchema.shape, async (params) => {
|
|
9
10
|
logger.info('list_messages', {
|
|
@@ -14,11 +15,22 @@ export function registerListMessages(server, config, auth) {
|
|
|
14
15
|
});
|
|
15
16
|
try {
|
|
16
17
|
assertMailboxConfigured(config, params.accountId, params.mailbox);
|
|
18
|
+
let select;
|
|
19
|
+
try {
|
|
20
|
+
select = resolveSelect(params.select, MESSAGE_SELECT_FIELDS, MESSAGE_SELECT_DEFAULT);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
if (err instanceof SelectValidationError) {
|
|
24
|
+
return errorResponse(new ToolError(err.message, 'INVALID_SELECT_FIELD'));
|
|
25
|
+
}
|
|
26
|
+
throw err;
|
|
27
|
+
}
|
|
17
28
|
const token = await auth.getAccessToken(params.accountId);
|
|
18
29
|
const client = createGraphClient(token);
|
|
19
30
|
const result = await listMessages(client, params.mailbox, {
|
|
20
31
|
folderId: params.folderId,
|
|
21
32
|
maxResults: params.maxResults,
|
|
33
|
+
select,
|
|
22
34
|
...(params.startDate !== undefined && { startDate: params.startDate }),
|
|
23
35
|
...(params.endDate !== undefined && { endDate: params.endDate }),
|
|
24
36
|
...(params.subject !== undefined && { subject: params.subject }),
|
package/dist/tools/schemas.js
CHANGED
|
@@ -19,6 +19,7 @@ export const ListMessagesSchema = AccountMailbox.extend({
|
|
|
19
19
|
bodySearch: z.string().optional().describe('Full-text search in message body. Note: cannot be combined with $filter — date/sender filters are applied client-side when this is used.'),
|
|
20
20
|
sender: z.string().email().optional().describe('Filter messages from this sender email address'),
|
|
21
21
|
maxResults: z.number().int().min(1).max(100).default(25).describe('Maximum number of messages to return (1–100)'),
|
|
22
|
+
select: z.array(z.string()).optional().describe('Fields to return. Omit for defaults (subject, from, receivedDateTime, isRead, hasAttachments, bodyPreview). Valid fields: id, subject, from, toRecipients, ccRecipients, bccRecipients, receivedDateTime, sentDateTime, bodyPreview, body, hasAttachments, attachmentCount, importance, isRead, isDraft, webLink, parentFolderId, conversationId'),
|
|
22
23
|
});
|
|
23
24
|
export const GetMessageSchema = AccountMailbox.extend({
|
|
24
25
|
messageId: z.string().describe('The message ID'),
|
|
@@ -50,6 +51,7 @@ export const ListEventsSchema = AccountMailbox.extend({
|
|
|
50
51
|
startDate: z.string().datetime().describe('Start of the date range (ISO 8601). Recurring events are expanded within this window.'),
|
|
51
52
|
endDate: z.string().datetime().describe('End of the date range (ISO 8601)'),
|
|
52
53
|
maxResults: z.number().int().min(1).max(100).default(25),
|
|
54
|
+
select: z.array(z.string()).optional().describe('Fields to return. Omit for defaults (subject, start, end, location, isOnlineMeeting). Valid fields: id, subject, start, end, location, isOnlineMeeting, onlineMeetingUrl, organizer, attendees, body, bodyPreview, categories, isRecurring, recurrence, sensitivity, showAs, uid'),
|
|
53
55
|
});
|
|
54
56
|
export const GetEventSchema = AccountMailbox.extend({
|
|
55
57
|
eventId: z.string().describe('The event ID'),
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// Allowed $select field lists and validation for list_messages and list_events.
|
|
2
|
+
// id is always forced into the select regardless of what the caller requests.
|
|
3
|
+
export const MESSAGE_SELECT_FIELDS = new Set([
|
|
4
|
+
'id',
|
|
5
|
+
'subject',
|
|
6
|
+
'from',
|
|
7
|
+
'toRecipients',
|
|
8
|
+
'ccRecipients',
|
|
9
|
+
'bccRecipients',
|
|
10
|
+
'receivedDateTime',
|
|
11
|
+
'sentDateTime',
|
|
12
|
+
'bodyPreview',
|
|
13
|
+
'body',
|
|
14
|
+
'hasAttachments',
|
|
15
|
+
'attachmentCount',
|
|
16
|
+
'importance',
|
|
17
|
+
'isRead',
|
|
18
|
+
'isDraft',
|
|
19
|
+
'webLink',
|
|
20
|
+
'parentFolderId',
|
|
21
|
+
'conversationId',
|
|
22
|
+
]);
|
|
23
|
+
export const EVENT_SELECT_FIELDS = new Set([
|
|
24
|
+
'id',
|
|
25
|
+
'subject',
|
|
26
|
+
'start',
|
|
27
|
+
'end',
|
|
28
|
+
'location',
|
|
29
|
+
'isOnlineMeeting',
|
|
30
|
+
'onlineMeetingUrl',
|
|
31
|
+
'organizer',
|
|
32
|
+
'attendees',
|
|
33
|
+
'body',
|
|
34
|
+
'bodyPreview',
|
|
35
|
+
'categories',
|
|
36
|
+
'isRecurring',
|
|
37
|
+
'recurrence',
|
|
38
|
+
'sensitivity',
|
|
39
|
+
'showAs',
|
|
40
|
+
'uid',
|
|
41
|
+
]);
|
|
42
|
+
export const MESSAGE_SELECT_DEFAULT = [
|
|
43
|
+
'subject',
|
|
44
|
+
'from',
|
|
45
|
+
'receivedDateTime',
|
|
46
|
+
'isRead',
|
|
47
|
+
'hasAttachments',
|
|
48
|
+
'bodyPreview',
|
|
49
|
+
];
|
|
50
|
+
export const EVENT_SELECT_DEFAULT = [
|
|
51
|
+
'subject',
|
|
52
|
+
'start',
|
|
53
|
+
'end',
|
|
54
|
+
'location',
|
|
55
|
+
'isOnlineMeeting',
|
|
56
|
+
];
|
|
57
|
+
/**
|
|
58
|
+
* Validate, normalise, and force-include `id` in a select list.
|
|
59
|
+
* Returns the final comma-separated string for Graph API $select,
|
|
60
|
+
* or throws with a descriptive message listing valid fields.
|
|
61
|
+
*/
|
|
62
|
+
export function resolveSelect(rawSelect, allowed, defaults) {
|
|
63
|
+
// Empty array → treat as omitted, use defaults
|
|
64
|
+
const fields = rawSelect && rawSelect.length > 0
|
|
65
|
+
? rawSelect.map((f) => f.trim()).filter(Boolean)
|
|
66
|
+
: defaults;
|
|
67
|
+
// Validate each field
|
|
68
|
+
for (const field of fields) {
|
|
69
|
+
if (!allowed.has(field)) {
|
|
70
|
+
throw new SelectValidationError(field, allowed);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// id must always be present
|
|
74
|
+
const withId = fields.includes('id') ? fields : ['id', ...fields];
|
|
75
|
+
return withId.join(',');
|
|
76
|
+
}
|
|
77
|
+
export class SelectValidationError extends Error {
|
|
78
|
+
constructor(invalidField, allowed) {
|
|
79
|
+
const validList = [...allowed].sort().join(', ');
|
|
80
|
+
super(`Invalid select field: "${invalidField}". Valid fields: ${validList}`);
|
|
81
|
+
this.name = 'SelectValidationError';
|
|
82
|
+
}
|
|
83
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@teamnetwork/m365-mcp-server",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "MCP server for Microsoft 365 Email and Calendar access using client credentials (app-only auth). Supports multiple accounts, shared mailboxes, per-mailbox read-only access, folder navigation, and date/content filtering.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|