@littlebearapps/outlook-assistant 3.5.2 → 3.7.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/README.md +9 -6
- package/advanced/index.js +5 -5
- package/auth/device-code.js +3 -1
- package/auth/oauth-server.js +4 -4
- package/auth/token-manager.js +2 -2
- package/calendar/list.js +3 -3
- package/categories/index.js +4 -4
- package/contacts/index.js +11 -18
- package/email/conversations.js +19 -17
- package/email/delta.js +1 -1
- package/email/draft.js +482 -0
- package/email/folder-utils.js +1 -1
- package/email/headers.js +10 -10
- package/email/index.js +80 -0
- package/email/list.js +1 -1
- package/email/mime.js +13 -12
- package/email/search.js +3 -2
- package/email/send.js +1 -1
- package/folder/list.js +1 -1
- package/llms.txt +8 -7
- package/package.json +2 -2
- package/rules/create.js +85 -161
- package/rules/index.js +162 -29
- package/rules/list.js +144 -62
- package/rules/rule-builder.js +284 -0
- package/rules/update.js +213 -0
- package/settings/index.js +10 -22
- package/utils/field-presets.js +20 -0
- package/utils/graph-api.js +9 -1
- package/utils/response-formatter.js +8 -6
- package/utils/safety.js +113 -0
package/email/mime.js
CHANGED
|
@@ -30,7 +30,7 @@ function parseMimeHeaders(mimeContent) {
|
|
|
30
30
|
lines.forEach((line) => {
|
|
31
31
|
if (line.startsWith(' ') || line.startsWith('\t')) {
|
|
32
32
|
// Continuation of previous header
|
|
33
|
-
currentValue +=
|
|
33
|
+
currentValue += ` ${line.trim()}`;
|
|
34
34
|
} else {
|
|
35
35
|
// Save previous header
|
|
36
36
|
if (currentHeader) {
|
|
@@ -57,6 +57,12 @@ function parseMimeHeaders(mimeContent) {
|
|
|
57
57
|
};
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
function formatBytes(bytes) {
|
|
61
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
62
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
63
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`;
|
|
64
|
+
}
|
|
65
|
+
|
|
60
66
|
/**
|
|
61
67
|
* Get MIME content size info
|
|
62
68
|
* @param {string} mimeContent - Raw MIME content
|
|
@@ -68,12 +74,7 @@ function getMimeStats(mimeContent) {
|
|
|
68
74
|
|
|
69
75
|
return {
|
|
70
76
|
bytes,
|
|
71
|
-
formattedSize:
|
|
72
|
-
bytes < 1024
|
|
73
|
-
? `${bytes} B`
|
|
74
|
-
: bytes < 1024 * 1024
|
|
75
|
-
? `${(bytes / 1024).toFixed(1)} KB`
|
|
76
|
-
: `${(bytes / (1024 * 1024)).toFixed(2)} MB`,
|
|
77
|
+
formattedSize: formatBytes(bytes),
|
|
77
78
|
lines,
|
|
78
79
|
};
|
|
79
80
|
}
|
|
@@ -184,17 +185,17 @@ async function handleGetMimeContent(args) {
|
|
|
184
185
|
}
|
|
185
186
|
|
|
186
187
|
// Build response
|
|
187
|
-
|
|
188
|
+
const output = [];
|
|
188
189
|
output.push(`# MIME Content${headersOnly ? ' (Headers Only)' : ''}\n`);
|
|
189
190
|
output.push(`**Size**: ${stats.formattedSize}`);
|
|
190
191
|
output.push(`**Lines**: ${stats.lines}`);
|
|
191
192
|
|
|
192
193
|
// Show key headers
|
|
193
|
-
if (parsed.headers
|
|
194
|
-
output.push(`**Subject**: ${parsed.headers
|
|
194
|
+
if (parsed.headers.Subject) {
|
|
195
|
+
output.push(`**Subject**: ${parsed.headers.Subject}`);
|
|
195
196
|
}
|
|
196
|
-
if (parsed.headers
|
|
197
|
-
output.push(`**From**: ${parsed.headers
|
|
197
|
+
if (parsed.headers.From) {
|
|
198
|
+
output.push(`**From**: ${parsed.headers.From}`);
|
|
198
199
|
}
|
|
199
200
|
if (parsed.headers['Content-Type']) {
|
|
200
201
|
output.push(
|
package/email/search.js
CHANGED
|
@@ -166,7 +166,7 @@ async function progressiveSearch(
|
|
|
166
166
|
) {
|
|
167
167
|
// Skip directly to boolean-only filter (step 3) — combined search is redundant
|
|
168
168
|
console.error('Only boolean filters provided, skipping combined search');
|
|
169
|
-
} else
|
|
169
|
+
} else {
|
|
170
170
|
try {
|
|
171
171
|
const params = buildSearchParams(
|
|
172
172
|
searchTerms,
|
|
@@ -193,6 +193,7 @@ async function progressiveSearch(
|
|
|
193
193
|
} catch (error) {
|
|
194
194
|
console.error(`Combined search failed: ${error.message}`);
|
|
195
195
|
}
|
|
196
|
+
}
|
|
196
197
|
|
|
197
198
|
// 2. Try each search term individually, starting with most specific
|
|
198
199
|
const searchPriority = ['from', 'to', 'subject', 'query'];
|
|
@@ -545,7 +546,7 @@ function formatSearchResults(response, folder, verbosity) {
|
|
|
545
546
|
const meta = {
|
|
546
547
|
returned: response.value.length,
|
|
547
548
|
totalAvailable: response['@odata.count'] || null,
|
|
548
|
-
hasMore:
|
|
549
|
+
hasMore: Boolean(response['@odata.nextLink']),
|
|
549
550
|
verbosity: verbosity,
|
|
550
551
|
};
|
|
551
552
|
|
package/email/send.js
CHANGED
|
@@ -138,7 +138,7 @@ async function handleSendEmail(args) {
|
|
|
138
138
|
content: [
|
|
139
139
|
{
|
|
140
140
|
type: 'text',
|
|
141
|
-
text: tipsText
|
|
141
|
+
text: `${tipsText}\n\n---\n\n${preview.content[0].text}`,
|
|
142
142
|
},
|
|
143
143
|
],
|
|
144
144
|
_meta: { mailTips: tipsResult._meta },
|
package/folder/list.js
CHANGED
|
@@ -272,7 +272,7 @@ function formatFolderHierarchy(folders, includeItemCounts) {
|
|
|
272
272
|
// Add children
|
|
273
273
|
const childLines = folder.children
|
|
274
274
|
.map((childId) => formatSubtree(childId, level + 1))
|
|
275
|
-
.filter((
|
|
275
|
+
.filter((childLine) => childLine.length > 0)
|
|
276
276
|
.join('\n');
|
|
277
277
|
|
|
278
278
|
return childLines.length > 0 ? `${line}\n${childLines}` : line;
|
package/llms.txt
CHANGED
|
@@ -11,12 +11,12 @@ Built by [Little Bear Apps](https://littlebearapps.com).
|
|
|
11
11
|
- **License**: MIT
|
|
12
12
|
- **Node.js**: >= 18.0.0
|
|
13
13
|
- **Authentication**: OAuth 2.0 with Microsoft Graph API (requires Azure app registration)
|
|
14
|
-
- **Tools**:
|
|
14
|
+
- **Tools**: 22 tools across 9 modules (reduced from 55 for optimal AI performance)
|
|
15
15
|
|
|
16
16
|
## Why Outlook Assistant?
|
|
17
17
|
|
|
18
18
|
- Read, search, send, and export emails directly from Claude instead of switching apps
|
|
19
|
-
-
|
|
19
|
+
- 8 email tools covering search, conversations, attachments, bulk export, pre-send mail tips, and draft management
|
|
20
20
|
- Manage calendar events, contacts, rules, categories, and mailbox settings in one place
|
|
21
21
|
- Export to multiple formats: MIME/EML, MBOX, Markdown, JSON, HTML
|
|
22
22
|
|
|
@@ -31,9 +31,10 @@ Built by [Little Bear Apps](https://littlebearapps.com).
|
|
|
31
31
|
|
|
32
32
|
## Safety & Token Efficiency
|
|
33
33
|
|
|
34
|
-
- **MCP safety annotations** on all
|
|
34
|
+
- **MCP safety annotations** on all 22 tools — AI clients auto-approve reads and prompt for destructive operations
|
|
35
35
|
- **Send-email protections**: pre-send mail tips, dry-run preview, session rate limiting, recipient allowlist
|
|
36
|
-
- **
|
|
36
|
+
- **Rule protections**: dry-run preview on create/update, rate limiting, recipient allowlist on forward/redirect, no permanent-delete action
|
|
37
|
+
- **Token-optimised**: 22 tools instead of 55 saves ~11,000 tokens per turn (~64% reduction), improving AI accuracy and context efficiency
|
|
37
38
|
- These safeguards reduce risk but are not foolproof — always review actions before approving
|
|
38
39
|
|
|
39
40
|
## Quick Start
|
|
@@ -58,11 +59,11 @@ Requires an Azure app registration with Microsoft Graph delegated permissions. S
|
|
|
58
59
|
## Tool Categories
|
|
59
60
|
|
|
60
61
|
- **Authentication (1 tool)**: `auth` — OAuth flow, status, about
|
|
61
|
-
- **Email (
|
|
62
|
+
- **Email (8 tools)**: `search-emails`, `read-email`, `send-email`, `draft`, `update-email`, `attachments`, `export`, `get-mail-tips`
|
|
62
63
|
- **Calendar (3 tools)**: `list-events`, `create-event`, `manage-event`
|
|
63
64
|
- **Contacts (2 tools)**: `manage-contact`, `search-people`
|
|
64
65
|
- **Folders (1 tool)**: `folders` — list, create, move, stats
|
|
65
|
-
- **Rules (1 tool)**: `manage-rules` — list, create, reorder
|
|
66
|
+
- **Rules (1 tool)**: `manage-rules` — list, create, update, reorder, delete
|
|
66
67
|
- **Categories (3 tools)**: `manage-category`, `apply-category`, `manage-focused-inbox`
|
|
67
68
|
- **Settings (1 tool)**: `mailbox-settings` — get, set auto-replies, set working hours
|
|
68
69
|
- **Advanced (2 tools)**: `access-shared-mailbox`, `find-meeting-rooms`
|
|
@@ -70,7 +71,7 @@ Requires an Azure app registration with Microsoft Graph delegated permissions. S
|
|
|
70
71
|
## Documentation
|
|
71
72
|
|
|
72
73
|
- [README](https://raw.githubusercontent.com/littlebearapps/outlook-assistant/main/README.md): Full documentation including setup, Azure configuration, and usage
|
|
73
|
-
- [Tools Reference](https://raw.githubusercontent.com/littlebearapps/outlook-assistant/main/docs/quickrefs/tools-reference.md): All
|
|
74
|
+
- [Tools Reference](https://raw.githubusercontent.com/littlebearapps/outlook-assistant/main/docs/quickrefs/tools-reference.md): All 22 tools with parameters and safety annotations
|
|
74
75
|
- [CLAUDE.md](https://raw.githubusercontent.com/littlebearapps/outlook-assistant/main/CLAUDE.md): Quick reference for development
|
|
75
76
|
- [CONTRIBUTING](https://raw.githubusercontent.com/littlebearapps/outlook-assistant/main/CONTRIBUTING.md): Contribution guidelines
|
|
76
77
|
- [CHANGELOG](https://raw.githubusercontent.com/littlebearapps/outlook-assistant/main/CHANGELOG.md): Version history
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@littlebearapps/outlook-assistant",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.7.0",
|
|
4
4
|
"mcpName": "io.github.littlebearapps/outlook-assistant",
|
|
5
|
-
"description": "Outlook Assistant — MCP server with
|
|
5
|
+
"description": "Outlook Assistant — MCP server with 22 tools for email, calendar, contacts, and settings via Microsoft Graph API",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"bin": {
|
|
8
8
|
"outlook-assistant": "./index.js"
|
package/rules/create.js
CHANGED
|
@@ -3,8 +3,16 @@
|
|
|
3
3
|
*/
|
|
4
4
|
const { callGraphAPI } = require('../utils/graph-api');
|
|
5
5
|
const { ensureAuthenticated } = require('../auth');
|
|
6
|
-
const {
|
|
6
|
+
const { checkRateLimit } = require('../utils/safety');
|
|
7
|
+
const { formatRuleDryRunPreview } = require('../utils/safety');
|
|
7
8
|
const { getInboxRules } = require('./list');
|
|
9
|
+
const {
|
|
10
|
+
buildConditions,
|
|
11
|
+
buildActions,
|
|
12
|
+
buildExceptions,
|
|
13
|
+
hasAnyCondition,
|
|
14
|
+
hasAnyAction,
|
|
15
|
+
} = require('./rule-builder');
|
|
8
16
|
|
|
9
17
|
/**
|
|
10
18
|
* Create rule handler
|
|
@@ -12,18 +20,13 @@ const { getInboxRules } = require('./list');
|
|
|
12
20
|
* @returns {object} - MCP response
|
|
13
21
|
*/
|
|
14
22
|
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;
|
|
23
|
+
const { name, isEnabled = true, sequence, dryRun } = args;
|
|
25
24
|
|
|
26
|
-
//
|
|
25
|
+
// Rate limit rule creation
|
|
26
|
+
const rateLimitError = checkRateLimit('manage-rules');
|
|
27
|
+
if (rateLimitError) return rateLimitError;
|
|
28
|
+
|
|
29
|
+
// Validate sequence parameter
|
|
27
30
|
if (sequence !== undefined && (isNaN(sequence) || sequence < 1)) {
|
|
28
31
|
return {
|
|
29
32
|
content: [
|
|
@@ -46,202 +49,96 @@ async function handleCreateRule(args) {
|
|
|
46
49
|
};
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
|
|
50
|
-
const hasCondition =
|
|
51
|
-
fromAddresses || containsSubject || hasAttachments === true;
|
|
52
|
-
const hasAction = moveToFolder || markAsRead === true;
|
|
53
|
-
|
|
54
|
-
if (!hasCondition) {
|
|
52
|
+
if (!hasAnyCondition(args)) {
|
|
55
53
|
return {
|
|
56
54
|
content: [
|
|
57
55
|
{
|
|
58
56
|
type: 'text',
|
|
59
|
-
text: 'At least one condition is required.
|
|
57
|
+
text: 'At least one condition is required. Available conditions: fromAddresses, containsSubject, bodyContains, bodyOrSubjectContains, senderContains, recipientContains, sentToAddresses, hasAttachments, importance, sensitivity, sentToMe, sentOnlyToMe, sentCcMe, isAutomaticReply.',
|
|
60
58
|
},
|
|
61
59
|
],
|
|
62
60
|
};
|
|
63
61
|
}
|
|
64
62
|
|
|
65
|
-
if (!
|
|
63
|
+
if (!hasAnyAction(args)) {
|
|
66
64
|
return {
|
|
67
65
|
content: [
|
|
68
66
|
{
|
|
69
67
|
type: 'text',
|
|
70
|
-
text: 'At least one action is required.
|
|
68
|
+
text: 'At least one action is required. Available actions: moveToFolder, copyToFolder, markAsRead, markImportance, forwardTo, redirectTo, assignCategories, stopProcessingRules, deleteMessage.',
|
|
71
69
|
},
|
|
72
70
|
],
|
|
73
71
|
};
|
|
74
72
|
}
|
|
75
73
|
|
|
76
74
|
try {
|
|
77
|
-
// Get access token
|
|
78
75
|
const accessToken = await ensureAuthenticated();
|
|
79
76
|
|
|
80
|
-
//
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
markAsRead,
|
|
88
|
-
isEnabled,
|
|
89
|
-
sequence,
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
let responseText = result.message;
|
|
77
|
+
// Build rule components
|
|
78
|
+
const { conditions, warnings: condWarnings } = buildConditions(args);
|
|
79
|
+
const { actions, warnings: actWarnings } = await buildActions(
|
|
80
|
+
args,
|
|
81
|
+
accessToken
|
|
82
|
+
);
|
|
83
|
+
const { exceptions, warnings: excWarnings } = buildExceptions(args);
|
|
93
84
|
|
|
94
|
-
|
|
95
|
-
if (!sequence && !result.error) {
|
|
96
|
-
responseText +=
|
|
97
|
-
"\n\nTip: You can specify a 'sequence' parameter when creating rules to control their execution order. Lower sequence numbers run first.";
|
|
98
|
-
}
|
|
85
|
+
const allWarnings = [...condWarnings, ...actWarnings, ...excWarnings];
|
|
99
86
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
type: 'text',
|
|
104
|
-
text: responseText,
|
|
105
|
-
},
|
|
106
|
-
],
|
|
107
|
-
};
|
|
108
|
-
} catch (error) {
|
|
109
|
-
if (error.message === 'Authentication required') {
|
|
87
|
+
// Check for fatal warnings (folder not found = no valid action)
|
|
88
|
+
const folderNotFound = actWarnings.some((w) => w.includes('not found'));
|
|
89
|
+
if (folderNotFound && Object.keys(actions).length === 0) {
|
|
110
90
|
return {
|
|
111
91
|
content: [
|
|
112
92
|
{
|
|
113
93
|
type: 'text',
|
|
114
|
-
text:
|
|
94
|
+
text: actWarnings.filter((w) => w.includes('not found')).join('\n'),
|
|
115
95
|
},
|
|
116
96
|
],
|
|
117
97
|
};
|
|
118
98
|
}
|
|
119
99
|
|
|
120
|
-
|
|
121
|
-
content: [
|
|
122
|
-
{
|
|
123
|
-
type: 'text',
|
|
124
|
-
text: `Error creating rule: ${error.message}`,
|
|
125
|
-
},
|
|
126
|
-
],
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Create a new inbox rule
|
|
133
|
-
* @param {string} accessToken - Access token
|
|
134
|
-
* @param {object} ruleOptions - Rule creation options
|
|
135
|
-
* @returns {Promise<object>} - Result object with status and message
|
|
136
|
-
*/
|
|
137
|
-
async function createInboxRule(accessToken, ruleOptions) {
|
|
138
|
-
try {
|
|
139
|
-
const {
|
|
140
|
-
name,
|
|
141
|
-
fromAddresses,
|
|
142
|
-
containsSubject,
|
|
143
|
-
hasAttachments,
|
|
144
|
-
moveToFolder,
|
|
145
|
-
markAsRead,
|
|
146
|
-
isEnabled,
|
|
147
|
-
sequence,
|
|
148
|
-
} = ruleOptions;
|
|
149
|
-
|
|
150
|
-
// Get existing rules to determine sequence if not provided
|
|
100
|
+
// Determine sequence
|
|
151
101
|
let ruleSequence = sequence;
|
|
152
102
|
if (!ruleSequence) {
|
|
103
|
+
ruleSequence = 100;
|
|
153
104
|
try {
|
|
154
|
-
// Default to 100 if we can't get existing rules
|
|
155
|
-
ruleSequence = 100;
|
|
156
|
-
|
|
157
|
-
// Get existing rules to find highest sequence
|
|
158
105
|
const existingRules = await getInboxRules(accessToken);
|
|
159
106
|
if (existingRules && existingRules.length > 0) {
|
|
160
|
-
// Find the highest sequence
|
|
161
107
|
const highestSequence = Math.max(
|
|
162
108
|
...existingRules.map((r) => r.sequence || 0)
|
|
163
109
|
);
|
|
164
|
-
// Set new rule sequence to be higher
|
|
165
110
|
ruleSequence = Math.max(highestSequence + 1, 100);
|
|
166
|
-
console.error(
|
|
167
|
-
`Auto-generated sequence: ${ruleSequence} (based on highest existing: ${highestSequence})`
|
|
168
|
-
);
|
|
169
111
|
}
|
|
170
|
-
} catch (
|
|
171
|
-
console.error(
|
|
172
|
-
`Error determining rule sequence: ${sequenceError.message}`
|
|
173
|
-
);
|
|
174
|
-
// Fall back to default value
|
|
112
|
+
} catch (_sequenceError) {
|
|
175
113
|
ruleSequence = 100;
|
|
176
114
|
}
|
|
177
115
|
}
|
|
178
|
-
|
|
179
|
-
console.error(`Using rule sequence: ${ruleSequence}`);
|
|
180
|
-
|
|
181
|
-
// Make sure sequence is a positive integer
|
|
182
116
|
ruleSequence = Math.max(1, Math.floor(ruleSequence));
|
|
183
117
|
|
|
184
|
-
// Build rule object
|
|
118
|
+
// Build the rule object
|
|
185
119
|
const rule = {
|
|
186
120
|
displayName: name,
|
|
187
121
|
isEnabled: isEnabled === true,
|
|
188
122
|
sequence: ruleSequence,
|
|
189
|
-
conditions
|
|
190
|
-
actions
|
|
123
|
+
conditions,
|
|
124
|
+
actions,
|
|
191
125
|
};
|
|
192
126
|
|
|
193
|
-
// Add
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
const emailAddresses = fromAddresses
|
|
197
|
-
.split(',')
|
|
198
|
-
.map((email) => email.trim())
|
|
199
|
-
.filter((email) => email)
|
|
200
|
-
.map((email) => ({
|
|
201
|
-
emailAddress: {
|
|
202
|
-
address: email,
|
|
203
|
-
},
|
|
204
|
-
}));
|
|
205
|
-
|
|
206
|
-
if (emailAddresses.length > 0) {
|
|
207
|
-
rule.conditions.fromAddresses = emailAddresses;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
if (containsSubject) {
|
|
212
|
-
rule.conditions.subjectContains = [containsSubject];
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
if (hasAttachments === true) {
|
|
216
|
-
rule.conditions.hasAttachment = true;
|
|
127
|
+
// Add exceptions if any were specified
|
|
128
|
+
if (Object.keys(exceptions).length > 0) {
|
|
129
|
+
rule.exceptions = exceptions;
|
|
217
130
|
}
|
|
218
131
|
|
|
219
|
-
//
|
|
220
|
-
if (
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
return {
|
|
226
|
-
success: false,
|
|
227
|
-
message: `Target folder "${moveToFolder}" not found. Please specify a valid folder name.`,
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
rule.actions.moveToFolder = folderId;
|
|
232
|
-
} catch (folderError) {
|
|
233
|
-
console.error(
|
|
234
|
-
`Error resolving folder "${moveToFolder}": ${folderError.message}`
|
|
235
|
-
);
|
|
236
|
-
return {
|
|
237
|
-
success: false,
|
|
238
|
-
message: `Error resolving folder "${moveToFolder}": ${folderError.message}`,
|
|
239
|
-
};
|
|
132
|
+
// Dry-run: preview without creating
|
|
133
|
+
if (dryRun) {
|
|
134
|
+
const preview = formatRuleDryRunPreview(rule);
|
|
135
|
+
let text = `DRY RUN — Rule preview (not created):\n\n${preview}`;
|
|
136
|
+
if (allWarnings.length > 0) {
|
|
137
|
+
text += `\n\nWarnings:\n${allWarnings.map((w) => `- ${w}`).join('\n')}`;
|
|
240
138
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
rule.actions.markAsRead = true;
|
|
139
|
+
return {
|
|
140
|
+
content: [{ type: 'text', text }],
|
|
141
|
+
};
|
|
245
142
|
}
|
|
246
143
|
|
|
247
144
|
// Create the rule
|
|
@@ -253,20 +150,47 @@ async function createInboxRule(accessToken, ruleOptions) {
|
|
|
253
150
|
);
|
|
254
151
|
|
|
255
152
|
if (response && response.id) {
|
|
153
|
+
let text = `Successfully created rule "${name}" with sequence ${ruleSequence}.`;
|
|
154
|
+
if (allWarnings.length > 0) {
|
|
155
|
+
text += `\n\nNotes:\n${allWarnings.map((w) => `- ${w}`).join('\n')}`;
|
|
156
|
+
}
|
|
157
|
+
if (!sequence) {
|
|
158
|
+
text +=
|
|
159
|
+
"\n\nTip: You can specify a 'sequence' parameter when creating rules to control their execution order. Lower sequence numbers run first.";
|
|
160
|
+
}
|
|
256
161
|
return {
|
|
257
|
-
|
|
258
|
-
message: `Successfully created rule "${name}" with sequence ${ruleSequence}.`,
|
|
259
|
-
ruleId: response.id,
|
|
162
|
+
content: [{ type: 'text', text }],
|
|
260
163
|
};
|
|
261
|
-
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
content: [
|
|
168
|
+
{
|
|
169
|
+
type: 'text',
|
|
170
|
+
text: "Failed to create rule. The server didn't return a rule ID.",
|
|
171
|
+
},
|
|
172
|
+
],
|
|
173
|
+
};
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (error.message === 'Authentication required') {
|
|
262
176
|
return {
|
|
263
|
-
|
|
264
|
-
|
|
177
|
+
content: [
|
|
178
|
+
{
|
|
179
|
+
type: 'text',
|
|
180
|
+
text: "Authentication required. Please use the 'auth' tool with action=authenticate first.",
|
|
181
|
+
},
|
|
182
|
+
],
|
|
265
183
|
};
|
|
266
184
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
content: [
|
|
188
|
+
{
|
|
189
|
+
type: 'text',
|
|
190
|
+
text: `Error creating rule: ${error.message}`,
|
|
191
|
+
},
|
|
192
|
+
],
|
|
193
|
+
};
|
|
270
194
|
}
|
|
271
195
|
}
|
|
272
196
|
|