@softeria/ms-365-mcp-server 0.12.3 → 0.13.1
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/.releaserc.json +1 -4
- package/README.md +52 -40
- 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/eslint.config.js +43 -0
- package/package.json +11 -4
package/.releaserc.json
CHANGED
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,42 +52,39 @@ 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
|
{
|
|
71
77
|
"mcpServers": {
|
|
72
78
|
"ms365": {
|
|
73
79
|
"command": "npx",
|
|
74
|
-
"args": [
|
|
75
|
-
"-y",
|
|
76
|
-
"@softeria/ms-365-mcp-server",
|
|
77
|
-
"--force-work-scopes"
|
|
78
|
-
]
|
|
80
|
+
"args": ["-y", "@softeria/ms-365-mcp-server", "--org-mode"]
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
83
|
}
|
|
82
84
|
```
|
|
83
85
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
**User Profile**
|
|
88
|
-
<sub>get-current-user</sub>
|
|
86
|
+
Organization mode must be enabled from the start to access work account features. Without this flag, only personal
|
|
87
|
+
account features (email, calendar, OneDrive, etc.) are available.
|
|
89
88
|
|
|
90
89
|
## Quick Start Example
|
|
91
90
|
|
|
@@ -110,10 +109,7 @@ Edit the config file under Settings > Developer:
|
|
|
110
109
|
"mcpServers": {
|
|
111
110
|
"ms365": {
|
|
112
111
|
"command": "npx",
|
|
113
|
-
"args": [
|
|
114
|
-
"-y",
|
|
115
|
-
"@softeria/ms-365-mcp-server"
|
|
116
|
-
]
|
|
112
|
+
"args": ["-y", "@softeria/ms-365-mcp-server"]
|
|
117
113
|
}
|
|
118
114
|
}
|
|
119
115
|
}
|
|
@@ -139,9 +135,9 @@ The server supports three authentication methods:
|
|
|
139
135
|
For interactive authentication via device code:
|
|
140
136
|
|
|
141
137
|
- **MCP client login**:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
138
|
+
- Call the `login` tool (auto-checks existing token)
|
|
139
|
+
- If needed, get URL+code, visit in browser
|
|
140
|
+
- Use `verify-login` tool to confirm
|
|
145
141
|
- **CLI login**:
|
|
146
142
|
```bash
|
|
147
143
|
npx @softeria/ms-365-mcp-server --login
|
|
@@ -174,21 +170,24 @@ To use OAuth mode with custom Azure credentials (recommended for production), yo
|
|
|
174
170
|
registration:
|
|
175
171
|
|
|
176
172
|
1. **Create Azure AD App Registration**:
|
|
177
|
-
- Go to [Azure Portal](https://portal.azure.com)
|
|
178
|
-
- Navigate to Azure Active Directory → App registrations → New registration
|
|
179
|
-
- Set name: "MS365 MCP Server"
|
|
180
173
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
174
|
+
- Go to [Azure Portal](https://portal.azure.com)
|
|
175
|
+
- Navigate to Azure Active Directory → App registrations → New registration
|
|
176
|
+
- Set name: "MS365 MCP Server"
|
|
177
|
+
|
|
178
|
+
1. **Configure Redirect URIs**:
|
|
179
|
+
Add these redirect URIs for testing with MCP Inspector (`npm run inspector`):
|
|
186
180
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
181
|
+
- `http://localhost:6274/oauth/callback`
|
|
182
|
+
- `http://localhost:6274/oauth/callback/debug`
|
|
183
|
+
- `http://localhost:3000/callback` (optional, for server callback)
|
|
190
184
|
|
|
191
|
-
|
|
185
|
+
1. **Get Credentials**:
|
|
186
|
+
|
|
187
|
+
- Copy the **Application (client) ID** from Overview page
|
|
188
|
+
- Go to Certificates & secrets → New client secret → Copy the secret value
|
|
189
|
+
|
|
190
|
+
1. **Configure Environment Variables**:
|
|
192
191
|
Create a `.env` file in your project root:
|
|
193
192
|
```env
|
|
194
193
|
MS365_MCP_CLIENT_ID=your-azure-ad-app-client-id-here
|
|
@@ -210,7 +209,7 @@ MS365_MCP_OAUTH_TOKEN=your_oauth_token npx @softeria/ms-365-mcp-server
|
|
|
210
209
|
This method:
|
|
211
210
|
|
|
212
211
|
- Bypasses the interactive authentication flows
|
|
213
|
-
-
|
|
212
|
+
- Use your pre-existing OAuth token for Microsoft Graph API requests
|
|
214
213
|
- Does not handle token refresh (token lifecycle management is your responsibility)
|
|
215
214
|
|
|
216
215
|
> **Note**: HTTP mode requires authentication. For unauthenticated testing, use stdio mode with device code flow.
|
|
@@ -226,7 +225,9 @@ The following options can be used when running ms-365-mcp-server directly from t
|
|
|
226
225
|
--login Login using device code flow
|
|
227
226
|
--logout Log out and clear saved credentials
|
|
228
227
|
--verify-login Verify login without starting the server
|
|
229
|
-
--
|
|
228
|
+
--org-mode Enable organization/work mode from start (includes Teams, SharePoint, etc.)
|
|
229
|
+
--work-mode Alias for --org-mode
|
|
230
|
+
--force-work-scopes Backwards compatibility alias for --org-mode (deprecated)
|
|
230
231
|
```
|
|
231
232
|
|
|
232
233
|
### Server Options
|
|
@@ -245,14 +246,25 @@ When running as an MCP server, the following options can be used:
|
|
|
245
246
|
Environment variables:
|
|
246
247
|
|
|
247
248
|
- `READ_ONLY=true|1`: Alternative to --read-only flag
|
|
248
|
-
- `ENABLED_TOOLS`: Filter tools using regex pattern (alternative to --enabled-tools flag)
|
|
249
|
-
- `
|
|
249
|
+
- `ENABLED_TOOLS`: Filter tools using a regex pattern (alternative to --enabled-tools flag)
|
|
250
|
+
- `MS365_MCP_ORG_MODE=true|1`: Enable organization/work mode (alternative to --org-mode flag)
|
|
251
|
+
- `MS365_MCP_FORCE_WORK_SCOPES=true|1`: Backwards compatibility for MS365_MCP_ORG_MODE
|
|
250
252
|
- `LOG_LEVEL`: Set logging level (default: 'info')
|
|
251
253
|
- `SILENT=true|1`: Disable console output
|
|
252
254
|
- `MS365_MCP_CLIENT_ID`: Custom Azure app client ID (defaults to built-in app)
|
|
253
255
|
- `MS365_MCP_TENANT_ID`: Custom tenant ID (defaults to 'common' for multi-tenant)
|
|
254
256
|
- `MS365_MCP_OAUTH_TOKEN`: Pre-existing OAuth token for Microsoft Graph API (BYOT method)
|
|
255
257
|
|
|
258
|
+
## Contributing
|
|
259
|
+
|
|
260
|
+
We welcome contributions! Before submitting a pull request, please ensure your changes meet our quality standards.
|
|
261
|
+
|
|
262
|
+
Run the verification script to check all code quality requirements:
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
npm run verify
|
|
266
|
+
```
|
|
267
|
+
|
|
256
268
|
## Support
|
|
257
269
|
|
|
258
270
|
If you're having problems or need help:
|
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();
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import js from '@eslint/js';
|
|
2
|
+
import globals from 'globals';
|
|
3
|
+
import tseslint from '@typescript-eslint/eslint-plugin';
|
|
4
|
+
import tsparser from '@typescript-eslint/parser';
|
|
5
|
+
|
|
6
|
+
export default [
|
|
7
|
+
js.configs.recommended,
|
|
8
|
+
{
|
|
9
|
+
files: ['**/*.{ts,tsx,js,mjs}'],
|
|
10
|
+
languageOptions: {
|
|
11
|
+
parser: tsparser,
|
|
12
|
+
parserOptions: {
|
|
13
|
+
ecmaVersion: 2022,
|
|
14
|
+
sourceType: 'module',
|
|
15
|
+
},
|
|
16
|
+
globals: {
|
|
17
|
+
...globals.node,
|
|
18
|
+
...globals.vitest,
|
|
19
|
+
...globals.jest,
|
|
20
|
+
fs: 'readonly',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
plugins: {
|
|
24
|
+
'@typescript-eslint': tseslint,
|
|
25
|
+
},
|
|
26
|
+
rules: {
|
|
27
|
+
...tseslint.configs.recommended.rules,
|
|
28
|
+
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
|
29
|
+
'@typescript-eslint/no-explicit-any': 'warn',
|
|
30
|
+
'no-console': 'off',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
ignores: [
|
|
35
|
+
'node_modules/**',
|
|
36
|
+
'dist/**',
|
|
37
|
+
'coverage/**',
|
|
38
|
+
'bin/**',
|
|
39
|
+
'src/generated/**',
|
|
40
|
+
'.venv/**',
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softeria/ms-365-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.1",
|
|
4
4
|
"description": "Microsoft 365 MCP Server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -9,15 +9,18 @@
|
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
11
|
"generate": "node bin/generate-graph-client.mjs",
|
|
12
|
-
"
|
|
12
|
+
"postinstall": "npm run generate",
|
|
13
13
|
"build": "tsup",
|
|
14
14
|
"test": "vitest run",
|
|
15
15
|
"test:watch": "vitest",
|
|
16
16
|
"dev": "tsx src/index.ts",
|
|
17
17
|
"dev:http": "tsx --watch src/index.ts --http 3000 -v",
|
|
18
18
|
"format": "prettier --write \"**/*.{ts,mts,js,mjs,json,md}\"",
|
|
19
|
-
"
|
|
20
|
-
"
|
|
19
|
+
"format:check": "prettier --check \"**/*.{ts,mts,js,mjs,json,md}\"",
|
|
20
|
+
"lint": "eslint .",
|
|
21
|
+
"lint:fix": "eslint . --fix",
|
|
22
|
+
"verify": "npm run lint && npm run format:check && npm run build && npm run test",
|
|
23
|
+
"inspector": "npx @modelcontextprotocol/inspector tsx src/index.ts"
|
|
21
24
|
},
|
|
22
25
|
"keywords": [
|
|
23
26
|
"microsoft",
|
|
@@ -49,6 +52,10 @@
|
|
|
49
52
|
"@semantic-release/npm": "^12.0.2",
|
|
50
53
|
"@types/express": "^5.0.3",
|
|
51
54
|
"@types/node": "^22.15.15",
|
|
55
|
+
"@typescript-eslint/eslint-plugin": "^8.38.0",
|
|
56
|
+
"@typescript-eslint/parser": "^8.38.0",
|
|
57
|
+
"eslint": "^9.31.0",
|
|
58
|
+
"globals": "^16.3.0",
|
|
52
59
|
"prettier": "^3.5.3",
|
|
53
60
|
"semantic-release": "^24.2.7",
|
|
54
61
|
"tsup": "^8.5.0",
|