diff --git a/ews_client.ts b/ews_client.ts
index 84415ef..8c2f983 100644
--- a/ews_client.ts
+++ b/ews_client.ts
@@ -537,12 +537,16 @@ ${restriction}
const changesXml = itemIds.map((id) => {
const updatesXml = updates.map((u) => {
- const val = typeof u.value === "boolean" ? (u.value ? "true" : "false") : u.value;
+ const val = typeof u.value === "boolean"
+ ? (u.value ? "true" : "false")
+ : u.value;
return `\
- ${String(val)}
+ ${String(val)}
`;
}).join("\n");
@@ -573,7 +577,9 @@ ${changesXml}
// ── GetAttachment ───────────────────────────────────────────────────────
- async getAttachment(attachmentId: string): Promise<{ content: Buffer; name: string; contentType: string }> {
+ async getAttachment(
+ attachmentId: string,
+ ): Promise<{ content: Buffer; name: string; contentType: string }> {
const soap = buildSoapEnvelope(`\
@@ -645,13 +651,17 @@ ${changesXml}
endDate: string,
requestedView: string = "DetailedMerged",
): Promise {
- const mailboxDataXml = emails.map((email) => `\
+ const mailboxDataXml = emails.map((email) =>
+ `\
- ${email.replace(/&/g, "&").replace(/
+ ${
+ email.replace(/&/g, "&").replace(/
Required
- `).join("\n");
+ `
+ ).join("\n");
const soap = `
{
+ async resolveNames(
+ query: string,
+ fullContact: boolean = true,
+ ): Promise {
const soap = buildSoapEnvelope(`\
- ${query.replace(/&/g, "&").replace(/
+ ${
+ query.replace(/&/g, "&").replace(/
`);
const data = await this.soapRequest(
diff --git a/main.ts b/main.ts
index 097250e..3ac4048 100644
--- a/main.ts
+++ b/main.ts
@@ -6,10 +6,10 @@ import crypto from "node:crypto";
import http from "node:http";
import { initDataDir } from "./ews_client.ts";
import { registerAuthTools } from "./tools/auth.ts";
+import { registerAvailabilityTools } from "./tools/availability.ts";
+import { registerCalendarTools } from "./tools/calendar.ts";
import { registerEmailTools } from "./tools/email.ts";
import { registerFolderTools } from "./tools/folders.ts";
-import { registerCalendarTools } from "./tools/calendar.ts";
-import { registerAvailabilityTools } from "./tools/availability.ts";
import { registerPeopleTools } from "./tools/people.ts";
const program = new Command()
diff --git a/tools/availability.ts b/tools/availability.ts
index 890fdc9..d412177 100644
--- a/tools/availability.ts
+++ b/tools/availability.ts
@@ -9,10 +9,18 @@ export function registerAvailabilityTools(server: McpServer): void {
description: "Find free time slots in your own calendar",
inputSchema: z.object({
startDate: z.string().describe("Start date in YYYY-MM-DD format"),
- endDate: z.string().optional().describe("End date in YYYY-MM-DD format (defaults to startDate)"),
- durationMinutes: z.number().default(30).describe("Minimum slot duration in minutes (default 30)"),
- startHour: z.number().default(9).describe("Working day start hour (0-23, default 9)"),
- endHour: z.number().default(18).describe("Working day end hour (0-23, default 18)"),
+ endDate: z.string().optional().describe(
+ "End date in YYYY-MM-DD format (defaults to startDate)",
+ ),
+ durationMinutes: z.number().default(30).describe(
+ "Minimum slot duration in minutes (default 30)",
+ ),
+ startHour: z.number().default(9).describe(
+ "Working day start hour (0-23, default 9)",
+ ),
+ endHour: z.number().default(18).describe(
+ "Working day end hour (0-23, default 18)",
+ ),
}),
},
async ({ startDate, endDate, durationMinutes, startHour, endHour }) => {
@@ -27,9 +35,13 @@ export function registerAvailabilityTools(server: McpServer): void {
ed,
);
- const body = data?.Envelope?.Body?.GetUserAvailabilityResponse ?? data?.Envelope?.Body ?? {};
- const freeBusyArray = body?.FreeBusyResponseArray?.FreeBusyResponse ?? [];
- const responses = Array.isArray(freeBusyArray) ? freeBusyArray : [freeBusyArray];
+ const body = data?.Envelope?.Body?.GetUserAvailabilityResponse
+ ?? data?.Envelope?.Body ?? {};
+ const freeBusyArray = body?.FreeBusyResponseArray?.FreeBusyResponse
+ ?? [];
+ const responses = Array.isArray(freeBusyArray)
+ ? freeBusyArray
+ : [freeBusyArray];
const busyPeriods: { start: Date; end: Date }[] = [];
@@ -63,7 +75,10 @@ export function registerAvailabilityTools(server: McpServer): void {
);
return {
- content: [{ type: "text" as const, text: JSON.stringify({ freeSlots }) }],
+ content: [{
+ type: "text" as const,
+ text: JSON.stringify({ freeSlots }),
+ }],
};
} catch (error: any) {
return {
@@ -81,25 +96,43 @@ export function registerAvailabilityTools(server: McpServer): void {
{
description: "Find meeting times that work for multiple people",
inputSchema: z.object({
- emails: z.string().describe("Comma-separated email addresses of attendees"),
+ emails: z.string().describe(
+ "Comma-separated email addresses of attendees",
+ ),
startDate: z.string().describe("Start date in YYYY-MM-DD format"),
- endDate: z.string().optional().describe("End date in YYYY-MM-DD format (defaults to startDate)"),
- durationMinutes: z.number().default(30).describe("Minimum slot duration in minutes (default 30)"),
- startHour: z.number().default(9).describe("Working day start hour (0-23, default 9)"),
- endHour: z.number().default(18).describe("Working day end hour (0-23, default 18)"),
+ endDate: z.string().optional().describe(
+ "End date in YYYY-MM-DD format (defaults to startDate)",
+ ),
+ durationMinutes: z.number().default(30).describe(
+ "Minimum slot duration in minutes (default 30)",
+ ),
+ startHour: z.number().default(9).describe(
+ "Working day start hour (0-23, default 9)",
+ ),
+ endHour: z.number().default(18).describe(
+ "Working day end hour (0-23, default 18)",
+ ),
}),
},
- async ({ emails, startDate, endDate, durationMinutes, startHour, endHour }) => {
+ async (
+ { emails, startDate, endDate, durationMinutes, startHour, endHour },
+ ) => {
try {
const client = new EwsClient(loadConfig());
- const emailList = emails.split(",").map((e) => e.trim()).filter(Boolean);
+ const emailList = emails.split(",").map((e) => e.trim()).filter(
+ Boolean,
+ );
const ed = endDate || startDate;
const data = await client.getUserAvailability(emailList, startDate, ed);
- const body = data?.Envelope?.Body?.GetUserAvailabilityResponse ?? data?.Envelope?.Body ?? {};
- const freeBusyArray = body?.FreeBusyResponseArray?.FreeBusyResponse ?? [];
- const responses = Array.isArray(freeBusyArray) ? freeBusyArray : [freeBusyArray];
+ const body = data?.Envelope?.Body?.GetUserAvailabilityResponse
+ ?? data?.Envelope?.Body ?? {};
+ const freeBusyArray = body?.FreeBusyResponseArray?.FreeBusyResponse
+ ?? [];
+ const responses = Array.isArray(freeBusyArray)
+ ? freeBusyArray
+ : [freeBusyArray];
const allBusy: { start: Date; end: Date }[] = [];
const attendeeInfo: any[] = [];
@@ -114,8 +147,14 @@ export function registerAvailabilityTools(server: McpServer): void {
const startTime = new Date(`${startDate}T00:00:00`);
const busyPeriods = parseFreeBusyString(mergedFb, startTime);
const busyCount = busyPeriods.length;
- const freeCount = mergedFb.split("").filter((c: string) => c === "0").length;
- attendeeInfo.push({ email, busySlots: busyCount, freeSlots: freeCount });
+ const freeCount = mergedFb.split("").filter((c: string) =>
+ c === "0"
+ ).length;
+ attendeeInfo.push({
+ email,
+ busySlots: busyCount,
+ freeSlots: freeCount,
+ });
allBusy.push(...busyPeriods);
} else {
const calEvents = fbView?.CalendarEventArray?.CalendarEvent ?? [];
@@ -125,7 +164,10 @@ export function registerAvailabilityTools(server: McpServer): void {
const startStr = ev.StartTime ?? "";
const endStr = ev.EndTime ?? "";
if (startStr && endStr) {
- allBusy.push({ start: new Date(startStr), end: new Date(endStr) });
+ allBusy.push({
+ start: new Date(startStr),
+ end: new Date(endStr),
+ });
}
}
}
@@ -184,10 +226,14 @@ function parseFreeBusyString(
return periods;
}
-function mergeBusyPeriods(periods: { start: Date; end: Date }[]): { start: Date; end: Date }[] {
+function mergeBusyPeriods(
+ periods: { start: Date; end: Date }[],
+): { start: Date; end: Date }[] {
if (!periods.length) return [];
- const sorted = [...periods].sort((a, b) => a.start.getTime() - b.start.getTime());
+ const sorted = [...periods].sort((a, b) =>
+ a.start.getTime() - b.start.getTime()
+ );
const merged: { start: Date; end: Date }[] = [{ ...sorted[0] }];
for (let i = 1; i < sorted.length; i++) {
@@ -210,7 +256,10 @@ function findFreeSlots(
endHour: number,
durationMinutes: number,
): Record {
- const result: Record = {};
+ const result: Record<
+ string,
+ { start: string; end: string; durationMinutes: number }[]
+ > = {};
const current = new Date(startDate);
current.setDate(current.getDate());
@@ -237,7 +286,8 @@ function findFreeSlots(
const merged = mergeBusyPeriods(dayBusy);
- const slots: { start: string; end: string; durationMinutes: number }[] = [];
+ const slots: { start: string; end: string; durationMinutes: number }[] =
+ [];
let cursor = new Date(dayStart);
for (const bp of merged) {
diff --git a/tools/calendar.ts b/tools/calendar.ts
index 84883a9..6ed2cd6 100644
--- a/tools/calendar.ts
+++ b/tools/calendar.ts
@@ -1,7 +1,7 @@
import { type McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
-import { z } from "zod/v4";
import fs from "node:fs";
import path from "node:path";
+import { z } from "zod/v4";
import { EwsClient, loadConfig } from "../ews_client.ts";
export function registerCalendarTools(server: McpServer): void {
@@ -34,10 +34,13 @@ export function registerCalendarTools(server: McpServer): void {
start: item.Start ?? "",
end: item.End ?? "",
location: item.Location ?? item.EnhancedLocation?.DisplayName ?? "",
- isAllDay: item.IsAllDayEvent === "true" || item.IsAllDayEvent === true,
- isCancelled: item.IsCancelled === "true" || item.IsCancelled === true,
+ isAllDay: item.IsAllDayEvent === "true"
+ || item.IsAllDayEvent === true,
+ isCancelled: item.IsCancelled === "true"
+ || item.IsCancelled === true,
isMeeting: true,
- isRecurring: item.IsRecurring === "true" || item.IsRecurring === true,
+ isRecurring: item.IsRecurring === "true"
+ || item.IsRecurring === true,
organizer: "",
organizerEmail: "",
myResponse: item.MyResponseType ?? "",
@@ -61,7 +64,10 @@ export function registerCalendarTools(server: McpServer): void {
}
return {
- content: [{ type: "text" as const, text: JSON.stringify({ events, count: events.length }) }],
+ content: [{
+ type: "text" as const,
+ text: JSON.stringify({ events, count: events.length }),
+ }],
};
} catch (error: any) {
return {
@@ -77,9 +83,12 @@ export function registerCalendarTools(server: McpServer): void {
server.registerTool(
"download_event_attachments",
{
- description: "Download all file attachments from a calendar event to disk",
+ description:
+ "Download all file attachments from a calendar event to disk",
inputSchema: z.object({
- itemId: z.string().describe("The Exchange ItemId of the calendar event"),
+ itemId: z.string().describe(
+ "The Exchange ItemId of the calendar event",
+ ),
targetFolder: z.string().default("/tmp/attachments").describe(
"Local directory to save files (default /tmp/attachments)",
),
@@ -130,7 +139,9 @@ export function registerCalendarTools(server: McpServer): void {
let counter = 1;
let finalName = filename;
while (usedNames.has(finalName.toLowerCase())) {
- finalName = extPart ? `${namePart}_${counter}${extPart}` : `${namePart}_${counter}`;
+ finalName = extPart
+ ? `${namePart}_${counter}${extPart}`
+ : `${namePart}_${counter}`;
counter++;
}
usedNames.add(finalName.toLowerCase());
@@ -145,7 +156,10 @@ export function registerCalendarTools(server: McpServer): void {
contentType: result.contentType,
});
} catch (e: any) {
- errors.push({ name: att.name || "unknown", error: e.message ?? String(e) });
+ errors.push({
+ name: att.name || "unknown",
+ error: e.message ?? String(e),
+ });
}
}
diff --git a/tools/email.ts b/tools/email.ts
index 3730c45..469aa5d 100644
--- a/tools/email.ts
+++ b/tools/email.ts
@@ -1,7 +1,7 @@
import { type McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
-import { z } from "zod/v4";
import fs from "node:fs";
import path from "node:path";
+import { z } from "zod/v4";
import { EwsClient, loadConfig } from "../ews_client.ts";
function getClient(): EwsClient {
@@ -293,8 +293,12 @@ export function registerEmailTools(server: McpServer): void {
{
description: "Mark one or more emails as read or unread",
inputSchema: z.object({
- itemIds: z.array(z.string()).describe("List of Exchange ItemIds to update"),
- isRead: z.boolean().default(true).describe("True to mark as read, False to mark as unread"),
+ itemIds: z.array(z.string()).describe(
+ "List of Exchange ItemIds to update",
+ ),
+ isRead: z.boolean().default(true).describe(
+ "True to mark as read, False to mark as unread",
+ ),
}),
},
async ({ itemIds, isRead }) => {
@@ -310,7 +314,10 @@ export function registerEmailTools(server: McpServer): void {
if (errors.length) {
return {
- content: [{ type: "text" as const, text: JSON.stringify({ error: errors.join("; ") }) }],
+ content: [{
+ type: "text" as const,
+ text: JSON.stringify({ error: errors.join("; ") }),
+ }],
};
}
@@ -391,7 +398,9 @@ export function registerEmailTools(server: McpServer): void {
let counter = 1;
let finalName = filename;
while (usedNames.has(finalName.toLowerCase())) {
- finalName = extPart ? `${namePart}_${counter}${extPart}` : `${namePart}_${counter}`;
+ finalName = extPart
+ ? `${namePart}_${counter}${extPart}`
+ : `${namePart}_${counter}`;
counter++;
}
usedNames.add(finalName.toLowerCase());
@@ -406,7 +415,10 @@ export function registerEmailTools(server: McpServer): void {
contentType: result.contentType,
});
} catch (e: any) {
- errors.push({ name: att.name || "unknown", error: e.message ?? String(e) });
+ errors.push({
+ name: att.name || "unknown",
+ error: e.message ?? String(e),
+ });
}
}
diff --git a/tools/people.ts b/tools/people.ts
index edbccf0..b3c411f 100644
--- a/tools/people.ts
+++ b/tools/people.ts
@@ -52,7 +52,8 @@ export function registerPeopleTools(server: McpServer): void {
const addrList = Array.isArray(addrs) ? addrs : [addrs];
for (const a of addrList) {
if (a?.Key === "Business") {
- const parts = [a.Street, a.City, a.PostalCode, a.CountryOrRegion].filter(Boolean);
+ const parts = [a.Street, a.City, a.PostalCode, a.CountryOrRegion]
+ .filter(Boolean);
if (parts.length) {
person.address = parts.join(", ");
}