@softeria/ms-365-mcp-server 0.28.1 → 0.28.3

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/.env.example CHANGED
@@ -4,7 +4,7 @@
4
4
  # Your Azure AD App Registration Client ID
5
5
  MS365_MCP_CLIENT_ID=your-azure-ad-app-client-id-here
6
6
 
7
- # Your Azure AD App Registration Client Secret
7
+ # Your Azure AD App Registration Client Secret (optional, for confidential clients)
8
8
  MS365_MCP_CLIENT_SECRET=your-azure-ad-app-client-secret-here
9
9
 
10
10
  # Tenant ID - use "common" for multi-tenant or your specific tenant ID
@@ -21,4 +21,24 @@ MS365_MCP_TENANT_ID=common
21
21
  # 5. Copy the Client ID from Overview page
22
22
  # 6. Go to Certificates & secrets → New client secret → Copy the secret value
23
23
  # 7. Replace the values above with your actual credentials
24
- # 8. Rename this file to .env
24
+ # 8. Rename this file to .env
25
+
26
+ # -------------------------------------------------------------------
27
+ # Azure Key Vault Integration (Optional)
28
+ # -------------------------------------------------------------------
29
+ # When set, secrets are fetched from Azure Key Vault instead of environment variables.
30
+ # This is useful for production deployments, especially with Azure Container Apps.
31
+ #
32
+ # MS365_MCP_KEYVAULT_URL=https://your-keyvault-name.vault.azure.net
33
+ #
34
+ # Key Vault secret names (store these in your Key Vault):
35
+ # - ms365-mcp-client-id (required)
36
+ # - ms365-mcp-tenant-id (optional, defaults to "common")
37
+ # - ms365-mcp-client-secret (optional)
38
+ #
39
+ # Authentication uses DefaultAzureCredential, which supports:
40
+ # - Managed Identity (recommended for Azure Container Apps)
41
+ # - Azure CLI credentials (for local development)
42
+ # - Environment variables (AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID)
43
+ #
44
+ # See README.md for detailed Azure Key Vault setup instructions.
package/README.md CHANGED
@@ -395,6 +395,74 @@ Environment variables:
395
395
  - `MS365_MCP_CLIENT_ID`: Custom Azure app client ID (defaults to built-in app)
396
396
  - `MS365_MCP_TENANT_ID`: Custom tenant ID (defaults to 'common' for multi-tenant)
397
397
  - `MS365_MCP_OAUTH_TOKEN`: Pre-existing OAuth token for Microsoft Graph API (BYOT method)
