@softeria/ms-365-mcp-server 0.1.10 → 0.2.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/README.md +69 -4
- package/bin/release.mjs +12 -2
- package/index.mjs +17 -504
- package/package.json +1 -1
- package/src/auth-tools.mjs +74 -0
- package/{auth.mjs → src/auth.mjs} +5 -1
- package/src/calendar-tools.mjs +814 -0
- package/{cli.mjs → src/cli.mjs} +7 -2
- package/src/excel-tools.mjs +317 -0
- package/src/files-tools.mjs +554 -0
- package/src/graph-client.mjs +293 -0
- package/{logger.mjs → src/logger.mjs} +2 -3
- package/src/mail-tools.mjs +159 -0
- package/src/server.mjs +45 -0
- package/src/version.mjs +9 -0
- package/test/cli.test.js +3 -4
- package/test/graph-api.test.js +2 -1
- package/test/mcp-server.test.js +2 -3
- package/test/integration.test.js +0 -40
- package/test/simple.test.js +0 -7
package/README.md
CHANGED
|
@@ -88,17 +88,24 @@ This repository uses GitHub Actions for continuous integration and deployment:
|
|
|
88
88
|
To create a new release:
|
|
89
89
|
|
|
90
90
|
```bash
|
|
91
|
+
# Default (patch version): 0.1.11 -> 0.1.12
|
|
91
92
|
npm run release
|
|
93
|
+
|
|
94
|
+
# Minor version: 0.1.11 -> 0.2.0
|
|
95
|
+
npm run release minor
|
|
96
|
+
|
|
97
|
+
# Major version: 0.1.11 -> 1.0.0
|
|
98
|
+
npm run release major
|
|
92
99
|
```
|
|
93
100
|
|
|
94
101
|
This script will:
|
|
95
102
|
|
|
96
103
|
1. Run tests to verify everything works
|
|
97
|
-
2. Bump the version number
|
|
104
|
+
2. Bump the version number according to the specified type (patch by default)
|
|
98
105
|
3. Commit the version changes
|
|
99
106
|
4. Push to GitHub
|
|
100
107
|
5. Create a GitHub release
|
|
101
|
-
6. Trigger the
|
|
108
|
+
6. Trigger the publishing workflow to publish to npm
|
|
102
109
|
|
|
103
110
|
## Usage
|
|
104
111
|
|
|
@@ -160,12 +167,34 @@ Authentication tokens are cached securely in your system's credential store with
|
|
|
160
167
|
|
|
161
168
|
### MCP Tools
|
|
162
169
|
|
|
163
|
-
This server provides several MCP tools for interacting with
|
|
170
|
+
This server provides several MCP tools for interacting with Microsoft 365 services:
|
|
171
|
+
|
|
172
|
+
#### Authentication Tools
|
|
164
173
|
|
|
165
174
|
- `login`: Start a new login process with Microsoft (returns login URL and code)
|
|
166
175
|
- `verify-login`: Check if login was completed successfully and verify Graph API access
|
|
167
176
|
- `logout`: Log out of Microsoft and clear credentials
|
|
168
177
|
- `test-login`: Test current authentication status and verify Graph API access
|
|
178
|
+
|
|
179
|
+
#### Files/OneDrive Tools
|
|
180
|
+
|
|
181
|
+
- `list-files`: List files and folders in a specified path
|
|
182
|
+
- `get-file`: Get details of a specific file
|
|
183
|
+
- `create-folder`: Create a new folder
|
|
184
|
+
- `delete-item`: Delete a file or folder
|
|
185
|
+
- `copy-item`: Copy a file or folder to a new location
|
|
186
|
+
- `move-item`: Move a file or folder to a new location
|
|
187
|
+
- `rename-item`: Rename a file or folder
|
|
188
|
+
- `search-files`: Search for files matching a query
|
|
189
|
+
- `get-shared-items`: Get a list of items shared with you
|
|
190
|
+
- `create-sharing-link`: Create a sharing link for a file or folder
|
|
191
|
+
- `get-file-content`: Get the content of a file
|
|
192
|
+
|
|
193
|
+
#### Excel Tools
|
|
194
|
+
|
|
195
|
+
All Excel tools require a `filePath` parameter to specify which Excel file to operate on. You can use the Files tools to
|
|
196
|
+
find and manage your Excel files.
|
|
197
|
+
|
|
169
198
|
- `update-excel`: Update cell values in an Excel worksheet
|
|
170
199
|
- `create-chart`: Create a chart in an Excel worksheet
|
|
171
200
|
- `format-range`: Apply formatting to a range of cells
|
|
@@ -173,6 +202,42 @@ This server provides several MCP tools for interacting with Excel files:
|
|
|
173
202
|
- `create-table`: Create a table from a range of cells
|
|
174
203
|
- `get-range`: Get values from a range of cells
|
|
175
204
|
- `list-worksheets`: List all worksheets in the workbook
|
|
176
|
-
- `close-session`: Close the
|
|
205
|
+
- `close-session`: Close the session for a specific Excel file
|
|
206
|
+
- `close-all-sessions`: Close all active Excel sessions
|
|
177
207
|
- `delete-chart`: Delete a chart from a worksheet
|
|
178
208
|
- `get-charts`: Get all charts in a worksheet
|
|
209
|
+
|
|
210
|
+
Example workflow:
|
|
211
|
+
|
|
212
|
+
1. Use `list-files` to find Excel files in your OneDrive
|
|
213
|
+
2. Use `list-worksheets` with the file path to see available worksheets
|
|
214
|
+
3. Use `get-range` to retrieve data from the Excel file
|
|
215
|
+
4. Use other Excel tools to manipulate the file as needed
|
|
216
|
+
|
|
217
|
+
#### Calendar Tools
|
|
218
|
+
|
|
219
|
+
Tools for working with Outlook calendars.
|
|
220
|
+
|
|
221
|
+
- `list-calendars`: List all calendars
|
|
222
|
+
- `get-default-calendar`: Get information about the default calendar
|
|
223
|
+
- `list-events`: List events from a calendar with optional filtering, sorting, and date ranges
|
|
224
|
+
- `get-detailed-events`: Get events with expanded properties and options to include body, attendees, extensions, etc.
|
|
225
|
+
- `get-event`: Get detailed information about a specific calendar event
|
|
226
|
+
- `create-event`: Create a new calendar event with comprehensive options (sensitivity, importance, free/busy status,
|
|
227
|
+
optional attendees, reminders, online meetings, categories)
|
|
228
|
+
- `create-recurring-event`: Create recurring events (daily, weekly, monthly, yearly) with the same comprehensive options
|
|
229
|
+
and flexible recurrence patterns
|
|
230
|
+
- `update-event`: Update an existing calendar event
|
|
231
|
+
- `delete-event`: Delete a calendar event
|
|
232
|
+
- `accept-event`: Accept a calendar meeting invitation
|
|
233
|
+
- `decline-event`: Decline a calendar meeting invitation
|
|
234
|
+
- `tentatively-accept-event`: Tentatively accept a calendar meeting invitation
|
|
235
|
+
- `find-meeting-times`: Find available meeting times for a set of attendees
|
|
236
|
+
- `get-schedules`: Get availability information for multiple users or resource rooms
|
|
237
|
+
|
|
238
|
+
#### Mail Tools
|
|
239
|
+
|
|
240
|
+
Tools for working with Outlook email.
|
|
241
|
+
|
|
242
|
+
- `list-messages`: List emails from any mail folder with powerful filtering, searching, and sorting options
|
|
243
|
+
- `get-message`: Get detailed information about a specific email message with options to include attachments and other metadata
|
package/bin/release.mjs
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
import { execSync } from 'child_process';
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
const releaseType = args[0] || 'patch';
|
|
8
|
+
|
|
9
|
+
if (!['major', 'minor', 'patch'].includes(releaseType)) {
|
|
10
|
+
console.error('Invalid release type. Must be one of: major, minor, patch');
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
console.log(`Release type: ${releaseType}`);
|
|
15
|
+
|
|
6
16
|
console.log('Running tests...');
|
|
7
17
|
try {
|
|
8
18
|
execSync('npm test', { stdio: 'inherit' });
|
|
@@ -11,8 +21,8 @@ try {
|
|
|
11
21
|
process.exit(1);
|
|
12
22
|
}
|
|
13
23
|
|
|
14
|
-
console.log(
|
|
15
|
-
execSync(
|
|
24
|
+
console.log(`Bumping ${releaseType} version...`);
|
|
25
|
+
execSync(`npm version --no-git-tag-version ${releaseType}`);
|
|
16
26
|
|
|
17
27
|
const packageJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
|
|
18
28
|
const version = packageJson.version;
|
package/index.mjs
CHANGED
|
@@ -1,510 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import {
|
|
8
|
-
import logger, { enableConsoleLogging } from './logger.mjs';
|
|
9
|
-
import AuthManager from './auth.mjs';
|
|
10
|
-
|
|
11
|
-
const packageJson = JSON.parse(readFileSync(new URL('./package.json', import.meta.url)));
|
|
12
|
-
export const version = packageJson.version;
|
|
13
|
-
|
|
14
|
-
const args = parseArgs();
|
|
15
|
-
const filePath = args.file || '/Livet.xlsx';
|
|
16
|
-
|
|
17
|
-
const authManager = new AuthManager();
|
|
18
|
-
await authManager.loadTokenCache();
|
|
19
|
-
|
|
20
|
-
let sessionId = null;
|
|
21
|
-
|
|
22
|
-
async function createSession() {
|
|
23
|
-
try {
|
|
24
|
-
logger.info('Creating new Excel session...');
|
|
25
|
-
const accessToken = await authManager.getToken();
|
|
26
|
-
|
|
27
|
-
const response = await fetch(
|
|
28
|
-
`https://graph.microsoft.com/v1.0/me/drive/root:${filePath}:/workbook/createSession`,
|
|
29
|
-
{
|
|
30
|
-
method: 'POST',
|
|
31
|
-
headers: {
|
|
32
|
-
Authorization: `Bearer ${accessToken}`,
|
|
33
|
-
'Content-Type': 'application/json',
|
|
34
|
-
},
|
|
35
|
-
body: JSON.stringify({ persistChanges: true }),
|
|
36
|
-
}
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
if (!response.ok) {
|
|
40
|
-
const errorText = await response.text();
|
|
41
|
-
logger.error(`Failed to create session: ${response.status} - ${errorText}`);
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const result = await response.json();
|
|
46
|
-
logger.info('Session created successfully');
|
|
47
|
-
sessionId = result.id;
|
|
48
|
-
return sessionId;
|
|
49
|
-
} catch (error) {
|
|
50
|
-
logger.error(`Error creating Excel session: ${error}`);
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async function graphRequest(endpoint, options = {}) {
|
|
56
|
-
try {
|
|
57
|
-
let accessToken = await authManager.getToken();
|
|
58
|
-
|
|
59
|
-
const headers = {
|
|
60
|
-
Authorization: `Bearer ${accessToken}`,
|
|
61
|
-
'Content-Type': 'application/json',
|
|
62
|
-
...(sessionId && { 'workbook-session-id': sessionId }),
|
|
63
|
-
...options.headers,
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
const response = await fetch(
|
|
67
|
-
`https://graph.microsoft.com/v1.0/me/drive/root:${filePath}:${endpoint}`,
|
|
68
|
-
{
|
|
69
|
-
headers,
|
|
70
|
-
...options,
|
|
71
|
-
}
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
if (response.status === 401) {
|
|
75
|
-
logger.info('Access token expired, refreshing...');
|
|
76
|
-
const newToken = await authManager.getToken(true);
|
|
77
|
-
await createSession();
|
|
78
|
-
|
|
79
|
-
headers.Authorization = `Bearer ${newToken}`;
|
|
80
|
-
if (sessionId) {
|
|
81
|
-
headers['workbook-session-id'] = sessionId;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const retryResponse = await fetch(
|
|
85
|
-
`https://graph.microsoft.com/v1.0/me/drive/root:${filePath}:${endpoint}`,
|
|
86
|
-
{
|
|
87
|
-
headers,
|
|
88
|
-
...options,
|
|
89
|
-
}
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
if (!retryResponse.ok) {
|
|
93
|
-
throw new Error(`Graph API error: ${retryResponse.status} ${await retryResponse.text()}`);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return formatResponse(retryResponse);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (!response.ok) {
|
|
100
|
-
throw new Error(`Graph API error: ${response.status} ${await response.text()}`);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return formatResponse(response);
|
|
104
|
-
} catch (error) {
|
|
105
|
-
logger.error(`Error in Graph API request: ${error}`);
|
|
106
|
-
return {
|
|
107
|
-
content: [{ type: 'text', text: JSON.stringify({ error: error.message }) }],
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function formatResponse(response) {
|
|
113
|
-
try {
|
|
114
|
-
if (response.status === 204) {
|
|
115
|
-
return {
|
|
116
|
-
content: [
|
|
117
|
-
{
|
|
118
|
-
type: 'text',
|
|
119
|
-
text: JSON.stringify({
|
|
120
|
-
message: 'Operation completed successfully',
|
|
121
|
-
}),
|
|
122
|
-
},
|
|
123
|
-
],
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const result = await response.json();
|
|
128
|
-
|
|
129
|
-
const removeODataProps = (obj) => {
|
|
130
|
-
if (!obj || typeof obj !== 'object') return;
|
|
131
|
-
|
|
132
|
-
if (Array.isArray(obj)) {
|
|
133
|
-
obj.forEach((item) => removeODataProps(item));
|
|
134
|
-
} else {
|
|
135
|
-
Object.keys(obj).forEach((key) => {
|
|
136
|
-
if (key.startsWith('@odata')) {
|
|
137
|
-
delete obj[key];
|
|
138
|
-
} else if (typeof obj[key] === 'object') {
|
|
139
|
-
removeODataProps(obj[key]);
|
|
140
|
-
}
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
removeODataProps(result);
|
|
146
|
-
|
|
147
|
-
return {
|
|
148
|
-
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
149
|
-
};
|
|
150
|
-
} catch (error) {
|
|
151
|
-
return {
|
|
152
|
-
content: [{ type: 'text', text: JSON.stringify({ message: 'Success' }) }],
|
|
153
|
-
};
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const server = new McpServer({
|
|
158
|
-
name: 'ExcelUpdater',
|
|
159
|
-
version,
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
server.tool('login', {}, async () => {
|
|
163
|
-
try {
|
|
164
|
-
const text = await new Promise((r) => {
|
|
165
|
-
authManager.acquireTokenByDeviceCode(r);
|
|
166
|
-
});
|
|
167
|
-
return {
|
|
168
|
-
content: [
|
|
169
|
-
{
|
|
170
|
-
type: 'text',
|
|
171
|
-
text,
|
|
172
|
-
},
|
|
173
|
-
],
|
|
174
|
-
};
|
|
175
|
-
} catch (error) {
|
|
176
|
-
return {
|
|
177
|
-
content: [
|
|
178
|
-
{
|
|
179
|
-
type: 'text',
|
|
180
|
-
text: JSON.stringify({ error: `Authentication failed: ${error.message}` }),
|
|
181
|
-
},
|
|
182
|
-
],
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
server.tool('logout', {}, async () => {
|
|
188
|
-
try {
|
|
189
|
-
await authManager.logout();
|
|
190
|
-
return {
|
|
191
|
-
content: [
|
|
192
|
-
{
|
|
193
|
-
type: 'text',
|
|
194
|
-
text: JSON.stringify({ message: 'Logged out successfully' }),
|
|
195
|
-
},
|
|
196
|
-
],
|
|
197
|
-
};
|
|
198
|
-
} catch (error) {
|
|
199
|
-
return {
|
|
200
|
-
content: [
|
|
201
|
-
{
|
|
202
|
-
type: 'text',
|
|
203
|
-
text: JSON.stringify({ error: 'Logout failed' }),
|
|
204
|
-
},
|
|
205
|
-
],
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
});
|
|
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
|
-
|
|
236
|
-
server.tool(
|
|
237
|
-
'update-excel',
|
|
238
|
-
{
|
|
239
|
-
worksheet: z.string().default('Sheet1').describe('Worksheet name'),
|
|
240
|
-
address: z.string().describe("Range address (e.g., 'A1:B5')"),
|
|
241
|
-
values: z.array(z.array(z.any())).describe('Values to update'),
|
|
242
|
-
},
|
|
243
|
-
async ({ worksheet, address, values }) => {
|
|
244
|
-
return graphRequest(`/workbook/worksheets('${worksheet}')/range(address='${address}')`, {
|
|
245
|
-
method: 'PATCH',
|
|
246
|
-
body: JSON.stringify({ values }),
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
);
|
|
250
|
-
|
|
251
|
-
server.tool(
|
|
252
|
-
'create-chart',
|
|
253
|
-
{
|
|
254
|
-
worksheet: z.string().default('Sheet1').describe('Worksheet name'),
|
|
255
|
-
type: z.string().describe("Chart type (e.g., 'ColumnClustered', 'Line', 'Pie')"),
|
|
256
|
-
dataRange: z.string().describe("Data range for the chart (e.g., 'A1:B10')"),
|
|
257
|
-
title: z.string().optional().describe('Title for the chart'),
|
|
258
|
-
position: z
|
|
259
|
-
.object({
|
|
260
|
-
x: z.number().describe('X position'),
|
|
261
|
-
y: z.number().describe('Y position'),
|
|
262
|
-
width: z.number().describe('Width'),
|
|
263
|
-
height: z.number().describe('Height'),
|
|
264
|
-
})
|
|
265
|
-
.describe('Chart position and dimensions'),
|
|
266
|
-
},
|
|
267
|
-
async ({ worksheet, type, dataRange, title, position }) => {
|
|
268
|
-
const body = {
|
|
269
|
-
type,
|
|
270
|
-
sourceData: dataRange,
|
|
271
|
-
position,
|
|
272
|
-
};
|
|
273
|
-
|
|
274
|
-
if (title) {
|
|
275
|
-
body.title = { text: title };
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
return graphRequest(`/workbook/worksheets('${worksheet}')/charts/add`, {
|
|
279
|
-
method: 'POST',
|
|
280
|
-
body: JSON.stringify(body),
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
);
|
|
284
|
-
|
|
285
|
-
server.tool(
|
|
286
|
-
'format-range',
|
|
287
|
-
{
|
|
288
|
-
worksheet: z.string().default('Sheet1').describe('Worksheet name'),
|
|
289
|
-
range: z.string().describe("Range address (e.g., 'A1:B5')"),
|
|
290
|
-
format: z
|
|
291
|
-
.object({
|
|
292
|
-
fill: z
|
|
293
|
-
.object({
|
|
294
|
-
color: z.string().optional().describe("Background color (e.g., '#FFFF00')"),
|
|
295
|
-
})
|
|
296
|
-
.optional(),
|
|
297
|
-
font: z
|
|
298
|
-
.object({
|
|
299
|
-
bold: z.boolean().optional().describe('Bold text'),
|
|
300
|
-
italic: z.boolean().optional().describe('Italic text'),
|
|
301
|
-
color: z.string().optional().describe("Font color (e.g., '#FF0000')"),
|
|
302
|
-
size: z.number().optional().describe('Font size'),
|
|
303
|
-
})
|
|
304
|
-
.optional(),
|
|
305
|
-
numberFormat: z.string().optional().describe("Number format (e.g., '0.00%', 'mm/dd/yyyy')"),
|
|
306
|
-
})
|
|
307
|
-
.describe('Formatting to apply'),
|
|
308
|
-
},
|
|
309
|
-
async ({ worksheet, range, format }) => {
|
|
310
|
-
return graphRequest(`/workbook/worksheets('${worksheet}')/range(address='${range}')/format`, {
|
|
311
|
-
method: 'PATCH',
|
|
312
|
-
body: JSON.stringify(format),
|
|
313
|
-
});
|
|
314
|
-
}
|
|
315
|
-
);
|
|
316
|
-
|
|
317
|
-
server.tool(
|
|
318
|
-
'sort-range',
|
|
319
|
-
{
|
|
320
|
-
worksheet: z.string().default('Sheet1').describe('Worksheet name'),
|
|
321
|
-
range: z.string().describe("Range address (e.g., 'A1:B5')"),
|
|
322
|
-
sortFields: z
|
|
323
|
-
.array(
|
|
324
|
-
z.object({
|
|
325
|
-
key: z.number().describe('Column index to sort by (zero-based)'),
|
|
326
|
-
sortOn: z
|
|
327
|
-
.string()
|
|
328
|
-
.optional()
|
|
329
|
-
.describe("Sorting criteria (e.g., 'Value', 'CellColor', 'FontColor', 'Icon')"),
|
|
330
|
-
ascending: z.boolean().optional().describe('Sort in ascending order (default: true)'),
|
|
331
|
-
color: z
|
|
332
|
-
.object({
|
|
333
|
-
color: z.string().describe('HTML color code'),
|
|
334
|
-
type: z.string().describe("Color type (e.g., 'Background', 'Font')"),
|
|
335
|
-
})
|
|
336
|
-
.optional()
|
|
337
|
-
.describe('Color information for sorting by color'),
|
|
338
|
-
dataOption: z
|
|
339
|
-
.string()
|
|
340
|
-
.optional()
|
|
341
|
-
.describe("Data option for sorting (e.g., 'Normal', 'TextAsNumber')"),
|
|
342
|
-
icon: z
|
|
343
|
-
.object({
|
|
344
|
-
set: z.string().describe('Icon set name'),
|
|
345
|
-
index: z.number().describe('Icon index'),
|
|
346
|
-
})
|
|
347
|
-
.optional()
|
|
348
|
-
.describe('Icon information for sorting by icon'),
|
|
349
|
-
})
|
|
350
|
-
)
|
|
351
|
-
.describe('Fields to sort by'),
|
|
352
|
-
matchCase: z.boolean().optional().describe('Whether the sort is case-sensitive'),
|
|
353
|
-
hasHeaders: z.boolean().optional().describe('Whether the range has headers (default: false)'),
|
|
354
|
-
orientation: z.string().optional().describe("Sort orientation ('Rows' or 'Columns')"),
|
|
355
|
-
method: z
|
|
356
|
-
.string()
|
|
357
|
-
.optional()
|
|
358
|
-
.describe("Sort method for Chinese characters ('PinYin' or 'StrokeCount')"),
|
|
359
|
-
},
|
|
360
|
-
async ({ worksheet, range, sortFields, matchCase, hasHeaders, orientation, method }) => {
|
|
361
|
-
const body = {
|
|
362
|
-
fields: sortFields,
|
|
363
|
-
};
|
|
364
|
-
|
|
365
|
-
if (matchCase !== undefined) body.matchCase = matchCase;
|
|
366
|
-
if (hasHeaders !== undefined) body.hasHeaders = hasHeaders;
|
|
367
|
-
if (orientation) body.orientation = orientation;
|
|
368
|
-
if (method) body.method = method;
|
|
369
|
-
|
|
370
|
-
return graphRequest(
|
|
371
|
-
`/workbook/worksheets('${worksheet}')/range(address='${range}')/sort/apply`,
|
|
372
|
-
{
|
|
373
|
-
method: 'POST',
|
|
374
|
-
body: JSON.stringify(body),
|
|
375
|
-
}
|
|
376
|
-
);
|
|
377
|
-
}
|
|
378
|
-
);
|
|
379
|
-
|
|
380
|
-
server.tool(
|
|
381
|
-
'create-table',
|
|
382
|
-
{
|
|
383
|
-
worksheet: z.string().default('Sheet1').describe('Worksheet name'),
|
|
384
|
-
range: z.string().describe("Range address (e.g., 'A1:B5')"),
|
|
385
|
-
hasHeaders: z.boolean().optional().describe('Whether the range has headers'),
|
|
386
|
-
tableName: z.string().optional().describe('Name for the new table'),
|
|
387
|
-
},
|
|
388
|
-
async ({ worksheet, range, hasHeaders = true, tableName }) => {
|
|
389
|
-
const body = {
|
|
390
|
-
address: range,
|
|
391
|
-
hasHeaders,
|
|
392
|
-
};
|
|
393
|
-
|
|
394
|
-
if (tableName) {
|
|
395
|
-
body.name = tableName;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
return graphRequest(`/workbook/worksheets('${worksheet}')/tables/add`, {
|
|
399
|
-
method: 'POST',
|
|
400
|
-
body: JSON.stringify(body),
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
);
|
|
404
|
-
|
|
405
|
-
server.tool(
|
|
406
|
-
'get-range',
|
|
407
|
-
{
|
|
408
|
-
worksheet: z.string().default('Sheet1').describe('Worksheet name'),
|
|
409
|
-
range: z.string().describe("Range address (e.g., 'A1:B5')"),
|
|
410
|
-
},
|
|
411
|
-
async ({ worksheet, range }) => {
|
|
412
|
-
return graphRequest(`/workbook/worksheets('${worksheet}')/range(address='${range}')`, {
|
|
413
|
-
method: 'GET',
|
|
414
|
-
});
|
|
415
|
-
}
|
|
416
|
-
);
|
|
417
|
-
|
|
418
|
-
server.tool('list-worksheets', {}, async () => {
|
|
419
|
-
return graphRequest('/workbook/worksheets', {
|
|
420
|
-
method: 'GET',
|
|
421
|
-
});
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
server.tool('close-session', {}, async () => {
|
|
425
|
-
if (!sessionId) {
|
|
426
|
-
return {
|
|
427
|
-
content: [
|
|
428
|
-
{
|
|
429
|
-
type: 'text',
|
|
430
|
-
text: JSON.stringify({ message: 'No active session' }),
|
|
431
|
-
},
|
|
432
|
-
],
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
try {
|
|
437
|
-
const accessToken = await authManager.getToken();
|
|
438
|
-
const response = await fetch(
|
|
439
|
-
`https://graph.microsoft.com/v1.0/me/drive/root:${filePath}:/workbook/closeSession`,
|
|
440
|
-
{
|
|
441
|
-
method: 'POST',
|
|
442
|
-
headers: {
|
|
443
|
-
Authorization: `Bearer ${accessToken}`,
|
|
444
|
-
'Content-Type': 'application/json',
|
|
445
|
-
'workbook-session-id': sessionId,
|
|
446
|
-
},
|
|
447
|
-
}
|
|
448
|
-
);
|
|
449
|
-
|
|
450
|
-
if (response.ok) {
|
|
451
|
-
sessionId = null;
|
|
452
|
-
return {
|
|
453
|
-
content: [
|
|
454
|
-
{
|
|
455
|
-
type: 'text',
|
|
456
|
-
text: JSON.stringify({ message: 'Session closed successfully' }),
|
|
457
|
-
},
|
|
458
|
-
],
|
|
459
|
-
};
|
|
460
|
-
} else {
|
|
461
|
-
throw new Error(`Failed to close session: ${response.status}`);
|
|
462
|
-
}
|
|
463
|
-
} catch (error) {
|
|
464
|
-
logger.error(`Error closing session: ${error}`);
|
|
465
|
-
return {
|
|
466
|
-
content: [
|
|
467
|
-
{
|
|
468
|
-
type: 'text',
|
|
469
|
-
text: JSON.stringify({ error: 'Failed to close session' }),
|
|
470
|
-
},
|
|
471
|
-
],
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
});
|
|
475
|
-
|
|
476
|
-
server.tool(
|
|
477
|
-
'delete-chart',
|
|
478
|
-
{
|
|
479
|
-
worksheet: z.string().default('Sheet1').describe('Worksheet name'),
|
|
480
|
-
chartName: z.string().describe('The name of the chart to delete'),
|
|
481
|
-
},
|
|
482
|
-
async ({ worksheet, chartName }) => {
|
|
483
|
-
return graphRequest(`/workbook/worksheets('${worksheet}')/charts('${chartName}')`, {
|
|
484
|
-
method: 'DELETE',
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
);
|
|
488
|
-
|
|
489
|
-
server.tool(
|
|
490
|
-
'get-charts',
|
|
491
|
-
{
|
|
492
|
-
worksheet: z.string().default('Sheet1').describe('Worksheet name'),
|
|
493
|
-
},
|
|
494
|
-
async ({ worksheet }) => {
|
|
495
|
-
return graphRequest(`/workbook/worksheets('${worksheet}')/charts`, {
|
|
496
|
-
method: 'GET',
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
);
|
|
3
|
+
import { parseArgs } from './src/cli.mjs';
|
|
4
|
+
import logger from './src/logger.mjs';
|
|
5
|
+
import AuthManager from './src/auth.mjs';
|
|
6
|
+
import MicrosoftGraphServer from './src/server.mjs';
|
|
7
|
+
import { version } from './src/version.mjs';
|
|
500
8
|
|
|
501
9
|
async function main() {
|
|
502
10
|
try {
|
|
503
|
-
|
|
504
|
-
enableConsoleLogging();
|
|
505
|
-
}
|
|
11
|
+
const args = parseArgs();
|
|
506
12
|
|
|
507
|
-
|
|
13
|
+
const authManager = new AuthManager();
|
|
14
|
+
await authManager.loadTokenCache();
|
|
508
15
|
|
|
509
16
|
if (args.login) {
|
|
510
17
|
await authManager.acquireTokenByDeviceCode();
|
|
@@ -515,15 +22,21 @@ async function main() {
|
|
|
515
22
|
}
|
|
516
23
|
|
|
517
24
|
if (args.testLogin) {
|
|
25
|
+
logger.info('Testing login...');
|
|
518
26
|
const result = await authManager.testLogin();
|
|
519
27
|
console.log(JSON.stringify(result));
|
|
520
28
|
process.exit(0);
|
|
521
29
|
}
|
|
522
30
|
|
|
523
|
-
|
|
31
|
+
if (args.logout) {
|
|
32
|
+
await authManager.logout();
|
|
33
|
+
console.log(JSON.stringify({ message: 'Logged out successfully' }));
|
|
34
|
+
process.exit(0);
|
|
35
|
+
}
|
|
524
36
|
|
|
525
|
-
const
|
|
526
|
-
await server.
|
|
37
|
+
const server = new MicrosoftGraphServer(authManager, args);
|
|
38
|
+
await server.initialize(version);
|
|
39
|
+
await server.start();
|
|
527
40
|
} catch (error) {
|
|
528
41
|
logger.error(`Startup error: ${error}`);
|
|
529
42
|
process.exit(1);
|