ms365-mcp-server 1.1.24 → 2.1.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/bin/cli.js +81 -31
- package/dist/index.js +59 -102
- package/dist/utils/config-dir.js +3 -1
- package/dist/utils/ms365-auth.js +1 -1
- package/dist/utils/ms365-operations.js +4 -9
- package/dist/utils/outlook-auth.js +614 -0
- package/dist/utils/outlook-credential-store.js +195 -0
- package/package.json +8 -1
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as os from 'os';
|
|
4
|
+
import { logger } from './api.js';
|
|
5
|
+
// Configuration directory
|
|
6
|
+
const CONFIG_DIR = path.join(os.homedir(), '.outlook-mcp');
|
|
7
|
+
const CREDENTIALS_FILE = path.join(CONFIG_DIR, 'credentials.json');
|
|
8
|
+
const TOKEN_FILE = path.join(CONFIG_DIR, 'token.json');
|
|
9
|
+
const MSAL_CACHE_FILE = path.join(CONFIG_DIR, 'msal-cache.json');
|
|
10
|
+
// File permissions
|
|
11
|
+
const FILE_MODE = 0o600; // Owner read/write only
|
|
12
|
+
const DIR_MODE = 0o700; // Owner read/write/execute only
|
|
13
|
+
/**
|
|
14
|
+
* Credential store for Outlook MCP server
|
|
15
|
+
* Uses ~/.outlook-mcp/ for secure storage
|
|
16
|
+
*/
|
|
17
|
+
export class OutlookCredentialStore {
|
|
18
|
+
constructor() {
|
|
19
|
+
this.ensureConfigDir();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Ensure configuration directory exists with proper permissions
|
|
23
|
+
*/
|
|
24
|
+
ensureConfigDir() {
|
|
25
|
+
try {
|
|
26
|
+
if (!fs.existsSync(CONFIG_DIR)) {
|
|
27
|
+
fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: DIR_MODE });
|
|
28
|
+
logger.log(`Created config directory: ${CONFIG_DIR}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
logger.error('Failed to create config directory:', error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Get the config directory path
|
|
37
|
+
*/
|
|
38
|
+
getConfigDir() {
|
|
39
|
+
return CONFIG_DIR;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Save credentials to file with secure permissions
|
|
43
|
+
*/
|
|
44
|
+
async saveCredentials(credentials) {
|
|
45
|
+
try {
|
|
46
|
+
this.ensureConfigDir();
|
|
47
|
+
const data = JSON.stringify(credentials, null, 2);
|
|
48
|
+
fs.writeFileSync(CREDENTIALS_FILE, data, { mode: FILE_MODE });
|
|
49
|
+
logger.log('Saved credentials to file');
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
logger.error('Failed to save credentials:', error);
|
|
53
|
+
throw new Error('Failed to save credentials');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Get stored credentials
|
|
58
|
+
*/
|
|
59
|
+
async getCredentials() {
|
|
60
|
+
try {
|
|
61
|
+
if (!fs.existsSync(CREDENTIALS_FILE)) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
const data = fs.readFileSync(CREDENTIALS_FILE, 'utf8');
|
|
65
|
+
return JSON.parse(data);
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
logger.error('Failed to read credentials:', error);
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Save tokens to file with secure permissions
|
|
74
|
+
*/
|
|
75
|
+
async saveTokens(tokens) {
|
|
76
|
+
try {
|
|
77
|
+
this.ensureConfigDir();
|
|
78
|
+
const data = JSON.stringify(tokens, null, 2);
|
|
79
|
+
fs.writeFileSync(TOKEN_FILE, data, { mode: FILE_MODE });
|
|
80
|
+
logger.log(`Saved tokens (expires: ${new Date(tokens.expiresOn).toLocaleString()})`);
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
logger.error('Failed to save tokens:', error);
|
|
84
|
+
throw new Error('Failed to save tokens');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Get stored tokens
|
|
89
|
+
*/
|
|
90
|
+
async getTokens() {
|
|
91
|
+
try {
|
|
92
|
+
if (!fs.existsSync(TOKEN_FILE)) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
const data = fs.readFileSync(TOKEN_FILE, 'utf8');
|
|
96
|
+
return JSON.parse(data);
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
logger.error('Failed to read tokens:', error);
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Save MSAL cache
|
|
105
|
+
*/
|
|
106
|
+
async saveMsalCache(cacheData) {
|
|
107
|
+
try {
|
|
108
|
+
this.ensureConfigDir();
|
|
109
|
+
fs.writeFileSync(MSAL_CACHE_FILE, cacheData, { mode: FILE_MODE });
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
logger.error('Failed to save MSAL cache:', error);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get MSAL cache
|
|
117
|
+
*/
|
|
118
|
+
async getMsalCache() {
|
|
119
|
+
try {
|
|
120
|
+
if (!fs.existsSync(MSAL_CACHE_FILE)) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
return fs.readFileSync(MSAL_CACHE_FILE, 'utf8');
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
logger.error('Failed to read MSAL cache:', error);
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Clear all stored data
|
|
132
|
+
*/
|
|
133
|
+
async clearAll() {
|
|
134
|
+
try {
|
|
135
|
+
if (fs.existsSync(TOKEN_FILE)) {
|
|
136
|
+
fs.unlinkSync(TOKEN_FILE);
|
|
137
|
+
logger.log('Deleted token file');
|
|
138
|
+
}
|
|
139
|
+
if (fs.existsSync(CREDENTIALS_FILE)) {
|
|
140
|
+
fs.unlinkSync(CREDENTIALS_FILE);
|
|
141
|
+
logger.log('Deleted credentials file');
|
|
142
|
+
}
|
|
143
|
+
if (fs.existsSync(MSAL_CACHE_FILE)) {
|
|
144
|
+
fs.unlinkSync(MSAL_CACHE_FILE);
|
|
145
|
+
logger.log('Deleted MSAL cache file');
|
|
146
|
+
}
|
|
147
|
+
logger.log('Cleared all stored authentication data');
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
logger.error('Failed to clear stored data:', error);
|
|
151
|
+
throw new Error('Failed to clear stored data');
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Clear only tokens (keep credentials)
|
|
156
|
+
*/
|
|
157
|
+
async clearTokens() {
|
|
158
|
+
try {
|
|
159
|
+
if (fs.existsSync(TOKEN_FILE)) {
|
|
160
|
+
fs.unlinkSync(TOKEN_FILE);
|
|
161
|
+
logger.log('Deleted token file');
|
|
162
|
+
}
|
|
163
|
+
if (fs.existsSync(MSAL_CACHE_FILE)) {
|
|
164
|
+
fs.unlinkSync(MSAL_CACHE_FILE);
|
|
165
|
+
logger.log('Deleted MSAL cache file');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
catch (error) {
|
|
169
|
+
logger.error('Failed to clear tokens:', error);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Check if tokens exist
|
|
174
|
+
*/
|
|
175
|
+
hasTokens() {
|
|
176
|
+
return fs.existsSync(TOKEN_FILE);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Check if credentials exist
|
|
180
|
+
*/
|
|
181
|
+
hasCredentials() {
|
|
182
|
+
return fs.existsSync(CREDENTIALS_FILE);
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Get storage location info
|
|
186
|
+
*/
|
|
187
|
+
getStorageInfo() {
|
|
188
|
+
return {
|
|
189
|
+
directory: CONFIG_DIR,
|
|
190
|
+
hasTokens: this.hasTokens(),
|
|
191
|
+
hasCredentials: this.hasCredentials()
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
export const outlookCredentialStore = new OutlookCredentialStore();
|
package/package.json
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ms365-mcp-server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.1.0",
|
|
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",
|
|
7
7
|
"bin": {
|
|
8
8
|
"ms365-mcp-server": "bin/cli.js"
|
|
9
9
|
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/syia-ai/ms365-mcp-server"
|
|
13
|
+
},
|
|
14
|
+
"publishConfig": {
|
|
15
|
+
"registry": "https://registry.npmjs.org"
|
|
16
|
+
},
|
|
10
17
|
"scripts": {
|
|
11
18
|
"test": "npx @modelcontextprotocol/inspector node dist/index.js",
|
|
12
19
|
"build": "tsc",
|