398
+ - `MS365_MCP_KEYVAULT_URL`: Azure Key Vault URL for secrets management (see Azure Key Vault section)
399
+
400
+ ## Azure Key Vault Integration
401
+
402
+ For production deployments, you can store secrets in Azure Key Vault instead of environment variables. This is particularly useful for Azure Container Apps with managed identity.
403
+
404
+ ### Setup
405
+
406
+ 1. **Create a Key Vault** (if you don't have one):
407
+
408
+ ```bash
409
+ az keyvault create --name your-keyvault-name --resource-group your-rg --location eastus
410
+ ```
411
+
412
+ 2. **Add secrets to Key Vault**:
413
+
414
+ ```bash
415
+ az keyvault secret set --vault-name your-keyvault-name --name ms365-mcp-client-id --value "your-client-id"
416
+ az keyvault secret set --vault-name your-keyvault-name --name ms365-mcp-tenant-id --value "your-tenant-id"
417
+ # Optional: if using confidential client flow
418
+ az keyvault secret set --vault-name your-keyvault-name --name ms365-mcp-client-secret --value "your-secret"
419
+ ```
420
+
421
+ 3. **Grant access to Key Vault**:
422
+
423
+ For Azure Container Apps with managed identity:
424
+
425
+ ```bash
426
+ # Get the managed identity principal ID
427
+ PRINCIPAL_ID=$(az containerapp show --name your-app --resource-group your-rg --query identity.principalId -o tsv)
428
+
429
+ # Grant access to Key Vault secrets
430
+ az keyvault set-policy --name your-keyvault-name --object-id $PRINCIPAL_ID --secret-permissions get list
431
+ ```
432
+
433
+ For local development with Azure CLI:
434
+
435
+ ```bash
436
+ # Your Azure CLI identity already has access if you have appropriate RBAC roles
437
+ az login
438
+ ```
439
+
440
+ 4. **Configure the server**:
441
+ ```bash
442
+ MS365_MCP_KEYVAULT_URL=https://your-keyvault-name.vault.azure.net npx @softeria/ms-365-mcp-server
443
+ ```
444
+
445
+ ### Secret Name Mapping
446
+
447
+ | Key Vault Secret Name | Environment Variable | Required |
448
+ | ----------------------- | ----------------------- | ------------------------- |
449
+ | ms365-mcp-client-id | MS365_MCP_CLIENT_ID | Yes |
450
+ | ms365-mcp-tenant-id | MS365_MCP_TENANT_ID | No (defaults to 'common') |
451
+ | ms365-mcp-client-secret | MS365_MCP_CLIENT_SECRET | No |
452
+
453
+ ### Authentication
454
+
455
+ The Key Vault integration uses `DefaultAzureCredential` from the Azure Identity SDK, which automatically tries multiple authentication methods in order:
456
+
457
+ 1. Environment variables (AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_TENANT_ID)
458
+ 2. Managed Identity (recommended for Azure Container Apps)
459
+ 3. Azure CLI credentials (for local development)
460
+ 4. Visual Studio Code credentials
461
+ 5. Azure PowerShell credentials
462
+
463
+ ### Optional Dependencies
464
+
465
+ The Azure Key Vault packages (`@azure/identity` and `@azure/keyvault-secrets`) are optional dependencies. They are only loaded when `MS365_MCP_KEYVAULT_URL` is configured. If you don't use Key Vault, these packages are not required.
398
466
 
399
467
  ## Contributing
400
468
 
package/dist/auth.js CHANGED
@@ -3,6 +3,7 @@ import logger from "./logger.js";
3
3
  import fs, { existsSync, readFileSync } from "fs";
4
4
  import { fileURLToPath } from "url";
5
5
  import path from "path";
6
+ import { getSecrets } from "./secrets.js";
6
7
  let keytar = null;
7
8
  async function getKeytar() {
8
9
  if (keytar === void 0) {
@@ -34,12 +35,14 @@ const SELECTED_ACCOUNT_KEY = "selected-account";
34
35
  const FALLBACK_DIR = path.dirname(fileURLToPath(import.meta.url));
35
36
  const FALLBACK_PATH = path.join(FALLBACK_DIR, "..", ".token-cache.json");
36
37
  const SELECTED_ACCOUNT_PATH = path.join(FALLBACK_DIR, "..", ".selected-account.json");
37
- const DEFAULT_CONFIG = {
38
- auth: {
39
- clientId: process.env.MS365_MCP_CLIENT_ID || "084a3e9f-a9f4-43f7-89f9-d229cf97853e",
40
- authority: `https://login.microsoftonline.com/${process.env.MS365_MCP_TENANT_ID || "common"}`
41
- }
42
- };
38
+ function createMsalConfig(secrets) {
39
+ return {
40
+ auth: {
41
+ clientId: secrets.clientId || "084a3e9f-a9f4-43f7-89f9-d229cf97853e",
42
+ authority: `https://login.microsoftonline.com/${secrets.tenantId || "common"}`
43
+ }
44
+ };
45
+ }
43
46
  const SCOPE_HIERARCHY = {
44
47
  "Mail.ReadWrite": ["Mail.Read"],
45
48
  "Calendars.ReadWrite": ["Calendars.Read"],
@@ -87,7 +90,7 @@ function buildScopesFromEndpoints(includeWorkAccountScopes = false, enabledTools
87
90
  return scopes;
88
91
  }
89
92
  class AuthManager {
90
- constructor(config = DEFAULT_CONFIG, scopes = buildScopesFromEndpoints()) {
93
+ constructor(config, scopes = buildScopesFromEndpoints()) {
91
94
  logger.info(`And scopes are ${scopes.join(", ")}`, scopes);
92
95
  this.config = config;
93
96
  this.scopes = scopes;
@@ -99,6 +102,15 @@ class AuthManager {
99
102
  this.oauthToken = oauthTokenFromEnv ?? null;
100
103
  this.isOAuthMode = oauthTokenFromEnv != null;
101
104
  }
105
+ /**
106
+ * Creates an AuthManager instance with secrets loaded from the configured provider.
107
+ * Uses Key Vault if MS365_MCP_KEYVAULT_URL is set, otherwise environment variables.
108
+ */
109
+ static async create(scopes = buildScopesFromEndpoints()) {
110
+ const secrets = await getSecrets();
111
+ const config = createMsalConfig(secrets);
112
+ return new AuthManager(config, scopes);
113
+ }
102
114
  async loadTokenCache() {
103
115
  try {
104
116
  let cacheData;
@@ -162,13 +174,13 @@ class AuthManager {
162
174
  if (kt) {
163
175
  await kt.setPassword(SERVICE_NAME, TOKEN_CACHE_ACCOUNT, cacheData);
164
176
  } else {
165
- fs.writeFileSync(FALLBACK_PATH, cacheData);
177
+ fs.writeFileSync(FALLBACK_PATH, cacheData, { mode: 384 });
166
178
  }
167
179
  } catch (keytarError) {
168
180
  logger.warn(
169
181
  `Keychain save failed, falling back to file storage: ${keytarError.message}`
170
182
  );
171
- fs.writeFileSync(FALLBACK_PATH, cacheData);
183
+ fs.writeFileSync(FALLBACK_PATH, cacheData, { mode: 384 });
172
184
  }
173
185
  } catch (error) {
174
186
  logger.error(`Error saving token cache: ${error.message}`);
@@ -182,13 +194,13 @@ class AuthManager {
182
194
  if (kt) {
183
195
  await kt.setPassword(SERVICE_NAME, SELECTED_ACCOUNT_KEY, selectedAccountData);
184
196
  } else {
185
- fs.writeFileSync(SELECTED_ACCOUNT_PATH, selectedAccountData);
197
+ fs.writeFileSync(SELECTED_ACCOUNT_PATH, selectedAccountData, { mode: 384 });
186
198
  }
187
199
  } catch (keytarError) {
188
200
  logger.warn(
189
201
  `Keychain save failed for selected account, falling back to file storage: ${keytarError.message}`
190
202
  );
191
- fs.writeFileSync(SELECTED_ACCOUNT_PATH, selectedAccountData);
203
+ fs.writeFileSync(SELECTED_ACCOUNT_PATH, selectedAccountData, { mode: 384 });
192
204
  }
193
205
  } catch (error) {
194
206
  logger.error(`Error saving selected account: ${error.message}`);
@@ -2,11 +2,12 @@ import logger from "./logger.js";
2
2
  import { refreshAccessToken } from "./lib/microsoft-auth.js";
3
3
  import { encode as toonEncode } from "@toon-format/toon";
4
4
  class GraphClient {
5
- constructor(authManager, outputFormat = "json") {
5
+ constructor(authManager, secrets, outputFormat = "json") {
6
6
  this.accessToken = null;
7
7
  this.refreshToken = null;
8
8
  this.outputFormat = "json";
9
9
  this.authManager = authManager;
10
+ this.secrets = secrets;
10
11
  this.outputFormat = outputFormat;
11
12
  }
12
13
  setOAuthTokens(accessToken, refreshToken) {
@@ -72,9 +73,9 @@ class GraphClient {
72
73
  }
73
74
  }
74
75
  async refreshAccessToken(refreshToken) {
75
- const tenantId = process.env.MS365_MCP_TENANT_ID || "common";
76
- const clientId = process.env.MS365_MCP_CLIENT_ID || "084a3e9f-a9f4-43f7-89f9-d229cf97853e";
77
- const clientSecret = process.env.MS365_MCP_CLIENT_SECRET;
76
+ const tenantId = this.secrets.tenantId || "common";
77
+ const clientId = this.secrets.clientId;
78
+ const clientSecret = this.secrets.clientSecret;
78
79
  if (clientSecret) {
79
80
  logger.info("GraphClient: Refreshing token with confidential client");
80
81
  } else {
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ async function main() {
13
13
  logger.info("Organization mode enabled - including work account scopes");
14
14
  }
15
15
  const scopes = buildScopesFromEndpoints(includeWorkScopes, args.enabledTools);
16
- const authManager = new AuthManager(void 0, scopes);
16
+ const authManager = await AuthManager.create(scopes);
17
17
  await authManager.loadTokenCache();
18
18
  if (args.login) {
19
19
  await authManager.acquireTokenByDeviceCode();
@@ -1,9 +1,9 @@
1
1
  import { ProxyOAuthServerProvider } from "@modelcontextprotocol/sdk/server/auth/providers/proxyProvider.js";
2
2
  import logger from "./logger.js";
3
3
  class MicrosoftOAuthProvider extends ProxyOAuthServerProvider {
4
- constructor(authManager) {
5
- const tenantId = process.env.MS365_MCP_TENANT_ID || "common";
6
- const clientId = process.env.MS365_MCP_CLIENT_ID || "084a3e9f-a9f4-43f7-89f9-d229cf97853e";
4
+ constructor(authManager, secrets) {
5
+ const tenantId = secrets.tenantId || "common";
6
+ const clientId = secrets.clientId;
7
7
  super({
8
8
  endpoints: {
9
9
  authorizationUrl: `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`,
@@ -0,0 +1,61 @@
1
+ import logger from "./logger.js";
2
+ class EnvironmentSecretsProvider {
3
+ async getSecrets() {
4
+ return {
5
+ clientId: process.env.MS365_MCP_CLIENT_ID || "",
6
+ tenantId: process.env.MS365_MCP_TENANT_ID || "common",
7
+ clientSecret: process.env.MS365_MCP_CLIENT_SECRET
8
+ };
9
+ }
10
+ }
11
+ class KeyVaultSecretsProvider {
12
+ constructor(vaultUrl) {
13
+ this.vaultUrl = vaultUrl;
14
+ }
15
+ async getSecrets() {
16
+ const { DefaultAzureCredential } = await import("@azure/identity");
17
+ const { SecretClient } = await import("@azure/keyvault-secrets");
18
+ const credential = new DefaultAzureCredential();
19
+ const client = new SecretClient(this.vaultUrl, credential);
20
+ logger.info(`Fetching secrets from Key Vault: ${this.vaultUrl}`);
21
+ const [clientIdSecret, tenantIdSecret, clientSecretResult] = await Promise.all([
22
+ client.getSecret("ms365-mcp-client-id"),
23
+ client.getSecret("ms365-mcp-tenant-id").catch(() => null),
24
+ client.getSecret("ms365-mcp-client-secret").catch(() => null)
25
+ ]);
26
+ if (!clientIdSecret.value) {
27
+ throw new Error("Required secret ms365-mcp-client-id not found in Key Vault");
28
+ }
29
+ logger.info("Successfully retrieved secrets from Key Vault");
30
+ return {
31
+ clientId: clientIdSecret.value,
32
+ tenantId: tenantIdSecret?.value || "common",
33
+ clientSecret: clientSecretResult?.value
34
+ };
35
+ }
36
+ }
37
+ function createSecretsProvider() {
38
+ const vaultUrl = process.env.MS365_MCP_KEYVAULT_URL;
39
+ if (vaultUrl) {
40
+ logger.info("Key Vault URL configured, using Azure Key Vault for secrets");
41
+ return new KeyVaultSecretsProvider(vaultUrl);
42
+ }
43
+ logger.info("Using environment variables for secrets");
44
+ return new EnvironmentSecretsProvider();
45
+ }
46
+ let cachedSecrets = null;
47
+ async function getSecrets() {
48
+ if (cachedSecrets) {
49
+ return cachedSecrets;
50
+ }
51
+ const provider = createSecretsProvider();
52
+ cachedSecrets = await provider.getSecrets();
53
+ return cachedSecrets;
54
+ }
55
+ function clearSecretsCache() {
56
+ cachedSecrets = null;
57
+ }
58
+ export {
59
+ clearSecretsCache,
60
+ getSecrets
61
+ };
package/dist/server.js CHANGED
@@ -3,7 +3,6 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
3
3
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
4
4
  import { mcpAuthRouter } from "@modelcontextprotocol/sdk/server/auth/router.js";
5
5
  import express from "express";
6
- import crypto from "crypto";
7
6
  import logger, { enableConsoleLogging } from "./logger.js";
8
7
  import { registerAuthTools } from "./auth-tools.js";
9
8
  import { registerGraphTools, registerDiscoveryTools } from "./graph-tools.js";
@@ -15,7 +14,7 @@ import {
15
14
  microsoftBearerTokenAuthMiddleware,
16
15
  refreshAccessToken
17
16
  } from "./lib/microsoft-auth.js";
18
- const registeredClients = /* @__PURE__ */ new Map();
17
+ import { getSecrets } from "./secrets.js";
19
18
  function parseHttpOption(httpOption) {
20
19
  if (typeof httpOption === "boolean") {
21
20
  return { host: void 0, port: 3e3 };
@@ -34,11 +33,14 @@ class MicrosoftGraphServer {
34
33
  constructor(authManager, options = {}) {
35
34
  this.authManager = authManager;
36
35
  this.options = options;
37
- const outputFormat = options.toon ? "toon" : "json";
38
- this.graphClient = new GraphClient(authManager, outputFormat);
36
+ this.graphClient = null;
39
37
  this.server = null;
38
+ this.secrets = null;
40
39
  }
41
40
  async initialize(version) {
41
+ this.secrets = await getSecrets();
42
+ const outputFormat = this.options.toon ? "toon" : "json";
43
+ this.graphClient = new GraphClient(this.authManager, this.secrets, outputFormat);
42
44
  this.server = new McpServer({
43
45
  name: "Microsoft365MCP",
44
46
  version
@@ -70,10 +72,10 @@ class MicrosoftGraphServer {
70
72
  enableConsoleLogging();
71
73
  }
72
74
  logger.info("Microsoft 365 MCP Server starting...");
73
- logger.info("Environment Variables Check:", {
74
- CLIENT_ID: process.env.MS365_MCP_CLIENT_ID ? `${process.env.MS365_MCP_CLIENT_ID.substring(0, 8)}...` : "NOT SET",
75
- CLIENT_SECRET: process.env.MS365_MCP_CLIENT_SECRET ? `${process.env.MS365_MCP_CLIENT_SECRET.substring(0, 8)}...` : "NOT SET",
76
- TENANT_ID: process.env.MS365_MCP_TENANT_ID || "NOT SET",
75
+ logger.info("Secrets Check:", {
76
+ CLIENT_ID: this.secrets?.clientId ? `${this.secrets.clientId.substring(0, 8)}...` : "NOT SET",
77
+ CLIENT_SECRET: this.secrets?.clientSecret ? "SET" : "NOT SET",
78
+ TENANT_ID: this.secrets?.tenantId || "NOT SET",
77
79
  NODE_ENV: process.env.NODE_ENV || "NOT SET"
78
80
  });
79
81
  if (this.options.readOnly) {
@@ -85,8 +87,9 @@ class MicrosoftGraphServer {
85
87
  app.set("trust proxy", true);
86
88
  app.use(express.json());
87
89
  app.use(express.urlencoded({ extended: true }));
90
+ const corsOrigin = process.env.MS365_MCP_CORS_ORIGIN || "*";
88
91
  app.use((req, res, next) => {
89
- res.header("Access-Control-Allow-Origin", "*");
92
+ res.header("Access-Control-Allow-Origin", corsOrigin);
90
93
  res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
91
94
  res.header(
92
95
  "Access-Control-Allow-Headers",
@@ -98,7 +101,7 @@ class MicrosoftGraphServer {
98
101
  }
99
102
  next();
100
103
  });
101
- const oauthProvider = new MicrosoftOAuthProvider(this.authManager);
104
+ const oauthProvider = new MicrosoftOAuthProvider(this.authManager, this.secrets);
102
105
  app.get("/.well-known/oauth-authorization-server", async (req, res) => {
103
106
  const protocol = req.secure ? "https" : "http";
104
107
  const url = new URL(`${protocol}://${req.get("host")}`);
@@ -107,7 +110,6 @@ class MicrosoftGraphServer {
107
110
  issuer: url.origin,
108
111
  authorization_endpoint: `${url.origin}/authorize`,
109
112
  token_endpoint: `${url.origin}/token`,
110
- registration_endpoint: `${url.origin}/register`,
111
113
  response_types_supported: ["code"],
112
114
  response_modes_supported: ["query"],
113
115
  grant_types_supported: ["authorization_code", "refresh_token"],
@@ -128,33 +130,10 @@ class MicrosoftGraphServer {
128
130
  resource_documentation: `${url.origin}`
129
131
  });
130
132
  });
131
- app.post("/register", async (req, res) => {
132
- const body = req.body;
133
- const clientId = crypto.randomUUID();
134
- registeredClients.set(clientId, {
135
- client_id: clientId,
136
- client_name: body.client_name || "MCP Client",
137
- redirect_uris: body.redirect_uris || [],
138
- grant_types: body.grant_types || ["authorization_code", "refresh_token"],
139
- response_types: body.response_types || ["code"],
140
- scope: body.scope,
141
- token_endpoint_auth_method: "none",
142
- created_at: Date.now()
143
- });
144
- res.status(201).json({
145
- client_id: clientId,
146
- client_name: body.client_name || "MCP Client",
147
- redirect_uris: body.redirect_uris || [],
148
- grant_types: body.grant_types || ["authorization_code", "refresh_token"],
149
- response_types: body.response_types || ["code"],
150
- scope: body.scope,
151
- token_endpoint_auth_method: "none"
152
- });
153
- });
154
133
  app.get("/authorize", async (req, res) => {
155
134
  const url = new URL(req.url, `${req.protocol}://${req.get("host")}`);
156
- const tenantId = process.env.MS365_MCP_TENANT_ID || "common";
157
- const clientId = process.env.MS365_MCP_CLIENT_ID || "084a3e9f-a9f4-43f7-89f9-d229cf97853e";
135
+ const tenantId = this.secrets?.tenantId || "common";
136
+ const clientId = this.secrets.clientId;
158
137
  const microsoftAuthUrl = new URL(
159
138
  `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`
160
139
  );
@@ -187,11 +166,8 @@ class MicrosoftGraphServer {
187
166
  logger.info("Token endpoint called", {
188
167
  method: req.method,
189
168
  url: req.url,
190
- headers: req.headers,
191
- bodyType: typeof req.body,
192
- body: req.body,
193
- rawBody: JSON.stringify(req.body),
194
- contentType: req.get("Content-Type")
169
+ contentType: req.get("Content-Type"),
170
+ grant_type: req.body?.grant_type
195
171
  });
196
172
  const body = req.body;
197
173
  if (!body) {
@@ -211,9 +187,9 @@ class MicrosoftGraphServer {
211
187
  return;
212
188
  }
213
189
  if (body.grant_type === "authorization_code") {
214
- const tenantId = process.env.MS365_MCP_TENANT_ID || "common";
215
- const clientId = process.env.MS365_MCP_CLIENT_ID || "084a3e9f-a9f4-43f7-89f9-d229cf97853e";
216
- const clientSecret = process.env.MS365_MCP_CLIENT_SECRET;
190
+ const tenantId = this.secrets?.tenantId || "common";
191
+ const clientId = this.secrets.clientId;
192
+ const clientSecret = this.secrets?.clientSecret;
217
193
  if (clientSecret) {
218
194
  logger.info("Token endpoint: Using confidential client with client_secret");
219
195
  } else {
@@ -229,9 +205,9 @@ class MicrosoftGraphServer {
229
205
  );
230
206
  res.json(result);
231
207
  } else if (body.grant_type === "refresh_token") {
232
- const tenantId = process.env.MS365_MCP_TENANT_ID || "common";
233
- const clientId = process.env.MS365_MCP_CLIENT_ID || "084a3e9f-a9f4-43f7-89f9-d229cf97853e";
234
- const clientSecret = process.env.MS365_MCP_CLIENT_SECRET;
208
+ const tenantId = this.secrets?.tenantId || "common";
209
+ const clientId = this.secrets.clientId;
210
+ const clientSecret = this.secrets?.clientSecret;
235
211
  if (clientSecret) {
236
212
  logger.info("Refresh endpoint: Using confidential client with client_secret");
237
213
  } else {
package/logs/error.log ADDED
File without changes
@@ -0,0 +1,5 @@
1
+ 2025-12-23 08:30:22 INFO: Using environment variables for secrets
2
+ 2025-12-23 08:30:22 INFO: Using environment variables for secrets
3
+ 2025-12-23 08:30:22 INFO: Using environment variables for secrets
4
+ 2025-12-23 08:30:22 INFO: Using environment variables for secrets
5
+ 2025-12-23 08:30:22 INFO: Using environment variables for secrets
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softeria/ms-365-mcp-server",
3
- "version": "0.28.1",
3
+ "version": "0.28.3",
4
4
  "description": " A Model Context Protocol (MCP) server for interacting with Microsoft 365 and Office services through the Graph API",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -44,6 +44,8 @@
44
44
  "zod": "^3.24.2"
45
45
  },
46
46
  "optionalDependencies": {
47
+ "@azure/identity": "^4.5.0",
48
+ "@azure/keyvault-secrets": "^4.9.0",
47
49
  "keytar": "^7.9.0"
48
50
  },
49
51
  "devDependencies": {
@@ -56,6 +58,7 @@
56
58
  "@types/node": "^22.15.15",
57
59
  "@typescript-eslint/eslint-plugin": "^8.38.0",
58
60
  "@typescript-eslint/parser": "^8.38.0",
61
+ "@vitest/coverage-v8": "^3.2.4",
59
62
  "eslint": "^9.31.0",
60
63
  "globals": "^16.3.0",
61
64
  "patch-package": "^8.0.1",