ms365-mcp-server 1.1.20 ā 1.1.22
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/dist/index.js
CHANGED
|
@@ -975,7 +975,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
975
975
|
const currentUser = await enhancedMS365Auth.getCurrentUser();
|
|
976
976
|
const storageInfo = enhancedMS365Auth.getStorageInfo();
|
|
977
977
|
const tokenInfo = await enhancedMS365Auth.getTokenExpirationInfo();
|
|
978
|
-
let statusText = `š Microsoft 365 Authentication Status\n\nš Authentication: ${isAuthenticated ? 'ā
Valid' : 'ā Not authenticated'}\nš¤ Current User: ${currentUser || 'None'}\n
|
|
978
|
+
let statusText = `š Microsoft 365 Authentication Status\n\nš Authentication: ${isAuthenticated ? 'ā
Valid' : 'ā Not authenticated'}\nš¤ Current User: ${currentUser || 'None'}\n`;
|
|
979
979
|
if (isAuthenticated) {
|
|
980
980
|
statusText += `\nā° Token expires in: ${tokenInfo.expiresInMinutes} minutes`;
|
|
981
981
|
if (tokenInfo.needsRefresh) {
|
|
@@ -1,57 +1,65 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
-
import * as os from 'os';
|
|
4
3
|
import { logger } from './api.js';
|
|
4
|
+
import { getConfigDirWithFallback } from './config-dir.js';
|
|
5
5
|
// Service name for keychain storage
|
|
6
6
|
const SERVICE_NAME = 'ms365-mcp-server';
|
|
7
|
-
//
|
|
8
|
-
const
|
|
7
|
+
// Directory for file-based storage (now primary storage method)
|
|
8
|
+
const STORAGE_DIR = getConfigDirWithFallback();
|
|
9
9
|
/**
|
|
10
|
-
* Secure credential store that uses
|
|
11
|
-
* with
|
|
10
|
+
* Secure credential store that uses file-based storage as default,
|
|
11
|
+
* with optional OS keychain support if specifically enabled.
|
|
12
12
|
*/
|
|
13
13
|
export class CredentialStore {
|
|
14
14
|
constructor() {
|
|
15
15
|
this.useKeychain = false;
|
|
16
16
|
this.keytar = null;
|
|
17
|
-
this.initialized =
|
|
18
|
-
this.
|
|
19
|
-
this.
|
|
20
|
-
|
|
17
|
+
this.initialized = true; // File storage is always ready
|
|
18
|
+
this.forceFileStorage = true; // Force file storage as default
|
|
19
|
+
this.ensureStorageDir();
|
|
20
|
+
// Don't initialize keychain by default - file storage is primary
|
|
21
|
+
logger.log('Using file-based credential storage as default');
|
|
21
22
|
}
|
|
22
23
|
/**
|
|
23
24
|
* Ensure the credential store is initialized
|
|
24
25
|
*/
|
|
25
26
|
async ensureInitialized() {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
this.initialized = true;
|
|
29
|
-
}
|
|
27
|
+
// File storage is always ready, no async initialization needed
|
|
28
|
+
return;
|
|
30
29
|
}
|
|
31
30
|
/**
|
|
32
|
-
*
|
|
31
|
+
* Enable keychain support (optional)
|
|
33
32
|
*/
|
|
34
|
-
async
|
|
33
|
+
async enableKeychain() {
|
|
35
34
|
try {
|
|
36
35
|
// Try to load keytar for secure OS keychain storage
|
|
37
36
|
// Use eval to prevent TypeScript from checking the import at compile time
|
|
38
37
|
const keytarModule = await eval('import("keytar")');
|
|
39
38
|
this.keytar = keytarModule.default || keytarModule;
|
|
40
39
|
this.useKeychain = true;
|
|
40
|
+
this.forceFileStorage = false;
|
|
41
41
|
logger.log('OS keychain support enabled');
|
|
42
|
+
return true;
|
|
42
43
|
}
|
|
43
44
|
catch (error) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
this.useKeychain = false;
|
|
45
|
+
logger.log('OS keychain not available, continuing with file storage');
|
|
46
|
+
return false;
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
49
|
/**
|
|
50
|
-
*
|
|
50
|
+
* Force file storage (disable keychain)
|
|
51
|
+
*/
|
|
52
|
+
forceFileStorageMode() {
|
|
53
|
+
this.useKeychain = false;
|
|
54
|
+
this.forceFileStorage = true;
|
|
55
|
+
logger.log('Forced file storage mode enabled');
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Ensure storage directory exists
|
|
51
59
|
*/
|
|
52
|
-
|
|
53
|
-
if (!fs.existsSync(
|
|
54
|
-
fs.mkdirSync(
|
|
60
|
+
ensureStorageDir() {
|
|
61
|
+
if (!fs.existsSync(STORAGE_DIR)) {
|
|
62
|
+
fs.mkdirSync(STORAGE_DIR, { recursive: true });
|
|
55
63
|
}
|
|
56
64
|
}
|
|
57
65
|
/**
|
|
@@ -59,27 +67,35 @@ export class CredentialStore {
|
|
|
59
67
|
*/
|
|
60
68
|
async setCredentials(account, credentials) {
|
|
61
69
|
await this.ensureInitialized();
|
|
70
|
+
// Always use file storage as primary method
|
|
71
|
+
if (this.forceFileStorage || !this.useKeychain || !this.keytar) {
|
|
72
|
+
await this.setCredentialsFile(account, credentials);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
// Only use keychain if explicitly enabled and file storage is disabled
|
|
62
76
|
const credentialString = JSON.stringify(credentials);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// Fall through to file storage
|
|
72
|
-
}
|
|
77
|
+
try {
|
|
78
|
+
await this.keytar.setPassword(SERVICE_NAME, account, credentialString);
|
|
79
|
+
logger.log(`Stored credentials for ${account} in OS keychain`);
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
logger.error(`Failed to store in keychain for ${account}:`, error);
|
|
83
|
+
// Fall back to file storage
|
|
84
|
+
await this.setCredentialsFile(account, credentials);
|
|
73
85
|
}
|
|
74
|
-
// Fallback to file storage
|
|
75
|
-
await this.setCredentialsFile(account, credentials);
|
|
76
86
|
}
|
|
77
87
|
/**
|
|
78
88
|
* Retrieve credentials securely
|
|
79
89
|
*/
|
|
80
90
|
async getCredentials(account) {
|
|
81
91
|
await this.ensureInitialized();
|
|
82
|
-
|
|
92
|
+
// Always try file storage first
|
|
93
|
+
const fileCredentials = await this.getCredentialsFile(account);
|
|
94
|
+
if (fileCredentials) {
|
|
95
|
+
return fileCredentials;
|
|
96
|
+
}
|
|
97
|
+
// Only check keychain if file storage failed and keychain is enabled
|
|
98
|
+
if (!this.forceFileStorage && this.useKeychain && this.keytar) {
|
|
83
99
|
try {
|
|
84
100
|
const credentialString = await this.keytar.getPassword(SERVICE_NAME, account);
|
|
85
101
|
if (credentialString) {
|
|
@@ -89,11 +105,9 @@ export class CredentialStore {
|
|
|
89
105
|
}
|
|
90
106
|
catch (error) {
|
|
91
107
|
logger.error(`Failed to retrieve from keychain for ${account}:`, error);
|
|
92
|
-
// Fall through to file storage
|
|
93
108
|
}
|
|
94
109
|
}
|
|
95
|
-
|
|
96
|
-
return await this.getCredentialsFile(account);
|
|
110
|
+
return null;
|
|
97
111
|
}
|
|
98
112
|
/**
|
|
99
113
|
* Delete credentials securely
|
|
@@ -138,11 +152,11 @@ export class CredentialStore {
|
|
|
138
152
|
return Array.from(accounts);
|
|
139
153
|
}
|
|
140
154
|
/**
|
|
141
|
-
* File-based credential storage (
|
|
155
|
+
* File-based credential storage (primary method)
|
|
142
156
|
*/
|
|
143
157
|
async setCredentialsFile(account, credentials) {
|
|
144
158
|
try {
|
|
145
|
-
const filePath = path.join(
|
|
159
|
+
const filePath = path.join(STORAGE_DIR, `token_${this.sanitizeFilename(account)}.json`);
|
|
146
160
|
const encryptedData = this.simpleEncrypt(JSON.stringify(credentials));
|
|
147
161
|
fs.writeFileSync(filePath, encryptedData);
|
|
148
162
|
logger.log(`Stored credentials for ${account} in file`);
|
|
@@ -153,11 +167,11 @@ export class CredentialStore {
|
|
|
153
167
|
}
|
|
154
168
|
}
|
|
155
169
|
/**
|
|
156
|
-
* File-based credential retrieval (
|
|
170
|
+
* File-based credential retrieval (primary method)
|
|
157
171
|
*/
|
|
158
172
|
async getCredentialsFile(account) {
|
|
159
173
|
try {
|
|
160
|
-
const filePath = path.join(
|
|
174
|
+
const filePath = path.join(STORAGE_DIR, `token_${this.sanitizeFilename(account)}.json`);
|
|
161
175
|
if (!fs.existsSync(filePath)) {
|
|
162
176
|
return null;
|
|
163
177
|
}
|
|
@@ -172,11 +186,11 @@ export class CredentialStore {
|
|
|
172
186
|
}
|
|
173
187
|
}
|
|
174
188
|
/**
|
|
175
|
-
* File-based credential deletion (
|
|
189
|
+
* File-based credential deletion (primary method)
|
|
176
190
|
*/
|
|
177
191
|
async deleteCredentialsFile(account) {
|
|
178
192
|
try {
|
|
179
|
-
const filePath = path.join(
|
|
193
|
+
const filePath = path.join(STORAGE_DIR, `token_${this.sanitizeFilename(account)}.json`);
|
|
180
194
|
if (fs.existsSync(filePath)) {
|
|
181
195
|
fs.unlinkSync(filePath);
|
|
182
196
|
logger.log(`Deleted credentials for ${account} from file`);
|
|
@@ -194,7 +208,7 @@ export class CredentialStore {
|
|
|
194
208
|
*/
|
|
195
209
|
async listFileAccounts() {
|
|
196
210
|
try {
|
|
197
|
-
const files = fs.readdirSync(
|
|
211
|
+
const files = fs.readdirSync(STORAGE_DIR);
|
|
198
212
|
return files
|
|
199
213
|
.filter(file => file.startsWith('token_') && file.endsWith('.json'))
|
|
200
214
|
.map(file => file.replace('token_', '').replace('.json', ''))
|
|
@@ -246,13 +260,22 @@ export class CredentialStore {
|
|
|
246
260
|
* Check if keychain is available
|
|
247
261
|
*/
|
|
248
262
|
isKeychainAvailable() {
|
|
249
|
-
return this.useKeychain && this.keytar !== null;
|
|
263
|
+
return this.useKeychain && this.keytar !== null && !this.forceFileStorage;
|
|
250
264
|
}
|
|
251
265
|
/**
|
|
252
266
|
* Get storage method being used
|
|
253
267
|
*/
|
|
254
268
|
getStorageMethod() {
|
|
255
|
-
|
|
269
|
+
if (this.forceFileStorage || !this.useKeychain || !this.keytar) {
|
|
270
|
+
return 'Encrypted File';
|
|
271
|
+
}
|
|
272
|
+
return 'OS Keychain';
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Get storage location
|
|
276
|
+
*/
|
|
277
|
+
getStorageLocation() {
|
|
278
|
+
return STORAGE_DIR;
|
|
256
279
|
}
|
|
257
280
|
}
|
|
258
281
|
export const credentialStore = new CredentialStore();
|
|
@@ -835,7 +835,7 @@ export class EnhancedMS365Auth {
|
|
|
835
835
|
getStorageInfo() {
|
|
836
836
|
return {
|
|
837
837
|
method: credentialStore.getStorageMethod(),
|
|
838
|
-
location: credentialStore.
|
|
838
|
+
location: credentialStore.getStorageLocation()
|
|
839
839
|
};
|
|
840
840
|
}
|
|
841
841
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ms365-mcp-server",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.22",
|
|
4
4
|
"description": "Microsoft 365 MCP Server for managing Microsoft 365 email through natural language interactions with full OAuth2 authentication support",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|