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/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();