refactor: drop skill file, rework client #3
+37
-5
@@ -17,15 +17,19 @@ export function registerAuthTools(server: McpServer): void {
|
|||||||
description: "Authenticate to Exchange EWS using NTLM credentials."
|
description: "Authenticate to Exchange EWS using NTLM credentials."
|
||||||
+ " On first use all fields except domain are required."
|
+ " On first use all fields except domain are required."
|
||||||
+ " On subsequent logins only password is needed — previously saved"
|
+ " On subsequent logins only password is needed — previously saved"
|
||||||
+ " serverUrl, email, username, and domain are reused automatically from config."
|
+ " serverUrl, email, username, and domain are reused automatically from config.",
|
||||||
+ " Returns {success, message} on success or {success, error} on failure.",
|
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
serverUrl: z.string().optional().describe("Server URL"),
|
serverUrl: z.string().optional().describe("EWS server URL"),
|
||||||
email: z.string().optional().describe("Email address"),
|
email: z.string().optional().describe("Email address"),
|
||||||
username: z.string().optional().describe("NTLM username"),
|
username: z.string().optional().describe("NTLM username"),
|
||||||
password: z.string().describe("NTLM password"),
|
password: z.string().describe("NTLM password"),
|
||||||
domain: z.string().optional().describe("NTLM domain"),
|
domain: z.string().optional().describe("NTLM domain"),
|
||||||
}),
|
}),
|
||||||
|
outputSchema: z.object({
|
||||||
|
success: z.boolean(),
|
||||||
|
message: z.string().optional(),
|
||||||
|
error: z.string().optional(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
async ({ serverUrl, email, username, password, domain }) => {
|
async ({ serverUrl, email, username, password, domain }) => {
|
||||||
try {
|
try {
|
||||||
@@ -103,8 +107,14 @@ export function registerAuthTools(server: McpServer): void {
|
|||||||
"check_session",
|
"check_session",
|
||||||
{
|
{
|
||||||
description:
|
description:
|
||||||
"Check whether the current EWS session is authenticated. No parameters. Returns {authenticated, email, serverUrl} or {authenticated, error}.",
|
"Check whether the current EWS session is authenticated. No parameters.",
|
||||||
inputSchema: z.object({}),
|
inputSchema: z.object({}),
|
||||||
|
outputSchema: z.object({
|
||||||
|
authenticated: z.boolean(),
|
||||||
|
email: z.string().optional(),
|
||||||
|
serverUrl: z.string().optional(),
|
||||||
|
error: z.string().optional(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
try {
|
try {
|
||||||
@@ -117,6 +127,10 @@ export function registerAuthTools(server: McpServer): void {
|
|||||||
error: "Not logged in. Password not found in memory.",
|
error: "Not logged in. Password not found in memory.",
|
||||||
}),
|
}),
|
||||||
}],
|
}],
|
||||||
|
structuredContent: {
|
||||||
|
authenticated: false,
|
||||||
|
error: "Not logged in. Password not found in memory.",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
@@ -133,6 +147,12 @@ export function registerAuthTools(server: McpServer): void {
|
|||||||
error: result.error,
|
error: result.error,
|
||||||
}),
|
}),
|
||||||
}],
|
}],
|
||||||
|
structuredContent: {
|
||||||
|
authenticated: result.ok,
|
||||||
|
email: config.email,
|
||||||
|
serverUrl: config.serverUrl,
|
||||||
|
error: result.error,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return {
|
return {
|
||||||
@@ -143,6 +163,10 @@ export function registerAuthTools(server: McpServer): void {
|
|||||||
error: error.message ?? String(error),
|
error: error.message ?? String(error),
|
||||||
}),
|
}),
|
||||||
}],
|
}],
|
||||||
|
structuredContent: {
|
||||||
|
authenticated: false,
|
||||||
|
error: error.message ?? String(error),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -152,8 +176,12 @@ export function registerAuthTools(server: McpServer): void {
|
|||||||
"logout",
|
"logout",
|
||||||
{
|
{
|
||||||
description:
|
description:
|
||||||
"Clear stored credentials (serverUrl, email, username, domain, password). No parameters. Returns {success, message}.",
|
"Clear stored credentials (serverUrl, email, username, domain, password). No parameters.",
|
||||||
inputSchema: z.object({}),
|
inputSchema: z.object({}),
|
||||||
|
outputSchema: z.object({
|
||||||
|
success: z.boolean(),
|
||||||
|
message: z.string().optional(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
async () => {
|
async () => {
|
||||||
clearConfig();
|
clearConfig();
|
||||||
@@ -165,6 +193,10 @@ export function registerAuthTools(server: McpServer): void {
|
|||||||
message: "Logged out. Credentials cleared.",
|
message: "Logged out. Credentials cleared.",
|
||||||
}),
|
}),
|
||||||
}],
|
}],
|
||||||
|
structuredContent: {
|
||||||
|
success: true,
|
||||||
|
message: "Logged out. Credentials cleared.",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
+47
-13
@@ -8,22 +8,30 @@ export function registerAvailabilityTools(server: McpServer): void {
|
|||||||
{
|
{
|
||||||
description: "Find free time slots in your own calendar."
|
description: "Find free time slots in your own calendar."
|
||||||
+ " Queries your free/busy status and returns available slots"
|
+ " Queries your free/busy status and returns available slots"
|
||||||
+ " within working hours that meet the minimum duration."
|
+ " within working hours that meet the minimum duration.",
|
||||||
+ " Returns {freeSlots: {date: [{start, end, durationMinutes}]}}."
|
|
||||||
+ " Defaults: durationMinutes=30, startHour=9, endHour=18.",
|
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
startDate: z.string().describe("Start date in YYYY-MM-DD format"),
|
startDate: z.string().describe("Start date in YYYY-MM-DD format"),
|
||||||
endDate: z.string().optional().describe(
|
endDate: z.string().optional().describe(
|
||||||
"End date in YYYY-MM-DD format. Defaults to startDate",
|
"End date in YYYY-MM-DD format",
|
||||||
),
|
),
|
||||||
durationMinutes: z.number().default(30).describe(
|
durationMinutes: z.number().default(30).describe(
|
||||||
"Minimum slot duration in minutes. Default: 30",
|
"Minimum slot duration in minutes",
|
||||||
),
|
),
|
||||||
startHour: z.number().default(9).describe(
|
startHour: z.number().default(9).describe(
|
||||||
"Working day start hour (0-23). Default: 9",
|
"Working day start hour (0-23)",
|
||||||
),
|
),
|
||||||
endHour: z.number().default(18).describe(
|
endHour: z.number().default(18).describe(
|
||||||
"Working day end hour (0-23). Default: 18",
|
"Working day end hour (0-23)",
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
outputSchema: z.object({
|
||||||
|
freeSlots: z.record(
|
||||||
|
z.string(),
|
||||||
|
z.array(z.object({
|
||||||
|
start: z.string(),
|
||||||
|
end: z.string(),
|
||||||
|
durationMinutes: z.number(),
|
||||||
|
})),
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -83,6 +91,7 @@ export function registerAvailabilityTools(server: McpServer): void {
|
|||||||
type: "text" as const,
|
type: "text" as const,
|
||||||
text: JSON.stringify({ freeSlots }),
|
text: JSON.stringify({ freeSlots }),
|
||||||
}],
|
}],
|
||||||
|
structuredContent: { freeSlots },
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return {
|
return {
|
||||||
@@ -98,24 +107,44 @@ export function registerAvailabilityTools(server: McpServer): void {
|
|||||||
server.registerTool(
|
server.registerTool(
|
||||||
"find_meeting_time",
|
"find_meeting_time",
|
||||||
{
|
{
|
||||||
description:
|
description: "Find common free time for multiple attendees."
|
||||||
"Find common free time for multiple attendees. Queries free/busy for all attendees and returns slots where everyone is available. Returns {period, attendees, freeSlots}. Defaults: durationMinutes=30, startHour=9, endHour=18.",
|
+ " Queries free/busy for all attendees and returns slots where everyone is available.",
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
emails: z.string().describe(
|
emails: z.string().describe(
|
||||||
"Comma-separated email addresses of attendees",
|
"Comma-separated email addresses of attendees",
|
||||||
),
|
),
|
||||||
startDate: z.string().describe("Start date in YYYY-MM-DD format"),
|
startDate: z.string().describe("Start date in YYYY-MM-DD format"),
|
||||||
endDate: z.string().optional().describe(
|
endDate: z.string().optional().describe(
|
||||||
"End date in YYYY-MM-DD format. Defaults to startDate",
|
"End date in YYYY-MM-DD format",
|
||||||
),
|
),
|
||||||
durationMinutes: z.number().default(30).describe(
|
durationMinutes: z.number().default(30).describe(
|
||||||
"Minimum slot duration in minutes. Default: 30",
|
"Minimum slot duration in minutes",
|
||||||
),
|
),
|
||||||
startHour: z.number().default(9).describe(
|
startHour: z.number().default(9).describe(
|
||||||
"Working day start hour (0-23). Default: 9",
|
"Working day start hour (0-23)",
|
||||||
),
|
),
|
||||||
endHour: z.number().default(18).describe(
|
endHour: z.number().default(18).describe(
|
||||||
"Working day end hour (0-23). Default: 18",
|
"Working day end hour (0-23)",
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
outputSchema: z.object({
|
||||||
|
period: z.object({
|
||||||
|
start: z.string(),
|
||||||
|
end: z.string(),
|
||||||
|
}),
|
||||||
|
attendees: z.array(z.object({
|
||||||
|
email: z.string(),
|
||||||
|
busySlots: z.number().optional(),
|
||||||
|
freeSlots: z.number().optional(),
|
||||||
|
calendarEvents: z.number().optional(),
|
||||||
|
})),
|
||||||
|
freeSlots: z.record(
|
||||||
|
z.string(),
|
||||||
|
z.array(z.object({
|
||||||
|
start: z.string(),
|
||||||
|
end: z.string(),
|
||||||
|
durationMinutes: z.number(),
|
||||||
|
})),
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -197,6 +226,11 @@ export function registerAvailabilityTools(server: McpServer): void {
|
|||||||
freeSlots: freeByDate,
|
freeSlots: freeByDate,
|
||||||
}),
|
}),
|
||||||
}],
|
}],
|
||||||
|
structuredContent: {
|
||||||
|
period: { start: startDate, end: ed },
|
||||||
|
attendees: attendeeInfo,
|
||||||
|
freeSlots: freeByDate,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
+51
-5
@@ -8,15 +8,35 @@ export function registerCalendarTools(server: McpServer): void {
|
|||||||
server.registerTool(
|
server.registerTool(
|
||||||
"get_calendar_events",
|
"get_calendar_events",
|
||||||
{
|
{
|
||||||
description:
|
description: "Get calendar events within a date range."
|
||||||
"Get calendar events within a date range. Set includeBody=true to fetch organizer, attendees, and body via GetItem. Returns {events, count}.",
|
+ " Set includeBody=true to fetch organizer, attendees, and body via GetItem.",
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
startDate: z.string().describe("Start date in YYYY-MM-DD format"),
|
startDate: z.string().describe("Start date in YYYY-MM-DD format"),
|
||||||
endDate: z.string().describe("End date in YYYY-MM-DD format"),
|
endDate: z.string().describe("End date in YYYY-MM-DD format"),
|
||||||
includeBody: z.boolean().default(false).describe(
|
includeBody: z.boolean().default(false).describe(
|
||||||
"If true, fetch full event details (organizer, attendees, body) via GetItem. Default: false",
|
"If true, fetch full event details (organizer, attendees, body) via GetItem",
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
|
outputSchema: z.object({
|
||||||
|
events: z.array(z.object({
|
||||||
|
subject: z.string(),
|
||||||
|
start: z.string(),
|
||||||
|
end: z.string(),
|
||||||
|
location: z.string(),
|
||||||
|
isAllDay: z.boolean(),
|
||||||
|
isCancelled: z.boolean(),
|
||||||
|
isMeeting: z.boolean(),
|
||||||
|
isRecurring: z.boolean(),
|
||||||
|
organizer: z.string(),
|
||||||
|
organizerEmail: z.string(),
|
||||||
|
myResponse: z.string(),
|
||||||
|
itemId: z.string(),
|
||||||
|
body: z.string(),
|
||||||
|
requiredAttendees: z.array(z.string()),
|
||||||
|
optionalAttendees: z.array(z.string()),
|
||||||
|
})),
|
||||||
|
count: z.number(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
async ({ startDate, endDate, includeBody }) => {
|
async ({ startDate, endDate, includeBody }) => {
|
||||||
try {
|
try {
|
||||||
@@ -69,6 +89,7 @@ export function registerCalendarTools(server: McpServer): void {
|
|||||||
type: "text" as const,
|
type: "text" as const,
|
||||||
text: JSON.stringify({ events, count: events.length }),
|
text: JSON.stringify({ events, count: events.length }),
|
||||||
}],
|
}],
|
||||||
|
structuredContent: { events, count: events.length },
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return {
|
return {
|
||||||
@@ -85,15 +106,31 @@ export function registerCalendarTools(server: McpServer): void {
|
|||||||
"download_event_attachments",
|
"download_event_attachments",
|
||||||
{
|
{
|
||||||
description:
|
description:
|
||||||
"Download all file attachments from a calendar event to disk. Inline (embedded) attachments are skipped — only standalone file attachments are downloaded. Returns {success, downloaded, count} or {success, downloaded, count, errors}.",
|
"Download all file attachments from a calendar event to disk."
|
||||||
|
+ " Inline (embedded) attachments are skipped — only standalone file attachments are downloaded.",
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
itemId: z.string().describe(
|
itemId: z.string().describe(
|
||||||
"The Exchange ItemId of the calendar event",
|
"The Exchange ItemId of the calendar event",
|
||||||
),
|
),
|
||||||
targetFolder: z.string().default("/tmp/attachments").describe(
|
targetFolder: z.string().default("/tmp/attachments").describe(
|
||||||
"Local directory to save files. Default: /tmp/attachments",
|
"Local directory to save files",
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
|
outputSchema: z.object({
|
||||||
|
success: z.boolean(),
|
||||||
|
downloaded: z.array(z.object({
|
||||||
|
name: z.string(),
|
||||||
|
path: z.string(),
|
||||||
|
size: z.number(),
|
||||||
|
contentType: z.string(),
|
||||||
|
})),
|
||||||
|
count: z.number(),
|
||||||
|
errors: z.array(z.object({
|
||||||
|
name: z.string(),
|
||||||
|
error: z.string(),
|
||||||
|
})).optional(),
|
||||||
|
message: z.string().optional(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
async ({ itemId, targetFolder }) => {
|
async ({ itemId, targetFolder }) => {
|
||||||
try {
|
try {
|
||||||
@@ -117,6 +154,12 @@ export function registerCalendarTools(server: McpServer): void {
|
|||||||
message: "No downloadable file attachments.",
|
message: "No downloadable file attachments.",
|
||||||
}),
|
}),
|
||||||
}],
|
}],
|
||||||
|
structuredContent: {
|
||||||
|
success: true,
|
||||||
|
downloaded: [],
|
||||||
|
count: 0,
|
||||||
|
message: "No downloadable file attachments.",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,6 +217,9 @@ export function registerCalendarTools(server: McpServer): void {
|
|||||||
...(errors.length ? { errors } : {}),
|
...(errors.length ? { errors } : {}),
|
||||||
}),
|
}),
|
||||||
}],
|
}],
|
||||||
|
structuredContent: errors.length === 0
|
||||||
|
? { success: true, downloaded, count: downloaded.length }
|
||||||
|
: { success: false, downloaded, count: downloaded.length, errors },
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
+97
-43
@@ -12,34 +12,40 @@ export function registerEmailTools(server: McpServer): void {
|
|||||||
server.registerTool(
|
server.registerTool(
|
||||||
"get_emails",
|
"get_emails",
|
||||||
{
|
{
|
||||||
description:
|
description: "Get emails from a mailbox folder."
|
||||||
"Get emails from a mailbox folder. Supports optional text search via query/queryScope. Returns {emails, count} or {itemIds, count} if idsOnly. Defaults: folder=Inbox, limit=10, offset=0, includeBody=false, unreadOnly=false, idsOnly=false, queryScope=all. Note: idsOnly raises the limit to 500.",
|
+ " Supports optional text search via query/queryScope.",
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
folder: z.string().default("Inbox").describe(
|
folder: z.string().default("Inbox").describe(
|
||||||
"Folder name (Inbox, Sent, Drafts, Deleted, Junk, or custom). Supports Russian folder names",
|
"Folder name (Inbox, Sent, Drafts, Deleted, Junk, or custom). Supports Russian folder names",
|
||||||
),
|
),
|
||||||
limit: z.number().default(10).describe(
|
limit: z.number().default(10).describe(
|
||||||
"Maximum number of emails to return. Default: 10. Max 50 (max 500 if idsOnly=true)",
|
"Maximum number of emails to return",
|
||||||
),
|
),
|
||||||
offset: z.number().default(0).describe(
|
offset: z.number().default(0).describe(
|
||||||
"Number of emails to skip for pagination. Default: 0",
|
"Number of emails to skip for pagination",
|
||||||
),
|
),
|
||||||
includeBody: z.boolean().default(false).describe(
|
includeBody: z.boolean().default(false).describe(
|
||||||
"If true, fetches full email body for each email (slower). Default: false",
|
"If true, fetches full email body for each email (slower)",
|
||||||
),
|
),
|
||||||
unreadOnly: z.boolean().default(false).describe(
|
unreadOnly: z.boolean().default(false).describe(
|
||||||
"If true, only return unread emails. Default: false",
|
"If true, only return unread emails",
|
||||||
),
|
|
||||||
idsOnly: z.boolean().default(false).describe(
|
|
||||||
"If true, return only item IDs, dates, and subjects — faster with higher limit of 500. Default: false",
|
|
||||||
),
|
),
|
||||||
query: z.string().optional().describe(
|
query: z.string().optional().describe(
|
||||||
"Optional text to search for within the folder",
|
"Optional text to search for within the folder",
|
||||||
),
|
),
|
||||||
queryScope: z.enum(["all", "subject", "body", "from"]).default("all")
|
queryScope: z.enum(["all", "subject", "body", "from"]).default("all")
|
||||||
.describe(
|
.describe("Search scope: all, subject, body, or from"),
|
||||||
"Scope for text search. Values: all (subject+body), subject, body, from. Only used when query is set. Default: all",
|
}),
|
||||||
),
|
outputSchema: z.object({
|
||||||
|
emails: z.array(z.object({
|
||||||
|
itemId: z.string(),
|
||||||
|
subject: z.string(),
|
||||||
|
from: z.string(),
|
||||||
|
date: z.string(),
|
||||||
|
isRead: z.boolean(),
|
||||||
|
hasAttachments: z.boolean(),
|
||||||
|
})),
|
||||||
|
count: z.number(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
async (
|
async (
|
||||||
@@ -49,19 +55,18 @@ export function registerEmailTools(server: McpServer): void {
|
|||||||
offset,
|
offset,
|
||||||
includeBody,
|
includeBody,
|
||||||
unreadOnly,
|
unreadOnly,
|
||||||
idsOnly,
|
|
||||||
query,
|
query,
|
||||||
queryScope,
|
queryScope,
|
||||||
},
|
},
|
||||||
) => {
|
) => {
|
||||||
try {
|
try {
|
||||||
const client = getClient();
|
const client = getClient();
|
||||||
const maxLimit = idsOnly ? 500 : 50;
|
const maxLimit = 50;
|
||||||
if (limit > maxLimit) limit = maxLimit;
|
if (limit > maxLimit) limit = maxLimit;
|
||||||
offset = Math.max(0, offset);
|
offset = Math.max(0, offset);
|
||||||
|
|
||||||
const folderId = await client.getFolderId(folder);
|
const folderId = await client.getFolderId(folder);
|
||||||
const baseShape = idsOnly ? "IdOnly" : "AllProperties";
|
const baseShape = "AllProperties";
|
||||||
|
|
||||||
if (query && !query.trim()) query = undefined;
|
if (query && !query.trim()) query = undefined;
|
||||||
|
|
||||||
@@ -112,16 +117,16 @@ export function registerEmailTools(server: McpServer): void {
|
|||||||
let restriction = "";
|
let restriction = "";
|
||||||
if (restrictions.length === 1) {
|
if (restrictions.length === 1) {
|
||||||
restriction = `\
|
restriction = `\
|
||||||
<m:Restriction>
|
<m:Restriction>
|
||||||
${restrictions[0]}
|
${restrictions[0]}
|
||||||
</m:Restriction>`;
|
</m:Restriction>`;
|
||||||
} else if (restrictions.length > 1) {
|
} else if (restrictions.length > 1) {
|
||||||
restriction = `\
|
restriction = `\
|
||||||
<m:Restriction>
|
<m:Restriction>
|
||||||
<t:And>
|
<t:And>
|
||||||
${restrictions.join("\n")}
|
${restrictions.join("\n")}
|
||||||
</t:And>
|
</t:And>
|
||||||
</m:Restriction>`;
|
</m:Restriction>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const items = await client.findItems(folderId, {
|
const items = await client.findItems(folderId, {
|
||||||
@@ -131,20 +136,6 @@ export function registerEmailTools(server: McpServer): void {
|
|||||||
restriction,
|
restriction,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (idsOnly) {
|
|
||||||
const result = items.map((item: any) => ({
|
|
||||||
itemId: item.ItemId?.["@_Id"] ?? "",
|
|
||||||
date: item.DateTimeReceived ?? "",
|
|
||||||
subject: item.Subject ?? "",
|
|
||||||
}));
|
|
||||||
return {
|
|
||||||
content: [{
|
|
||||||
type: "text" as const,
|
|
||||||
text: JSON.stringify({ itemIds: result, count: result.length }),
|
|
||||||
}],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const emails = [];
|
const emails = [];
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const email = client.extractEmailSummary(item);
|
const email = client.extractEmailSummary(item);
|
||||||
@@ -165,6 +156,7 @@ export function registerEmailTools(server: McpServer): void {
|
|||||||
type: "text" as const,
|
type: "text" as const,
|
||||||
text: JSON.stringify({ emails, count: emails.length }),
|
text: JSON.stringify({ emails, count: emails.length }),
|
||||||
}],
|
}],
|
||||||
|
structuredContent: { emails, count: emails.length },
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return {
|
return {
|
||||||
@@ -181,12 +173,32 @@ export function registerEmailTools(server: McpServer): void {
|
|||||||
"get_email",
|
"get_email",
|
||||||
{
|
{
|
||||||
description:
|
description:
|
||||||
"Get a single email with full body and details by Exchange ItemId. Returns the full Email object (subject, from, to, cc, body, date, attachments, etc.).",
|
"Get a single email with full body and details by Exchange ItemId.",
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
itemId: z.string().describe(
|
itemId: z.string().describe(
|
||||||
"The Exchange ItemId of the email to retrieve",
|
"The Exchange ItemId of the email to retrieve",
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
|
outputSchema: z.object({
|
||||||
|
itemId: z.string(),
|
||||||
|
subject: z.string(),
|
||||||
|
from: z.string(),
|
||||||
|
fromName: z.string().optional(),
|
||||||
|
to: z.array(z.string()).optional(),
|
||||||
|
cc: z.array(z.string()).optional(),
|
||||||
|
date: z.string(),
|
||||||
|
body: z.string().optional(),
|
||||||
|
isRead: z.boolean().optional(),
|
||||||
|
hasAttachments: z.boolean().optional(),
|
||||||
|
hasLinks: z.boolean().optional(),
|
||||||
|
attachments: z.array(z.object({
|
||||||
|
name: z.string(),
|
||||||
|
size: z.number(),
|
||||||
|
contentType: z.string(),
|
||||||
|
attachmentId: z.string().optional(),
|
||||||
|
isInline: z.boolean().optional(),
|
||||||
|
})).optional(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
async ({ itemId }) => {
|
async ({ itemId }) => {
|
||||||
try {
|
try {
|
||||||
@@ -196,6 +208,7 @@ export function registerEmailTools(server: McpServer): void {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text" as const, text: JSON.stringify(email) }],
|
content: [{ type: "text" as const, text: JSON.stringify(email) }],
|
||||||
|
structuredContent: { emails: [email] },
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return {
|
return {
|
||||||
@@ -212,15 +225,20 @@ export function registerEmailTools(server: McpServer): void {
|
|||||||
"mark_email_read",
|
"mark_email_read",
|
||||||
{
|
{
|
||||||
description:
|
description:
|
||||||
"Mark one or more emails as read or unread by their Exchange ItemIds. Returns {success, message} or {error}.",
|
"Mark one or more emails as read or unread by their Exchange ItemIds.",
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
itemIds: z.array(z.string()).describe(
|
itemIds: z.array(z.string()).describe(
|
||||||
"List of Exchange ItemIds to update",
|
"List of Exchange ItemIds to update",
|
||||||
),
|
),
|
||||||
isRead: z.boolean().default(true).describe(
|
isRead: z.boolean().default(true).describe(
|
||||||
"True to mark as read, false to mark as unread. Default: true",
|
"True to mark as read, false to mark as unread",
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
|
outputSchema: z.object({
|
||||||
|
success: z.boolean(),
|
||||||
|
message: z.string().optional(),
|
||||||
|
error: z.string().optional(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
async ({ itemIds, isRead }) => {
|
async ({ itemIds, isRead }) => {
|
||||||
try {
|
try {
|
||||||
@@ -239,6 +257,10 @@ export function registerEmailTools(server: McpServer): void {
|
|||||||
type: "text" as const,
|
type: "text" as const,
|
||||||
text: JSON.stringify({ error: errors.join("; ") }),
|
text: JSON.stringify({ error: errors.join("; ") }),
|
||||||
}],
|
}],
|
||||||
|
structuredContent: {
|
||||||
|
success: false,
|
||||||
|
error: errors.join("; "),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -251,6 +273,10 @@ export function registerEmailTools(server: McpServer): void {
|
|||||||
message: `Marked ${itemIds.length} email(s) as ${status}.`,
|
message: `Marked ${itemIds.length} email(s) as ${status}.`,
|
||||||
}),
|
}),
|
||||||
}],
|
}],
|
||||||
|
structuredContent: {
|
||||||
|
success: true,
|
||||||
|
message: `Marked ${itemIds.length} email(s) as ${status}.`,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return {
|
return {
|
||||||
@@ -258,6 +284,10 @@ export function registerEmailTools(server: McpServer): void {
|
|||||||
type: "text" as const,
|
type: "text" as const,
|
||||||
text: JSON.stringify({ error: error.message ?? String(error) }),
|
text: JSON.stringify({ error: error.message ?? String(error) }),
|
||||||
}],
|
}],
|
||||||
|
structuredContent: {
|
||||||
|
success: false,
|
||||||
|
error: error.message ?? String(error),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -266,14 +296,29 @@ export function registerEmailTools(server: McpServer): void {
|
|||||||
server.registerTool(
|
server.registerTool(
|
||||||
"download_attachments",
|
"download_attachments",
|
||||||
{
|
{
|
||||||
description:
|
description: "Download all file attachments from an email to disk."
|
||||||
"Download all file attachments from an email to disk. Inline (embedded) attachments are skipped — only standalone file attachments are downloaded. Returns {success, downloaded, count} or {success, downloaded, count, errors}.",
|
+ " Inline (embedded) attachments are skipped — only standalone file attachments are downloaded.",
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
itemId: z.string().describe("The Exchange ItemId of the email"),
|
itemId: z.string().describe("The Exchange ItemId of the email"),
|
||||||
targetFolder: z.string().default("/tmp/attachments").describe(
|
targetFolder: z.string().default("/tmp/attachments").describe(
|
||||||
"Local directory to save files. Default: /tmp/attachments",
|
"Local directory to save files",
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
|
outputSchema: z.object({
|
||||||
|
success: z.boolean(),
|
||||||
|
downloaded: z.array(z.object({
|
||||||
|
name: z.string(),
|
||||||
|
path: z.string(),
|
||||||
|
size: z.number(),
|
||||||
|
contentType: z.string(),
|
||||||
|
})),
|
||||||
|
count: z.number(),
|
||||||
|
errors: z.array(z.object({
|
||||||
|
name: z.string(),
|
||||||
|
error: z.string(),
|
||||||
|
})).optional(),
|
||||||
|
message: z.string().optional(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
async ({ itemId, targetFolder }) => {
|
async ({ itemId, targetFolder }) => {
|
||||||
try {
|
try {
|
||||||
@@ -297,6 +342,12 @@ export function registerEmailTools(server: McpServer): void {
|
|||||||
message: "No downloadable file attachments.",
|
message: "No downloadable file attachments.",
|
||||||
}),
|
}),
|
||||||
}],
|
}],
|
||||||
|
structuredContent: {
|
||||||
|
success: true,
|
||||||
|
downloaded: [],
|
||||||
|
count: 0,
|
||||||
|
message: "No downloadable file attachments.",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -354,6 +405,9 @@ export function registerEmailTools(server: McpServer): void {
|
|||||||
...(errors.length ? { errors } : {}),
|
...(errors.length ? { errors } : {}),
|
||||||
}),
|
}),
|
||||||
}],
|
}],
|
||||||
|
structuredContent: errors.length === 0
|
||||||
|
? { success: true, downloaded, count: downloaded.length }
|
||||||
|
: { success: false, downloaded, count: downloaded.length, errors },
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
+13
-4
@@ -6,16 +6,24 @@ export function registerFolderTools(server: McpServer): void {
|
|||||||
server.registerTool(
|
server.registerTool(
|
||||||
"get_folders",
|
"get_folders",
|
||||||
{
|
{
|
||||||
description:
|
description: "List mailbox folders from the Exchange mailbox.",
|
||||||
"List mailbox folders from the Exchange mailbox. Returns a list of Folder objects (name, id, totalCount, unreadCount, childFolderCount).",
|
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
parentFolderId: z.string().default("msgfolderroot").describe(
|
parentFolderId: z.string().default("msgfolderroot").describe(
|
||||||
"Parent folder to list children of. Supports distinguished names (e.g. inbox, calendar). Default: msgfolderroot",
|
"Parent folder to list children of. Supports distinguished names (e.g. inbox, calendar)",
|
||||||
),
|
),
|
||||||
recursive: z.boolean().default(false).describe(
|
recursive: z.boolean().default(false).describe(
|
||||||
"If true, recursively traverse all subfolders. Default: false",
|
"If true, recursively traverse all subfolders",
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
|
outputSchema: z.object({
|
||||||
|
folders: z.array(z.object({
|
||||||
|
name: z.string(),
|
||||||
|
id: z.string(),
|
||||||
|
totalCount: z.number(),
|
||||||
|
unreadCount: z.number(),
|
||||||
|
childFolderCount: z.number(),
|
||||||
|
})),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
async ({ parentFolderId, recursive }) => {
|
async ({ parentFolderId, recursive }) => {
|
||||||
try {
|
try {
|
||||||
@@ -24,6 +32,7 @@ export function registerFolderTools(server: McpServer): void {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text" as const, text: JSON.stringify(folders) }],
|
content: [{ type: "text" as const, text: JSON.stringify(folders) }],
|
||||||
|
structuredContent: { folders },
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
+23
-2
@@ -7,17 +7,37 @@ export function registerPeopleTools(server: McpServer): void {
|
|||||||
"find_person",
|
"find_person",
|
||||||
{
|
{
|
||||||
description:
|
description:
|
||||||
"Search for people in the corporate directory (Active Directory) by name, email, or keyword. Returns a list of Person objects with name, email, jobTitle, department, company, office, phones, manager, directReports, and more.",
|
"Search for people in the corporate directory (Active Directory) by name, email, or keyword.",
|
||||||
inputSchema: z.object({
|
inputSchema: z.object({
|
||||||
query: z.string().describe(
|
query: z.string().describe(
|
||||||
"Name, email address, or keyword to search for",
|
"Name, email address, or keyword to search for",
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
|
outputSchema: z.object({
|
||||||
|
name: z.string(),
|
||||||
|
email: z.string(),
|
||||||
|
mailboxType: z.string(),
|
||||||
|
firstName: z.string(),
|
||||||
|
lastName: z.string(),
|
||||||
|
jobTitle: z.string(),
|
||||||
|
department: z.string(),
|
||||||
|
company: z.string(),
|
||||||
|
office: z.string(),
|
||||||
|
alias: z.string(),
|
||||||
|
manager: z.string(),
|
||||||
|
managerEmail: z.string(),
|
||||||
|
phones: z.record(z.string(), z.string()),
|
||||||
|
address: z.string(),
|
||||||
|
directReports: z.array(z.object({
|
||||||
|
name: z.string(),
|
||||||
|
email: z.string(),
|
||||||
|
})),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
async ({ query }) => {
|
async ({ query }) => {
|
||||||
try {
|
try {
|
||||||
const client = new EwsClient(loadConfig());
|
const client = new EwsClient(loadConfig());
|
||||||
const resolutions = await client.resolveNames(query, true);
|
const resolutions = await client.resolveNames(query);
|
||||||
|
|
||||||
const people = resolutions.map((r: any) => {
|
const people = resolutions.map((r: any) => {
|
||||||
const mailbox = r.Mailbox ?? {};
|
const mailbox = r.Mailbox ?? {};
|
||||||
@@ -85,6 +105,7 @@ export function registerPeopleTools(server: McpServer): void {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
content: [{ type: "text" as const, text: JSON.stringify(people) }],
|
content: [{ type: "text" as const, text: JSON.stringify(people) }],
|
||||||
|
structuredContent: { people },
|
||||||
};
|
};
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user