ms365-mcp-server 1.0.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/LICENSE +21 -0
- package/README.md +497 -0
- package/bin/cli.js +247 -0
- package/dist/index.js +1251 -0
- package/dist/utils/api.js +43 -0
- package/dist/utils/credential-store.js +258 -0
- package/dist/utils/ms365-auth-enhanced.js +639 -0
- package/dist/utils/ms365-auth.js +363 -0
- package/dist/utils/ms365-operations.js +644 -0
- package/dist/utils/multi-user-auth.js +359 -0
- package/install.js +41 -0
- package/package.json +59 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { spawn } from 'child_process';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
|
|
9
|
+
// Debug info - write to a debug file
|
|
10
|
+
const debugFile = path.join(os.tmpdir(), 'ms365-mcp-debug.log');
|
|
11
|
+
const writeDebug = (message) => {
|
|
12
|
+
try {
|
|
13
|
+
fs.appendFileSync(debugFile, `${new Date().toISOString()} - ${message}\n`);
|
|
14
|
+
} catch (err) {
|
|
15
|
+
// Silently fail if we can't write to the debug file
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Start debugging
|
|
20
|
+
writeDebug('MS365 MCP CLI script started');
|
|
21
|
+
writeDebug(`Node version: ${process.version}`);
|
|
22
|
+
writeDebug(`Platform: ${process.platform}`);
|
|
23
|
+
writeDebug(`CLI Arguments: ${process.argv.join(' ')}`);
|
|
24
|
+
writeDebug(`Is stdin a TTY: ${process.stdin.isTTY}`);
|
|
25
|
+
writeDebug(`Is stdout a TTY: ${process.stdout.isTTY}`);
|
|
26
|
+
writeDebug(`Process PID: ${process.pid}`);
|
|
27
|
+
writeDebug(`Executable path: ${process.execPath}`);
|
|
28
|
+
writeDebug(`Current directory: ${process.cwd()}`);
|
|
29
|
+
|
|
30
|
+
// Print debug file location to stderr (not stdout)
|
|
31
|
+
console.error(`Debug log: ${debugFile}`);
|
|
32
|
+
|
|
33
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
34
|
+
const __dirname = path.dirname(__filename);
|
|
35
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
36
|
+
|
|
37
|
+
writeDebug(`__filename: ${__filename}`);
|
|
38
|
+
writeDebug(`__dirname: ${__dirname}`);
|
|
39
|
+
writeDebug(`packageRoot: ${packageRoot}`);
|
|
40
|
+
|
|
41
|
+
// Check if bin/cli.js is executable
|
|
42
|
+
try {
|
|
43
|
+
const stats = fs.statSync(__filename);
|
|
44
|
+
const isExecutable = !!(stats.mode & fs.constants.S_IXUSR);
|
|
45
|
+
writeDebug(`Is CLI executable: ${isExecutable}`);
|
|
46
|
+
|
|
47
|
+
// Make it executable if it's not
|
|
48
|
+
if (!isExecutable) {
|
|
49
|
+
fs.chmodSync(__filename, '755');
|
|
50
|
+
writeDebug('Made CLI executable');
|
|
51
|
+
}
|
|
52
|
+
} catch (err) {
|
|
53
|
+
writeDebug(`Error checking/setting executable: ${err.message}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Parse command line arguments
|
|
57
|
+
const args = process.argv.slice(2);
|
|
58
|
+
let debug = false;
|
|
59
|
+
let nonInteractive = false;
|
|
60
|
+
let setupAuth = false;
|
|
61
|
+
let resetAuth = false;
|
|
62
|
+
let multiUser = false;
|
|
63
|
+
let login = false;
|
|
64
|
+
let logout = false;
|
|
65
|
+
let verifyLogin = false;
|
|
66
|
+
|
|
67
|
+
// Detect if we're running under an MCP context (Claude/ChatGPT/etc.)
|
|
68
|
+
const isMcpContext =
|
|
69
|
+
!process.stdin.isTTY ||
|
|
70
|
+
process.env.npm_execpath?.includes('npx') ||
|
|
71
|
+
process.env.CLAUDE_API_KEY ||
|
|
72
|
+
args.includes('--non-interactive') || args.includes('-n');
|
|
73
|
+
|
|
74
|
+
writeDebug(`Detected MCP context: ${isMcpContext}`);
|
|
75
|
+
if (isMcpContext) {
|
|
76
|
+
nonInteractive = true;
|
|
77
|
+
writeDebug('Setting non-interactive mode due to MCP context detection');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Process command line arguments
|
|
81
|
+
for (let i = 0; i < args.length; i++) {
|
|
82
|
+
const arg = args[i];
|
|
83
|
+
if (arg === '--debug') {
|
|
84
|
+
debug = true;
|
|
85
|
+
writeDebug('Debug mode enabled');
|
|
86
|
+
} else if (arg === '--non-interactive' || arg === '-n') {
|
|
87
|
+
nonInteractive = true;
|
|
88
|
+
writeDebug('Non-interactive mode enabled via flag');
|
|
89
|
+
} else if (arg === '--setup-auth') {
|
|
90
|
+
setupAuth = true;
|
|
91
|
+
writeDebug('Setup auth mode enabled');
|
|
92
|
+
} else if (arg === '--reset-auth') {
|
|
93
|
+
resetAuth = true;
|
|
94
|
+
writeDebug('Reset auth mode enabled');
|
|
95
|
+
} else if (arg === '--multi-user') {
|
|
96
|
+
multiUser = true;
|
|
97
|
+
writeDebug('Multi-user mode enabled');
|
|
98
|
+
} else if (arg === '--login') {
|
|
99
|
+
login = true;
|
|
100
|
+
writeDebug('Login mode enabled');
|
|
101
|
+
} else if (arg === '--logout') {
|
|
102
|
+
logout = true;
|
|
103
|
+
writeDebug('Logout mode enabled');
|
|
104
|
+
} else if (arg === '--verify-login') {
|
|
105
|
+
verifyLogin = true;
|
|
106
|
+
writeDebug('Verify login mode enabled');
|
|
107
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
108
|
+
console.log(`
|
|
109
|
+
MS365 MCP Server - Microsoft 365 Integration for Claude Desktop
|
|
110
|
+
|
|
111
|
+
Usage: npx ms365-mcp-server [options]
|
|
112
|
+
|
|
113
|
+
Options:
|
|
114
|
+
--setup-auth Set up MS365 API credentials
|
|
115
|
+
--reset-auth Clear stored authentication tokens
|
|
116
|
+
--multi-user Enable multi-user authentication mode
|
|
117
|
+
--login Login to MS365
|
|
118
|
+
--logout Logout from MS365
|
|
119
|
+
--verify-login Verify login to MS365
|
|
120
|
+
--debug Enable debug output
|
|
121
|
+
--non-interactive, -n Run in non-interactive mode (no prompt)
|
|
122
|
+
--help, -h Show this help message
|
|
123
|
+
|
|
124
|
+
Setup:
|
|
125
|
+
1. Run: npx ms365-mcp-server --setup-auth
|
|
126
|
+
2. Follow the instructions to set up Azure App Registration
|
|
127
|
+
3. Run: npx ms365-mcp-server to start the server
|
|
128
|
+
|
|
129
|
+
Authentication Setup:
|
|
130
|
+
The server supports two methods for providing MS365 API credentials:
|
|
131
|
+
|
|
132
|
+
1. Environment Variables (Recommended):
|
|
133
|
+
- MS365_CLIENT_ID: Your Azure app client ID
|
|
134
|
+
- MS365_CLIENT_SECRET: Your Azure app client secret
|
|
135
|
+
- MS365_TENANT_ID: Your Azure tenant ID (or "common")
|
|
136
|
+
- MS365_REDIRECT_URI: OAuth redirect URI (optional)
|
|
137
|
+
|
|
138
|
+
2. Credentials File:
|
|
139
|
+
- Run --setup-auth for interactive setup
|
|
140
|
+
- Saves credentials to ~/.ms365-mcp/credentials.json
|
|
141
|
+
|
|
142
|
+
Azure App Registration:
|
|
143
|
+
1. Go to https://portal.azure.com
|
|
144
|
+
2. Navigate to Azure Active Directory > App registrations
|
|
145
|
+
3. Click "New registration"
|
|
146
|
+
4. Set redirect URI to: http://localhost:44001/oauth2callback
|
|
147
|
+
5. Grant required API permissions for Microsoft Graph:
|
|
148
|
+
- Mail.ReadWrite
|
|
149
|
+
- Mail.Send
|
|
150
|
+
- MailboxSettings.Read
|
|
151
|
+
- Contacts.Read
|
|
152
|
+
- User.Read
|
|
153
|
+
|
|
154
|
+
Note: Device code flow (--login) doesn't require Azure app registration!
|
|
155
|
+
|
|
156
|
+
Examples:
|
|
157
|
+
npx ms365-mcp-server --setup-auth # Set up authentication
|
|
158
|
+
npx ms365-mcp-server # Start the server
|
|
159
|
+
npx ms365-mcp-server --multi-user # Start in multi-user mode
|
|
160
|
+
npx ms365-mcp-server --reset-auth # Clear auth tokens
|
|
161
|
+
`);
|
|
162
|
+
process.exit(0);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function startServer() {
|
|
167
|
+
const serverPath = path.join(packageRoot, 'dist', 'index.js');
|
|
168
|
+
|
|
169
|
+
// Check if the compiled server exists
|
|
170
|
+
if (!fs.existsSync(serverPath)) {
|
|
171
|
+
console.error('Server not found. Building...');
|
|
172
|
+
|
|
173
|
+
// Try to build the project
|
|
174
|
+
const buildProcess = spawn('npm', ['run', 'build'], {
|
|
175
|
+
cwd: packageRoot,
|
|
176
|
+
stdio: 'inherit'
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
buildProcess.on('close', (code) => {
|
|
180
|
+
if (code === 0) {
|
|
181
|
+
startServerWithPath(serverPath);
|
|
182
|
+
} else {
|
|
183
|
+
console.error('Build failed. Please run "npm run build" manually.');
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
} else {
|
|
188
|
+
startServerWithPath(serverPath);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function startServerWithPath(serverPath) {
|
|
193
|
+
writeDebug(`Starting server with path: ${serverPath}`);
|
|
194
|
+
|
|
195
|
+
// Prepare arguments for the server
|
|
196
|
+
const serverArgs = [];
|
|
197
|
+
|
|
198
|
+
if (setupAuth) serverArgs.push('--setup-auth');
|
|
199
|
+
if (resetAuth) serverArgs.push('--reset-auth');
|
|
200
|
+
if (multiUser) serverArgs.push('--multi-user');
|
|
201
|
+
if (login) serverArgs.push('--login');
|
|
202
|
+
if (logout) serverArgs.push('--logout');
|
|
203
|
+
if (verifyLogin) serverArgs.push('--verify-login');
|
|
204
|
+
if (debug) serverArgs.push('--debug');
|
|
205
|
+
if (nonInteractive) serverArgs.push('--non-interactive');
|
|
206
|
+
|
|
207
|
+
writeDebug(`Server arguments: ${serverArgs.join(' ')}`);
|
|
208
|
+
|
|
209
|
+
// Start the server process
|
|
210
|
+
const serverProcess = spawn(process.execPath, [serverPath, ...serverArgs], {
|
|
211
|
+
stdio: 'inherit',
|
|
212
|
+
env: {
|
|
213
|
+
...process.env,
|
|
214
|
+
// Ensure environment variables are passed through
|
|
215
|
+
NODE_PATH: process.env.NODE_PATH || '',
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
// Handle server process events
|
|
220
|
+
serverProcess.on('error', (err) => {
|
|
221
|
+
writeDebug(`Server process error: ${err.message}`);
|
|
222
|
+
console.error('Failed to start MS365 MCP server:', err.message);
|
|
223
|
+
process.exit(1);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
serverProcess.on('close', (code, signal) => {
|
|
227
|
+
writeDebug(`Server process closed with code ${code} and signal ${signal}`);
|
|
228
|
+
if (code !== 0) {
|
|
229
|
+
console.error(`MS365 MCP server exited with code ${code}`);
|
|
230
|
+
}
|
|
231
|
+
process.exit(code || 0);
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Handle SIGINT and SIGTERM
|
|
235
|
+
process.on('SIGINT', () => {
|
|
236
|
+
writeDebug('Received SIGINT, terminating server process');
|
|
237
|
+
serverProcess.kill('SIGINT');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
process.on('SIGTERM', () => {
|
|
241
|
+
writeDebug('Received SIGTERM, terminating server process');
|
|
242
|
+
serverProcess.kill('SIGTERM');
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Start the server
|
|
247
|
+
startServer();
|