@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 CHANGED
@@ -3,10 +3,7 @@
3
3
  "plugins": [
4
4
  "@semantic-release/commit-analyzer",
5
5
  "@semantic-release/release-notes-generator",
6
- ["@semantic-release/exec", {
7
- "prepareCmd": "npm run build"
8
- }],
9
6
  "@semantic-release/npm",
10
7
  "@semantic-release/github"
11
8
  ]
12
- }
9
+ }
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 & SharePoint Files**
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
- **Teams & Chats** (Work/School accounts only)
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** (Work/School accounts only)
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
- ### Work Scopes Issues
71
+ ## Organization/Work Mode
65
72
 
66
- If you're having issues accessing work/school features (Teams, SharePoint, etc.), you should pass the
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
- While the server should attempt to force a re-login when work scopes are needed, passing the flag explicitly is safer
85
- and ensures proper scope permissions from the start.
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
- - Call the `login` tool (auto-checks existing token)
143
- - If needed, get URL+code, visit in browser
144
- - Use `verify-login` tool to confirm
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
- 2. **Configure Redirect URIs**:
182
- Add these redirect URIs for testing with MCP Inspector:
183
- - `http://localhost:6274/oauth/callback`
184
- - `http://localhost:6274/oauth/callback/debug`
185
- - `http://localhost:3000/callback` (optional, for server callback)
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
- 3. **Get Credentials**:
188
- - Copy the **Application (client) ID** from Overview page
189
- - Go to Certificates & secrets → New client secret → Copy the secret value
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
- 4. **Configure Environment Variables**:
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
- - Uses your pre-existing OAuth token for Microsoft Graph API requests
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
- --force-work-scopes Force inclusion of work account scopes during login (includes Teams, SharePoint, etc.)
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
- - `MS365_MCP_FORCE_WORK_SCOPES=true|1`: Force inclusion of work account scopes (alternative to --force-work-scopes flag)
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
- "--force-work-scopes",
21
- "Force inclusion of work account scopes during login (includes Teams, SharePoint, etc.)"
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 {
@@ -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
- const hasWorkPermissions = await this.authManager.hasWorkAccountPermissions();
94
- if (!hasWorkPermissions) {
95
- logger.info("403 scope error detected, attempting to expand to work account scopes...");
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 scope error: ${response.status} ${response.statusText} - ${errorText}`
98
+ `Microsoft Graph API error: ${response.status} ${response.statusText} - ${errorText}`
108
99
  );
109
100
  }
110
101
  if (!response.ok) {
@@ -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
- function registerGraphTools(server, graphClient, readOnly = false, enabledToolsPattern) {
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 path = tool.path;
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
- path = path.replace(`{${paramName}}`, encodeURIComponent(paramValue)).replace(`:${paramName}`, encodeURIComponent(paramValue));
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
- path = `${path}${path.includes("?") ? "&" : "?"}${queryString}`;
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") || path.endsWith("/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 ${path} with options: ${JSON.stringify(options)}`);
122
- let response = await graphClient.graphRequest(path, options);
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
- let includeWorkScopes = args.forceWorkScopes;
13
- if (!includeWorkScopes) {
14
- const tempAuthManager = new AuthManager(void 0, buildScopesFromEndpoints(false));
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 { microsoftBearerTokenAuthMiddleware, exchangeCodeForToken, refreshAccessToken } from "./lib/microsoft-auth.js";
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("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization, mcp-protocol-version");
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(`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`);
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(` - OAuth discovery: http://localhost:${port}/.well-known/oauth-authorization-server`);
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();
@@ -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.12.3",
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
- "prebuild": "npm run generate",
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
- "inspect": "npx @modelcontextprotocol/inspector tsx src/index.ts",
20
- "prepublishOnly": "npm run build"
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",