@alfe.ai/microsoft-mcp 0.1.6 → 0.1.8

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/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # @alfe.ai/microsoft-mcp
2
2
 
3
- Microsoft 365 MCP server — multi-account Microsoft Graph access using Alfe OAuth credentials with server-mediated token refresh
3
+ Microsoft 365 MCP server — bounded, multi-account Microsoft Graph access using
4
+ Alfe OAuth credentials with server-mediated token refresh.
4
5
 
5
6
  Part of [**Alfe**](https://alfe.ai) — the operating system for AI agents: build, deploy, and run agents with persistent memory, identity, integrations, and channels. See the [documentation](https://docs.alfe.ai) to get started.
6
7
 
@@ -10,6 +11,28 @@ Part of [**Alfe**](https://alfe.ai) — the operating system for AI agents: buil
10
11
  npm install @alfe.ai/microsoft-mcp
11
12
  ```
12
13
 
14
+ The Alfe integration launches the server over stdio. It exposes three tools:
15
+
16
+ - `microsoft_list_accounts` lists safe account metadata.
17
+ - `microsoft_run_command` sends `[METHOD] /v1.0-relative-path [json-body]` for
18
+ one explicitly selected email/account.
19
+ - `microsoft_disconnect_account` removes one explicitly selected connection.
20
+
21
+ There is no implicit default account. Graph calls are locked to HTTPS
22
+ `graph.microsoft.com/v1.0`, reject redirects, use a deadline, and bound command,
23
+ request, and response sizes. Refresh tokens and Microsoft OAuth client
24
+ credentials stay in the Alfe connect service; the MCP receives short-lived
25
+ access tokens and uses the server-mediated per-account refresh endpoint.
26
+
27
+ ## Development
28
+
29
+ ```bash
30
+ pnpm --filter @alfe.ai/microsoft-mcp lint
31
+ pnpm --filter @alfe.ai/microsoft-mcp typecheck
32
+ pnpm --filter @alfe.ai/microsoft-mcp test
33
+ pnpm --filter @alfe.ai/microsoft-mcp build
34
+ ```
35
+
13
36
  ## Links
14
37
 
15
38
  - 🌐 Website: <https://alfe.ai>
@@ -0,0 +1,538 @@
1
+ #!/usr/bin/env node
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ let node_module = require("node:module");
4
+ let node_fs = require("node:fs");
5
+ let node_url = require("node:url");
6
+ let _modelcontextprotocol_sdk_server_mcp_js = require("@modelcontextprotocol/sdk/server/mcp.js");
7
+ let _modelcontextprotocol_sdk_server_stdio_js = require("@modelcontextprotocol/sdk/server/stdio.js");
8
+ let zod = require("zod");
9
+ let _alfe_ai_config = require("@alfe.ai/config");
10
+ let _alfe_ai_agent_api_client = require("@alfe.ai/agent-api-client");
11
+ //#region src/boundary.ts
12
+ const GRAPH_BASE = "https://graph.microsoft.com/v1.0";
13
+ const MAX_ACCOUNTS = 128;
14
+ const MAX_IDENTIFIER_CHARS = 512;
15
+ const MAX_EMAIL_CHARS = 320;
16
+ const MAX_DISPLAY_NAME_CHARS = 512;
17
+ const MAX_ACCESS_TOKEN_CHARS = 64 * 1024;
18
+ const MAX_COMMAND_CHARS = 256 * 1024;
19
+ const MAX_GRAPH_PATH_CHARS = 16 * 1024;
20
+ const MAX_REQUEST_BODY_BYTES = 1024 * 1024;
21
+ const MAX_RESPONSE_BYTES = 5 * 1024 * 1024;
22
+ const MAX_ERROR_RESPONSE_BYTES = 256 * 1024;
23
+ const MAX_JSON_DEPTH = 64;
24
+ const MAX_JSON_NODES = 5e4;
25
+ const TOKEN_SKEW_MS = 120 * 1e3;
26
+ const SUPPORTED_METHODS = new Set([
27
+ "GET",
28
+ "POST",
29
+ "PATCH",
30
+ "PUT",
31
+ "DELETE"
32
+ ]);
33
+ const UNSAFE_KEYS = new Set([
34
+ "__proto__",
35
+ "prototype",
36
+ "constructor"
37
+ ]);
38
+ function parseCommand(command) {
39
+ const trimmed = requireBoundedString(command, "command", MAX_COMMAND_CHARS).trim();
40
+ if (!trimmed) throw new Error("Command cannot be empty");
41
+ let method = "GET";
42
+ let rest = trimmed;
43
+ const firstToken = /^(\S+)\s+([\s\S]+)$/u.exec(trimmed);
44
+ if (firstToken && /^[A-Za-z]+$/u.test(firstToken[1])) {
45
+ const candidate = firstToken[1].toUpperCase();
46
+ if (!SUPPORTED_METHODS.has(candidate)) throw new Error(`Unsupported Graph method: ${candidate.slice(0, 32)}`);
47
+ method = candidate;
48
+ rest = firstToken[2].trim();
49
+ }
50
+ let path = rest;
51
+ let body;
52
+ const bodyDelimiter = /\s+(\{|\[)/u.exec(rest);
53
+ if (bodyDelimiter) {
54
+ path = rest.slice(0, bodyDelimiter.index).trim();
55
+ const rawBody = rest.slice(bodyDelimiter.index).trim();
56
+ if (Buffer.byteLength(rawBody, "utf8") > 1048576) throw new Error(`Graph request body exceeds ${String(MAX_REQUEST_BODY_BYTES)} bytes`);
57
+ try {
58
+ body = JSON.parse(rawBody);
59
+ } catch {
60
+ throw new Error("Invalid JSON body in command");
61
+ }
62
+ assertJsonBudget(body, "Graph request body");
63
+ } else if (/\s/u.test(rest)) throw new Error("Graph paths cannot contain unescaped whitespace");
64
+ if (method === "GET" && body !== void 0) throw new Error("GET Graph requests cannot include a body");
65
+ path = validateGraphPath(path);
66
+ return {
67
+ method,
68
+ path,
69
+ body
70
+ };
71
+ }
72
+ function resolveGraphUrl(path) {
73
+ const checkedPath = validateGraphPath(path);
74
+ const url = new URL(checkedPath.slice(1), `${GRAPH_BASE}/`);
75
+ const pathname = url.pathname;
76
+ if (url.protocol !== "https:" || url.hostname.toLowerCase() !== "graph.microsoft.com" || url.username || url.password || url.hash || pathname !== "/v1.0" && !pathname.startsWith("/v1.0/")) throw new Error("Refusing to send a Microsoft token outside the Graph v1.0 authority");
77
+ return url.toString();
78
+ }
79
+ function tokenIsExpired(account, now = Date.now()) {
80
+ if (!account.accessToken) return true;
81
+ if (!account.accessTokenExpiresAt) return false;
82
+ const expiresAt = Date.parse(account.accessTokenExpiresAt);
83
+ if (Number.isNaN(expiresAt)) return true;
84
+ return expiresAt - now <= TOKEN_SKEW_MS;
85
+ }
86
+ function normalizeMicrosoftAccounts(value) {
87
+ const root = requireRecord(value, "Microsoft accounts response");
88
+ if (!Array.isArray(root.accounts) || root.accounts.length > MAX_ACCOUNTS) throw new Error(`Microsoft accounts response must contain at most ${String(MAX_ACCOUNTS)} accounts`);
89
+ const selectorOwners = /* @__PURE__ */ new Map();
90
+ const connectionIds = /* @__PURE__ */ new Set();
91
+ return root.accounts.map((raw, index) => {
92
+ const record = requireRecord(raw, `Microsoft account ${String(index)}`);
93
+ const connectionId = requireIdentifier(record.connectionId, "Microsoft account connectionId", MAX_IDENTIFIER_CHARS);
94
+ if (connectionIds.has(connectionId)) throw new Error(`Duplicate Microsoft connection: ${connectionId}`);
95
+ connectionIds.add(connectionId);
96
+ const accountIdentifier = requireIdentifier(record.accountIdentifier, "Microsoft account accountIdentifier", MAX_IDENTIFIER_CHARS);
97
+ const email = requireIdentifier(typeof record.email === "string" && record.email.length > 0 ? record.email : accountIdentifier, "Microsoft account email", MAX_EMAIL_CHARS);
98
+ const account = {
99
+ connectionId,
100
+ accountIdentifier,
101
+ email,
102
+ displayName: optionalBoundedString(record.displayName, "Microsoft account displayName", MAX_DISPLAY_NAME_CHARS),
103
+ connectedAt: optionalBoundedString(record.connectedAt, "Microsoft account connectedAt", 128),
104
+ microsoftTenantId: optionalBoundedString(record.microsoftTenantId, "Microsoft account microsoftTenantId", MAX_IDENTIFIER_CHARS),
105
+ accessToken: validateAccessToken(record.accessToken ?? "", true),
106
+ accessTokenExpiresAt: validateExpiry(record.accessTokenExpiresAt ?? "")
107
+ };
108
+ for (const selector of [email, accountIdentifier]) {
109
+ const normalized = normalizeAccountSelector(selector);
110
+ const owner = selectorOwners.get(normalized);
111
+ if (owner !== void 0 && owner !== connectionId) throw new Error(`Microsoft account selector is ambiguous: ${selector.slice(0, 128)}`);
112
+ selectorOwners.set(normalized, connectionId);
113
+ }
114
+ return account;
115
+ });
116
+ }
117
+ function normalizeAccountSelector(value) {
118
+ const normalized = requireBoundedString(value, "Microsoft account selector", MAX_IDENTIFIER_CHARS).trim().toLowerCase();
119
+ if (!normalized) throw new Error("Microsoft account selector cannot be empty");
120
+ return normalized;
121
+ }
122
+ function validateRefreshResult(value) {
123
+ const record = requireRecord(value, "Microsoft token refresh response");
124
+ return {
125
+ accessToken: validateAccessToken(record.accessToken, false),
126
+ accessTokenExpiresAt: validateExpiry(record.accessTokenExpiresAt ?? "")
127
+ };
128
+ }
129
+ function validateRequestTimeout(value) {
130
+ if (!Number.isInteger(value) || value < 1e3 || value > 12e4) throw new Error("Microsoft Graph request timeout must be between 1000 and 120000 milliseconds");
131
+ return value;
132
+ }
133
+ async function readResponseText(response, maxBytes) {
134
+ const declaredLength = response.headers.get("content-length");
135
+ if (declaredLength !== null) {
136
+ const length = Number(declaredLength);
137
+ if (!Number.isSafeInteger(length) || length < 0 || length > maxBytes) {
138
+ await response.body?.cancel().catch(() => void 0);
139
+ throw new Error(`Microsoft Graph response exceeds ${String(maxBytes)} bytes`);
140
+ }
141
+ }
142
+ if (!response.body) return "";
143
+ const reader = response.body.getReader();
144
+ const chunks = [];
145
+ let total = 0;
146
+ try {
147
+ for (;;) {
148
+ const chunk = await reader.read();
149
+ if (chunk.done) break;
150
+ if (chunk.value === void 0) throw new Error("Microsoft Graph returned an invalid response chunk");
151
+ total += chunk.value.byteLength;
152
+ if (total > maxBytes) {
153
+ await reader.cancel("response exceeds limit");
154
+ throw new Error(`Microsoft Graph response exceeds ${String(maxBytes)} bytes`);
155
+ }
156
+ chunks.push(chunk.value);
157
+ }
158
+ } finally {
159
+ reader.releaseLock();
160
+ }
161
+ return Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)), total).toString("utf8");
162
+ }
163
+ function assertJsonBudget(value, label) {
164
+ const stack = [{
165
+ value,
166
+ depth: 0
167
+ }];
168
+ let nodes = 0;
169
+ while (stack.length > 0) {
170
+ const current = stack.pop();
171
+ if (!current) break;
172
+ nodes += 1;
173
+ if (nodes > MAX_JSON_NODES || current.depth > MAX_JSON_DEPTH) throw new Error(`${label} is too deeply or broadly nested`);
174
+ if (current.value === null || typeof current.value !== "object") continue;
175
+ if (Array.isArray(current.value)) {
176
+ for (const child of current.value) stack.push({
177
+ value: child,
178
+ depth: current.depth + 1
179
+ });
180
+ continue;
181
+ }
182
+ for (const [key, child] of Object.entries(current.value)) {
183
+ if (UNSAFE_KEYS.has(key)) throw new Error(`${label} contains an unsafe object key`);
184
+ stack.push({
185
+ value: child,
186
+ depth: current.depth + 1
187
+ });
188
+ }
189
+ }
190
+ }
191
+ function safeErrorMessage(error, secrets = []) {
192
+ let output = (error instanceof Error ? error.message : String(error)).slice(0, 4096).replace(/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/giu, "$1 [REDACTED]").replace(/\b(api[_-]?key|access[_-]?token|refresh[_-]?token|secret|password)\s*[=:]\s*[^\s,;]+/giu, "$1=[REDACTED]").replace(/https?:\/\/[^\s/@:]+:[^\s/@]+@/giu, "https://[REDACTED]@").replace(/\balfe_[A-Za-z0-9_-]{8,}/gu, "[REDACTED]");
193
+ for (const secret of secrets) if (secret.length >= 4) output = output.split(secret).join("[REDACTED]");
194
+ return flattenControls(output);
195
+ }
196
+ function validateGraphPath(value) {
197
+ let path = requireBoundedString(value, "Graph path", MAX_GRAPH_PATH_CHARS).trim();
198
+ if (!path) throw new Error("Command must include a Graph path (e.g. 'GET /me/messages')");
199
+ if (hasControlCharacters(path) || /\s/u.test(path)) throw new Error("Graph paths cannot contain unescaped whitespace or control characters");
200
+ if (/^[a-z][a-z0-9+.-]*:/iu.test(path) || path.startsWith("//")) throw new Error("Graph path must be a relative path, not an absolute or protocol-relative URL");
201
+ if (!path.startsWith("/")) path = `/${path}`;
202
+ return path;
203
+ }
204
+ function validateAccessToken(value, allowEmpty) {
205
+ if (allowEmpty && value === "") return "";
206
+ const token = requireBoundedString(value, "Microsoft access token", MAX_ACCESS_TOKEN_CHARS);
207
+ if (token !== token.trim() || /\s/u.test(token)) throw new Error("Microsoft access token is invalid");
208
+ return token;
209
+ }
210
+ function validateExpiry(value) {
211
+ if (value === "") return "";
212
+ const expiry = requireBoundedString(value, "Microsoft access-token expiry", 128);
213
+ if (Number.isNaN(Date.parse(expiry))) throw new Error("Microsoft access-token expiry is invalid");
214
+ return expiry;
215
+ }
216
+ function requireRecord(value, label) {
217
+ if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object`);
218
+ return value;
219
+ }
220
+ function requireBoundedString(value, label, maxChars) {
221
+ if (typeof value !== "string" || value.length < 1 || value.length > maxChars || hasControlCharacters(value)) throw new Error(`${label} must contain 1 to ${String(maxChars)} non-control characters`);
222
+ return value;
223
+ }
224
+ function requireIdentifier(value, label, maxChars) {
225
+ const checked = requireBoundedString(value, label, maxChars);
226
+ if (checked !== checked.trim()) throw new Error(`${label} cannot have leading or trailing whitespace`);
227
+ return checked;
228
+ }
229
+ function optionalBoundedString(value, label, maxChars) {
230
+ if (value === void 0 || value === null || value === "") return void 0;
231
+ return requireBoundedString(value, label, maxChars);
232
+ }
233
+ function hasControlCharacters(value) {
234
+ return Array.from(value).some((character) => {
235
+ const codePoint = character.codePointAt(0) ?? 0;
236
+ return codePoint < 32 || codePoint === 127;
237
+ });
238
+ }
239
+ function flattenControls(value) {
240
+ let output = "";
241
+ let previousWasControl = false;
242
+ for (const character of value) {
243
+ const codePoint = character.codePointAt(0) ?? 0;
244
+ const isControl = codePoint < 32 || codePoint === 127;
245
+ if (isControl) {
246
+ if (!previousWasControl) output += " ";
247
+ } else output += character;
248
+ previousWasControl = isControl;
249
+ }
250
+ return output;
251
+ }
252
+ const SERVER_VERSION = validatePackageVersion((0, node_module.createRequire)(require("url").pathToFileURL(__filename).href)("../package.json").version);
253
+ const DEFAULT_REQUEST_TIMEOUT_MS = 2e4;
254
+ var MicrosoftRuntime = class {
255
+ client;
256
+ accounts = [];
257
+ cacheRefreshPromise;
258
+ tokenRefreshes = /* @__PURE__ */ new Map();
259
+ tokenVersions = /* @__PURE__ */ new Map();
260
+ fetchImpl;
261
+ now;
262
+ requestTimeoutMs;
263
+ constructor(options) {
264
+ this.client = options.client;
265
+ this.fetchImpl = options.fetchImpl ?? fetch;
266
+ this.now = options.now ?? Date.now;
267
+ this.requestTimeoutMs = validateRequestTimeout(options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS);
268
+ }
269
+ async listAccounts() {
270
+ return this.refreshAccountCache();
271
+ }
272
+ async runCommand(email, command) {
273
+ const account = await this.resolveAccount(email);
274
+ const spec = parseCommand(command);
275
+ return {
276
+ account: account.email,
277
+ request: `${spec.method} ${spec.path}`,
278
+ result: await this.graphRequest(account, spec)
279
+ };
280
+ }
281
+ async disconnectAccount(email) {
282
+ const account = await this.resolveAccount(email);
283
+ await this.getClient().disconnectMicrosoftAccount(account.accountIdentifier);
284
+ return {
285
+ account,
286
+ remainingAccounts: await this.refreshAccountCache(true)
287
+ };
288
+ }
289
+ getClient() {
290
+ if (!this.client) {
291
+ const config = (0, _alfe_ai_config.resolveConfig)();
292
+ this.client = new _alfe_ai_agent_api_client.AgentApiClient({
293
+ apiKey: config.apiKey,
294
+ apiUrl: config.apiUrl
295
+ });
296
+ }
297
+ return this.client;
298
+ }
299
+ async refreshAccountCache(force = false) {
300
+ if (this.cacheRefreshPromise) {
301
+ if (!force) return this.cacheRefreshPromise;
302
+ await this.cacheRefreshPromise;
303
+ }
304
+ const versionsAtStart = new Map(this.tokenVersions);
305
+ const operation = (async () => {
306
+ const next = normalizeMicrosoftAccounts(await this.getClient().getMicrosoftAccounts());
307
+ for (const account of next) {
308
+ const versionBefore = versionsAtStart.get(account.connectionId) ?? 0;
309
+ if ((this.tokenVersions.get(account.connectionId) ?? 0) !== versionBefore) {
310
+ const current = this.accounts.find((item) => item.connectionId === account.connectionId);
311
+ if (current) {
312
+ account.accessToken = current.accessToken;
313
+ account.accessTokenExpiresAt = current.accessTokenExpiresAt;
314
+ }
315
+ }
316
+ }
317
+ this.accounts = next;
318
+ return next;
319
+ })();
320
+ this.cacheRefreshPromise = operation;
321
+ try {
322
+ return await operation;
323
+ } finally {
324
+ if (this.cacheRefreshPromise === operation) this.cacheRefreshPromise = void 0;
325
+ }
326
+ }
327
+ async resolveAccount(selector) {
328
+ const normalized = normalizeAccountSelector(selector);
329
+ const find = () => this.accounts.find((account) => normalizeAccountSelector(account.email) === normalized || normalizeAccountSelector(account.accountIdentifier) === normalized);
330
+ const hit = find();
331
+ if (hit) return hit;
332
+ await this.refreshAccountCache();
333
+ const refreshed = find();
334
+ if (refreshed) return refreshed;
335
+ throw new Error(`Microsoft account "${selector.slice(0, 128)}" not found. Available: ${this.accounts.map((account) => account.email).join(", ")}`);
336
+ }
337
+ ensureAccessToken(account, force = false) {
338
+ if (!force && !tokenIsExpired(account, this.now())) return Promise.resolve(account.accessToken);
339
+ const existing = this.tokenRefreshes.get(account.connectionId);
340
+ if (existing) return existing;
341
+ const refresh = (async () => {
342
+ const next = validateRefreshResult(await this.getClient().refreshMicrosoftAccountToken(account.accountIdentifier));
343
+ this.updateAccountToken(account.connectionId, next.accessToken, next.accessTokenExpiresAt);
344
+ account.accessToken = next.accessToken;
345
+ account.accessTokenExpiresAt = next.accessTokenExpiresAt;
346
+ return next.accessToken;
347
+ })();
348
+ this.tokenRefreshes.set(account.connectionId, refresh);
349
+ const cleanup = () => {
350
+ if (this.tokenRefreshes.get(account.connectionId) === refresh) this.tokenRefreshes.delete(account.connectionId);
351
+ };
352
+ refresh.then(cleanup, cleanup);
353
+ return refresh;
354
+ }
355
+ updateAccountToken(connectionId, accessToken, expiresAt) {
356
+ const current = this.accounts.find((account) => account.connectionId === connectionId);
357
+ if (current) {
358
+ current.accessToken = accessToken;
359
+ current.accessTokenExpiresAt = expiresAt;
360
+ }
361
+ this.tokenVersions.set(connectionId, (this.tokenVersions.get(connectionId) ?? 0) + 1);
362
+ }
363
+ async graphRequest(account, spec) {
364
+ const url = resolveGraphUrl(spec.path);
365
+ const encodedBody = encodeRequestBody(spec.body);
366
+ const doFetch = async (accessToken) => {
367
+ try {
368
+ return await this.fetchImpl(url, {
369
+ method: spec.method,
370
+ headers: {
371
+ Authorization: `Bearer ${accessToken}`,
372
+ ...encodedBody === void 0 ? {} : { "Content-Type": "application/json" }
373
+ },
374
+ body: encodedBody,
375
+ redirect: "error",
376
+ signal: AbortSignal.timeout(this.requestTimeoutMs)
377
+ });
378
+ } catch (error) {
379
+ throw new Error(`Microsoft Graph ${spec.method} ${new URL(url).pathname} request failed`, { cause: error });
380
+ }
381
+ };
382
+ let accessToken = await this.ensureAccessToken(account);
383
+ let response = await doFetch(accessToken);
384
+ if (response.status === 401) {
385
+ await response.body?.cancel().catch(() => void 0);
386
+ accessToken = await this.ensureAccessToken(account, true);
387
+ response = await doFetch(accessToken);
388
+ }
389
+ const text = await readResponseText(response, response.ok ? MAX_RESPONSE_BYTES : MAX_ERROR_RESPONSE_BYTES);
390
+ let data = null;
391
+ if (text) try {
392
+ data = JSON.parse(text);
393
+ assertJsonBudget(data, "Microsoft Graph response");
394
+ } catch (error) {
395
+ if (!response.ok && error instanceof SyntaxError) data = { message: "Microsoft Graph returned a non-JSON error response" };
396
+ else if (error instanceof SyntaxError) throw new Error("Microsoft Graph returned invalid JSON", { cause: error });
397
+ else throw error;
398
+ }
399
+ return {
400
+ status: response.status,
401
+ ok: response.ok,
402
+ data
403
+ };
404
+ }
405
+ };
406
+ function jsonResult(data, isError = false) {
407
+ return {
408
+ content: [{
409
+ type: "text",
410
+ text: JSON.stringify(data)
411
+ }],
412
+ ...isError ? { isError: true } : {}
413
+ };
414
+ }
415
+ function createRuntimeServer(options = {}) {
416
+ const runtime = new MicrosoftRuntime(options);
417
+ const server = new _modelcontextprotocol_sdk_server_mcp_js.McpServer({
418
+ name: "microsoft-mcp",
419
+ version: SERVER_VERSION
420
+ });
421
+ const registerTool = server.registerTool.bind(server);
422
+ registerTool("microsoft_list_accounts", {
423
+ description: "List all connected Microsoft 365 accounts. Use this before a credential-touching tool to select the account explicitly.",
424
+ inputSchema: {}
425
+ }, async () => {
426
+ const accounts = await runtime.listAccounts();
427
+ return jsonResult({
428
+ accounts: accounts.map((account) => ({
429
+ email: account.email,
430
+ displayName: account.displayName,
431
+ connectedAt: account.connectedAt
432
+ })),
433
+ count: accounts.length
434
+ });
435
+ });
436
+ registerTool("microsoft_run_command", {
437
+ description: "Call Microsoft Graph v1.0 for one explicit Microsoft 365 account. Command format: '[METHOD] /path [json-body]'; method defaults to GET.",
438
+ inputSchema: {
439
+ command: zod.z.string().trim().min(1).max(MAX_COMMAND_CHARS),
440
+ email: zod.z.string().trim().min(1).max(512)
441
+ }
442
+ }, async ({ command, email }) => {
443
+ const call = await runtime.runCommand(email, command);
444
+ return jsonResult({
445
+ account: call.account,
446
+ request: call.request,
447
+ status: call.result.status,
448
+ ok: call.result.ok,
449
+ data: call.result.data
450
+ }, !call.result.ok);
451
+ });
452
+ registerTool("microsoft_disconnect_account", {
453
+ description: "Disconnect one explicit Microsoft 365 account from this agent.",
454
+ inputSchema: { email: zod.z.string().trim().min(1).max(512) }
455
+ }, async ({ email }) => {
456
+ const result = await runtime.disconnectAccount(email);
457
+ return jsonResult({
458
+ message: `${result.account.email} has been disconnected`,
459
+ remainingAccounts: result.remainingAccounts.map((account) => ({
460
+ email: account.email,
461
+ displayName: account.displayName,
462
+ connectedAt: account.connectedAt
463
+ }))
464
+ });
465
+ });
466
+ return {
467
+ server,
468
+ runtime
469
+ };
470
+ }
471
+ function createServer(options = {}) {
472
+ return createRuntimeServer(options).server;
473
+ }
474
+ async function main() {
475
+ const { server, runtime } = createRuntimeServer();
476
+ try {
477
+ const accounts = await runtime.listAccounts();
478
+ log(`Cached ${String(accounts.length)} Microsoft account(s)`);
479
+ } catch (error) {
480
+ log(`Failed to pre-cache Microsoft accounts (will fetch on demand): ${safeErrorMessage(error)}`);
481
+ }
482
+ try {
483
+ await server.connect(new _modelcontextprotocol_sdk_server_stdio_js.StdioServerTransport());
484
+ log("Microsoft 365 MCP server running (multi-account, direct Graph REST)");
485
+ return server;
486
+ } catch (error) {
487
+ await server.close().catch(() => void 0);
488
+ throw error;
489
+ }
490
+ }
491
+ function isProcessEntrypoint(argvPath, metaUrl) {
492
+ try {
493
+ if (!argvPath) return false;
494
+ return (0, node_url.pathToFileURL)((0, node_fs.realpathSync)(argvPath)).href === (0, node_url.pathToFileURL)((0, node_fs.realpathSync)((0, node_url.fileURLToPath)(metaUrl))).href;
495
+ } catch {
496
+ return false;
497
+ }
498
+ }
499
+ function encodeRequestBody(body) {
500
+ if (body === void 0) return void 0;
501
+ let encoded;
502
+ try {
503
+ encoded = JSON.stringify(body);
504
+ } catch (error) {
505
+ throw new Error("Microsoft Graph request body is not valid JSON", { cause: error });
506
+ }
507
+ if (Buffer.byteLength(encoded, "utf8") > 1048576) throw new Error(`Graph request body exceeds ${String(MAX_REQUEST_BODY_BYTES)} bytes`);
508
+ return encoded;
509
+ }
510
+ function validatePackageVersion(value) {
511
+ if (typeof value !== "string" || !/^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/u.test(value)) throw new Error("microsoft-mcp package version is invalid");
512
+ return value;
513
+ }
514
+ function log(message) {
515
+ process.stderr.write(`[microsoft-mcp] ${message}\n`);
516
+ }
517
+ if (isProcessEntrypoint(process.argv[1], require("url").pathToFileURL(__filename).href)) main().then((server) => {
518
+ let shutdownPromise;
519
+ const shutdown = () => {
520
+ if (shutdownPromise) return;
521
+ shutdownPromise = server.close().catch((error) => {
522
+ log(`Failed to close cleanly: ${safeErrorMessage(error)}`);
523
+ }).then(() => process.exit(0));
524
+ };
525
+ process.once("SIGTERM", shutdown);
526
+ process.once("SIGINT", shutdown);
527
+ }).catch((error) => {
528
+ log(`Fatal: ${safeErrorMessage(error)}`);
529
+ process.exitCode = 1;
530
+ });
531
+ //#endregion
532
+ exports.SERVER_VERSION = SERVER_VERSION;
533
+ exports.createServer = createServer;
534
+ exports.isProcessEntrypoint = isProcessEntrypoint;
535
+ exports.parseCommand = parseCommand;
536
+ exports.resolveGraphUrl = resolveGraphUrl;
537
+ exports.safeErrorMessage = safeErrorMessage;
538
+ exports.tokenIsExpired = tokenIsExpired;