ms365-mcp-server 1.1.16 → 1.1.17
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/dist/index.js +1122 -9
- package/dist/utils/batch-performance-monitor.js +106 -0
- package/dist/utils/batch-test-scenarios.js +277 -0
- package/dist/utils/context-aware-search.js +499 -0
- package/dist/utils/cross-reference-detector.js +352 -0
- package/dist/utils/document-workflow.js +433 -0
- package/dist/utils/enhanced-fuzzy-search.js +514 -0
- package/dist/utils/error-handler.js +337 -0
- package/dist/utils/intelligence-engine.js +71 -0
- package/dist/utils/intelligent-cache.js +379 -0
- package/dist/utils/large-mailbox-search.js +599 -0
- package/dist/utils/ms365-operations.js +657 -181
- package/dist/utils/performance-monitor.js +395 -0
- package/dist/utils/proactive-intelligence.js +390 -0
- package/dist/utils/rate-limiter.js +284 -0
- package/dist/utils/search-batch-pipeline.js +222 -0
- package/dist/utils/thread-reconstruction.js +700 -0
- package/package.json +1 -1
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { logger } from './api.js';
|
|
2
|
+
export class CircuitBreaker {
|
|
3
|
+
constructor(failureThreshold = 5, recoveryTimeout = 30000, // 30 seconds
|
|
4
|
+
successThreshold = 2) {
|
|
5
|
+
this.failureThreshold = failureThreshold;
|
|
6
|
+
this.recoveryTimeout = recoveryTimeout;
|
|
7
|
+
this.successThreshold = successThreshold;
|
|
8
|
+
this.failures = 0;
|
|
9
|
+
this.lastFailureTime = 0;
|
|
10
|
+
this.state = 'closed';
|
|
11
|
+
}
|
|
12
|
+
async execute(operation, operationName) {
|
|
13
|
+
if (this.state === 'open') {
|
|
14
|
+
if (Date.now() - this.lastFailureTime > this.recoveryTimeout) {
|
|
15
|
+
this.state = 'half-open';
|
|
16
|
+
logger.log(`🔄 Circuit breaker entering half-open state for ${operationName}`);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
throw new Error(`Circuit breaker is open for ${operationName}. Retry after ${Math.ceil((this.recoveryTimeout - (Date.now() - this.lastFailureTime)) / 1000)} seconds.`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const result = await operation();
|
|
24
|
+
this.onSuccess(operationName);
|
|
25
|
+
return result;
|
|
26
|
+
}
|
|
27
|
+
catch (error) {
|
|
28
|
+
this.onFailure(operationName);
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
onSuccess(operationName) {
|
|
33
|
+
if (this.state === 'half-open') {
|
|
34
|
+
this.failures = 0;
|
|
35
|
+
this.state = 'closed';
|
|
36
|
+
logger.log(`✅ Circuit breaker closed for ${operationName}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
onFailure(operationName) {
|
|
40
|
+
this.failures++;
|
|
41
|
+
this.lastFailureTime = Date.now();
|
|
42
|
+
if (this.failures >= this.failureThreshold) {
|
|
43
|
+
this.state = 'open';
|
|
44
|
+
logger.log(`🚨 Circuit breaker opened for ${operationName} after ${this.failures} failures`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
getState() {
|
|
48
|
+
return {
|
|
49
|
+
state: this.state,
|
|
50
|
+
failures: this.failures,
|
|
51
|
+
lastFailureTime: this.lastFailureTime
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export class EnhancedErrorHandler {
|
|
56
|
+
/**
|
|
57
|
+
* Handle errors with comprehensive analysis and user-friendly messages
|
|
58
|
+
*/
|
|
59
|
+
static handleError(error, context) {
|
|
60
|
+
const errorType = this.classifyError(error);
|
|
61
|
+
const userMessage = this.generateUserMessage(error, errorType, context);
|
|
62
|
+
const suggestedActions = this.generateSuggestedActions(error, errorType, context);
|
|
63
|
+
const recoverable = this.isRecoverable(error, errorType);
|
|
64
|
+
// Track error frequency
|
|
65
|
+
this.trackError(context.operation, errorType);
|
|
66
|
+
// Log structured error information
|
|
67
|
+
logger.error(`🚨 Error in ${context.operation}:`, {
|
|
68
|
+
type: errorType,
|
|
69
|
+
message: error.message,
|
|
70
|
+
context,
|
|
71
|
+
stack: error.stack,
|
|
72
|
+
recoverable
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
success: false,
|
|
76
|
+
error: {
|
|
77
|
+
type: errorType,
|
|
78
|
+
message: error.message,
|
|
79
|
+
userMessage,
|
|
80
|
+
recoverable,
|
|
81
|
+
retryAfter: this.getRetryAfter(error, errorType),
|
|
82
|
+
suggestedActions
|
|
83
|
+
},
|
|
84
|
+
context
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Execute operation with circuit breaker protection
|
|
89
|
+
*/
|
|
90
|
+
static async executeWithCircuitBreaker(operation, operationName, context) {
|
|
91
|
+
try {
|
|
92
|
+
const circuitBreaker = this.getCircuitBreaker(operationName);
|
|
93
|
+
const result = await circuitBreaker.execute(operation, operationName);
|
|
94
|
+
return { success: true, data: result };
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
return this.handleError(error, context);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Execute operation with automatic retry and exponential backoff
|
|
102
|
+
*/
|
|
103
|
+
static async executeWithRetry(operation, context, maxRetries = 3, baseDelay = 1000) {
|
|
104
|
+
let lastError;
|
|
105
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
106
|
+
try {
|
|
107
|
+
const result = await operation();
|
|
108
|
+
return { success: true, data: result };
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
lastError = error;
|
|
112
|
+
const errorType = this.classifyError(error);
|
|
113
|
+
// Don't retry on non-recoverable errors
|
|
114
|
+
if (!this.isRecoverable(error, errorType)) {
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
if (attempt === maxRetries) {
|
|
118
|
+
break;
|
|
119
|
+
}
|
|
120
|
+
// Exponential backoff with jitter
|
|
121
|
+
const delay = baseDelay * Math.pow(2, attempt - 1) + Math.random() * 1000;
|
|
122
|
+
logger.log(`🔄 Retrying ${context.operation} (attempt ${attempt + 1}/${maxRetries}) after ${delay}ms`);
|
|
123
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return this.handleError(lastError, context);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Classify error into specific types
|
|
130
|
+
*/
|
|
131
|
+
static classifyError(error) {
|
|
132
|
+
if (!error)
|
|
133
|
+
return 'unknown';
|
|
134
|
+
// Authentication errors
|
|
135
|
+
if (error.code === 'InvalidAuthenticationToken' ||
|
|
136
|
+
error.code === 'Unauthorized' ||
|
|
137
|
+
error.status === 401 ||
|
|
138
|
+
error.message?.includes('authentication') ||
|
|
139
|
+
error.message?.includes('token')) {
|
|
140
|
+
return 'authentication';
|
|
141
|
+
}
|
|
142
|
+
// Rate limiting errors
|
|
143
|
+
if (error.status === 429 ||
|
|
144
|
+
error.code === 'TooManyRequests' ||
|
|
145
|
+
error.message?.includes('throttle') ||
|
|
146
|
+
error.message?.includes('rate limit')) {
|
|
147
|
+
return 'rate_limit';
|
|
148
|
+
}
|
|
149
|
+
// Permission errors
|
|
150
|
+
if (error.status === 403 ||
|
|
151
|
+
error.code === 'Forbidden' ||
|
|
152
|
+
error.message?.includes('permission') ||
|
|
153
|
+
error.message?.includes('access denied')) {
|
|
154
|
+
return 'permission';
|
|
155
|
+
}
|
|
156
|
+
// Network errors
|
|
157
|
+
if (error.code === 'ECONNRESET' ||
|
|
158
|
+
error.code === 'ENOTFOUND' ||
|
|
159
|
+
error.code === 'ETIMEDOUT' ||
|
|
160
|
+
error.message?.includes('network') ||
|
|
161
|
+
error.message?.includes('timeout')) {
|
|
162
|
+
return 'network';
|
|
163
|
+
}
|
|
164
|
+
// Validation errors
|
|
165
|
+
if (error.status === 400 ||
|
|
166
|
+
error.code === 'InvalidRequest' ||
|
|
167
|
+
error.message?.includes('validation') ||
|
|
168
|
+
error.message?.includes('invalid')) {
|
|
169
|
+
return 'validation';
|
|
170
|
+
}
|
|
171
|
+
// Server errors
|
|
172
|
+
if (error.status >= 500 ||
|
|
173
|
+
error.code === 'InternalServerError' ||
|
|
174
|
+
error.message?.includes('server error')) {
|
|
175
|
+
return 'server';
|
|
176
|
+
}
|
|
177
|
+
return 'unknown';
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Generate user-friendly error messages
|
|
181
|
+
*/
|
|
182
|
+
static generateUserMessage(error, errorType, context) {
|
|
183
|
+
const operation = context.operation;
|
|
184
|
+
switch (errorType) {
|
|
185
|
+
case 'authentication':
|
|
186
|
+
return `🔐 Authentication expired. Please re-authenticate with Microsoft 365 to continue using ${operation}.`;
|
|
187
|
+
case 'rate_limit':
|
|
188
|
+
return `⏸️ Microsoft 365 is temporarily limiting requests. Please wait a moment and try again.`;
|
|
189
|
+
case 'permission':
|
|
190
|
+
return `🚫 You don't have permission to perform this action. Please check your Microsoft 365 permissions.`;
|
|
191
|
+
case 'network':
|
|
192
|
+
return `🌐 Network connection issue. Please check your internet connection and try again.`;
|
|
193
|
+
case 'validation':
|
|
194
|
+
return `📋 Invalid input provided. Please check your parameters and try again.`;
|
|
195
|
+
case 'server':
|
|
196
|
+
return `🔧 Microsoft 365 service is temporarily unavailable. Please try again in a few minutes.`;
|
|
197
|
+
default:
|
|
198
|
+
return `❌ An unexpected error occurred while ${operation}. Please try again or contact support if the problem persists.`;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Generate suggested actions for error recovery
|
|
203
|
+
*/
|
|
204
|
+
static generateSuggestedActions(error, errorType, context) {
|
|
205
|
+
const actions = [];
|
|
206
|
+
switch (errorType) {
|
|
207
|
+
case 'authentication':
|
|
208
|
+
actions.push('Use the "authenticate" tool to re-authenticate with Microsoft 365');
|
|
209
|
+
actions.push('Check if your Microsoft 365 account is still active');
|
|
210
|
+
break;
|
|
211
|
+
case 'rate_limit':
|
|
212
|
+
actions.push('Wait 1-2 minutes before trying again');
|
|
213
|
+
actions.push('Use more specific search terms to reduce API calls');
|
|
214
|
+
actions.push('Consider using the AI assistant with large mailbox strategy');
|
|
215
|
+
break;
|
|
216
|
+
case 'permission':
|
|
217
|
+
actions.push('Contact your Microsoft 365 administrator');
|
|
218
|
+
actions.push('Check if you have the required permissions for this operation');
|
|
219
|
+
break;
|
|
220
|
+
case 'network':
|
|
221
|
+
actions.push('Check your internet connection');
|
|
222
|
+
actions.push('Try again in a few seconds');
|
|
223
|
+
actions.push('If using VPN, try disconnecting and reconnecting');
|
|
224
|
+
break;
|
|
225
|
+
case 'validation':
|
|
226
|
+
actions.push('Check all required parameters are provided');
|
|
227
|
+
actions.push('Verify date formats (use YYYY-MM-DD)');
|
|
228
|
+
actions.push('Ensure email addresses are valid');
|
|
229
|
+
break;
|
|
230
|
+
case 'server':
|
|
231
|
+
actions.push('Wait 2-3 minutes and try again');
|
|
232
|
+
actions.push('Check Microsoft 365 service status');
|
|
233
|
+
actions.push('Try a simpler operation first');
|
|
234
|
+
break;
|
|
235
|
+
default:
|
|
236
|
+
actions.push('Try the operation again');
|
|
237
|
+
actions.push('If the problem persists, restart the MCP server');
|
|
238
|
+
actions.push('Check the debug logs for more details');
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
return actions;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Determine if error is recoverable
|
|
245
|
+
*/
|
|
246
|
+
static isRecoverable(error, errorType) {
|
|
247
|
+
switch (errorType) {
|
|
248
|
+
case 'authentication':
|
|
249
|
+
case 'permission':
|
|
250
|
+
case 'validation':
|
|
251
|
+
return false; // These require user intervention
|
|
252
|
+
case 'rate_limit':
|
|
253
|
+
case 'network':
|
|
254
|
+
case 'server':
|
|
255
|
+
return true; // These can be retried
|
|
256
|
+
default:
|
|
257
|
+
return true; // Default to recoverable
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Get retry delay from error
|
|
262
|
+
*/
|
|
263
|
+
static getRetryAfter(error, errorType) {
|
|
264
|
+
if (errorType === 'rate_limit') {
|
|
265
|
+
// Check for Retry-After header
|
|
266
|
+
if (error.headers?.['retry-after']) {
|
|
267
|
+
return parseInt(error.headers['retry-after']) * 1000;
|
|
268
|
+
}
|
|
269
|
+
return 60000; // Default 60 seconds for rate limits
|
|
270
|
+
}
|
|
271
|
+
if (errorType === 'server') {
|
|
272
|
+
return 30000; // 30 seconds for server errors
|
|
273
|
+
}
|
|
274
|
+
return undefined;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Get or create circuit breaker for operation
|
|
278
|
+
*/
|
|
279
|
+
static getCircuitBreaker(operationName) {
|
|
280
|
+
if (!this.circuitBreakers.has(operationName)) {
|
|
281
|
+
this.circuitBreakers.set(operationName, new CircuitBreaker());
|
|
282
|
+
}
|
|
283
|
+
return this.circuitBreakers.get(operationName);
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Track error frequency
|
|
287
|
+
*/
|
|
288
|
+
static trackError(operation, errorType) {
|
|
289
|
+
const key = `${operation}:${errorType}`;
|
|
290
|
+
const now = Date.now();
|
|
291
|
+
const hourMs = 60 * 60 * 1000;
|
|
292
|
+
if (!this.errorCounts.has(key)) {
|
|
293
|
+
this.errorCounts.set(key, { count: 0, lastReset: now });
|
|
294
|
+
}
|
|
295
|
+
const errorData = this.errorCounts.get(key);
|
|
296
|
+
// Reset counter if it's been more than an hour
|
|
297
|
+
if (now - errorData.lastReset > hourMs) {
|
|
298
|
+
errorData.count = 0;
|
|
299
|
+
errorData.lastReset = now;
|
|
300
|
+
}
|
|
301
|
+
errorData.count++;
|
|
302
|
+
// Log warning if error rate is high
|
|
303
|
+
if (errorData.count > 10) {
|
|
304
|
+
logger.error(`⚠️ High error rate detected for ${operation}:${errorType} (${errorData.count} errors in the last hour)`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Get error statistics
|
|
309
|
+
*/
|
|
310
|
+
static getErrorStats() {
|
|
311
|
+
return Object.fromEntries(this.errorCounts);
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Get circuit breaker status
|
|
315
|
+
*/
|
|
316
|
+
static getCircuitBreakerStatus() {
|
|
317
|
+
const status = {};
|
|
318
|
+
for (const [name, breaker] of this.circuitBreakers) {
|
|
319
|
+
status[name] = breaker.getState();
|
|
320
|
+
}
|
|
321
|
+
return status;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
EnhancedErrorHandler.circuitBreakers = new Map();
|
|
325
|
+
EnhancedErrorHandler.errorCounts = new Map();
|
|
326
|
+
// Helper function to wrap results
|
|
327
|
+
export function wrapResult(data) {
|
|
328
|
+
return { success: true, data };
|
|
329
|
+
}
|
|
330
|
+
// Helper function to check if result is success
|
|
331
|
+
export function isSuccess(result) {
|
|
332
|
+
return result.success;
|
|
333
|
+
}
|
|
334
|
+
// Helper function to check if result is error
|
|
335
|
+
export function isError(result) {
|
|
336
|
+
return !result.success;
|
|
337
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { CrossReferenceDetector } from './cross-reference-detector.js';
|
|
2
|
+
import { EnhancedFuzzySearch } from './enhanced-fuzzy-search.js';
|
|
3
|
+
import { ThreadReconstruction } from './thread-reconstruction.js';
|
|
4
|
+
import { logger } from './api.js';
|
|
5
|
+
export class IntelligenceEngine {
|
|
6
|
+
constructor(ms365Operations) {
|
|
7
|
+
this.ms365Operations = ms365Operations;
|
|
8
|
+
this.crossRefDetector = new CrossReferenceDetector(ms365Operations);
|
|
9
|
+
this.fuzzySearch = new EnhancedFuzzySearch(ms365Operations);
|
|
10
|
+
this.threadReconstruction = new ThreadReconstruction(ms365Operations);
|
|
11
|
+
}
|
|
12
|
+
async intelligentSearch(query, emails, options = {}) {
|
|
13
|
+
const startTime = Date.now();
|
|
14
|
+
logger.log(`🧠 Starting intelligent search for: "${query}"`);
|
|
15
|
+
const result = {
|
|
16
|
+
originalQuery: query,
|
|
17
|
+
results: [],
|
|
18
|
+
crossReferences: new Map(),
|
|
19
|
+
fuzzyMatches: [],
|
|
20
|
+
threads: new Map(),
|
|
21
|
+
insights: {
|
|
22
|
+
totalEmails: emails.length,
|
|
23
|
+
highPriorityEmails: 0,
|
|
24
|
+
threadCount: 0,
|
|
25
|
+
averageConfidence: 0,
|
|
26
|
+
processingTime: 0
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
// 1. Enhanced fuzzy search
|
|
30
|
+
if (options.enableFuzzySearch !== false) {
|
|
31
|
+
result.fuzzyMatches = await this.fuzzySearch.naturalLanguageSearch(query, emails);
|
|
32
|
+
result.results = result.fuzzyMatches.map(match => match.email);
|
|
33
|
+
}
|
|
34
|
+
// 2. Cross-reference detection
|
|
35
|
+
if (options.enableCrossReference !== false && result.results.length > 0) {
|
|
36
|
+
result.crossReferences = await this.crossRefDetector.findAllCrossReferences(result.results);
|
|
37
|
+
}
|
|
38
|
+
// 3. Thread reconstruction
|
|
39
|
+
if (options.enableThreadReconstruction !== false && result.results.length > 0) {
|
|
40
|
+
result.threads = await this.threadReconstruction.batchReconstructThreads(result.results);
|
|
41
|
+
}
|
|
42
|
+
// 4. Calculate insights
|
|
43
|
+
result.insights = this.calculateInsights(result, startTime);
|
|
44
|
+
logger.log(`🧠 Intelligent search completed in ${result.insights.processingTime}ms`);
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
calculateInsights(result, startTime) {
|
|
48
|
+
const highPriorityEmails = result.results.filter(email => email.importance === 'high' ||
|
|
49
|
+
this.isGovernmentEmail(email) ||
|
|
50
|
+
this.isUrgentEmail(email)).length;
|
|
51
|
+
const totalConfidence = result.fuzzyMatches.reduce((sum, match) => sum + match.score, 0);
|
|
52
|
+
const averageConfidence = result.fuzzyMatches.length > 0 ? totalConfidence / result.fuzzyMatches.length : 0;
|
|
53
|
+
return {
|
|
54
|
+
totalEmails: result.results.length,
|
|
55
|
+
highPriorityEmails,
|
|
56
|
+
threadCount: result.threads.size,
|
|
57
|
+
averageConfidence,
|
|
58
|
+
processingTime: Date.now() - startTime
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
isGovernmentEmail(email) {
|
|
62
|
+
const govPatterns = ['.gov', 'government', 'federal', 'state', 'municipal', 'irs', 'tax'];
|
|
63
|
+
const text = `${email.from.address} ${email.subject} ${email.bodyPreview}`.toLowerCase();
|
|
64
|
+
return govPatterns.some(pattern => text.includes(pattern));
|
|
65
|
+
}
|
|
66
|
+
isUrgentEmail(email) {
|
|
67
|
+
const urgentPatterns = ['urgent', 'asap', 'immediately', 'deadline', 'due'];
|
|
68
|
+
const text = `${email.subject} ${email.bodyPreview}`.toLowerCase();
|
|
69
|
+
return urgentPatterns.some(pattern => text.includes(pattern));
|
|
70
|
+
}
|
|
71
|
+
}
|