@littlebearapps/outlook-assistant 3.3.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/.env.example +22 -0
- package/LICENSE +21 -0
- package/README.md +422 -0
- package/advanced/index.js +652 -0
- package/auth/index.js +32 -0
- package/auth/oauth-server.js +233 -0
- package/auth/token-manager.js +105 -0
- package/auth/token-storage.js +359 -0
- package/auth/tools.js +159 -0
- package/calendar/accept.js +72 -0
- package/calendar/cancel.js +72 -0
- package/calendar/create.js +115 -0
- package/calendar/decline.js +72 -0
- package/calendar/delete.js +67 -0
- package/calendar/index.js +130 -0
- package/calendar/list.js +108 -0
- package/categories/index.js +955 -0
- package/config.js +95 -0
- package/contacts/index.js +754 -0
- package/email/attachments.js +365 -0
- package/email/conversations.js +666 -0
- package/email/delta.js +210 -0
- package/email/export.js +572 -0
- package/email/folder-utils.js +192 -0
- package/email/headers.js +344 -0
- package/email/index.js +537 -0
- package/email/list.js +136 -0
- package/email/mark-as-read.js +114 -0
- package/email/mime.js +286 -0
- package/email/read.js +161 -0
- package/email/search.js +628 -0
- package/email/send.js +169 -0
- package/folder/create.js +137 -0
- package/folder/delete.js +108 -0
- package/folder/index.js +112 -0
- package/folder/list.js +289 -0
- package/folder/move.js +186 -0
- package/folder/stats.js +322 -0
- package/index.js +162 -0
- package/llms.txt +76 -0
- package/outlook-auth-server.js +384 -0
- package/package.json +97 -0
- package/rules/create.js +273 -0
- package/rules/index.js +276 -0
- package/rules/list.js +216 -0
- package/settings/index.js +678 -0
- package/utils/field-presets.js +311 -0
- package/utils/graph-api.js +268 -0
- package/utils/mock-data.js +154 -0
- package/utils/odata-helpers.js +33 -0
- package/utils/response-formatter.js +457 -0
- package/utils/safety.js +123 -0
package/email/delta.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delta sync functionality for email
|
|
3
|
+
*
|
|
4
|
+
* Provides incremental sync using Microsoft Graph delta queries.
|
|
5
|
+
* Returns only changes since last sync, with deltaToken for next call.
|
|
6
|
+
*/
|
|
7
|
+
const { callGraphAPI } = require('../utils/graph-api');
|
|
8
|
+
const { ensureAuthenticated } = require('../auth');
|
|
9
|
+
const { formatEmailList, VERBOSITY } = require('../utils/response-formatter');
|
|
10
|
+
const { getEmailFields } = require('../utils/field-presets');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* List emails delta handler - incremental sync
|
|
14
|
+
* @param {object} args - Tool arguments
|
|
15
|
+
* @param {string} [args.folder] - Folder to sync (default: inbox)
|
|
16
|
+
* @param {string} [args.deltaToken] - Token from previous delta call (omit for initial sync)
|
|
17
|
+
* @param {number} [args.maxResults] - Max results per page (default: 100)
|
|
18
|
+
* @param {string} [args.outputVerbosity] - Output detail level
|
|
19
|
+
* @returns {object} - MCP response with emails, deltaToken, and change summary
|
|
20
|
+
*/
|
|
21
|
+
async function handleListEmailsDelta(args) {
|
|
22
|
+
const folder = args.folder || 'inbox';
|
|
23
|
+
const deltaToken = args.deltaToken;
|
|
24
|
+
const maxResults = Math.min(args.maxResults || 100, 200);
|
|
25
|
+
const verbosity = args.outputVerbosity || 'standard';
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const accessToken = await ensureAuthenticated();
|
|
29
|
+
|
|
30
|
+
// Build delta query
|
|
31
|
+
let endpoint;
|
|
32
|
+
let queryParams = {};
|
|
33
|
+
|
|
34
|
+
if (deltaToken) {
|
|
35
|
+
// Continue from previous sync - use deltaLink directly
|
|
36
|
+
endpoint = deltaToken;
|
|
37
|
+
} else {
|
|
38
|
+
// Initial sync - start fresh
|
|
39
|
+
endpoint = `me/mailFolders/${folder}/messages/delta`;
|
|
40
|
+
queryParams = {
|
|
41
|
+
$select: getEmailFields('delta'),
|
|
42
|
+
$top: maxResults.toString(),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Fetch delta results
|
|
47
|
+
const response = await callGraphAPI(
|
|
48
|
+
accessToken,
|
|
49
|
+
'GET',
|
|
50
|
+
endpoint,
|
|
51
|
+
null,
|
|
52
|
+
deltaToken ? {} : queryParams
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// Process results
|
|
56
|
+
const emails = response.value || [];
|
|
57
|
+
const nextLink = response['@odata.nextLink'];
|
|
58
|
+
const deltaLink = response['@odata.deltaLink'];
|
|
59
|
+
|
|
60
|
+
// Categorise changes
|
|
61
|
+
const changesSummary = {
|
|
62
|
+
created: 0,
|
|
63
|
+
updated: 0,
|
|
64
|
+
deleted: 0,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const processedEmails = [];
|
|
68
|
+
for (const email of emails) {
|
|
69
|
+
if (email['@removed']) {
|
|
70
|
+
// Deleted email
|
|
71
|
+
changesSummary.deleted++;
|
|
72
|
+
processedEmails.push({
|
|
73
|
+
id: email.id,
|
|
74
|
+
removed: true,
|
|
75
|
+
reason: email['@removed'].reason || 'deleted',
|
|
76
|
+
});
|
|
77
|
+
} else if (deltaToken) {
|
|
78
|
+
// With deltaToken, all non-removed items are changes
|
|
79
|
+
// We can't reliably distinguish created vs updated via delta
|
|
80
|
+
changesSummary.updated++;
|
|
81
|
+
processedEmails.push(email);
|
|
82
|
+
} else {
|
|
83
|
+
// Initial sync - all items are "created" for our purposes
|
|
84
|
+
changesSummary.created++;
|
|
85
|
+
processedEmails.push(email);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Build response
|
|
90
|
+
const isInitialSync = !deltaToken;
|
|
91
|
+
const hasMoreChanges = !!nextLink;
|
|
92
|
+
const newDeltaToken = deltaLink || nextLink;
|
|
93
|
+
|
|
94
|
+
// Format output based on verbosity
|
|
95
|
+
let resultText;
|
|
96
|
+
if (verbosity === 'minimal') {
|
|
97
|
+
resultText = `## Delta Sync\n\n`;
|
|
98
|
+
resultText += `| Metric | Value |\n`;
|
|
99
|
+
resultText += `|--------|-------|\n`;
|
|
100
|
+
resultText += `| Items | ${processedEmails.length} |\n`;
|
|
101
|
+
resultText += `| Type | ${isInitialSync ? 'Initial' : 'Incremental'} |\n`;
|
|
102
|
+
resultText += `| More | ${hasMoreChanges ? 'Yes' : 'No'} |\n`;
|
|
103
|
+
if (newDeltaToken) {
|
|
104
|
+
resultText += `\n**Delta Token** (save for next call):\n\`\`\`\n${newDeltaToken}\n\`\`\`\n`;
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
resultText = `## Delta Sync ${isInitialSync ? '(Initial)' : '(Incremental)'}\n\n`;
|
|
108
|
+
|
|
109
|
+
// Changes summary
|
|
110
|
+
resultText += `### Changes Summary\n\n`;
|
|
111
|
+
resultText += `| Change Type | Count |\n`;
|
|
112
|
+
resultText += `|-------------|-------|\n`;
|
|
113
|
+
if (!isInitialSync) {
|
|
114
|
+
resultText += `| Created/Updated | ${changesSummary.updated} |\n`;
|
|
115
|
+
resultText += `| Deleted | ${changesSummary.deleted} |\n`;
|
|
116
|
+
} else {
|
|
117
|
+
resultText += `| Synced | ${changesSummary.created} |\n`;
|
|
118
|
+
}
|
|
119
|
+
resultText += `| Total | ${processedEmails.length} |\n`;
|
|
120
|
+
|
|
121
|
+
// Emails list (non-deleted only)
|
|
122
|
+
const activeEmails = processedEmails.filter((e) => !e.removed);
|
|
123
|
+
if (activeEmails.length > 0) {
|
|
124
|
+
resultText += `\n### Emails\n\n`;
|
|
125
|
+
resultText += formatEmailList(
|
|
126
|
+
activeEmails,
|
|
127
|
+
verbosity === 'full' ? VERBOSITY.FULL : VERBOSITY.STANDARD
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Deleted items
|
|
132
|
+
const deletedEmails = processedEmails.filter((e) => e.removed);
|
|
133
|
+
if (deletedEmails.length > 0) {
|
|
134
|
+
resultText += `\n### Deleted Items (${deletedEmails.length})\n\n`;
|
|
135
|
+
for (const del of deletedEmails.slice(0, 10)) {
|
|
136
|
+
resultText += `- ID: \`${del.id}\`\n`;
|
|
137
|
+
}
|
|
138
|
+
if (deletedEmails.length > 10) {
|
|
139
|
+
resultText += `- ... and ${deletedEmails.length - 10} more\n`;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Pagination info
|
|
144
|
+
if (hasMoreChanges) {
|
|
145
|
+
resultText += `\n### More Changes Available\n`;
|
|
146
|
+
resultText += `Use the deltaToken below to fetch next page.\n`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Delta token
|
|
150
|
+
if (newDeltaToken) {
|
|
151
|
+
resultText += `\n### Delta Token\n`;
|
|
152
|
+
resultText += `**Save this token for next sync call:**\n\`\`\`\n${newDeltaToken}\n\`\`\`\n`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
content: [
|
|
158
|
+
{
|
|
159
|
+
type: 'text',
|
|
160
|
+
text: resultText,
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
_meta: {
|
|
164
|
+
syncType: isInitialSync ? 'initial' : 'incremental',
|
|
165
|
+
folder: folder,
|
|
166
|
+
itemCount: processedEmails.length,
|
|
167
|
+
hasMoreChanges: hasMoreChanges,
|
|
168
|
+
changesSummary: changesSummary,
|
|
169
|
+
deltaToken: newDeltaToken,
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
} catch (error) {
|
|
173
|
+
if (error.message === 'Authentication required') {
|
|
174
|
+
return {
|
|
175
|
+
content: [
|
|
176
|
+
{
|
|
177
|
+
type: 'text',
|
|
178
|
+
text: "Authentication required. Please use the 'authenticate' tool first.",
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Handle expired delta token
|
|
185
|
+
if (
|
|
186
|
+
error.message.includes('410') ||
|
|
187
|
+
error.message.includes('resyncRequired')
|
|
188
|
+
) {
|
|
189
|
+
return {
|
|
190
|
+
content: [
|
|
191
|
+
{
|
|
192
|
+
type: 'text',
|
|
193
|
+
text: `## Delta Token Expired\n\nThe provided delta token has expired. Please start a new initial sync by calling without a deltaToken.\n\n**Error:** ${error.message}`,
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return {
|
|
200
|
+
content: [
|
|
201
|
+
{
|
|
202
|
+
type: 'text',
|
|
203
|
+
text: `Delta sync failed: ${error.message}`,
|
|
204
|
+
},
|
|
205
|
+
],
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
module.exports = handleListEmailsDelta;
|