outlook-cli 1.2.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/CLI.md +89 -0
- package/LICENSE +21 -0
- package/QUICKSTART.md +104 -0
- package/README.md +994 -0
- package/auth/index.js +36 -0
- package/auth/oauth-server.js +139 -0
- package/auth/token-manager.js +199 -0
- package/auth/token-storage.js +282 -0
- package/auth/tools.js +127 -0
- package/calendar/accept.js +64 -0
- package/calendar/cancel.js +64 -0
- package/calendar/create.js +69 -0
- package/calendar/decline.js +64 -0
- package/calendar/delete.js +59 -0
- package/calendar/index.js +123 -0
- package/calendar/list.js +77 -0
- package/cli.js +1349 -0
- package/config.js +84 -0
- package/docs/PROJECT-STRUCTURE.md +52 -0
- package/docs/PUBLISHING.md +86 -0
- package/docs/REFERENCE.md +679 -0
- package/email/folder-utils.js +171 -0
- package/email/index.js +157 -0
- package/email/list.js +89 -0
- package/email/mark-as-read.js +101 -0
- package/email/read.js +128 -0
- package/email/search.js +282 -0
- package/email/send.js +120 -0
- package/folder/create.js +124 -0
- package/folder/index.js +78 -0
- package/folder/list.js +264 -0
- package/folder/move.js +163 -0
- package/index.js +136 -0
- package/outlook-auth-server.js +305 -0
- package/package.json +76 -0
- package/rules/create.js +248 -0
- package/rules/index.js +177 -0
- package/rules/list.js +202 -0
- package/tool-registry.js +54 -0
- package/utils/graph-api.js +120 -0
- package/utils/mock-data.js +145 -0
- package/utils/odata-helpers.js +40 -0
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
const http = require('http');
|
|
3
|
+
const url = require('url');
|
|
4
|
+
const querystring = require('querystring');
|
|
5
|
+
const https = require('https');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const config = require('./config');
|
|
9
|
+
|
|
10
|
+
// Load environment variables from .env file
|
|
11
|
+
require('dotenv').config();
|
|
12
|
+
|
|
13
|
+
// Log to console
|
|
14
|
+
console.log('Starting Outlook Authentication Server');
|
|
15
|
+
|
|
16
|
+
// Authentication configuration
|
|
17
|
+
const AUTH_CONFIG = {
|
|
18
|
+
clientId: config.AUTH_CONFIG.clientId,
|
|
19
|
+
clientSecret: config.AUTH_CONFIG.clientSecret,
|
|
20
|
+
redirectUri: config.AUTH_CONFIG.redirectUri,
|
|
21
|
+
scopes: Array.from(new Set(['offline_access', ...config.AUTH_CONFIG.scopes, 'Contacts.Read'])),
|
|
22
|
+
tokenStorePath: config.AUTH_CONFIG.tokenStorePath || path.join(process.env.HOME || process.env.USERPROFILE, '.outlook-mcp-tokens.json')
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Create HTTP server
|
|
26
|
+
const server = http.createServer((req, res) => {
|
|
27
|
+
const parsedUrl = url.parse(req.url, true);
|
|
28
|
+
const pathname = parsedUrl.pathname;
|
|
29
|
+
|
|
30
|
+
console.log(`Request received: ${pathname}`);
|
|
31
|
+
|
|
32
|
+
if (pathname === '/auth/callback') {
|
|
33
|
+
const query = parsedUrl.query;
|
|
34
|
+
|
|
35
|
+
if (query.error) {
|
|
36
|
+
console.error(`Authentication error: ${query.error} - ${query.error_description}`);
|
|
37
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
38
|
+
res.end(`
|
|
39
|
+
<html>
|
|
40
|
+
<head>
|
|
41
|
+
<title>Authentication Error</title>
|
|
42
|
+
<style>
|
|
43
|
+
body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
44
|
+
h1 { color: #d9534f; }
|
|
45
|
+
.error-box { background-color: #f8d7da; border: 1px solid #f5c6cb; padding: 15px; border-radius: 4px; }
|
|
46
|
+
</style>
|
|
47
|
+
</head>
|
|
48
|
+
<body>
|
|
49
|
+
<h1>Authentication Error</h1>
|
|
50
|
+
<div class="error-box">
|
|
51
|
+
<p><strong>Error:</strong> ${query.error}</p>
|
|
52
|
+
<p><strong>Description:</strong> ${query.error_description || 'No description provided'}</p>
|
|
53
|
+
</div>
|
|
54
|
+
<p>Please close this window and try again.</p>
|
|
55
|
+
</body>
|
|
56
|
+
</html>
|
|
57
|
+
`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (query.code) {
|
|
62
|
+
console.log('Authorization code received, exchanging for tokens...');
|
|
63
|
+
|
|
64
|
+
// Exchange code for tokens
|
|
65
|
+
exchangeCodeForTokens(query.code)
|
|
66
|
+
.then((tokens) => {
|
|
67
|
+
console.log('Token exchange successful');
|
|
68
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
69
|
+
res.end(`
|
|
70
|
+
<html>
|
|
71
|
+
<head>
|
|
72
|
+
<title>Authentication Successful</title>
|
|
73
|
+
<style>
|
|
74
|
+
body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
75
|
+
h1 { color: #5cb85c; }
|
|
76
|
+
.success-box { background-color: #d4edda; border: 1px solid #c3e6cb; padding: 15px; border-radius: 4px; }
|
|
77
|
+
</style>
|
|
78
|
+
</head>
|
|
79
|
+
<body>
|
|
80
|
+
<h1>Authentication Successful!</h1>
|
|
81
|
+
<div class="success-box">
|
|
82
|
+
<p>You have successfully authenticated with Microsoft Graph API.</p>
|
|
83
|
+
<p>The access token has been saved securely.</p>
|
|
84
|
+
</div>
|
|
85
|
+
<p>You can now close this window and return to Claude.</p>
|
|
86
|
+
</body>
|
|
87
|
+
</html>
|
|
88
|
+
`);
|
|
89
|
+
})
|
|
90
|
+
.catch((error) => {
|
|
91
|
+
console.error(`Token exchange error: ${error.message}`);
|
|
92
|
+
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
93
|
+
res.end(`
|
|
94
|
+
<html>
|
|
95
|
+
<head>
|
|
96
|
+
<title>Token Exchange Error</title>
|
|
97
|
+
<style>
|
|
98
|
+
body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
99
|
+
h1 { color: #d9534f; }
|
|
100
|
+
.error-box { background-color: #f8d7da; border: 1px solid #f5c6cb; padding: 15px; border-radius: 4px; }
|
|
101
|
+
</style>
|
|
102
|
+
</head>
|
|
103
|
+
<body>
|
|
104
|
+
<h1>Token Exchange Error</h1>
|
|
105
|
+
<div class="error-box">
|
|
106
|
+
<p>${error.message}</p>
|
|
107
|
+
</div>
|
|
108
|
+
<p>Please close this window and try again.</p>
|
|
109
|
+
</body>
|
|
110
|
+
</html>
|
|
111
|
+
`);
|
|
112
|
+
});
|
|
113
|
+
} else {
|
|
114
|
+
console.error('No authorization code provided');
|
|
115
|
+
res.writeHead(400, { 'Content-Type': 'text/html' });
|
|
116
|
+
res.end(`
|
|
117
|
+
<html>
|
|
118
|
+
<head>
|
|
119
|
+
<title>Missing Authorization Code</title>
|
|
120
|
+
<style>
|
|
121
|
+
body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
122
|
+
h1 { color: #d9534f; }
|
|
123
|
+
.error-box { background-color: #f8d7da; border: 1px solid #f5c6cb; padding: 15px; border-radius: 4px; }
|
|
124
|
+
</style>
|
|
125
|
+
</head>
|
|
126
|
+
<body>
|
|
127
|
+
<h1>Missing Authorization Code</h1>
|
|
128
|
+
<div class="error-box">
|
|
129
|
+
<p>No authorization code was provided in the callback.</p>
|
|
130
|
+
</div>
|
|
131
|
+
<p>Please close this window and try again.</p>
|
|
132
|
+
</body>
|
|
133
|
+
</html>
|
|
134
|
+
`);
|
|
135
|
+
}
|
|
136
|
+
} else if (pathname === '/auth') {
|
|
137
|
+
// Handle the /auth route - redirect to Microsoft's OAuth authorization endpoint
|
|
138
|
+
console.log('Auth request received, redirecting to Microsoft login...');
|
|
139
|
+
|
|
140
|
+
// Verify credentials are set
|
|
141
|
+
if (!AUTH_CONFIG.clientId || !AUTH_CONFIG.clientSecret) {
|
|
142
|
+
res.writeHead(500, { 'Content-Type': 'text/html' });
|
|
143
|
+
res.end(`
|
|
144
|
+
<html>
|
|
145
|
+
<head>
|
|
146
|
+
<title>Configuration Error</title>
|
|
147
|
+
<style>
|
|
148
|
+
body { font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
149
|
+
h1 { color: #d9534f; }
|
|
150
|
+
.error-box { background-color: #f8d7da; border: 1px solid #f5c6cb; padding: 15px; border-radius: 4px; }
|
|
151
|
+
code { background: #f4f4f4; padding: 2px 4px; border-radius: 4px; }
|
|
152
|
+
</style>
|
|
153
|
+
</head>
|
|
154
|
+
<body>
|
|
155
|
+
<h1>Configuration Error</h1>
|
|
156
|
+
<div class="error-box">
|
|
157
|
+
<p>Microsoft Graph API credentials are not set. Please set the following environment variables:</p>
|
|
158
|
+
<ul>
|
|
159
|
+
<li><code>OUTLOOK_CLIENT_ID</code> or <code>MS_CLIENT_ID</code></li>
|
|
160
|
+
<li><code>OUTLOOK_CLIENT_SECRET</code> or <code>MS_CLIENT_SECRET</code></li>
|
|
161
|
+
</ul>
|
|
162
|
+
</div>
|
|
163
|
+
</body>
|
|
164
|
+
</html>
|
|
165
|
+
`);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Get client_id from query parameters or use the default
|
|
170
|
+
const query = parsedUrl.query;
|
|
171
|
+
const clientId = query.client_id || AUTH_CONFIG.clientId;
|
|
172
|
+
|
|
173
|
+
// Build the authorization URL
|
|
174
|
+
const authParams = {
|
|
175
|
+
client_id: clientId,
|
|
176
|
+
response_type: 'code',
|
|
177
|
+
redirect_uri: AUTH_CONFIG.redirectUri,
|
|
178
|
+
scope: AUTH_CONFIG.scopes.join(' '),
|
|
179
|
+
response_mode: 'query',
|
|
180
|
+
state: Date.now().toString() // Simple state parameter for security
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const authUrl = `https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?${querystring.stringify(authParams)}`;
|
|
184
|
+
console.log(`Redirecting to: ${authUrl}`);
|
|
185
|
+
|
|
186
|
+
// Redirect to Microsoft's login page
|
|
187
|
+
res.writeHead(302, { 'Location': authUrl });
|
|
188
|
+
res.end();
|
|
189
|
+
} else if (pathname === '/') {
|
|
190
|
+
// Root path - provide instructions
|
|
191
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
192
|
+
res.end(`
|
|
193
|
+
<html>
|
|
194
|
+
<head>
|
|
195
|
+
<title>Outlook Authentication Server</title>
|
|
196
|
+
<style>
|
|
197
|
+
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
|
|
198
|
+
h1 { color: #0078d4; }
|
|
199
|
+
.info-box { background-color: #e7f6fd; border: 1px solid #b3e0ff; padding: 15px; border-radius: 4px; }
|
|
200
|
+
code { background: #f4f4f4; padding: 2px 4px; border-radius: 4px; }
|
|
201
|
+
</style>
|
|
202
|
+
</head>
|
|
203
|
+
<body>
|
|
204
|
+
<h1>Outlook Authentication Server</h1>
|
|
205
|
+
<div class="info-box">
|
|
206
|
+
<p>This server is running to handle Microsoft Graph API authentication callbacks.</p>
|
|
207
|
+
<p>Don't navigate here directly. Instead, use the <code>authenticate</code> tool in Claude to start the authentication process.</p>
|
|
208
|
+
<p>Make sure you've set <code>OUTLOOK_CLIENT_ID</code>/<code>OUTLOOK_CLIENT_SECRET</code> or <code>MS_CLIENT_ID</code>/<code>MS_CLIENT_SECRET</code>.</p>
|
|
209
|
+
</div>
|
|
210
|
+
<p>Server is running at http://localhost:3333</p>
|
|
211
|
+
</body>
|
|
212
|
+
</html>
|
|
213
|
+
`);
|
|
214
|
+
} else {
|
|
215
|
+
// Not found
|
|
216
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
217
|
+
res.end('Not Found');
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
function exchangeCodeForTokens(code) {
|
|
222
|
+
return new Promise((resolve, reject) => {
|
|
223
|
+
const postData = querystring.stringify({
|
|
224
|
+
client_id: AUTH_CONFIG.clientId,
|
|
225
|
+
client_secret: AUTH_CONFIG.clientSecret,
|
|
226
|
+
code: code,
|
|
227
|
+
redirect_uri: AUTH_CONFIG.redirectUri,
|
|
228
|
+
grant_type: 'authorization_code',
|
|
229
|
+
scope: AUTH_CONFIG.scopes.join(' ')
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const options = {
|
|
233
|
+
hostname: 'login.microsoftonline.com',
|
|
234
|
+
path: '/consumers/oauth2/v2.0/token',
|
|
235
|
+
method: 'POST',
|
|
236
|
+
headers: {
|
|
237
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
238
|
+
'Content-Length': Buffer.byteLength(postData)
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const req = https.request(options, (res) => {
|
|
243
|
+
let data = '';
|
|
244
|
+
|
|
245
|
+
res.on('data', (chunk) => {
|
|
246
|
+
data += chunk;
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
res.on('end', () => {
|
|
250
|
+
if (res.statusCode >= 200 && res.statusCode < 300) {
|
|
251
|
+
try {
|
|
252
|
+
const tokenResponse = JSON.parse(data);
|
|
253
|
+
|
|
254
|
+
// Calculate expiration time (current time + expires_in seconds)
|
|
255
|
+
const expiresAt = Date.now() + (tokenResponse.expires_in * 1000);
|
|
256
|
+
|
|
257
|
+
// Add expires_at for easier expiration checking
|
|
258
|
+
tokenResponse.expires_at = expiresAt;
|
|
259
|
+
|
|
260
|
+
// Save tokens to file
|
|
261
|
+
fs.writeFileSync(AUTH_CONFIG.tokenStorePath, JSON.stringify(tokenResponse, null, 2), 'utf8');
|
|
262
|
+
console.log(`Tokens saved to ${AUTH_CONFIG.tokenStorePath}`);
|
|
263
|
+
|
|
264
|
+
resolve(tokenResponse);
|
|
265
|
+
} catch (error) {
|
|
266
|
+
reject(new Error(`Error parsing token response: ${error.message}`));
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
reject(new Error(`Token exchange failed with status ${res.statusCode}: ${data}`));
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
req.on('error', (error) => {
|
|
275
|
+
reject(error);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
req.write(postData);
|
|
279
|
+
req.end();
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Start server
|
|
284
|
+
const PORT = 3333;
|
|
285
|
+
server.listen(PORT, () => {
|
|
286
|
+
console.log(`Authentication server running at http://localhost:${PORT}`);
|
|
287
|
+
console.log(`Waiting for authentication callback at ${AUTH_CONFIG.redirectUri}`);
|
|
288
|
+
console.log(`Token will be stored at: ${AUTH_CONFIG.tokenStorePath}`);
|
|
289
|
+
|
|
290
|
+
if (!AUTH_CONFIG.clientId || !AUTH_CONFIG.clientSecret) {
|
|
291
|
+
console.log('\n⚠️ WARNING: Microsoft Graph API credentials are not set.');
|
|
292
|
+
console.log(' Please set OUTLOOK_CLIENT_ID/OUTLOOK_CLIENT_SECRET or MS_CLIENT_ID/MS_CLIENT_SECRET.');
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// Handle termination
|
|
297
|
+
process.on('SIGINT', () => {
|
|
298
|
+
console.log('Authentication server shutting down');
|
|
299
|
+
process.exit(0);
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
process.on('SIGTERM', () => {
|
|
303
|
+
console.log('Authentication server shutting down');
|
|
304
|
+
process.exit(0);
|
|
305
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "outlook-cli",
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Production-ready Outlook CLI with optional MCP server mode powered by Microsoft Graph API",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"outlook-cli",
|
|
7
|
+
"cli",
|
|
8
|
+
"claude",
|
|
9
|
+
"outlook",
|
|
10
|
+
"mcp",
|
|
11
|
+
"microsoft-graph",
|
|
12
|
+
"email"
|
|
13
|
+
],
|
|
14
|
+
"homepage": "https://github.com/selvin-paul-raj/outlook-cli#readme",
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/selvin-paul-raj/outlook-cli/issues"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/selvin-paul-raj/outlook-cli.git"
|
|
21
|
+
},
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"author": "Selvin PaulRaj K",
|
|
24
|
+
"type": "commonjs",
|
|
25
|
+
"main": "index.js",
|
|
26
|
+
"bin": {
|
|
27
|
+
"outlook-cli": "cli.js"
|
|
28
|
+
},
|
|
29
|
+
"directories": {
|
|
30
|
+
"doc": "docs",
|
|
31
|
+
"test": "test"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"auth",
|
|
35
|
+
"calendar",
|
|
36
|
+
"email",
|
|
37
|
+
"folder",
|
|
38
|
+
"rules",
|
|
39
|
+
"utils",
|
|
40
|
+
"cli.js",
|
|
41
|
+
"index.js",
|
|
42
|
+
"tool-registry.js",
|
|
43
|
+
"config.js",
|
|
44
|
+
"outlook-auth-server.js",
|
|
45
|
+
"README.md",
|
|
46
|
+
"CLI.md",
|
|
47
|
+
"QUICKSTART.md",
|
|
48
|
+
"docs",
|
|
49
|
+
"LICENSE"
|
|
50
|
+
],
|
|
51
|
+
"scripts": {
|
|
52
|
+
"start": "node cli.js",
|
|
53
|
+
"mcp-server": "node index.js",
|
|
54
|
+
"cli": "node cli.js",
|
|
55
|
+
"doctor": "node cli.js doctor",
|
|
56
|
+
"auth-server": "node outlook-auth-server.js",
|
|
57
|
+
"test-mode": "node -e \"process.env.USE_TEST_MODE='true'; require('./index.js');\"",
|
|
58
|
+
"inspect": "npx @modelcontextprotocol/inspector node index.js",
|
|
59
|
+
"test": "jest",
|
|
60
|
+
"prepublishOnly": "npm test && npm pack --dry-run"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"@modelcontextprotocol/sdk": "^1.1.0",
|
|
64
|
+
"chalk": "^4.1.2",
|
|
65
|
+
"dotenv": "^16.5.0"
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@modelcontextprotocol/inspector": "^0.10.2",
|
|
69
|
+
"jest": "^29.7.0",
|
|
70
|
+
"supertest": "^7.0.0"
|
|
71
|
+
},
|
|
72
|
+
"engines": {
|
|
73
|
+
"node": ">=18.0.0"
|
|
74
|
+
},
|
|
75
|
+
"preferGlobal": true
|
|
76
|
+
}
|
package/rules/create.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create rule functionality
|
|
3
|
+
*/
|
|
4
|
+
const { callGraphAPI } = require('../utils/graph-api');
|
|
5
|
+
const { ensureAuthenticated } = require('../auth');
|
|
6
|
+
const { getFolderIdByName } = require('../email/folder-utils');
|
|
7
|
+
const { getInboxRules } = require('./list');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create rule handler
|
|
11
|
+
* @param {object} args - Tool arguments
|
|
12
|
+
* @returns {object} - MCP response
|
|
13
|
+
*/
|
|
14
|
+
async function handleCreateRule(args) {
|
|
15
|
+
const {
|
|
16
|
+
name,
|
|
17
|
+
fromAddresses,
|
|
18
|
+
containsSubject,
|
|
19
|
+
hasAttachments,
|
|
20
|
+
moveToFolder,
|
|
21
|
+
markAsRead,
|
|
22
|
+
isEnabled = true,
|
|
23
|
+
sequence
|
|
24
|
+
} = args;
|
|
25
|
+
|
|
26
|
+
// Add validation for sequence parameter
|
|
27
|
+
if (sequence !== undefined && (isNaN(sequence) || sequence < 1)) {
|
|
28
|
+
return {
|
|
29
|
+
content: [{
|
|
30
|
+
type: "text",
|
|
31
|
+
text: "Sequence must be a positive number greater than zero."
|
|
32
|
+
}]
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!name) {
|
|
37
|
+
return {
|
|
38
|
+
content: [{
|
|
39
|
+
type: "text",
|
|
40
|
+
text: "Rule name is required."
|
|
41
|
+
}]
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Validate that at least one condition or action is specified
|
|
46
|
+
const hasCondition = fromAddresses || containsSubject || hasAttachments === true;
|
|
47
|
+
const hasAction = moveToFolder || markAsRead === true;
|
|
48
|
+
|
|
49
|
+
if (!hasCondition) {
|
|
50
|
+
return {
|
|
51
|
+
content: [{
|
|
52
|
+
type: "text",
|
|
53
|
+
text: "At least one condition is required. Specify fromAddresses, containsSubject, or hasAttachments."
|
|
54
|
+
}]
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!hasAction) {
|
|
59
|
+
return {
|
|
60
|
+
content: [{
|
|
61
|
+
type: "text",
|
|
62
|
+
text: "At least one action is required. Specify moveToFolder or markAsRead."
|
|
63
|
+
}]
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
// Get access token
|
|
69
|
+
const accessToken = await ensureAuthenticated();
|
|
70
|
+
|
|
71
|
+
// Create rule
|
|
72
|
+
const result = await createInboxRule(accessToken, {
|
|
73
|
+
name,
|
|
74
|
+
fromAddresses,
|
|
75
|
+
containsSubject,
|
|
76
|
+
hasAttachments,
|
|
77
|
+
moveToFolder,
|
|
78
|
+
markAsRead,
|
|
79
|
+
isEnabled,
|
|
80
|
+
sequence
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
let responseText = result.message;
|
|
84
|
+
|
|
85
|
+
// Add a tip about sequence if it wasn't provided
|
|
86
|
+
if (!sequence && !result.error) {
|
|
87
|
+
responseText += "\n\nTip: You can specify a 'sequence' parameter when creating rules to control their execution order. Lower sequence numbers run first.";
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
content: [{
|
|
92
|
+
type: "text",
|
|
93
|
+
text: responseText
|
|
94
|
+
}]
|
|
95
|
+
};
|
|
96
|
+
} catch (error) {
|
|
97
|
+
if (error.message === 'Authentication required') {
|
|
98
|
+
return {
|
|
99
|
+
content: [{
|
|
100
|
+
type: "text",
|
|
101
|
+
text: "Authentication required. Please use the 'authenticate' tool first."
|
|
102
|
+
}]
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
content: [{
|
|
108
|
+
type: "text",
|
|
109
|
+
text: `Error creating rule: ${error.message}`
|
|
110
|
+
}]
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Create a new inbox rule
|
|
117
|
+
* @param {string} accessToken - Access token
|
|
118
|
+
* @param {object} ruleOptions - Rule creation options
|
|
119
|
+
* @returns {Promise<object>} - Result object with status and message
|
|
120
|
+
*/
|
|
121
|
+
async function createInboxRule(accessToken, ruleOptions) {
|
|
122
|
+
try {
|
|
123
|
+
const {
|
|
124
|
+
name,
|
|
125
|
+
fromAddresses,
|
|
126
|
+
containsSubject,
|
|
127
|
+
hasAttachments,
|
|
128
|
+
moveToFolder,
|
|
129
|
+
markAsRead,
|
|
130
|
+
isEnabled,
|
|
131
|
+
sequence
|
|
132
|
+
} = ruleOptions;
|
|
133
|
+
|
|
134
|
+
// Get existing rules to determine sequence if not provided
|
|
135
|
+
let ruleSequence = sequence;
|
|
136
|
+
if (!ruleSequence) {
|
|
137
|
+
try {
|
|
138
|
+
// Default to 100 if we can't get existing rules
|
|
139
|
+
ruleSequence = 100;
|
|
140
|
+
|
|
141
|
+
// Get existing rules to find highest sequence
|
|
142
|
+
const existingRules = await getInboxRules(accessToken);
|
|
143
|
+
if (existingRules && existingRules.length > 0) {
|
|
144
|
+
// Find the highest sequence
|
|
145
|
+
const highestSequence = Math.max(...existingRules.map(r => r.sequence || 0));
|
|
146
|
+
// Set new rule sequence to be higher
|
|
147
|
+
ruleSequence = Math.max(highestSequence + 1, 100);
|
|
148
|
+
console.error(`Auto-generated sequence: ${ruleSequence} (based on highest existing: ${highestSequence})`);
|
|
149
|
+
}
|
|
150
|
+
} catch (sequenceError) {
|
|
151
|
+
console.error(`Error determining rule sequence: ${sequenceError.message}`);
|
|
152
|
+
// Fall back to default value
|
|
153
|
+
ruleSequence = 100;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.error(`Using rule sequence: ${ruleSequence}`);
|
|
158
|
+
|
|
159
|
+
// Make sure sequence is a positive integer
|
|
160
|
+
ruleSequence = Math.max(1, Math.floor(ruleSequence));
|
|
161
|
+
|
|
162
|
+
// Build rule object with sequence
|
|
163
|
+
const rule = {
|
|
164
|
+
displayName: name,
|
|
165
|
+
isEnabled: isEnabled === true,
|
|
166
|
+
sequence: ruleSequence,
|
|
167
|
+
conditions: {},
|
|
168
|
+
actions: {}
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
// Add conditions
|
|
172
|
+
if (fromAddresses) {
|
|
173
|
+
// Parse email addresses
|
|
174
|
+
const emailAddresses = fromAddresses.split(',')
|
|
175
|
+
.map(email => email.trim())
|
|
176
|
+
.filter(email => email)
|
|
177
|
+
.map(email => ({
|
|
178
|
+
emailAddress: {
|
|
179
|
+
address: email
|
|
180
|
+
}
|
|
181
|
+
}));
|
|
182
|
+
|
|
183
|
+
if (emailAddresses.length > 0) {
|
|
184
|
+
rule.conditions.fromAddresses = emailAddresses;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (containsSubject) {
|
|
189
|
+
rule.conditions.subjectContains = [containsSubject];
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (hasAttachments === true) {
|
|
193
|
+
rule.conditions.hasAttachment = true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Add actions
|
|
197
|
+
if (moveToFolder) {
|
|
198
|
+
// Get folder ID
|
|
199
|
+
try {
|
|
200
|
+
const folderId = await getFolderIdByName(accessToken, moveToFolder);
|
|
201
|
+
if (!folderId) {
|
|
202
|
+
return {
|
|
203
|
+
success: false,
|
|
204
|
+
message: `Target folder "${moveToFolder}" not found. Please specify a valid folder name.`
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
rule.actions.moveToFolder = folderId;
|
|
209
|
+
} catch (folderError) {
|
|
210
|
+
console.error(`Error resolving folder "${moveToFolder}": ${folderError.message}`);
|
|
211
|
+
return {
|
|
212
|
+
success: false,
|
|
213
|
+
message: `Error resolving folder "${moveToFolder}": ${folderError.message}`
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (markAsRead === true) {
|
|
219
|
+
rule.actions.markAsRead = true;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Create the rule
|
|
223
|
+
const response = await callGraphAPI(
|
|
224
|
+
accessToken,
|
|
225
|
+
'POST',
|
|
226
|
+
'me/mailFolders/inbox/messageRules',
|
|
227
|
+
rule
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
if (response && response.id) {
|
|
231
|
+
return {
|
|
232
|
+
success: true,
|
|
233
|
+
message: `Successfully created rule "${name}" with sequence ${ruleSequence}.`,
|
|
234
|
+
ruleId: response.id
|
|
235
|
+
};
|
|
236
|
+
} else {
|
|
237
|
+
return {
|
|
238
|
+
success: false,
|
|
239
|
+
message: "Failed to create rule. The server didn't return a rule ID."
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
} catch (error) {
|
|
243
|
+
console.error(`Error creating rule: ${error.message}`);
|
|
244
|
+
throw error;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
module.exports = handleCreateRule;
|