@littlebearapps/outlook-assistant 3.8.1 → 3.8.2
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/auth/device-code.js +11 -0
- package/auth/tools.js +67 -4
- package/index.js +4 -113
- package/package.json +4 -2
package/auth/device-code.js
CHANGED
|
@@ -11,6 +11,10 @@ const https = require('https');
|
|
|
11
11
|
const querystring = require('querystring');
|
|
12
12
|
const config = require('../config');
|
|
13
13
|
|
|
14
|
+
// Fail fast if the OAuth endpoint is unreachable (e.g. blocked outbound
|
|
15
|
+
// egress in a sandboxed connector) instead of hanging indefinitely. (#213)
|
|
16
|
+
const REQUEST_TIMEOUT_MS = 15000;
|
|
17
|
+
|
|
14
18
|
/**
|
|
15
19
|
* POST helper for OAuth2 endpoints
|
|
16
20
|
* @param {string} url - Full URL to POST to
|
|
@@ -41,6 +45,13 @@ function postRequest(url, postData) {
|
|
|
41
45
|
}
|
|
42
46
|
);
|
|
43
47
|
req.on('error', reject);
|
|
48
|
+
req.setTimeout(REQUEST_TIMEOUT_MS, () => {
|
|
49
|
+
req.destroy(
|
|
50
|
+
new Error(
|
|
51
|
+
'Device code request timed out — network egress to login.microsoftonline.com may be blocked.'
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
});
|
|
44
55
|
req.write(postData);
|
|
45
56
|
req.end();
|
|
46
57
|
});
|
package/auth/tools.js
CHANGED
|
@@ -208,10 +208,22 @@ async function handleDeviceCodeAuth() {
|
|
|
208
208
|
}
|
|
209
209
|
|
|
210
210
|
console.error('[AUTH] Starting device code flow...');
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
)
|
|
211
|
+
|
|
212
|
+
// #213 — initiation can throw (blocked network egress in a sandboxed
|
|
213
|
+
// connector, AADSTS9002331 audience mismatch, invalid_client, non-JSON
|
|
214
|
+
// proxy page). Without this guard the exception propagates to the
|
|
215
|
+
// top-level dispatcher, which returns a content-less error and the client
|
|
216
|
+
// renders EMPTY OUTPUT. Mirror the try/catch that step 2
|
|
217
|
+
// (handleDeviceCodeComplete) already has, and surface actionable hints.
|
|
218
|
+
let response;
|
|
219
|
+
try {
|
|
220
|
+
response = await initiateDeviceCodeFlow(
|
|
221
|
+
clientId,
|
|
222
|
+
config.AUTH_CONFIG.scopes
|
|
223
|
+
);
|
|
224
|
+
} catch (error) {
|
|
225
|
+
return buildDeviceCodeErrorResponse(error);
|
|
226
|
+
}
|
|
215
227
|
|
|
216
228
|
// Store in memory and persist to disk
|
|
217
229
|
pendingDeviceCode = {
|
|
@@ -242,6 +254,57 @@ async function handleDeviceCodeAuth() {
|
|
|
242
254
|
};
|
|
243
255
|
}
|
|
244
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Build a visible, actionable MCP error result for a failed device-code
|
|
259
|
+
* initiation. Adds remediation hints for the common remote-connector
|
|
260
|
+
* failure modes. (#213)
|
|
261
|
+
* @param {Error} error
|
|
262
|
+
* @returns {object} - MCP response ({ content, isError: true })
|
|
263
|
+
*/
|
|
264
|
+
function buildDeviceCodeErrorResponse(error) {
|
|
265
|
+
const msg = (error && error.message) || String(error);
|
|
266
|
+
const code = error && error.code;
|
|
267
|
+
const hints = [];
|
|
268
|
+
|
|
269
|
+
if (/AADSTS9002331/i.test(msg) || /personal.*account/i.test(msg)) {
|
|
270
|
+
hints.push(
|
|
271
|
+
'This app registration appears to accept personal Microsoft accounts only. Set `OUTLOOK_AUTH_AUDIENCE=consumers` (or the correct tenant GUID) in your MCP env and retry.'
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
if (
|
|
275
|
+
/invalid_client|unauthorized_client|AADSTS7000218|AADSTS700016/i.test(msg)
|
|
276
|
+
) {
|
|
277
|
+
hints.push(
|
|
278
|
+
"Enable 'Allow public client flows' under Azure → App registration → Authentication → Advanced settings, and add the `nativeclient` redirect URI (Mobile and desktop applications platform)."
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
if (
|
|
282
|
+
code === 'ENOTFOUND' ||
|
|
283
|
+
code === 'ETIMEDOUT' ||
|
|
284
|
+
code === 'ECONNREFUSED' ||
|
|
285
|
+
code === 'ECONNRESET' ||
|
|
286
|
+
/timed out|ENOTFOUND|ETIMEDOUT|ECONNREFUSED|ECONNRESET|network|Failed to parse response/i.test(
|
|
287
|
+
msg
|
|
288
|
+
)
|
|
289
|
+
) {
|
|
290
|
+
hints.push(
|
|
291
|
+
'Could not reach `login.microsoftonline.com`. Outbound network access may be blocked in this environment (e.g. a sandboxed connector or corporate proxy). Check egress/firewall/proxy settings.'
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const lines = ['## Device Code Authentication Failed', '', `Error: ${msg}`];
|
|
296
|
+
if (hints.length) {
|
|
297
|
+
lines.push('', 'Suggested fixes:', ...hints.map((h) => `- ${h}`));
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
console.error(`[AUTH] Device code initiation failed: ${msg}`);
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
304
|
+
isError: true,
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
|
|
245
308
|
/**
|
|
246
309
|
* Device code flow step 2 — poll until the user completes authentication.
|
|
247
310
|
* Checks in-memory state first, falls back to disk-persisted state.
|
package/index.js
CHANGED
|
@@ -10,7 +10,7 @@ const {
|
|
|
10
10
|
StdioServerTransport,
|
|
11
11
|
} = require('@modelcontextprotocol/sdk/server/stdio.js');
|
|
12
12
|
const config = require('./config');
|
|
13
|
-
const {
|
|
13
|
+
const { createRequestHandler } = require('./request-handler');
|
|
14
14
|
|
|
15
15
|
// Import module tools
|
|
16
16
|
const { authTools, setToolCount } = require('./auth');
|
|
@@ -69,118 +69,9 @@ const server = new Server(
|
|
|
69
69
|
}
|
|
70
70
|
);
|
|
71
71
|
|
|
72
|
-
// Handle all requests
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const { method, params, id } = request;
|
|
76
|
-
console.error(`REQUEST: ${method} [${id}]`);
|
|
77
|
-
|
|
78
|
-
// Initialize handler
|
|
79
|
-
if (method === 'initialize') {
|
|
80
|
-
console.error(`INITIALIZE REQUEST: ID [${id}]`);
|
|
81
|
-
return {
|
|
82
|
-
protocolVersion: '2024-11-05',
|
|
83
|
-
capabilities: {
|
|
84
|
-
tools: TOOLS.reduce((acc, tool) => {
|
|
85
|
-
acc[tool.name] = {};
|
|
86
|
-
return acc;
|
|
87
|
-
}, {}),
|
|
88
|
-
},
|
|
89
|
-
serverInfo: {
|
|
90
|
-
name: config.SERVER_NAME,
|
|
91
|
-
version: config.SERVER_VERSION,
|
|
92
|
-
},
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Tools list handler
|
|
97
|
-
if (method === 'tools/list') {
|
|
98
|
-
console.error(`TOOLS LIST REQUEST: ID [${id}]`);
|
|
99
|
-
console.error(`TOOLS COUNT: ${TOOLS.length}`);
|
|
100
|
-
console.error(`TOOLS NAMES: ${TOOLS.map((t) => t.name).join(', ')}`);
|
|
101
|
-
|
|
102
|
-
return {
|
|
103
|
-
tools: TOOLS.map((tool) => ({
|
|
104
|
-
name: tool.name,
|
|
105
|
-
description: tool.description,
|
|
106
|
-
inputSchema: tool.inputSchema,
|
|
107
|
-
...(tool.annotations && { annotations: tool.annotations }),
|
|
108
|
-
})),
|
|
109
|
-
};
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Required empty responses for other capabilities
|
|
113
|
-
if (method === 'resources/list') return { resources: [] };
|
|
114
|
-
if (method === 'prompts/list') return { prompts: [] };
|
|
115
|
-
|
|
116
|
-
// Tool call handler
|
|
117
|
-
if (method === 'tools/call') {
|
|
118
|
-
try {
|
|
119
|
-
const { name, arguments: args = {} } = params || {};
|
|
120
|
-
|
|
121
|
-
console.error(`TOOL CALL: ${name}`);
|
|
122
|
-
|
|
123
|
-
// Find the tool handler
|
|
124
|
-
const tool = TOOLS.find((t) => t.name === name);
|
|
125
|
-
|
|
126
|
-
if (tool && tool.handler) {
|
|
127
|
-
// Coerce + validate args against the tool's inputSchema before
|
|
128
|
-
// dispatching. Catches array-as-string, boolean-as-string, unknown
|
|
129
|
-
// params, and out-of-enum action values at the MCP boundary so
|
|
130
|
-
// handlers receive properly-typed JS values. (#160, #162)
|
|
131
|
-
if (tool.inputSchema) {
|
|
132
|
-
const coerced = coerceArgsAgainstSchema(args, tool.inputSchema);
|
|
133
|
-
if (coerced.error) {
|
|
134
|
-
return {
|
|
135
|
-
content: [
|
|
136
|
-
{
|
|
137
|
-
type: 'text',
|
|
138
|
-
text: `Invalid arguments for tool '${name}':\n${coerced.error}`,
|
|
139
|
-
},
|
|
140
|
-
],
|
|
141
|
-
isError: true,
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
return await tool.handler(coerced.args);
|
|
145
|
-
}
|
|
146
|
-
return await tool.handler(args);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Tool not found
|
|
150
|
-
return {
|
|
151
|
-
error: {
|
|
152
|
-
code: -32601,
|
|
153
|
-
message: `Tool not found: ${name}`,
|
|
154
|
-
},
|
|
155
|
-
};
|
|
156
|
-
} catch (error) {
|
|
157
|
-
console.error(`Error in tools/call:`, error);
|
|
158
|
-
return {
|
|
159
|
-
error: {
|
|
160
|
-
code: -32603,
|
|
161
|
-
message: `Error processing tool call: ${error.message}`,
|
|
162
|
-
},
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// For any other method, return method not found
|
|
168
|
-
return {
|
|
169
|
-
error: {
|
|
170
|
-
code: -32601,
|
|
171
|
-
message: `Method not found: ${method}`,
|
|
172
|
-
},
|
|
173
|
-
};
|
|
174
|
-
} catch (error) {
|
|
175
|
-
console.error(`Error in fallbackRequestHandler:`, error);
|
|
176
|
-
return {
|
|
177
|
-
error: {
|
|
178
|
-
code: -32603,
|
|
179
|
-
message: `Error processing request: ${error.message}`,
|
|
180
|
-
},
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
};
|
|
72
|
+
// Handle all requests. Dispatch + error-shaping logic lives in
|
|
73
|
+
// request-handler.js so it is unit-testable without starting the transport.
|
|
74
|
+
server.fallbackRequestHandler = createRequestHandler(TOOLS);
|
|
184
75
|
|
|
185
76
|
// Make the script executable
|
|
186
77
|
process.on('SIGTERM', () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@littlebearapps/outlook-assistant",
|
|
3
|
-
"version": "3.8.
|
|
3
|
+
"version": "3.8.2",
|
|
4
4
|
"mcpName": "io.github.littlebearapps/outlook-assistant",
|
|
5
5
|
"description": "Outlook Assistant — MCP server with 22 tools for email, calendar, contacts, and settings via Microsoft Graph API",
|
|
6
6
|
"main": "index.js",
|
|
@@ -38,7 +38,9 @@
|
|
|
38
38
|
"calendar",
|
|
39
39
|
"contacts",
|
|
40
40
|
"model-context-protocol",
|
|
41
|
-
"
|
|
41
|
+
"mcp-server",
|
|
42
|
+
"anthropic",
|
|
43
|
+
"outlook-assistant"
|
|
42
44
|
],
|
|
43
45
|
"author": "Little Bear Apps <hello@littlebearapps.com>",
|
|
44
46
|
"license": "MIT",
|