@joshluedeman/m365-mcp 0.1.0
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/LICENSE +21 -0
- package/README.md +196 -0
- package/dist/chunk-JEMHJMEL.js +207 -0
- package/dist/chunk-JEMHJMEL.js.map +1 -0
- package/dist/index.cjs +1183 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +796 -0
- package/dist/index.js.map +1 -0
- package/dist/setup-K6EYBEFH.js +72 -0
- package/dist/setup-K6EYBEFH.js.map +1 -0
- package/package.json +66 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1183 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
16
|
+
var __copyProps = (to, from, except, desc) => {
|
|
17
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
18
|
+
for (let key of __getOwnPropNames(from))
|
|
19
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
20
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
25
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
26
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
27
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
28
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
29
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
30
|
+
mod
|
|
31
|
+
));
|
|
32
|
+
|
|
33
|
+
// node_modules/tsup/assets/cjs_shims.js
|
|
34
|
+
var init_cjs_shims = __esm({
|
|
35
|
+
"node_modules/tsup/assets/cjs_shims.js"() {
|
|
36
|
+
"use strict";
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
// src/auth/token-cache.ts
|
|
41
|
+
function ensureCacheDir() {
|
|
42
|
+
if (!import_fs.default.existsSync(CACHE_DIR)) {
|
|
43
|
+
import_fs.default.mkdirSync(CACHE_DIR, { recursive: true });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
var import_fs, import_path, import_os, CACHE_DIR, CACHE_PATH, tokenCachePlugin;
|
|
47
|
+
var init_token_cache = __esm({
|
|
48
|
+
"src/auth/token-cache.ts"() {
|
|
49
|
+
"use strict";
|
|
50
|
+
init_cjs_shims();
|
|
51
|
+
import_fs = __toESM(require("fs"), 1);
|
|
52
|
+
import_path = __toESM(require("path"), 1);
|
|
53
|
+
import_os = __toESM(require("os"), 1);
|
|
54
|
+
CACHE_DIR = import_path.default.join(import_os.default.homedir(), ".config", "m365-mcp");
|
|
55
|
+
CACHE_PATH = import_path.default.join(CACHE_DIR, "token-cache.json");
|
|
56
|
+
tokenCachePlugin = {
|
|
57
|
+
async beforeCacheAccess(tokenCacheContext) {
|
|
58
|
+
try {
|
|
59
|
+
if (import_fs.default.existsSync(CACHE_PATH)) {
|
|
60
|
+
const data = import_fs.default.readFileSync(CACHE_PATH, "utf-8");
|
|
61
|
+
tokenCacheContext.tokenCache.deserialize(data);
|
|
62
|
+
}
|
|
63
|
+
} catch (err) {
|
|
64
|
+
process.stderr.write(`[m365-mcp] token cache read error (starting fresh): ${String(err)}
|
|
65
|
+
`);
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
async afterCacheAccess(tokenCacheContext) {
|
|
69
|
+
if (!tokenCacheContext.cacheHasChanged) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
ensureCacheDir();
|
|
74
|
+
const data = tokenCacheContext.tokenCache.serialize();
|
|
75
|
+
import_fs.default.writeFileSync(CACHE_PATH, data, { encoding: "utf-8", mode: 384 });
|
|
76
|
+
import_fs.default.chmodSync(CACHE_PATH, 384);
|
|
77
|
+
} catch (err) {
|
|
78
|
+
process.stderr.write(`[m365-mcp] token cache write error: ${String(err)}
|
|
79
|
+
`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// src/auth/app-config.ts
|
|
87
|
+
var DEFAULT_CLIENT_ID;
|
|
88
|
+
var init_app_config = __esm({
|
|
89
|
+
"src/auth/app-config.ts"() {
|
|
90
|
+
"use strict";
|
|
91
|
+
init_cjs_shims();
|
|
92
|
+
DEFAULT_CLIENT_ID = "dc9f42bd-30a5-4c43-a498-305ef0c3ad87";
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// src/auth/msal-client.ts
|
|
97
|
+
function readConfigFile(filePath) {
|
|
98
|
+
try {
|
|
99
|
+
if (import_fs2.default.existsSync(filePath)) {
|
|
100
|
+
const raw = import_fs2.default.readFileSync(filePath, "utf-8");
|
|
101
|
+
return JSON.parse(raw);
|
|
102
|
+
}
|
|
103
|
+
} catch {
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
function loadClientId() {
|
|
108
|
+
const envClientId = process.env["M365_MCP_CLIENT_ID"];
|
|
109
|
+
if (envClientId) {
|
|
110
|
+
return envClientId;
|
|
111
|
+
}
|
|
112
|
+
const userConfig = readConfigFile(USER_CONFIG_PATH);
|
|
113
|
+
if (userConfig?.clientId) {
|
|
114
|
+
return userConfig.clientId;
|
|
115
|
+
}
|
|
116
|
+
const localConfig = readConfigFile(LOCAL_CONFIG_PATH);
|
|
117
|
+
if (localConfig?.clientId) {
|
|
118
|
+
return localConfig.clientId;
|
|
119
|
+
}
|
|
120
|
+
if (DEFAULT_CLIENT_ID) {
|
|
121
|
+
return DEFAULT_CLIENT_ID;
|
|
122
|
+
}
|
|
123
|
+
throw new Error('No client ID configured. Run "npx m365-mcp setup" to get started.');
|
|
124
|
+
}
|
|
125
|
+
function loadTenantId() {
|
|
126
|
+
const envTenantId = process.env["M365_MCP_TENANT_ID"];
|
|
127
|
+
if (envTenantId) {
|
|
128
|
+
return envTenantId;
|
|
129
|
+
}
|
|
130
|
+
const userConfig = readConfigFile(USER_CONFIG_PATH);
|
|
131
|
+
if (userConfig?.tenantId) {
|
|
132
|
+
return userConfig.tenantId;
|
|
133
|
+
}
|
|
134
|
+
const localConfig = readConfigFile(LOCAL_CONFIG_PATH);
|
|
135
|
+
if (localConfig?.tenantId) {
|
|
136
|
+
return localConfig.tenantId;
|
|
137
|
+
}
|
|
138
|
+
return "common";
|
|
139
|
+
}
|
|
140
|
+
function getMsalClient() {
|
|
141
|
+
if (_msalClient) {
|
|
142
|
+
return _msalClient;
|
|
143
|
+
}
|
|
144
|
+
const clientId = loadClientId();
|
|
145
|
+
const tenantId = loadTenantId();
|
|
146
|
+
const msalConfig = {
|
|
147
|
+
auth: {
|
|
148
|
+
clientId,
|
|
149
|
+
authority: `https://login.microsoftonline.com/${tenantId}`
|
|
150
|
+
},
|
|
151
|
+
cache: {
|
|
152
|
+
cachePlugin: tokenCachePlugin
|
|
153
|
+
},
|
|
154
|
+
system: {
|
|
155
|
+
loggerOptions: {
|
|
156
|
+
loggerCallback: (_level, message, containsPii) => {
|
|
157
|
+
if (!containsPii) {
|
|
158
|
+
process.stderr.write(`[msal] ${message}
|
|
159
|
+
`);
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
piiLoggingEnabled: false
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
_msalClient = new import_msal_node.PublicClientApplication(msalConfig);
|
|
167
|
+
return _msalClient;
|
|
168
|
+
}
|
|
169
|
+
var import_fs2, import_path2, import_os2, import_msal_node, USER_CONFIG_PATH, LOCAL_CONFIG_PATH, _msalClient;
|
|
170
|
+
var init_msal_client = __esm({
|
|
171
|
+
"src/auth/msal-client.ts"() {
|
|
172
|
+
"use strict";
|
|
173
|
+
init_cjs_shims();
|
|
174
|
+
import_fs2 = __toESM(require("fs"), 1);
|
|
175
|
+
import_path2 = __toESM(require("path"), 1);
|
|
176
|
+
import_os2 = __toESM(require("os"), 1);
|
|
177
|
+
import_msal_node = require("@azure/msal-node");
|
|
178
|
+
init_token_cache();
|
|
179
|
+
init_app_config();
|
|
180
|
+
USER_CONFIG_PATH = import_path2.default.join(import_os2.default.homedir(), ".config", "m365-mcp", "config.json");
|
|
181
|
+
LOCAL_CONFIG_PATH = import_path2.default.join(process.cwd(), ".m365-mcp.json");
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// src/auth/scopes.ts
|
|
186
|
+
var GRAPH_SCOPES;
|
|
187
|
+
var init_scopes = __esm({
|
|
188
|
+
"src/auth/scopes.ts"() {
|
|
189
|
+
"use strict";
|
|
190
|
+
init_cjs_shims();
|
|
191
|
+
GRAPH_SCOPES = [
|
|
192
|
+
"Mail.ReadWrite",
|
|
193
|
+
"Mail.Send",
|
|
194
|
+
"Calendars.ReadWrite",
|
|
195
|
+
"Tasks.ReadWrite",
|
|
196
|
+
"Contacts.ReadWrite",
|
|
197
|
+
"offline_access",
|
|
198
|
+
"User.Read"
|
|
199
|
+
];
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// src/auth/device-code-flow.ts
|
|
204
|
+
async function trySilentAcquire() {
|
|
205
|
+
const client = getMsalClient();
|
|
206
|
+
const accounts = await client.getAllAccounts();
|
|
207
|
+
if (accounts.length === 0) {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
for (const account of accounts) {
|
|
211
|
+
try {
|
|
212
|
+
const result = await client.acquireTokenSilent({
|
|
213
|
+
account,
|
|
214
|
+
scopes: GRAPH_SCOPES
|
|
215
|
+
});
|
|
216
|
+
if (result?.accessToken) {
|
|
217
|
+
return result.accessToken;
|
|
218
|
+
}
|
|
219
|
+
} catch {
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
async function acquireToken() {
|
|
225
|
+
const silentToken = await trySilentAcquire();
|
|
226
|
+
if (silentToken) {
|
|
227
|
+
return silentToken;
|
|
228
|
+
}
|
|
229
|
+
const client = getMsalClient();
|
|
230
|
+
const deviceCodeRequest = {
|
|
231
|
+
scopes: GRAPH_SCOPES,
|
|
232
|
+
deviceCodeCallback: (response) => {
|
|
233
|
+
process.stderr.write("\n[m365-mcp] Authentication required\n");
|
|
234
|
+
process.stderr.write(`[m365-mcp] ${response.message}
|
|
235
|
+
|
|
236
|
+
`);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
const result = await client.acquireTokenByDeviceCode(deviceCodeRequest);
|
|
240
|
+
if (!result?.accessToken) {
|
|
241
|
+
throw new Error("Device code authentication did not return an access token.");
|
|
242
|
+
}
|
|
243
|
+
return result.accessToken;
|
|
244
|
+
}
|
|
245
|
+
async function runAuthCommand() {
|
|
246
|
+
process.stderr.write("[m365-mcp] Starting authentication...\n");
|
|
247
|
+
try {
|
|
248
|
+
const token = await acquireToken();
|
|
249
|
+
if (!token) {
|
|
250
|
+
process.stderr.write("[m365-mcp] Authentication failed \u2014 no token returned.\n");
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
const client = getMsalClient();
|
|
254
|
+
const accounts = await client.getAllAccounts();
|
|
255
|
+
const account = accounts[0];
|
|
256
|
+
if (account) {
|
|
257
|
+
process.stderr.write(`[m365-mcp] Authenticated as: ${account.username}
|
|
258
|
+
`);
|
|
259
|
+
} else {
|
|
260
|
+
process.stderr.write("[m365-mcp] Authentication successful.\n");
|
|
261
|
+
}
|
|
262
|
+
} catch (err) {
|
|
263
|
+
process.stderr.write(`[m365-mcp] Authentication error: ${String(err)}
|
|
264
|
+
`);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
var init_device_code_flow = __esm({
|
|
269
|
+
"src/auth/device-code-flow.ts"() {
|
|
270
|
+
"use strict";
|
|
271
|
+
init_cjs_shims();
|
|
272
|
+
init_msal_client();
|
|
273
|
+
init_scopes();
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// src/setup/prompts.ts
|
|
278
|
+
function createRl() {
|
|
279
|
+
let input;
|
|
280
|
+
try {
|
|
281
|
+
input = (0, import_fs3.createReadStream)("/dev/tty");
|
|
282
|
+
} catch {
|
|
283
|
+
input = process.stdin;
|
|
284
|
+
}
|
|
285
|
+
return (0, import_readline.createInterface)({
|
|
286
|
+
input,
|
|
287
|
+
output: process.stderr,
|
|
288
|
+
terminal: true
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
async function question(prompt) {
|
|
292
|
+
const rl = createRl();
|
|
293
|
+
return new Promise((resolve) => {
|
|
294
|
+
rl.question(prompt, (answer) => {
|
|
295
|
+
rl.close();
|
|
296
|
+
resolve(answer.trim());
|
|
297
|
+
});
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
async function confirm(prompt, defaultYes = false) {
|
|
301
|
+
const hint = defaultYes ? "[Y/n]" : "[y/N]";
|
|
302
|
+
const answer = await question(`${prompt} ${hint}: `);
|
|
303
|
+
if (answer === "") {
|
|
304
|
+
return defaultYes;
|
|
305
|
+
}
|
|
306
|
+
return answer.toLowerCase().startsWith("y");
|
|
307
|
+
}
|
|
308
|
+
var import_readline, import_fs3;
|
|
309
|
+
var init_prompts = __esm({
|
|
310
|
+
"src/setup/prompts.ts"() {
|
|
311
|
+
"use strict";
|
|
312
|
+
init_cjs_shims();
|
|
313
|
+
import_readline = require("readline");
|
|
314
|
+
import_fs3 = require("fs");
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
// src/setup/index.ts
|
|
319
|
+
var setup_exports = {};
|
|
320
|
+
__export(setup_exports, {
|
|
321
|
+
runSetupCommand: () => runSetupCommand
|
|
322
|
+
});
|
|
323
|
+
function stderr(msg) {
|
|
324
|
+
process.stderr.write(msg + "\n");
|
|
325
|
+
}
|
|
326
|
+
async function runSetupCommand() {
|
|
327
|
+
stderr("m365-mcp \u2014 Microsoft 365 for Claude");
|
|
328
|
+
stderr("");
|
|
329
|
+
stderr("This will grant access to:");
|
|
330
|
+
stderr(" \u2022 Mail (read, send)");
|
|
331
|
+
stderr(" \u2022 Calendar (read, create, update)");
|
|
332
|
+
stderr(" \u2022 Tasks (read, create, update, delete)");
|
|
333
|
+
stderr(" \u2022 Contacts (read, create, update)");
|
|
334
|
+
stderr("");
|
|
335
|
+
const cacheExists = import_fs4.default.existsSync(TOKEN_CACHE_PATH);
|
|
336
|
+
if (cacheExists) {
|
|
337
|
+
const reauth = await confirm("You're already signed in. Re-authenticate?", false);
|
|
338
|
+
if (!reauth) {
|
|
339
|
+
stderr("Setup cancelled.");
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
await runAuthCommand();
|
|
344
|
+
stderr("Setup complete! Add m365-mcp to your Claude Desktop or Claude Code config to get started.");
|
|
345
|
+
}
|
|
346
|
+
var import_fs4, import_path3, import_os3, TOKEN_CACHE_PATH;
|
|
347
|
+
var init_setup = __esm({
|
|
348
|
+
"src/setup/index.ts"() {
|
|
349
|
+
"use strict";
|
|
350
|
+
init_cjs_shims();
|
|
351
|
+
import_fs4 = __toESM(require("fs"), 1);
|
|
352
|
+
import_path3 = __toESM(require("path"), 1);
|
|
353
|
+
import_os3 = __toESM(require("os"), 1);
|
|
354
|
+
init_device_code_flow();
|
|
355
|
+
init_prompts();
|
|
356
|
+
TOKEN_CACHE_PATH = import_path3.default.join(import_os3.default.homedir(), ".config", "m365-mcp", "token-cache.json");
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// src/index.ts
|
|
361
|
+
init_cjs_shims();
|
|
362
|
+
var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
363
|
+
init_device_code_flow();
|
|
364
|
+
|
|
365
|
+
// src/server.ts
|
|
366
|
+
init_cjs_shims();
|
|
367
|
+
var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
368
|
+
|
|
369
|
+
// src/tools/tasks/index.ts
|
|
370
|
+
init_cjs_shims();
|
|
371
|
+
|
|
372
|
+
// src/tools/tasks/schemas.ts
|
|
373
|
+
init_cjs_shims();
|
|
374
|
+
var import_zod = require("zod");
|
|
375
|
+
var ListTasksSchema = {
|
|
376
|
+
listId: import_zod.z.string().describe("The ID of the To Do task list"),
|
|
377
|
+
filter: import_zod.z.string().optional().describe(`OData $filter expression (e.g. "status eq 'notStarted'")`),
|
|
378
|
+
top: import_zod.z.number().int().min(1).max(100).optional().describe("Maximum number of tasks to return (1-100)")
|
|
379
|
+
};
|
|
380
|
+
var GetTaskSchema = {
|
|
381
|
+
listId: import_zod.z.string().describe("The ID of the To Do task list"),
|
|
382
|
+
taskId: import_zod.z.string().describe("The ID of the task")
|
|
383
|
+
};
|
|
384
|
+
var CreateTaskSchema = {
|
|
385
|
+
listId: import_zod.z.string().describe("The ID of the To Do task list"),
|
|
386
|
+
title: import_zod.z.string().describe("Task title"),
|
|
387
|
+
body: import_zod.z.string().optional().describe("Task body / notes (plain text)"),
|
|
388
|
+
dueDate: import_zod.z.string().optional().describe("Due date in ISO 8601 format (e.g. 2026-06-01)"),
|
|
389
|
+
importance: import_zod.z.enum(["low", "normal", "high"]).optional().describe("Task importance level")
|
|
390
|
+
};
|
|
391
|
+
var UpdateTaskSchema = {
|
|
392
|
+
listId: import_zod.z.string().describe("The ID of the To Do task list"),
|
|
393
|
+
taskId: import_zod.z.string().describe("The ID of the task"),
|
|
394
|
+
title: import_zod.z.string().optional().describe("Updated task title"),
|
|
395
|
+
body: import_zod.z.string().optional().describe("Updated task body / notes"),
|
|
396
|
+
dueDate: import_zod.z.string().optional().describe("Updated due date in ISO 8601 format"),
|
|
397
|
+
importance: import_zod.z.enum(["low", "normal", "high"]).optional().describe("Updated importance level")
|
|
398
|
+
};
|
|
399
|
+
var CompleteTaskSchema = {
|
|
400
|
+
listId: import_zod.z.string().describe("The ID of the To Do task list"),
|
|
401
|
+
taskId: import_zod.z.string().describe("The ID of the task to mark complete")
|
|
402
|
+
};
|
|
403
|
+
var DeleteTaskSchema = {
|
|
404
|
+
listId: import_zod.z.string().describe("The ID of the To Do task list"),
|
|
405
|
+
taskId: import_zod.z.string().describe("The ID of the task to delete")
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
// src/tools/tasks/handlers.ts
|
|
409
|
+
init_cjs_shims();
|
|
410
|
+
init_device_code_flow();
|
|
411
|
+
|
|
412
|
+
// src/graph/client.ts
|
|
413
|
+
init_cjs_shims();
|
|
414
|
+
var import_microsoft_graph_client = require("@microsoft/microsoft-graph-client");
|
|
415
|
+
function getGraphClient(accessToken) {
|
|
416
|
+
const authProvider = {
|
|
417
|
+
getAccessToken: async () => accessToken
|
|
418
|
+
};
|
|
419
|
+
return import_microsoft_graph_client.Client.initWithMiddleware({ authProvider });
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// src/utils/pagination.ts
|
|
423
|
+
init_cjs_shims();
|
|
424
|
+
async function collectAllPages(client, url) {
|
|
425
|
+
const results = [];
|
|
426
|
+
let nextUrl = url;
|
|
427
|
+
while (nextUrl !== void 0) {
|
|
428
|
+
const response = await client.api(nextUrl).get();
|
|
429
|
+
if (Array.isArray(response.value)) {
|
|
430
|
+
results.push(...response.value);
|
|
431
|
+
}
|
|
432
|
+
nextUrl = response["@odata.nextLink"];
|
|
433
|
+
}
|
|
434
|
+
return results;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// src/utils/formatting.ts
|
|
438
|
+
init_cjs_shims();
|
|
439
|
+
function formatResponse(data) {
|
|
440
|
+
return {
|
|
441
|
+
content: [
|
|
442
|
+
{
|
|
443
|
+
type: "text",
|
|
444
|
+
text: JSON.stringify(data, null, 2)
|
|
445
|
+
}
|
|
446
|
+
]
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
function formatError(message) {
|
|
450
|
+
return {
|
|
451
|
+
content: [
|
|
452
|
+
{
|
|
453
|
+
type: "text",
|
|
454
|
+
text: message
|
|
455
|
+
}
|
|
456
|
+
],
|
|
457
|
+
isError: true
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// src/utils/errors.ts
|
|
462
|
+
init_cjs_shims();
|
|
463
|
+
var import_types = require("@modelcontextprotocol/sdk/types.js");
|
|
464
|
+
function parseGraphError(err) {
|
|
465
|
+
if (err instanceof Error) {
|
|
466
|
+
const graphErr = err;
|
|
467
|
+
if (graphErr.statusCode === 429) {
|
|
468
|
+
let retryAfter = "unknown";
|
|
469
|
+
try {
|
|
470
|
+
if (typeof graphErr.body === "string") {
|
|
471
|
+
const parsed = JSON.parse(graphErr.body);
|
|
472
|
+
retryAfter = parsed.error?.innerError?.["retry-after"] ?? "unknown";
|
|
473
|
+
} else if (typeof graphErr.body === "object" && graphErr.body !== null) {
|
|
474
|
+
retryAfter = graphErr.body.error?.innerError?.["retry-after"] ?? "unknown";
|
|
475
|
+
}
|
|
476
|
+
} catch {
|
|
477
|
+
}
|
|
478
|
+
return `Graph API rate limit exceeded (429). Retry after ${retryAfter} seconds.`;
|
|
479
|
+
}
|
|
480
|
+
if (graphErr.statusCode !== void 0 && graphErr.body !== void 0) {
|
|
481
|
+
try {
|
|
482
|
+
const body = typeof graphErr.body === "string" ? JSON.parse(graphErr.body) : graphErr.body;
|
|
483
|
+
const code = body.error?.code ?? "UnknownError";
|
|
484
|
+
const message = body.error?.message ?? graphErr.message;
|
|
485
|
+
return `Graph API error [${graphErr.statusCode}] ${code}: ${message}`;
|
|
486
|
+
} catch {
|
|
487
|
+
return `Graph API error [${graphErr.statusCode}]: ${graphErr.message}`;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return `Error: ${err.message}`;
|
|
491
|
+
}
|
|
492
|
+
return `Unexpected error: ${String(err)}`;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// src/tools/tasks/handlers.ts
|
|
496
|
+
async function handleListTaskLists() {
|
|
497
|
+
try {
|
|
498
|
+
const token = await acquireToken();
|
|
499
|
+
const client = getGraphClient(token);
|
|
500
|
+
const lists = await collectAllPages(client, "/me/todo/lists");
|
|
501
|
+
return formatResponse(lists);
|
|
502
|
+
} catch (err) {
|
|
503
|
+
return formatError(parseGraphError(err));
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
async function handleListTasks(listId, filter, top) {
|
|
507
|
+
try {
|
|
508
|
+
const token = await acquireToken();
|
|
509
|
+
const client = getGraphClient(token);
|
|
510
|
+
let request = client.api(`/me/todo/lists/${listId}/tasks`);
|
|
511
|
+
if (filter !== void 0) {
|
|
512
|
+
request = request.filter(filter);
|
|
513
|
+
}
|
|
514
|
+
if (top !== void 0) {
|
|
515
|
+
request = request.top(top);
|
|
516
|
+
}
|
|
517
|
+
const response = await request.get();
|
|
518
|
+
const tasks = response.value ?? [];
|
|
519
|
+
if (top === void 0 && response["@odata.nextLink"] !== void 0) {
|
|
520
|
+
const remaining = await collectAllPages(
|
|
521
|
+
client,
|
|
522
|
+
response["@odata.nextLink"]
|
|
523
|
+
);
|
|
524
|
+
tasks.push(...remaining);
|
|
525
|
+
}
|
|
526
|
+
return formatResponse(tasks);
|
|
527
|
+
} catch (err) {
|
|
528
|
+
return formatError(parseGraphError(err));
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
async function handleGetTask(listId, taskId) {
|
|
532
|
+
try {
|
|
533
|
+
const token = await acquireToken();
|
|
534
|
+
const client = getGraphClient(token);
|
|
535
|
+
const task = await client.api(`/me/todo/lists/${listId}/tasks/${taskId}`).get();
|
|
536
|
+
return formatResponse(task);
|
|
537
|
+
} catch (err) {
|
|
538
|
+
return formatError(parseGraphError(err));
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
async function handleCreateTask(params) {
|
|
542
|
+
try {
|
|
543
|
+
const token = await acquireToken();
|
|
544
|
+
const client = getGraphClient(token);
|
|
545
|
+
const body = {
|
|
546
|
+
title: params.title
|
|
547
|
+
};
|
|
548
|
+
if (params.body !== void 0) {
|
|
549
|
+
body.body = { content: params.body, contentType: "text" };
|
|
550
|
+
}
|
|
551
|
+
if (params.dueDate !== void 0) {
|
|
552
|
+
body.dueDateTime = { dateTime: `${params.dueDate}T00:00:00`, timeZone: "UTC" };
|
|
553
|
+
}
|
|
554
|
+
if (params.importance !== void 0) {
|
|
555
|
+
body.importance = params.importance;
|
|
556
|
+
}
|
|
557
|
+
const created = await client.api(`/me/todo/lists/${params.listId}/tasks`).post(body);
|
|
558
|
+
return formatResponse(created);
|
|
559
|
+
} catch (err) {
|
|
560
|
+
return formatError(parseGraphError(err));
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
async function handleUpdateTask(params) {
|
|
564
|
+
try {
|
|
565
|
+
const token = await acquireToken();
|
|
566
|
+
const client = getGraphClient(token);
|
|
567
|
+
const patch = {};
|
|
568
|
+
if (params.title !== void 0) patch.title = params.title;
|
|
569
|
+
if (params.body !== void 0) patch.body = { content: params.body, contentType: "text" };
|
|
570
|
+
if (params.dueDate !== void 0) {
|
|
571
|
+
patch.dueDateTime = { dateTime: `${params.dueDate}T00:00:00`, timeZone: "UTC" };
|
|
572
|
+
}
|
|
573
|
+
if (params.importance !== void 0) patch.importance = params.importance;
|
|
574
|
+
const updated = await client.api(`/me/todo/lists/${params.listId}/tasks/${params.taskId}`).patch(patch);
|
|
575
|
+
return formatResponse(updated);
|
|
576
|
+
} catch (err) {
|
|
577
|
+
return formatError(parseGraphError(err));
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
async function handleCompleteTask(listId, taskId) {
|
|
581
|
+
try {
|
|
582
|
+
const token = await acquireToken();
|
|
583
|
+
const client = getGraphClient(token);
|
|
584
|
+
const updated = await client.api(`/me/todo/lists/${listId}/tasks/${taskId}`).patch({ status: "completed" });
|
|
585
|
+
return formatResponse(updated);
|
|
586
|
+
} catch (err) {
|
|
587
|
+
return formatError(parseGraphError(err));
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
async function handleDeleteTask(listId, taskId) {
|
|
591
|
+
try {
|
|
592
|
+
const token = await acquireToken();
|
|
593
|
+
const client = getGraphClient(token);
|
|
594
|
+
await client.api(`/me/todo/lists/${listId}/tasks/${taskId}`).delete();
|
|
595
|
+
return formatResponse({ deleted: true, taskId });
|
|
596
|
+
} catch (err) {
|
|
597
|
+
return formatError(parseGraphError(err));
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// src/tools/tasks/index.ts
|
|
602
|
+
function register(server) {
|
|
603
|
+
server.tool(
|
|
604
|
+
"list_task_lists",
|
|
605
|
+
"List all Microsoft To Do task lists for the signed-in user",
|
|
606
|
+
{},
|
|
607
|
+
async () => handleListTaskLists()
|
|
608
|
+
);
|
|
609
|
+
server.tool(
|
|
610
|
+
"list_tasks",
|
|
611
|
+
"List tasks in a specific To Do task list, with optional filter and limit",
|
|
612
|
+
ListTasksSchema,
|
|
613
|
+
async ({ listId, filter, top }) => handleListTasks(listId, filter, top)
|
|
614
|
+
);
|
|
615
|
+
server.tool(
|
|
616
|
+
"get_task",
|
|
617
|
+
"Get a single To Do task by list ID and task ID",
|
|
618
|
+
GetTaskSchema,
|
|
619
|
+
async ({ listId, taskId }) => handleGetTask(listId, taskId)
|
|
620
|
+
);
|
|
621
|
+
server.tool(
|
|
622
|
+
"create_task",
|
|
623
|
+
"Create a new task in a To Do task list",
|
|
624
|
+
CreateTaskSchema,
|
|
625
|
+
async (params) => handleCreateTask(params)
|
|
626
|
+
);
|
|
627
|
+
server.tool(
|
|
628
|
+
"update_task",
|
|
629
|
+
"Update an existing To Do task (title, body, due date, importance)",
|
|
630
|
+
UpdateTaskSchema,
|
|
631
|
+
async (params) => handleUpdateTask(params)
|
|
632
|
+
);
|
|
633
|
+
server.tool(
|
|
634
|
+
"complete_task",
|
|
635
|
+
"Mark a To Do task as completed",
|
|
636
|
+
CompleteTaskSchema,
|
|
637
|
+
async ({ listId, taskId }) => handleCompleteTask(listId, taskId)
|
|
638
|
+
);
|
|
639
|
+
server.tool(
|
|
640
|
+
"delete_task",
|
|
641
|
+
"Delete a To Do task permanently",
|
|
642
|
+
DeleteTaskSchema,
|
|
643
|
+
async ({ listId, taskId }) => handleDeleteTask(listId, taskId)
|
|
644
|
+
);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// src/tools/calendar/index.ts
|
|
648
|
+
init_cjs_shims();
|
|
649
|
+
|
|
650
|
+
// src/tools/calendar/schemas.ts
|
|
651
|
+
init_cjs_shims();
|
|
652
|
+
var import_zod2 = require("zod");
|
|
653
|
+
var SearchEventsSchema = {
|
|
654
|
+
query: import_zod2.z.string().optional().describe("Search query string for event subject/content"),
|
|
655
|
+
startDate: import_zod2.z.string().optional().describe("Filter events starting on or after this date (ISO 8601, e.g. 2026-06-01)"),
|
|
656
|
+
endDate: import_zod2.z.string().optional().describe("Filter events ending on or before this date (ISO 8601, e.g. 2026-06-30)"),
|
|
657
|
+
top: import_zod2.z.number().int().min(1).max(100).optional().describe("Maximum number of events to return (1-100)")
|
|
658
|
+
};
|
|
659
|
+
var GetEventSchema = {
|
|
660
|
+
eventId: import_zod2.z.string().describe("The ID of the calendar event")
|
|
661
|
+
};
|
|
662
|
+
var CreateEventSchema = {
|
|
663
|
+
subject: import_zod2.z.string().describe("Event subject / title"),
|
|
664
|
+
start: import_zod2.z.string().describe("Event start time in ISO 8601 format (e.g. 2026-06-01T10:00:00)"),
|
|
665
|
+
end: import_zod2.z.string().describe("Event end time in ISO 8601 format (e.g. 2026-06-01T11:00:00)"),
|
|
666
|
+
timeZone: import_zod2.z.string().optional().default("UTC").describe("IANA time zone for start/end (default: UTC)"),
|
|
667
|
+
attendees: import_zod2.z.array(import_zod2.z.string()).optional().describe("List of attendee email addresses"),
|
|
668
|
+
body: import_zod2.z.string().optional().describe("Event body / description (HTML or plain text)"),
|
|
669
|
+
location: import_zod2.z.string().optional().describe("Event location"),
|
|
670
|
+
isTeamsMeeting: import_zod2.z.boolean().optional().describe("If true, create a Teams online meeting link")
|
|
671
|
+
};
|
|
672
|
+
var UpdateEventSchema = {
|
|
673
|
+
eventId: import_zod2.z.string().describe("The ID of the calendar event to update"),
|
|
674
|
+
subject: import_zod2.z.string().optional().describe("Updated event subject"),
|
|
675
|
+
start: import_zod2.z.string().optional().describe("Updated start time in ISO 8601 format"),
|
|
676
|
+
end: import_zod2.z.string().optional().describe("Updated end time in ISO 8601 format"),
|
|
677
|
+
timeZone: import_zod2.z.string().optional().describe("IANA time zone for updated start/end"),
|
|
678
|
+
attendees: import_zod2.z.array(import_zod2.z.string()).optional().describe("Updated list of attendee email addresses"),
|
|
679
|
+
body: import_zod2.z.string().optional().describe("Updated event body"),
|
|
680
|
+
location: import_zod2.z.string().optional().describe("Updated event location")
|
|
681
|
+
};
|
|
682
|
+
var FindAvailabilitySchema = {
|
|
683
|
+
emails: import_zod2.z.array(import_zod2.z.string()).describe("List of email addresses to check availability for"),
|
|
684
|
+
startTime: import_zod2.z.string().describe("Start of the availability window in ISO 8601 format"),
|
|
685
|
+
endTime: import_zod2.z.string().describe("End of the availability window in ISO 8601 format"),
|
|
686
|
+
timeZone: import_zod2.z.string().optional().default("UTC").describe("IANA time zone for the window (default: UTC)"),
|
|
687
|
+
intervalMinutes: import_zod2.z.number().int().min(5).max(1440).optional().default(30).describe("Schedule slot interval in minutes (default: 30)")
|
|
688
|
+
};
|
|
689
|
+
|
|
690
|
+
// src/tools/calendar/handlers.ts
|
|
691
|
+
init_cjs_shims();
|
|
692
|
+
init_device_code_flow();
|
|
693
|
+
async function handleSearchEvents(query, startDate, endDate, top) {
|
|
694
|
+
try {
|
|
695
|
+
const token = await acquireToken();
|
|
696
|
+
const client = getGraphClient(token);
|
|
697
|
+
let request = client.api("/me/events");
|
|
698
|
+
if (query !== void 0) {
|
|
699
|
+
request = request.search(`"${query}"`);
|
|
700
|
+
}
|
|
701
|
+
const filters = [];
|
|
702
|
+
if (startDate !== void 0) {
|
|
703
|
+
filters.push(`start/dateTime ge '${startDate}T00:00:00Z'`);
|
|
704
|
+
}
|
|
705
|
+
if (endDate !== void 0) {
|
|
706
|
+
filters.push(`end/dateTime le '${endDate}T23:59:59Z'`);
|
|
707
|
+
}
|
|
708
|
+
if (filters.length > 0) {
|
|
709
|
+
request = request.filter(filters.join(" and "));
|
|
710
|
+
}
|
|
711
|
+
if (top !== void 0) {
|
|
712
|
+
request = request.top(top);
|
|
713
|
+
}
|
|
714
|
+
request = request.select("id,subject,start,end,location,attendees,bodyPreview,isOnlineMeeting,onlineMeetingUrl");
|
|
715
|
+
const response = await request.get();
|
|
716
|
+
return formatResponse(response.value ?? []);
|
|
717
|
+
} catch (err) {
|
|
718
|
+
return formatError(parseGraphError(err));
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
async function handleGetEvent(eventId) {
|
|
722
|
+
try {
|
|
723
|
+
const token = await acquireToken();
|
|
724
|
+
const client = getGraphClient(token);
|
|
725
|
+
const event = await client.api(`/me/events/${eventId}`).get();
|
|
726
|
+
return formatResponse(event);
|
|
727
|
+
} catch (err) {
|
|
728
|
+
return formatError(parseGraphError(err));
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
async function handleCreateEvent(params) {
|
|
732
|
+
try {
|
|
733
|
+
const token = await acquireToken();
|
|
734
|
+
const client = getGraphClient(token);
|
|
735
|
+
const tz = params.timeZone ?? "UTC";
|
|
736
|
+
const eventBody = {
|
|
737
|
+
subject: params.subject,
|
|
738
|
+
start: { dateTime: params.start, timeZone: tz },
|
|
739
|
+
end: { dateTime: params.end, timeZone: tz }
|
|
740
|
+
};
|
|
741
|
+
if (params.body !== void 0) {
|
|
742
|
+
eventBody.body = { contentType: "text", content: params.body };
|
|
743
|
+
}
|
|
744
|
+
if (params.location !== void 0) {
|
|
745
|
+
eventBody.location = { displayName: params.location };
|
|
746
|
+
}
|
|
747
|
+
if (params.attendees !== void 0 && params.attendees.length > 0) {
|
|
748
|
+
eventBody.attendees = params.attendees.map((email) => ({
|
|
749
|
+
emailAddress: { address: email },
|
|
750
|
+
type: "required"
|
|
751
|
+
}));
|
|
752
|
+
}
|
|
753
|
+
if (params.isTeamsMeeting === true) {
|
|
754
|
+
eventBody.isOnlineMeeting = true;
|
|
755
|
+
}
|
|
756
|
+
const created = await client.api("/me/events").post(eventBody);
|
|
757
|
+
return formatResponse(created);
|
|
758
|
+
} catch (err) {
|
|
759
|
+
return formatError(parseGraphError(err));
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
async function handleUpdateEvent(params) {
|
|
763
|
+
try {
|
|
764
|
+
const token = await acquireToken();
|
|
765
|
+
const client = getGraphClient(token);
|
|
766
|
+
const patch = {};
|
|
767
|
+
const tz = params.timeZone ?? "UTC";
|
|
768
|
+
if (params.subject !== void 0) patch.subject = params.subject;
|
|
769
|
+
if (params.start !== void 0) patch.start = { dateTime: params.start, timeZone: tz };
|
|
770
|
+
if (params.end !== void 0) patch.end = { dateTime: params.end, timeZone: tz };
|
|
771
|
+
if (params.body !== void 0) patch.body = { contentType: "text", content: params.body };
|
|
772
|
+
if (params.location !== void 0) patch.location = { displayName: params.location };
|
|
773
|
+
if (params.attendees !== void 0) {
|
|
774
|
+
patch.attendees = params.attendees.map((email) => ({
|
|
775
|
+
emailAddress: { address: email },
|
|
776
|
+
type: "required"
|
|
777
|
+
}));
|
|
778
|
+
}
|
|
779
|
+
const updated = await client.api(`/me/events/${params.eventId}`).patch(patch);
|
|
780
|
+
return formatResponse(updated);
|
|
781
|
+
} catch (err) {
|
|
782
|
+
return formatError(parseGraphError(err));
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
async function handleFindAvailability(params) {
|
|
786
|
+
try {
|
|
787
|
+
const token = await acquireToken();
|
|
788
|
+
const client = getGraphClient(token);
|
|
789
|
+
const tz = params.timeZone ?? "UTC";
|
|
790
|
+
const interval = params.intervalMinutes ?? 30;
|
|
791
|
+
const requestBody = {
|
|
792
|
+
schedules: params.emails,
|
|
793
|
+
startTime: { dateTime: params.startTime, timeZone: tz },
|
|
794
|
+
endTime: { dateTime: params.endTime, timeZone: tz },
|
|
795
|
+
availabilityViewInterval: interval
|
|
796
|
+
};
|
|
797
|
+
const response = await client.api("/me/calendar/getSchedule").post(requestBody);
|
|
798
|
+
return formatResponse(response.value ?? []);
|
|
799
|
+
} catch (err) {
|
|
800
|
+
return formatError(parseGraphError(err));
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// src/tools/calendar/index.ts
|
|
805
|
+
function register2(server) {
|
|
806
|
+
server.tool(
|
|
807
|
+
"search_events",
|
|
808
|
+
"Search calendar events by query, date range, or both",
|
|
809
|
+
SearchEventsSchema,
|
|
810
|
+
async ({ query, startDate, endDate, top }) => handleSearchEvents(query, startDate, endDate, top)
|
|
811
|
+
);
|
|
812
|
+
server.tool(
|
|
813
|
+
"get_event",
|
|
814
|
+
"Get a single calendar event by ID",
|
|
815
|
+
GetEventSchema,
|
|
816
|
+
async ({ eventId }) => handleGetEvent(eventId)
|
|
817
|
+
);
|
|
818
|
+
server.tool(
|
|
819
|
+
"create_event",
|
|
820
|
+
"Create a new calendar event, optionally with attendees and a Teams meeting link",
|
|
821
|
+
CreateEventSchema,
|
|
822
|
+
async (params) => handleCreateEvent(params)
|
|
823
|
+
);
|
|
824
|
+
server.tool(
|
|
825
|
+
"update_event",
|
|
826
|
+
"Update an existing calendar event (subject, times, attendees, body, location)",
|
|
827
|
+
UpdateEventSchema,
|
|
828
|
+
async (params) => handleUpdateEvent(params)
|
|
829
|
+
);
|
|
830
|
+
server.tool(
|
|
831
|
+
"find_availability",
|
|
832
|
+
"Check free/busy availability for a list of people over a time window",
|
|
833
|
+
FindAvailabilitySchema,
|
|
834
|
+
async (params) => handleFindAvailability(params)
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
// src/tools/mail/index.ts
|
|
839
|
+
init_cjs_shims();
|
|
840
|
+
|
|
841
|
+
// src/tools/mail/schemas.ts
|
|
842
|
+
init_cjs_shims();
|
|
843
|
+
var import_zod3 = require("zod");
|
|
844
|
+
var SearchEmailsSchema = {
|
|
845
|
+
query: import_zod3.z.string().optional().describe("Full-text search query across email subject and body"),
|
|
846
|
+
from: import_zod3.z.string().optional().describe("Filter by sender email address"),
|
|
847
|
+
startDate: import_zod3.z.string().optional().describe("Filter emails received on or after this date (ISO 8601)"),
|
|
848
|
+
endDate: import_zod3.z.string().optional().describe("Filter emails received on or before this date (ISO 8601)"),
|
|
849
|
+
folder: import_zod3.z.string().optional().describe('Mail folder name or well-known name (e.g. "inbox", "sentitems", "drafts")'),
|
|
850
|
+
top: import_zod3.z.number().int().min(1).max(100).optional().describe("Maximum number of emails to return (1-100)")
|
|
851
|
+
};
|
|
852
|
+
var ReadEmailSchema = {
|
|
853
|
+
messageId: import_zod3.z.string().describe("The ID of the email message")
|
|
854
|
+
};
|
|
855
|
+
var SendEmailSchema = {
|
|
856
|
+
to: import_zod3.z.array(import_zod3.z.string()).describe("List of recipient email addresses"),
|
|
857
|
+
subject: import_zod3.z.string().describe("Email subject"),
|
|
858
|
+
body: import_zod3.z.string().describe("Email body content"),
|
|
859
|
+
cc: import_zod3.z.array(import_zod3.z.string()).optional().describe("CC recipient email addresses"),
|
|
860
|
+
bcc: import_zod3.z.array(import_zod3.z.string()).optional().describe("BCC recipient email addresses"),
|
|
861
|
+
contentType: import_zod3.z.enum(["text", "html"]).optional().default("text").describe("Body content type (default: text)")
|
|
862
|
+
};
|
|
863
|
+
var FlagEmailSchema = {
|
|
864
|
+
messageId: import_zod3.z.string().describe("The ID of the email message"),
|
|
865
|
+
flagStatus: import_zod3.z.enum(["flagged", "complete", "notFlagged"]).describe("Flag status to set on the message")
|
|
866
|
+
};
|
|
867
|
+
var MoveEmailSchema = {
|
|
868
|
+
messageId: import_zod3.z.string().describe("The ID of the email message to move"),
|
|
869
|
+
destinationFolderId: import_zod3.z.string().describe("ID or well-known folder name of the destination folder")
|
|
870
|
+
};
|
|
871
|
+
|
|
872
|
+
// src/tools/mail/handlers.ts
|
|
873
|
+
init_cjs_shims();
|
|
874
|
+
init_device_code_flow();
|
|
875
|
+
async function handleSearchEmails(params) {
|
|
876
|
+
try {
|
|
877
|
+
const token = await acquireToken();
|
|
878
|
+
const client = getGraphClient(token);
|
|
879
|
+
const folderSegment = params.folder !== void 0 ? `/me/mailFolders/${params.folder}/messages` : "/me/messages";
|
|
880
|
+
let request = client.api(folderSegment);
|
|
881
|
+
if (params.query !== void 0) {
|
|
882
|
+
request = request.search(`"${params.query}"`);
|
|
883
|
+
}
|
|
884
|
+
const filters = [];
|
|
885
|
+
if (params.from !== void 0) {
|
|
886
|
+
filters.push(`from/emailAddress/address eq '${params.from}'`);
|
|
887
|
+
}
|
|
888
|
+
if (params.startDate !== void 0) {
|
|
889
|
+
filters.push(`receivedDateTime ge ${params.startDate}T00:00:00Z`);
|
|
890
|
+
}
|
|
891
|
+
if (params.endDate !== void 0) {
|
|
892
|
+
filters.push(`receivedDateTime le ${params.endDate}T23:59:59Z`);
|
|
893
|
+
}
|
|
894
|
+
if (filters.length > 0) {
|
|
895
|
+
request = request.filter(filters.join(" and "));
|
|
896
|
+
}
|
|
897
|
+
if (params.top !== void 0) {
|
|
898
|
+
request = request.top(params.top);
|
|
899
|
+
}
|
|
900
|
+
request = request.select("id,subject,from,receivedDateTime,bodyPreview,isRead,flag,hasAttachments");
|
|
901
|
+
const response = await request.get();
|
|
902
|
+
return formatResponse(response.value ?? []);
|
|
903
|
+
} catch (err) {
|
|
904
|
+
return formatError(parseGraphError(err));
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
async function handleReadEmail(messageId) {
|
|
908
|
+
try {
|
|
909
|
+
const token = await acquireToken();
|
|
910
|
+
const client = getGraphClient(token);
|
|
911
|
+
const message = await client.api(`/me/messages/${messageId}`).select("id,subject,from,toRecipients,ccRecipients,receivedDateTime,body,isRead,flag,hasAttachments").get();
|
|
912
|
+
return formatResponse(message);
|
|
913
|
+
} catch (err) {
|
|
914
|
+
return formatError(parseGraphError(err));
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
function toRecipients(emails) {
|
|
918
|
+
return emails.map((address) => ({ emailAddress: { address } }));
|
|
919
|
+
}
|
|
920
|
+
async function handleSendEmail(params) {
|
|
921
|
+
try {
|
|
922
|
+
const token = await acquireToken();
|
|
923
|
+
const client = getGraphClient(token);
|
|
924
|
+
const contentType = params.contentType ?? "text";
|
|
925
|
+
const message = {
|
|
926
|
+
message: {
|
|
927
|
+
subject: params.subject,
|
|
928
|
+
body: { contentType, content: params.body },
|
|
929
|
+
toRecipients: toRecipients(params.to),
|
|
930
|
+
...params.cc !== void 0 && { ccRecipients: toRecipients(params.cc) },
|
|
931
|
+
...params.bcc !== void 0 && { bccRecipients: toRecipients(params.bcc) }
|
|
932
|
+
}
|
|
933
|
+
};
|
|
934
|
+
await client.api("/me/sendMail").post(message);
|
|
935
|
+
return formatResponse({ sent: true, subject: params.subject });
|
|
936
|
+
} catch (err) {
|
|
937
|
+
return formatError(parseGraphError(err));
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
async function handleFlagEmail(messageId, flagStatus) {
|
|
941
|
+
try {
|
|
942
|
+
const token = await acquireToken();
|
|
943
|
+
const client = getGraphClient(token);
|
|
944
|
+
const updated = await client.api(`/me/messages/${messageId}`).patch({ flag: { flagStatus } });
|
|
945
|
+
return formatResponse(updated);
|
|
946
|
+
} catch (err) {
|
|
947
|
+
return formatError(parseGraphError(err));
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
async function handleListMailFolders() {
|
|
951
|
+
try {
|
|
952
|
+
const token = await acquireToken();
|
|
953
|
+
const client = getGraphClient(token);
|
|
954
|
+
const folders = await collectAllPages(client, "/me/mailFolders");
|
|
955
|
+
return formatResponse(folders);
|
|
956
|
+
} catch (err) {
|
|
957
|
+
return formatError(parseGraphError(err));
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
async function handleMoveEmail(messageId, destinationFolderId) {
|
|
961
|
+
try {
|
|
962
|
+
const token = await acquireToken();
|
|
963
|
+
const client = getGraphClient(token);
|
|
964
|
+
const moved = await client.api(`/me/messages/${messageId}/move`).post({ destinationId: destinationFolderId });
|
|
965
|
+
return formatResponse(moved);
|
|
966
|
+
} catch (err) {
|
|
967
|
+
return formatError(parseGraphError(err));
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
// src/tools/mail/index.ts
|
|
972
|
+
function register3(server) {
|
|
973
|
+
server.tool(
|
|
974
|
+
"search_emails",
|
|
975
|
+
"Search emails by query, sender, date range, or folder",
|
|
976
|
+
SearchEmailsSchema,
|
|
977
|
+
async (params) => handleSearchEmails(params)
|
|
978
|
+
);
|
|
979
|
+
server.tool(
|
|
980
|
+
"read_email",
|
|
981
|
+
"Read the full content of an email message by ID",
|
|
982
|
+
ReadEmailSchema,
|
|
983
|
+
async ({ messageId }) => handleReadEmail(messageId)
|
|
984
|
+
);
|
|
985
|
+
server.tool(
|
|
986
|
+
"send_email",
|
|
987
|
+
"Send an email to one or more recipients",
|
|
988
|
+
SendEmailSchema,
|
|
989
|
+
async (params) => handleSendEmail(params)
|
|
990
|
+
);
|
|
991
|
+
server.tool(
|
|
992
|
+
"flag_email",
|
|
993
|
+
"Set the flag status on an email (flagged, complete, or notFlagged)",
|
|
994
|
+
FlagEmailSchema,
|
|
995
|
+
async ({ messageId, flagStatus }) => handleFlagEmail(messageId, flagStatus)
|
|
996
|
+
);
|
|
997
|
+
server.tool(
|
|
998
|
+
"list_mail_folders",
|
|
999
|
+
"List all mail folders for the signed-in user",
|
|
1000
|
+
{},
|
|
1001
|
+
async () => handleListMailFolders()
|
|
1002
|
+
);
|
|
1003
|
+
server.tool(
|
|
1004
|
+
"move_email",
|
|
1005
|
+
"Move an email to a different mail folder",
|
|
1006
|
+
MoveEmailSchema,
|
|
1007
|
+
async ({ messageId, destinationFolderId }) => handleMoveEmail(messageId, destinationFolderId)
|
|
1008
|
+
);
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
// src/tools/contacts/index.ts
|
|
1012
|
+
init_cjs_shims();
|
|
1013
|
+
|
|
1014
|
+
// src/tools/contacts/schemas.ts
|
|
1015
|
+
init_cjs_shims();
|
|
1016
|
+
var import_zod4 = require("zod");
|
|
1017
|
+
var EmailAddressSchema = import_zod4.z.object({
|
|
1018
|
+
address: import_zod4.z.string().describe("Email address"),
|
|
1019
|
+
name: import_zod4.z.string().optional().describe("Display name for this email address")
|
|
1020
|
+
});
|
|
1021
|
+
var SearchContactsSchema = {
|
|
1022
|
+
query: import_zod4.z.string().describe("Search query (matches against name, email, company, etc.)"),
|
|
1023
|
+
top: import_zod4.z.number().int().min(1).max(100).optional().describe("Maximum number of contacts to return (1-100)")
|
|
1024
|
+
};
|
|
1025
|
+
var GetContactSchema = {
|
|
1026
|
+
contactId: import_zod4.z.string().describe("The ID of the contact")
|
|
1027
|
+
};
|
|
1028
|
+
var CreateContactSchema = {
|
|
1029
|
+
givenName: import_zod4.z.string().describe("Contact first name"),
|
|
1030
|
+
surname: import_zod4.z.string().optional().describe("Contact last name"),
|
|
1031
|
+
emailAddresses: import_zod4.z.array(EmailAddressSchema).optional().describe("Email addresses for the contact"),
|
|
1032
|
+
businessPhones: import_zod4.z.array(import_zod4.z.string()).optional().describe("Business phone numbers"),
|
|
1033
|
+
jobTitle: import_zod4.z.string().optional().describe("Job title"),
|
|
1034
|
+
companyName: import_zod4.z.string().optional().describe("Company or organization name")
|
|
1035
|
+
};
|
|
1036
|
+
var UpdateContactSchema = {
|
|
1037
|
+
contactId: import_zod4.z.string().describe("The ID of the contact to update"),
|
|
1038
|
+
givenName: import_zod4.z.string().optional().describe("Updated first name"),
|
|
1039
|
+
surname: import_zod4.z.string().optional().describe("Updated last name"),
|
|
1040
|
+
emailAddresses: import_zod4.z.array(EmailAddressSchema).optional().describe("Updated email addresses"),
|
|
1041
|
+
businessPhones: import_zod4.z.array(import_zod4.z.string()).optional().describe("Updated business phone numbers"),
|
|
1042
|
+
jobTitle: import_zod4.z.string().optional().describe("Updated job title"),
|
|
1043
|
+
companyName: import_zod4.z.string().optional().describe("Updated company name")
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
// src/tools/contacts/handlers.ts
|
|
1047
|
+
init_cjs_shims();
|
|
1048
|
+
init_device_code_flow();
|
|
1049
|
+
async function handleSearchContacts(query, top) {
|
|
1050
|
+
try {
|
|
1051
|
+
const token = await acquireToken();
|
|
1052
|
+
const client = getGraphClient(token);
|
|
1053
|
+
let request = client.api("/me/contacts").search(`"${query}"`).select("id,displayName,givenName,surname,emailAddresses,businessPhones,jobTitle,companyName");
|
|
1054
|
+
if (top !== void 0) {
|
|
1055
|
+
request = request.top(top);
|
|
1056
|
+
}
|
|
1057
|
+
const response = await request.get();
|
|
1058
|
+
return formatResponse(response.value ?? []);
|
|
1059
|
+
} catch (err) {
|
|
1060
|
+
return formatError(parseGraphError(err));
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
async function handleGetContact(contactId) {
|
|
1064
|
+
try {
|
|
1065
|
+
const token = await acquireToken();
|
|
1066
|
+
const client = getGraphClient(token);
|
|
1067
|
+
const contact = await client.api(`/me/contacts/${contactId}`).get();
|
|
1068
|
+
return formatResponse(contact);
|
|
1069
|
+
} catch (err) {
|
|
1070
|
+
return formatError(parseGraphError(err));
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
async function handleCreateContact(params) {
|
|
1074
|
+
try {
|
|
1075
|
+
const token = await acquireToken();
|
|
1076
|
+
const client = getGraphClient(token);
|
|
1077
|
+
const body = {
|
|
1078
|
+
givenName: params.givenName
|
|
1079
|
+
};
|
|
1080
|
+
if (params.surname !== void 0) body.surname = params.surname;
|
|
1081
|
+
if (params.emailAddresses !== void 0) body.emailAddresses = params.emailAddresses;
|
|
1082
|
+
if (params.businessPhones !== void 0) body.businessPhones = params.businessPhones;
|
|
1083
|
+
if (params.jobTitle !== void 0) body.jobTitle = params.jobTitle;
|
|
1084
|
+
if (params.companyName !== void 0) body.companyName = params.companyName;
|
|
1085
|
+
const created = await client.api("/me/contacts").post(body);
|
|
1086
|
+
return formatResponse(created);
|
|
1087
|
+
} catch (err) {
|
|
1088
|
+
return formatError(parseGraphError(err));
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
async function handleUpdateContact(params) {
|
|
1092
|
+
try {
|
|
1093
|
+
const token = await acquireToken();
|
|
1094
|
+
const client = getGraphClient(token);
|
|
1095
|
+
const patch = {};
|
|
1096
|
+
if (params.givenName !== void 0) patch.givenName = params.givenName;
|
|
1097
|
+
if (params.surname !== void 0) patch.surname = params.surname;
|
|
1098
|
+
if (params.emailAddresses !== void 0) patch.emailAddresses = params.emailAddresses;
|
|
1099
|
+
if (params.businessPhones !== void 0) patch.businessPhones = params.businessPhones;
|
|
1100
|
+
if (params.jobTitle !== void 0) patch.jobTitle = params.jobTitle;
|
|
1101
|
+
if (params.companyName !== void 0) patch.companyName = params.companyName;
|
|
1102
|
+
const updated = await client.api(`/me/contacts/${params.contactId}`).patch(patch);
|
|
1103
|
+
return formatResponse(updated);
|
|
1104
|
+
} catch (err) {
|
|
1105
|
+
return formatError(parseGraphError(err));
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// src/tools/contacts/index.ts
|
|
1110
|
+
function register4(server) {
|
|
1111
|
+
server.tool(
|
|
1112
|
+
"search_contacts",
|
|
1113
|
+
"Search contacts by name, email, company, or other fields",
|
|
1114
|
+
SearchContactsSchema,
|
|
1115
|
+
async ({ query, top }) => handleSearchContacts(query, top)
|
|
1116
|
+
);
|
|
1117
|
+
server.tool(
|
|
1118
|
+
"get_contact",
|
|
1119
|
+
"Get a single contact by ID",
|
|
1120
|
+
GetContactSchema,
|
|
1121
|
+
async ({ contactId }) => handleGetContact(contactId)
|
|
1122
|
+
);
|
|
1123
|
+
server.tool(
|
|
1124
|
+
"create_contact",
|
|
1125
|
+
"Create a new contact in the signed-in user's contacts",
|
|
1126
|
+
CreateContactSchema,
|
|
1127
|
+
async (params) => handleCreateContact(params)
|
|
1128
|
+
);
|
|
1129
|
+
server.tool(
|
|
1130
|
+
"update_contact",
|
|
1131
|
+
"Update an existing contact (name, email, phone, title, company)",
|
|
1132
|
+
UpdateContactSchema,
|
|
1133
|
+
async (params) => handleUpdateContact(params)
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
// src/version.ts
|
|
1138
|
+
init_cjs_shims();
|
|
1139
|
+
var VERSION = true ? "0.1.0" : "0.0.0-dev";
|
|
1140
|
+
|
|
1141
|
+
// src/server.ts
|
|
1142
|
+
function createServer() {
|
|
1143
|
+
const server = new import_mcp.McpServer({
|
|
1144
|
+
name: "m365-mcp",
|
|
1145
|
+
version: VERSION
|
|
1146
|
+
});
|
|
1147
|
+
register(server);
|
|
1148
|
+
register2(server);
|
|
1149
|
+
register3(server);
|
|
1150
|
+
register4(server);
|
|
1151
|
+
return server;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// src/index.ts
|
|
1155
|
+
var command = process.argv[2];
|
|
1156
|
+
async function main() {
|
|
1157
|
+
if (command === "setup") {
|
|
1158
|
+
const { runSetupCommand: runSetupCommand2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
|
|
1159
|
+
await runSetupCommand2();
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
if (command === "auth") {
|
|
1163
|
+
await runAuthCommand();
|
|
1164
|
+
return;
|
|
1165
|
+
}
|
|
1166
|
+
const server = createServer();
|
|
1167
|
+
const transport = new import_stdio.StdioServerTransport();
|
|
1168
|
+
process.on("uncaughtException", (err) => {
|
|
1169
|
+
process.stderr.write(`[m365-mcp] Uncaught exception: ${String(err)}
|
|
1170
|
+
`);
|
|
1171
|
+
});
|
|
1172
|
+
process.on("unhandledRejection", (reason) => {
|
|
1173
|
+
process.stderr.write(`[m365-mcp] Unhandled rejection: ${String(reason)}
|
|
1174
|
+
`);
|
|
1175
|
+
});
|
|
1176
|
+
await server.connect(transport);
|
|
1177
|
+
}
|
|
1178
|
+
main().catch((err) => {
|
|
1179
|
+
process.stderr.write(`[m365-mcp] Fatal startup error: ${String(err)}
|
|
1180
|
+
`);
|
|
1181
|
+
process.exit(1);
|
|
1182
|
+
});
|
|
1183
|
+
//# sourceMappingURL=index.cjs.map
|