@softeria/ms-365-mcp-server 0.1.9 → 0.1.11
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/.github/workflows/build.yml +31 -0
- package/.github/workflows/npm-publish.yml +0 -3
- package/README.md +125 -17
- package/auth.mjs +66 -3
- package/bin/release.mjs +33 -0
- package/index.mjs +37 -22
- package/package.json +1 -1
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
name: Build
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main ]
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
build:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
strategy:
|
|
15
|
+
matrix:
|
|
16
|
+
node-version: [ 18.x, 20.x ]
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
|
|
21
|
+
- name: Use Node.js ${{ matrix.node-version }}
|
|
22
|
+
uses: actions/setup-node@v4
|
|
23
|
+
with:
|
|
24
|
+
node-version: ${{ matrix.node-version }}
|
|
25
|
+
cache: 'npm'
|
|
26
|
+
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: npm ci
|
|
29
|
+
|
|
30
|
+
- name: Run tests
|
|
31
|
+
run: npm test
|
package/README.md
CHANGED
|
@@ -4,51 +4,157 @@ Microsoft 365 MCP Server
|
|
|
4
4
|
|
|
5
5
|
A Model Context Protocol (MCP) server for interacting with Microsoft 365 services through the Graph API.
|
|
6
6
|
|
|
7
|
-
[](https://github.com/softeria-eu/ms-365-mcp-server/actions/workflows/build.yml)
|
|
8
|
+
[](https://www.npmjs.com/package/@softeria/ms-365-mcp-server)
|
|
8
9
|
|
|
9
10
|
## Features
|
|
10
11
|
|
|
11
12
|
- Authentication using Microsoft Authentication Library (MSAL)
|
|
12
13
|
- Excel file operations:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
- Update cell values
|
|
15
|
+
- Create and manage charts
|
|
16
|
+
- Format cells
|
|
17
|
+
- Sort data
|
|
18
|
+
- Create tables
|
|
19
|
+
- Read cell values
|
|
20
|
+
- List worksheets
|
|
20
21
|
- Built on the Model Context Protocol
|
|
21
22
|
|
|
22
23
|
## Installation
|
|
23
24
|
|
|
24
25
|
```bash
|
|
25
|
-
|
|
26
|
+
npx @softeria/ms-365-mcp-server
|
|
26
27
|
```
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
## Integration with Claude
|
|
30
|
+
|
|
31
|
+
### Claude Code CLI
|
|
32
|
+
|
|
33
|
+
To add this MCP server to Claude Code CLI:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
claude mcp add ms -- npx @softeria/ms-365-mcp-server
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Claude Desktop
|
|
40
|
+
|
|
41
|
+
To add this MCP server to Claude Desktop:
|
|
42
|
+
|
|
43
|
+
1. Launch Claude Desktop
|
|
44
|
+
2. Go to Settings > MCPs
|
|
45
|
+
3. Click "Add MCP"
|
|
46
|
+
4. Set the following configuration:
|
|
47
|
+
- Name: `ms` (or any name you prefer)
|
|
48
|
+
- Command: `npx @softeria/ms-365-mcp-server`
|
|
49
|
+
- Click "Add"
|
|
50
|
+
|
|
51
|
+
### Direct Configuration
|
|
52
|
+
|
|
53
|
+
You can also use this configuration JSON in compatible Claude interfaces:
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"name": "ms",
|
|
58
|
+
"command": "npx @softeria/ms-365-mcp-server"
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Development
|
|
63
|
+
|
|
64
|
+
### Setup
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Clone the repository
|
|
68
|
+
git clone https://github.com/softeria-eu/ms-365-mcp-server.git
|
|
69
|
+
cd ms-365-mcp-server
|
|
70
|
+
|
|
71
|
+
# Install dependencies
|
|
72
|
+
npm install
|
|
73
|
+
|
|
74
|
+
# Run tests
|
|
75
|
+
npm test
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### GitHub Actions
|
|
79
|
+
|
|
80
|
+
This repository uses GitHub Actions for continuous integration and deployment:
|
|
81
|
+
|
|
82
|
+
- **Build Workflow**: Runs on all pushes to main and pull requests. Verifies the project builds successfully and passes
|
|
83
|
+
all tests.
|
|
84
|
+
- **Publish Workflow**: Automatically publishes to npm when a new GitHub release is created.
|
|
85
|
+
|
|
86
|
+
### Release Process
|
|
87
|
+
|
|
88
|
+
To create a new release:
|
|
29
89
|
|
|
30
90
|
```bash
|
|
31
|
-
|
|
91
|
+
npm run release
|
|
32
92
|
```
|
|
33
93
|
|
|
94
|
+
This script will:
|
|
95
|
+
|
|
96
|
+
1. Run tests to verify everything works
|
|
97
|
+
2. Bump the version number
|
|
98
|
+
3. Commit the version changes
|
|
99
|
+
4. Push to GitHub
|
|
100
|
+
5. Create a GitHub release
|
|
101
|
+
6. Trigger the publish workflow to publish to npm
|
|
102
|
+
|
|
34
103
|
## Usage
|
|
35
104
|
|
|
36
105
|
### Command Line Options
|
|
37
106
|
|
|
38
107
|
```bash
|
|
39
|
-
npx ms-365-mcp-server [options]
|
|
108
|
+
npx @softeria/ms-365-mcp-server [options]
|
|
40
109
|
```
|
|
41
110
|
|
|
42
111
|
Options:
|
|
43
112
|
|
|
44
|
-
- `--login`: Force login using device code flow
|
|
113
|
+
- `--login`: Force login using device code flow and verify Graph API access
|
|
45
114
|
- `--logout`: Log out and clear saved credentials
|
|
46
|
-
- `--
|
|
47
|
-
-
|
|
115
|
+
- `--test-login`: Test current authentication and verify Graph API access without starting the server
|
|
116
|
+
- `-v`: Enable verbose logging
|
|
48
117
|
|
|
49
118
|
### Authentication
|
|
50
119
|
|
|
51
|
-
|
|
120
|
+
**Important:** You must authenticate before using the MCP server. There are two ways to authenticate:
|
|
121
|
+
|
|
122
|
+
1. Running the server with the `--login` flag:
|
|
123
|
+
```bash
|
|
124
|
+
npx @softeria/ms-365-mcp-server --login
|
|
125
|
+
```
|
|
126
|
+
This will display the login URL and code in the terminal.
|
|
127
|
+
|
|
128
|
+
2. When using Claude Code or other MCP clients, use the login tools:
|
|
129
|
+
- First use the `login` tool, which will return the login URL and code
|
|
130
|
+
- Visit the URL and enter the code in your browser
|
|
131
|
+
- Then use the `verify-login` tool to check if the login was successful
|
|
132
|
+
|
|
133
|
+
Both methods trigger the device code flow authentication, but they handle the UI interaction differently:
|
|
134
|
+
|
|
135
|
+
- CLI version displays the instructions directly in the terminal
|
|
136
|
+
- MCP tool version returns the instructions as data that can be shown in the client UI
|
|
137
|
+
|
|
138
|
+
You can verify your authentication status with the `--test-login` flag, which will check if your token can successfully
|
|
139
|
+
fetch user data from Microsoft Graph API:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npx @softeria/ms-365-mcp-server --test-login
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Both `--login` and `--test-login` will return a JSON response that includes your basic user information from Microsoft
|
|
146
|
+
Graph API if authentication is successful:
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"success": true,
|
|
151
|
+
"message": "Login successful",
|
|
152
|
+
"userData": {
|
|
153
|
+
"displayName": "Your Name",
|
|
154
|
+
"userPrincipalName": "your.email@example.com"
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
52
158
|
|
|
53
159
|
Authentication tokens are cached securely in your system's credential store with fallback to file storage if needed.
|
|
54
160
|
|
|
@@ -56,8 +162,10 @@ Authentication tokens are cached securely in your system's credential store with
|
|
|
56
162
|
|
|
57
163
|
This server provides several MCP tools for interacting with Excel files:
|
|
58
164
|
|
|
59
|
-
- `login`:
|
|
165
|
+
- `login`: Start a new login process with Microsoft (returns login URL and code)
|
|
166
|
+
- `verify-login`: Check if login was completed successfully and verify Graph API access
|
|
60
167
|
- `logout`: Log out of Microsoft and clear credentials
|
|
168
|
+
- `test-login`: Test current authentication status and verify Graph API access
|
|
61
169
|
- `update-excel`: Update cell values in an Excel worksheet
|
|
62
170
|
- `create-chart`: Create a chart in an Excel worksheet
|
|
63
171
|
- `format-range`: Apply formatting to a range of cells
|
package/auth.mjs
CHANGED
|
@@ -101,18 +101,24 @@ class AuthManager {
|
|
|
101
101
|
throw new Error('No valid token found');
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
async acquireTokenByDeviceCode() {
|
|
104
|
+
async acquireTokenByDeviceCode(hack) {
|
|
105
105
|
const deviceCodeRequest = {
|
|
106
106
|
scopes: this.scopes,
|
|
107
107
|
deviceCodeCallback: (response) => {
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
const text = ['\n', response.message, '\n'].join('');
|
|
109
|
+
if (hack) {
|
|
110
|
+
hack(text + 'After login run the "test login" command');
|
|
111
|
+
} else {
|
|
112
|
+
console.log(text);
|
|
113
|
+
}
|
|
110
114
|
logger.info('Device code login initiated');
|
|
111
115
|
},
|
|
112
116
|
};
|
|
113
117
|
|
|
114
118
|
try {
|
|
119
|
+
logger.info('Requesting device code...');
|
|
115
120
|
const response = await this.msalApp.acquireTokenByDeviceCode(deviceCodeRequest);
|
|
121
|
+
logger.info('Device code login successful');
|
|
116
122
|
this.accessToken = response.accessToken;
|
|
117
123
|
this.tokenExpiry = new Date(response.expiresOn).getTime();
|
|
118
124
|
await this.saveTokenCache();
|
|
@@ -123,6 +129,63 @@ class AuthManager {
|
|
|
123
129
|
}
|
|
124
130
|
}
|
|
125
131
|
|
|
132
|
+
async testLogin() {
|
|
133
|
+
try {
|
|
134
|
+
logger.info('Testing login...');
|
|
135
|
+
const token = await this.getToken();
|
|
136
|
+
|
|
137
|
+
if (!token) {
|
|
138
|
+
logger.error('Login test failed - no token received');
|
|
139
|
+
return {
|
|
140
|
+
success: false,
|
|
141
|
+
message: 'Login failed - no token received',
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
logger.info('Token retrieved successfully, testing Graph API access...');
|
|
146
|
+
|
|
147
|
+
try {
|
|
148
|
+
const response = await fetch('https://graph.microsoft.com/v1.0/me', {
|
|
149
|
+
headers: {
|
|
150
|
+
Authorization: `Bearer ${token}`,
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
if (response.ok) {
|
|
155
|
+
const userData = await response.json();
|
|
156
|
+
logger.info('Graph API user data fetch successful');
|
|
157
|
+
return {
|
|
158
|
+
success: true,
|
|
159
|
+
message: 'Login successful',
|
|
160
|
+
userData: {
|
|
161
|
+
displayName: userData.displayName,
|
|
162
|
+
userPrincipalName: userData.userPrincipalName,
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
} else {
|
|
166
|
+
const errorText = await response.text();
|
|
167
|
+
logger.error(`Graph API user data fetch failed: ${response.status} - ${errorText}`);
|
|
168
|
+
return {
|
|
169
|
+
success: false,
|
|
170
|
+
message: `Login successful but Graph API access failed: ${response.status}`,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
} catch (graphError) {
|
|
174
|
+
logger.error(`Error fetching user data: ${graphError.message}`);
|
|
175
|
+
return {
|
|
176
|
+
success: false,
|
|
177
|
+
message: `Login successful but Graph API access failed: ${graphError.message}`,
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
} catch (error) {
|
|
181
|
+
logger.error(`Login test failed: ${error.message}`);
|
|
182
|
+
return {
|
|
183
|
+
success: false,
|
|
184
|
+
message: `Login failed: ${error.message}`,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
126
189
|
async logout() {
|
|
127
190
|
try {
|
|
128
191
|
const accounts = await this.msalApp.getTokenCache().getAllAccounts();
|
package/bin/release.mjs
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
|
|
6
|
+
console.log('Running tests...');
|
|
7
|
+
try {
|
|
8
|
+
execSync('npm test', { stdio: 'inherit' });
|
|
9
|
+
} catch (error) {
|
|
10
|
+
console.error('Tests failed! Aborting release.');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
console.log('Bumping version...');
|
|
15
|
+
execSync('npm version --no-git-tag-version patch');
|
|
16
|
+
|
|
17
|
+
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
|
18
|
+
const version = packageJson.version;
|
|
19
|
+
|
|
20
|
+
console.log('Committing version change...');
|
|
21
|
+
execSync('git add package.json package-lock.json');
|
|
22
|
+
execSync(`git commit -m "Bump version to ${version}"`);
|
|
23
|
+
|
|
24
|
+
console.log('Pushing to remote...');
|
|
25
|
+
execSync('git push');
|
|
26
|
+
|
|
27
|
+
console.log(`Creating GitHub release for v${version}...`);
|
|
28
|
+
execSync(`gh release create v${version} --title 'v${version}' --notes 'Version ${version}'`, {
|
|
29
|
+
stdio: 'inherit',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
console.log(`Release v${version} created successfully!`);
|
|
33
|
+
// GitHub Actions workflow will handle the npm publish automatically
|
package/index.mjs
CHANGED
|
@@ -161,12 +161,14 @@ const server = new McpServer({
|
|
|
161
161
|
|
|
162
162
|
server.tool('login', {}, async () => {
|
|
163
163
|
try {
|
|
164
|
-
await
|
|
164
|
+
const text = await new Promise((r) => {
|
|
165
|
+
authManager.acquireTokenByDeviceCode(r);
|
|
166
|
+
});
|
|
165
167
|
return {
|
|
166
168
|
content: [
|
|
167
169
|
{
|
|
168
170
|
type: 'text',
|
|
169
|
-
text
|
|
171
|
+
text,
|
|
170
172
|
},
|
|
171
173
|
],
|
|
172
174
|
};
|
|
@@ -175,7 +177,7 @@ server.tool('login', {}, async () => {
|
|
|
175
177
|
content: [
|
|
176
178
|
{
|
|
177
179
|
type: 'text',
|
|
178
|
-
text: JSON.stringify({ error:
|
|
180
|
+
text: JSON.stringify({ error: `Authentication failed: ${error.message}` }),
|
|
179
181
|
},
|
|
180
182
|
],
|
|
181
183
|
};
|
|
@@ -205,6 +207,32 @@ server.tool('logout', {}, async () => {
|
|
|
205
207
|
}
|
|
206
208
|
});
|
|
207
209
|
|
|
210
|
+
server.tool('test-login', {}, async () => {
|
|
211
|
+
const result = await authManager.testLogin();
|
|
212
|
+
return {
|
|
213
|
+
content: [
|
|
214
|
+
{
|
|
215
|
+
type: 'text',
|
|
216
|
+
text: JSON.stringify(result),
|
|
217
|
+
},
|
|
218
|
+
],
|
|
219
|
+
};
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
server.tool('verify-login', {}, async () => {
|
|
223
|
+
// Test the login after the user has completed the device code authentication
|
|
224
|
+
const testResult = await authManager.testLogin();
|
|
225
|
+
|
|
226
|
+
return {
|
|
227
|
+
content: [
|
|
228
|
+
{
|
|
229
|
+
type: 'text',
|
|
230
|
+
text: JSON.stringify(testResult),
|
|
231
|
+
},
|
|
232
|
+
],
|
|
233
|
+
};
|
|
234
|
+
});
|
|
235
|
+
|
|
208
236
|
server.tool(
|
|
209
237
|
'update-excel',
|
|
210
238
|
{
|
|
@@ -480,28 +508,15 @@ async function main() {
|
|
|
480
508
|
|
|
481
509
|
if (args.login) {
|
|
482
510
|
await authManager.acquireTokenByDeviceCode();
|
|
483
|
-
logger.info('Login completed,
|
|
484
|
-
|
|
511
|
+
logger.info('Login completed, testing connection with Graph API...');
|
|
512
|
+
const result = await authManager.testLogin();
|
|
513
|
+
console.log(JSON.stringify(result));
|
|
514
|
+
process.exit(0);
|
|
485
515
|
}
|
|
486
516
|
|
|
487
517
|
if (args.testLogin) {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
const token = await authManager.getToken();
|
|
491
|
-
if (token) {
|
|
492
|
-
logger.info('Login test successful');
|
|
493
|
-
|
|
494
|
-
console.log(JSON.stringify({ success: true, message: 'Login successful' }));
|
|
495
|
-
} else {
|
|
496
|
-
logger.error('Login test failed - no token received');
|
|
497
|
-
console.log(
|
|
498
|
-
JSON.stringify({ success: false, message: 'Login failed - no token received' })
|
|
499
|
-
);
|
|
500
|
-
}
|
|
501
|
-
} catch (error) {
|
|
502
|
-
logger.error(`Login test failed: ${error.message}`);
|
|
503
|
-
console.log(JSON.stringify({ success: false, message: `Login failed: ${error.message}` }));
|
|
504
|
-
}
|
|
518
|
+
const result = await authManager.testLogin();
|
|
519
|
+
console.log(JSON.stringify(result));
|
|
505
520
|
process.exit(0);
|
|
506
521
|
}
|
|
507
522
|
|