@softeria/ms-365-mcp-server 0.12.3 → 0.13.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 +21 -15
- package/dist/auth.js +0 -43
- package/dist/cli.js +9 -3
- package/dist/graph-client.js +4 -13
- package/dist/graph-tools.js +20 -7
- package/dist/index.js +4 -11
- package/dist/server.js +19 -15
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -21,15 +21,17 @@ API.
|
|
|
21
21
|
|
|
22
22
|
## Supported Services & Tools
|
|
23
23
|
|
|
24
|
+
### Personal Account Tools (Available by default)
|
|
25
|
+
|
|
24
26
|
**Email (Outlook)**
|
|
25
27
|
<sub>list-mail-messages, list-mail-folders, list-mail-folder-messages, get-mail-message, send-mail,
|
|
26
|
-
delete-mail-message</sub>
|
|
28
|
+
delete-mail-message, create-draft-email, move-mail-message</sub>
|
|
27
29
|
|
|
28
30
|
**Calendar**
|
|
29
31
|
<sub>list-calendars, list-calendar-events, get-calendar-event, get-calendar-view, create-calendar-event,
|
|
30
32
|
update-calendar-event, delete-calendar-event</sub>
|
|
31
33
|
|
|
32
|
-
**OneDrive
|
|
34
|
+
**OneDrive Files**
|
|
33
35
|
<sub>list-drives, get-drive-root-item, list-folder-files, download-onedrive-file-content, upload-file-content,
|
|
34
36
|
upload-new-file, delete-onedrive-file</sub>
|
|
35
37
|
|
|
@@ -50,21 +52,25 @@ create-onenote-page</sub>
|
|
|
50
52
|
<sub>list-outlook-contacts, get-outlook-contact, create-outlook-contact, update-outlook-contact,
|
|
51
53
|
delete-outlook-contact</sub>
|
|
52
54
|
|
|
53
|
-
**
|
|
55
|
+
**User Profile**
|
|
56
|
+
<sub>get-current-user</sub>
|
|
57
|
+
|
|
58
|
+
### Organization Account Tools (Requires --org-mode flag)
|
|
59
|
+
|
|
60
|
+
**Teams & Chats**
|
|
54
61
|
<sub>list-chats, get-chat, list-chat-messages, get-chat-message, send-chat-message, list-chat-message-replies,
|
|
55
62
|
reply-to-chat-message, list-joined-teams, get-team, list-team-channels, get-team-channel, list-channel-messages,
|
|
56
63
|
get-channel-message, send-channel-message, list-team-members</sub>
|
|
57
64
|
|
|
58
|
-
**SharePoint Sites**
|
|
65
|
+
**SharePoint Sites**
|
|
59
66
|
<sub>search-sharepoint-sites, get-sharepoint-site, get-sharepoint-site-by-path, list-sharepoint-site-drives,
|
|
60
67
|
get-sharepoint-site-drive-by-id, list-sharepoint-site-items, get-sharepoint-site-item, list-sharepoint-site-lists,
|
|
61
68
|
get-sharepoint-site-list, list-sharepoint-site-list-items, get-sharepoint-site-list-item,
|
|
62
69
|
get-sharepoint-sites-delta</sub>
|
|
63
70
|
|
|
64
|
-
|
|
71
|
+
## Organization/Work Mode
|
|
65
72
|
|
|
66
|
-
|
|
67
|
-
`--force-work-scopes` flag!
|
|
73
|
+
To access work/school features (Teams, SharePoint, etc.), enable organization mode using any of these flags:
|
|
68
74
|
|
|
69
75
|
```json
|
|
70
76
|
{
|
|
@@ -74,18 +80,15 @@ If you're having issues accessing work/school features (Teams, SharePoint, etc.)
|
|
|
74
80
|
"args": [
|
|
75
81
|
"-y",
|
|
76
82
|
"@softeria/ms-365-mcp-server",
|
|
77
|
-
"--
|
|
83
|
+
"--org-mode"
|
|
78
84
|
]
|
|
79
85
|
}
|
|
80
86
|
}
|
|
81
87
|
}
|
|
82
88
|
```
|
|
83
89
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
**User Profile**
|
|
88
|
-
<sub>get-current-user</sub>
|
|
90
|
+
Organization mode must be enabled from the start to access work account features. Without this flag, only personal
|
|
91
|
+
account features (email, calendar, OneDrive, etc.) are available.
|
|
89
92
|
|
|
90
93
|
## Quick Start Example
|
|
91
94
|
|
|
@@ -226,7 +229,9 @@ The following options can be used when running ms-365-mcp-server directly from t
|
|
|
226
229
|
--login Login using device code flow
|
|
227
230
|
--logout Log out and clear saved credentials
|
|
228
231
|
--verify-login Verify login without starting the server
|
|
229
|
-
--
|
|
232
|
+
--org-mode Enable organization/work mode from start (includes Teams, SharePoint, etc.)
|
|
233
|
+
--work-mode Alias for --org-mode
|
|
234
|
+
--force-work-scopes Backwards compatibility alias for --org-mode (deprecated)
|
|
230
235
|
```
|
|
231
236
|
|
|
232
237
|
### Server Options
|
|
@@ -246,7 +251,8 @@ Environment variables:
|
|
|
246
251
|
|
|
247
252
|
- `READ_ONLY=true|1`: Alternative to --read-only flag
|
|
248
253
|
- `ENABLED_TOOLS`: Filter tools using regex pattern (alternative to --enabled-tools flag)
|
|
249
|
-
- `
|
|
254
|
+
- `MS365_MCP_ORG_MODE=true|1`: Enable organization/work mode (alternative to --org-mode flag)
|
|
255
|
+
- `MS365_MCP_FORCE_WORK_SCOPES=true|1`: Backwards compatibility for MS365_MCP_ORG_MODE
|
|
250
256
|
- `LOG_LEVEL`: Set logging level (default: 'info')
|
|
251
257
|
- `SILENT=true|1`: Disable console output
|
|
252
258
|
- `MS365_MCP_CLIENT_ID`: Custom Azure app client ID (defaults to built-in app)
|
package/dist/auth.js
CHANGED
|
@@ -49,9 +49,6 @@ function buildScopesFromEndpoints(includeWorkAccountScopes = false) {
|
|
|
49
49
|
});
|
|
50
50
|
return Array.from(scopesSet);
|
|
51
51
|
}
|
|
52
|
-
function buildAllScopes() {
|
|
53
|
-
return buildScopesFromEndpoints(true);
|
|
54
|
-
}
|
|
55
52
|
class AuthManager {
|
|
56
53
|
constructor(config = DEFAULT_CONFIG, scopes = buildScopesFromEndpoints()) {
|
|
57
54
|
logger.info(`And scopes are ${scopes.join(", ")}`, scopes);
|
|
@@ -324,45 +321,6 @@ class AuthManager {
|
|
|
324
321
|
return false;
|
|
325
322
|
}
|
|
326
323
|
}
|
|
327
|
-
async expandToWorkAccountScopes(hack) {
|
|
328
|
-
try {
|
|
329
|
-
logger.info("Expanding to work account scopes...");
|
|
330
|
-
const allScopes = buildAllScopes();
|
|
331
|
-
const deviceCodeRequest = {
|
|
332
|
-
scopes: allScopes,
|
|
333
|
-
deviceCodeCallback: (response2) => {
|
|
334
|
-
const text = [
|
|
335
|
-
"\n",
|
|
336
|
-
"\u{1F504} This feature requires additional permissions (work account scopes)",
|
|
337
|
-
"\n",
|
|
338
|
-
response2.message,
|
|
339
|
-
"\n"
|
|
340
|
-
].join("");
|
|
341
|
-
if (hack) {
|
|
342
|
-
hack(text + 'After login run the "verify login" command');
|
|
343
|
-
} else {
|
|
344
|
-
console.log(text);
|
|
345
|
-
}
|
|
346
|
-
logger.info("Work account scope expansion initiated");
|
|
347
|
-
}
|
|
348
|
-
};
|
|
349
|
-
const response = await this.msalApp.acquireTokenByDeviceCode(deviceCodeRequest);
|
|
350
|
-
logger.info("Work account scope expansion successful");
|
|
351
|
-
this.accessToken = response?.accessToken || null;
|
|
352
|
-
this.tokenExpiry = response?.expiresOn ? new Date(response.expiresOn).getTime() : null;
|
|
353
|
-
this.scopes = allScopes;
|
|
354
|
-
if (response?.account) {
|
|
355
|
-
this.selectedAccountId = response.account.homeAccountId;
|
|
356
|
-
await this.saveSelectedAccount();
|
|
357
|
-
logger.info(`Updated selected account after scope expansion: ${response.account.username}`);
|
|
358
|
-
}
|
|
359
|
-
await this.saveTokenCache();
|
|
360
|
-
return true;
|
|
361
|
-
} catch (error) {
|
|
362
|
-
logger.error(`Error expanding to work account scopes: ${error.message}`);
|
|
363
|
-
return false;
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
324
|
// Multi-account support methods
|
|
367
325
|
async listAccounts() {
|
|
368
326
|
return await this.msalApp.getTokenCache().getAllAccounts();
|
|
@@ -431,7 +389,6 @@ class AuthManager {
|
|
|
431
389
|
}
|
|
432
390
|
var auth_default = AuthManager;
|
|
433
391
|
export {
|
|
434
|
-
buildAllScopes,
|
|
435
392
|
buildScopesFromEndpoints,
|
|
436
393
|
auth_default as default
|
|
437
394
|
};
|
package/dist/cli.js
CHANGED
|
@@ -17,9 +17,9 @@ program.name("ms-365-mcp-server").description("Microsoft 365 MCP Server").versio
|
|
|
17
17
|
"--enabled-tools <pattern>",
|
|
18
18
|
'Filter tools using regex pattern (e.g., "excel|contact" to enable Excel and Contact tools)'
|
|
19
19
|
).option(
|
|
20
|
-
"--
|
|
21
|
-
"
|
|
22
|
-
);
|
|
20
|
+
"--org-mode",
|
|
21
|
+
"Enable organization/work mode from start (includes Teams, SharePoint, etc.)"
|
|
22
|
+
).option("--work-mode", "Alias for --org-mode").option("--force-work-scopes", "Backwards compatibility alias for --org-mode (deprecated)");
|
|
23
23
|
function parseArgs() {
|
|
24
24
|
program.parse();
|
|
25
25
|
const options = program.opts();
|
|
@@ -29,9 +29,15 @@ function parseArgs() {
|
|
|
29
29
|
if (process.env.ENABLED_TOOLS) {
|
|
30
30
|
options.enabledTools = process.env.ENABLED_TOOLS;
|
|
31
31
|
}
|
|
32
|
+
if (process.env.MS365_MCP_ORG_MODE === "true" || process.env.MS365_MCP_ORG_MODE === "1") {
|
|
33
|
+
options.orgMode = true;
|
|
34
|
+
}
|
|
32
35
|
if (process.env.MS365_MCP_FORCE_WORK_SCOPES === "true" || process.env.MS365_MCP_FORCE_WORK_SCOPES === "1") {
|
|
33
36
|
options.forceWorkScopes = true;
|
|
34
37
|
}
|
|
38
|
+
if (options.workMode || options.forceWorkScopes) {
|
|
39
|
+
options.orgMode = true;
|
|
40
|
+
}
|
|
35
41
|
return options;
|
|
36
42
|
}
|
|
37
43
|
export {
|
package/dist/graph-client.js
CHANGED
|
@@ -90,21 +90,12 @@ class GraphClient {
|
|
|
90
90
|
if (response.status === 403) {
|
|
91
91
|
const errorText = await response.text();
|
|
92
92
|
if (errorText.includes("scope") || errorText.includes("permission")) {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const expanded = await this.authManager.expandToWorkAccountScopes();
|
|
97
|
-
if (expanded) {
|
|
98
|
-
const newToken = await this.authManager.getToken();
|
|
99
|
-
if (newToken) {
|
|
100
|
-
logger.info("Retrying request with expanded scopes...");
|
|
101
|
-
return this.performRequest(endpoint, newToken, options);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
93
|
+
throw new Error(
|
|
94
|
+
`Microsoft Graph API scope error: ${response.status} ${response.statusText} - ${errorText}. This tool requires organization mode. Please restart with --org-mode flag.`
|
|
95
|
+
);
|
|
105
96
|
}
|
|
106
97
|
throw new Error(
|
|
107
|
-
`Microsoft Graph API
|
|
98
|
+
`Microsoft Graph API error: ${response.status} ${response.statusText} - ${errorText}`
|
|
108
99
|
);
|
|
109
100
|
}
|
|
110
101
|
if (!response.ok) {
|
package/dist/graph-tools.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import logger from "./logger.js";
|
|
2
2
|
import { api } from "./generated/client.js";
|
|
3
3
|
import { z } from "zod";
|
|
4
|
-
|
|
4
|
+
import { readFileSync } from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
const endpointsData = JSON.parse(
|
|
10
|
+
readFileSync(path.join(__dirname, "endpoints.json"), "utf8")
|
|
11
|
+
);
|
|
12
|
+
function registerGraphTools(server, graphClient, readOnly = false, enabledToolsPattern, orgMode = false) {
|
|
5
13
|
let enabledToolsRegex;
|
|
6
14
|
if (enabledToolsPattern) {
|
|
7
15
|
try {
|
|
@@ -12,6 +20,11 @@ function registerGraphTools(server, graphClient, readOnly = false, enabledToolsP
|
|
|
12
20
|
}
|
|
13
21
|
}
|
|
14
22
|
for (const tool of api.endpoints) {
|
|
23
|
+
const endpointConfig = endpointsData.find((e) => e.toolName === tool.alias);
|
|
24
|
+
if (endpointConfig?.requiresWorkAccount && !orgMode) {
|
|
25
|
+
logger.info(`Skipping work account tool ${tool.alias} - not in org mode`);
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
15
28
|
if (readOnly && tool.method.toUpperCase() !== "GET") {
|
|
16
29
|
logger.info(`Skipping write operation ${tool.alias} in read-only mode`);
|
|
17
30
|
continue;
|
|
@@ -46,7 +59,7 @@ function registerGraphTools(server, graphClient, readOnly = false, enabledToolsP
|
|
|
46
59
|
try {
|
|
47
60
|
logger.info(`params: ${JSON.stringify(params)}`);
|
|
48
61
|
const parameterDefinitions = tool.parameters || [];
|
|
49
|
-
let
|
|
62
|
+
let path2 = tool.path;
|
|
50
63
|
const queryParams = {};
|
|
51
64
|
const headers = {};
|
|
52
65
|
let body = null;
|
|
@@ -70,7 +83,7 @@ function registerGraphTools(server, graphClient, readOnly = false, enabledToolsP
|
|
|
70
83
|
if (paramDef) {
|
|
71
84
|
switch (paramDef.type) {
|
|
72
85
|
case "Path":
|
|
73
|
-
|
|
86
|
+
path2 = path2.replace(`{${paramName}}`, encodeURIComponent(paramValue)).replace(`:${paramName}`, encodeURIComponent(paramValue));
|
|
74
87
|
break;
|
|
75
88
|
case "Query":
|
|
76
89
|
queryParams[fixedParamName] = `${paramValue}`;
|
|
@@ -105,7 +118,7 @@ function registerGraphTools(server, graphClient, readOnly = false, enabledToolsP
|
|
|
105
118
|
}
|
|
106
119
|
if (Object.keys(queryParams).length > 0) {
|
|
107
120
|
const queryString = Object.entries(queryParams).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
|
|
108
|
-
|
|
121
|
+
path2 = `${path2}${path2.includes("?") ? "&" : "?"}${queryString}`;
|
|
109
122
|
}
|
|
110
123
|
const options = {
|
|
111
124
|
method: tool.method.toUpperCase(),
|
|
@@ -114,12 +127,12 @@ function registerGraphTools(server, graphClient, readOnly = false, enabledToolsP
|
|
|
114
127
|
if (options.method !== "GET" && body) {
|
|
115
128
|
options.body = typeof body === "string" ? body : JSON.stringify(body);
|
|
116
129
|
}
|
|
117
|
-
const isProbablyMediaContent = tool.errors?.some((error) => error.description === "Retrieved media content") ||
|
|
130
|
+
const isProbablyMediaContent = tool.errors?.some((error) => error.description === "Retrieved media content") || path2.endsWith("/content");
|
|
118
131
|
if (isProbablyMediaContent) {
|
|
119
132
|
options.rawResponse = true;
|
|
120
133
|
}
|
|
121
|
-
logger.info(`Making graph request to ${
|
|
122
|
-
let response = await graphClient.graphRequest(
|
|
134
|
+
logger.info(`Making graph request to ${path2} with options: ${JSON.stringify(options)}`);
|
|
135
|
+
let response = await graphClient.graphRequest(path2, options);
|
|
123
136
|
const fetchAllPages = params.fetchAllPages === true;
|
|
124
137
|
if (fetchAllPages && response && response.content && response.content.length > 0) {
|
|
125
138
|
try {
|
package/dist/index.js
CHANGED
|
@@ -2,22 +2,15 @@
|
|
|
2
2
|
import "dotenv/config";
|
|
3
3
|
import { parseArgs } from "./cli.js";
|
|
4
4
|
import logger from "./logger.js";
|
|
5
|
-
import AuthManager from "./auth.js";
|
|
5
|
+
import AuthManager, { buildScopesFromEndpoints } from "./auth.js";
|
|
6
6
|
import MicrosoftGraphServer from "./server.js";
|
|
7
7
|
import { version } from "./version.js";
|
|
8
|
-
import { buildScopesFromEndpoints } from "./auth.js";
|
|
9
8
|
async function main() {
|
|
10
9
|
try {
|
|
11
10
|
const args = parseArgs();
|
|
12
|
-
|
|
13
|
-
if (
|
|
14
|
-
|
|
15
|
-
await tempAuthManager.loadTokenCache();
|
|
16
|
-
const hasWorkPermissions = await tempAuthManager.hasWorkAccountPermissions();
|
|
17
|
-
if (hasWorkPermissions) {
|
|
18
|
-
includeWorkScopes = true;
|
|
19
|
-
logger.info("Detected existing work account permissions, including work scopes");
|
|
20
|
-
}
|
|
11
|
+
const includeWorkScopes = args.orgMode || false;
|
|
12
|
+
if (includeWorkScopes) {
|
|
13
|
+
logger.info("Organization mode enabled - including work account scopes");
|
|
21
14
|
}
|
|
22
15
|
const scopes = buildScopesFromEndpoints(includeWorkScopes);
|
|
23
16
|
const authManager = new AuthManager(void 0, scopes);
|
package/dist/server.js
CHANGED
|
@@ -9,7 +9,11 @@ import { registerAuthTools } from "./auth-tools.js";
|
|
|
9
9
|
import { registerGraphTools } from "./graph-tools.js";
|
|
10
10
|
import GraphClient from "./graph-client.js";
|
|
11
11
|
import { MicrosoftOAuthProvider } from "./oauth-provider.js";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
exchangeCodeForToken,
|
|
14
|
+
microsoftBearerTokenAuthMiddleware,
|
|
15
|
+
refreshAccessToken
|
|
16
|
+
} from "./lib/microsoft-auth.js";
|
|
13
17
|
const registeredClients = /* @__PURE__ */ new Map();
|
|
14
18
|
class MicrosoftGraphServer {
|
|
15
19
|
constructor(authManager, options = {}) {
|
|
@@ -31,7 +35,8 @@ class MicrosoftGraphServer {
|
|
|
31
35
|
this.server,
|
|
32
36
|
this.graphClient,
|
|
33
37
|
this.options.readOnly,
|
|
34
|
-
this.options.enabledTools
|
|
38
|
+
this.options.enabledTools,
|
|
39
|
+
this.options.orgMode
|
|
35
40
|
);
|
|
36
41
|
}
|
|
37
42
|
async start() {
|
|
@@ -56,7 +61,10 @@ class MicrosoftGraphServer {
|
|
|
56
61
|
app.use((req, res, next) => {
|
|
57
62
|
res.header("Access-Control-Allow-Origin", "*");
|
|
58
63
|
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
|
59
|
-
res.header(
|
|
64
|
+
res.header(
|
|
65
|
+
"Access-Control-Allow-Headers",
|
|
66
|
+
"Origin, X-Requested-With, Content-Type, Accept, Authorization, mcp-protocol-version"
|
|
67
|
+
);
|
|
60
68
|
if (req.method === "OPTIONS") {
|
|
61
69
|
res.sendStatus(200);
|
|
62
70
|
return;
|
|
@@ -76,11 +84,7 @@ class MicrosoftGraphServer {
|
|
|
76
84
|
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
77
85
|
token_endpoint_auth_methods_supported: ["none"],
|
|
78
86
|
code_challenge_methods_supported: ["S256"],
|
|
79
|
-
scopes_supported: [
|
|
80
|
-
"User.Read",
|
|
81
|
-
"Files.Read",
|
|
82
|
-
"Mail.Read"
|
|
83
|
-
]
|
|
87
|
+
scopes_supported: ["User.Read", "Files.Read", "Mail.Read"]
|
|
84
88
|
});
|
|
85
89
|
});
|
|
86
90
|
app.get("/.well-known/oauth-protected-resource", async (req, res) => {
|
|
@@ -88,11 +92,7 @@ class MicrosoftGraphServer {
|
|
|
88
92
|
res.json({
|
|
89
93
|
resource: `${url.origin}/mcp`,
|
|
90
94
|
authorization_servers: [url.origin],
|
|
91
|
-
scopes_supported: [
|
|
92
|
-
"User.Read",
|
|
93
|
-
"Files.Read",
|
|
94
|
-
"Mail.Read"
|
|
95
|
-
],
|
|
95
|
+
scopes_supported: ["User.Read", "Files.Read", "Mail.Read"],
|
|
96
96
|
bearer_methods_supported: ["header"],
|
|
97
97
|
resource_documentation: `${url.origin}`
|
|
98
98
|
});
|
|
@@ -124,7 +124,9 @@ class MicrosoftGraphServer {
|
|
|
124
124
|
const url = new URL(req.url, `${req.protocol}://${req.get("host")}`);
|
|
125
125
|
const tenantId = process.env.MS365_MCP_TENANT_ID || "common";
|
|
126
126
|
const clientId = process.env.MS365_MCP_CLIENT_ID || "084a3e9f-a9f4-43f7-89f9-d229cf97853e";
|
|
127
|
-
const microsoftAuthUrl = new URL(
|
|
127
|
+
const microsoftAuthUrl = new URL(
|
|
128
|
+
`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`
|
|
129
|
+
);
|
|
128
130
|
const allowedParams = [
|
|
129
131
|
"response_type",
|
|
130
132
|
"redirect_uri",
|
|
@@ -275,7 +277,9 @@ class MicrosoftGraphServer {
|
|
|
275
277
|
logger.info(`Server listening on HTTP port ${port}`);
|
|
276
278
|
logger.info(` - MCP endpoint: http://localhost:${port}/mcp`);
|
|
277
279
|
logger.info(` - OAuth endpoints: http://localhost:${port}/auth/*`);
|
|
278
|
-
logger.info(
|
|
280
|
+
logger.info(
|
|
281
|
+
` - OAuth discovery: http://localhost:${port}/.well-known/oauth-authorization-server`
|
|
282
|
+
);
|
|
279
283
|
});
|
|
280
284
|
} else {
|
|
281
285
|
const transport = new StdioServerTransport();
|