@softeria/ms-365-mcp-server 0.3.3 → 0.3.5
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 +7 -11
- package/package.json +1 -1
- package/src/auth.mjs +29 -9
- package/src/dynamic-tools.mjs +225 -14
- package/test/mappings.test.js +29 -0
package/README.md
CHANGED
|
@@ -17,6 +17,11 @@ A Model Context Protocol (MCP) server for interacting with Microsoft 365 service
|
|
|
17
17
|
- Calendar event management
|
|
18
18
|
- Mail operations
|
|
19
19
|
- OneDrive file management
|
|
20
|
+
- OneNote notebooks and pages
|
|
21
|
+
- To Do tasks and task lists
|
|
22
|
+
- Planner plans and tasks
|
|
23
|
+
- Outlook contacts
|
|
24
|
+
- User management
|
|
20
25
|
- Dynamic tools powered by Microsoft Graph OpenAPI spec
|
|
21
26
|
- Built on the Model Context Protocol
|
|
22
27
|
|
|
@@ -24,7 +29,7 @@ A Model Context Protocol (MCP) server for interacting with Microsoft 365 service
|
|
|
24
29
|
|
|
25
30
|
Test login in Claude Desktop:
|
|
26
31
|
|
|
27
|
-

|
|
28
33
|
|
|
29
34
|
## Examples
|
|
30
35
|
|
|
@@ -69,23 +74,14 @@ integration method.
|
|
|
69
74
|
- Call the `login` tool (auto-checks existing token)
|
|
70
75
|
- If needed, get URL+code, visit in browser
|
|
71
76
|
- Use `verify-login` tool to confirm
|
|
72
|
-
-
|
|
73
77
|
2. **Optional CLI login**:
|
|
74
78
|
```bash
|
|
75
79
|
npx @softeria/ms-365-mcp-server --login
|
|
76
80
|
```
|
|
77
|
-
Follow the URL and code prompt in terminal.
|
|
81
|
+
Follow the URL and code prompt in the terminal.
|
|
78
82
|
|
|
79
83
|
Tokens are cached securely in your OS credential store (fallback to file).
|
|
80
84
|
|
|
81
|
-
## Tools
|
|
82
|
-
|
|
83
|
-
- **Authentication:** `login`, `logout`, `verify-login`
|
|
84
|
-
- **Excel:** list worksheets, get/set ranges, format, sort, chart
|
|
85
|
-
- **Calendar:** list/create/update/delete events
|
|
86
|
-
- **Mail:** send, read, delete messages
|
|
87
|
-
- **OneDrive:** upload, download, list files
|
|
88
|
-
|
|
89
85
|
## License
|
|
90
86
|
|
|
91
87
|
MIT © 2025 Softeria
|
package/package.json
CHANGED
package/src/auth.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'url';
|
|
|
4
4
|
import path from 'path';
|
|
5
5
|
import fs from 'fs';
|
|
6
6
|
import logger from './logger.mjs';
|
|
7
|
+
import { TARGET_ENDPOINTS } from './dynamic-tools.mjs';
|
|
7
8
|
|
|
8
9
|
const SERVICE_NAME = 'ms-365-mcp-server';
|
|
9
10
|
const TOKEN_CACHE_ACCOUNT = 'msal-token-cache';
|
|
@@ -17,17 +18,36 @@ const DEFAULT_CONFIG = {
|
|
|
17
18
|
},
|
|
18
19
|
};
|
|
19
20
|
|
|
20
|
-
const
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
const SCOPE_HIERARCHY = {
|
|
22
|
+
'Mail.ReadWrite': ['Mail.Read', 'Mail.Send'],
|
|
23
|
+
'Calendars.ReadWrite': ['Calendars.Read'],
|
|
24
|
+
'Files.ReadWrite': ['Files.Read'],
|
|
25
|
+
'Tasks.ReadWrite': ['Tasks.Read'],
|
|
26
|
+
'Contacts.ReadWrite': ['Contacts.Read'],
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
function buildScopesFromEndpoints() {
|
|
30
|
+
const scopesSet = new Set();
|
|
31
|
+
|
|
32
|
+
TARGET_ENDPOINTS.forEach((endpoint) => {
|
|
33
|
+
if (endpoint.scopes && Array.isArray(endpoint.scopes)) {
|
|
34
|
+
endpoint.scopes.forEach((scope) => scopesSet.add(scope));
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
Object.entries(SCOPE_HIERARCHY).forEach(([higherScope, lowerScopes]) => {
|
|
39
|
+
if (lowerScopes.every((scope) => scopesSet.has(scope))) {
|
|
40
|
+
lowerScopes.forEach((scope) => scopesSet.delete(scope));
|
|
41
|
+
scopesSet.add(higherScope);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return Array.from(scopesSet);
|
|
46
|
+
}
|
|
28
47
|
|
|
29
48
|
class AuthManager {
|
|
30
|
-
constructor(config = DEFAULT_CONFIG, scopes =
|
|
49
|
+
constructor(config = DEFAULT_CONFIG, scopes = buildScopesFromEndpoints()) {
|
|
50
|
+
logger.info(`And scopes are ${scopes.join(', ')}`, scopes);
|
|
31
51
|
this.config = config;
|
|
32
52
|
this.scopes = scopes;
|
|
33
53
|
this.msalApp = new PublicClientApplication(this.config);
|
package/src/dynamic-tools.mjs
CHANGED
|
@@ -6,136 +6,337 @@ import {
|
|
|
6
6
|
isMethodWithBody,
|
|
7
7
|
loadOpenApiSpec,
|
|
8
8
|
} from './openapi-helpers.mjs';
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Validates all endpoints in TARGET_ENDPOINTS against the OpenAPI spec.
|
|
13
|
+
* Returns an array of endpoints that don't exist in the spec.
|
|
14
|
+
*
|
|
15
|
+
* @returns {Array} Array of missing endpoints
|
|
16
|
+
*/
|
|
17
|
+
export function validateEndpoints() {
|
|
18
|
+
const openapi = loadOpenApiSpec();
|
|
19
|
+
const missingEndpoints = [];
|
|
20
|
+
|
|
21
|
+
for (const endpoint of TARGET_ENDPOINTS) {
|
|
22
|
+
const result = findPathAndOperation(openapi, endpoint.pathPattern, endpoint.method);
|
|
23
|
+
if (!result) {
|
|
24
|
+
missingEndpoints.push({
|
|
25
|
+
toolName: endpoint.toolName,
|
|
26
|
+
pathPattern: endpoint.pathPattern,
|
|
27
|
+
method: endpoint.method,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return missingEndpoints;
|
|
33
|
+
}
|
|
9
34
|
|
|
10
35
|
export const TARGET_ENDPOINTS = [
|
|
11
36
|
{
|
|
12
37
|
pathPattern: '/me/messages',
|
|
13
38
|
method: 'get',
|
|
14
39
|
toolName: 'list-mail-messages',
|
|
40
|
+
scopes: ['Mail.Read'],
|
|
15
41
|
},
|
|
16
42
|
{
|
|
17
43
|
pathPattern: '/me/mailFolders',
|
|
18
44
|
method: 'get',
|
|
19
45
|
toolName: 'list-mail-folders',
|
|
46
|
+
scopes: ['Mail.Read'],
|
|
20
47
|
},
|
|
21
48
|
{
|
|
22
49
|
pathPattern: '/me/mailFolders/{mailFolder-id}/messages',
|
|
23
50
|
method: 'get',
|
|
24
51
|
toolName: 'list-mail-folder-messages',
|
|
52
|
+
scopes: ['Mail.Read'],
|
|
25
53
|
},
|
|
26
54
|
{
|
|
27
55
|
pathPattern: '/me/messages/{message-id}',
|
|
28
56
|
method: 'get',
|
|
29
57
|
toolName: 'get-mail-message',
|
|
58
|
+
scopes: ['Mail.Read'],
|
|
30
59
|
},
|
|
60
|
+
{
|
|
61
|
+
pathPattern: '/me/messages',
|
|
62
|
+
method: 'post',
|
|
63
|
+
toolName: 'send-mail',
|
|
64
|
+
scopes: ['Mail.Send'],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
pathPattern: '/me/messages/{message-id}',
|
|
68
|
+
method: 'delete',
|
|
69
|
+
toolName: 'delete-mail-message',
|
|
70
|
+
scopes: ['Mail.ReadWrite'],
|
|
71
|
+
},
|
|
72
|
+
|
|
31
73
|
{
|
|
32
74
|
pathPattern: '/me/events',
|
|
33
75
|
method: 'get',
|
|
34
76
|
toolName: 'list-calendar-events',
|
|
77
|
+
scopes: ['Calendars.Read'],
|
|
35
78
|
},
|
|
36
79
|
{
|
|
37
80
|
pathPattern: '/me/events/{event-id}',
|
|
38
81
|
method: 'get',
|
|
39
82
|
toolName: 'get-calendar-event',
|
|
83
|
+
scopes: ['Calendars.Read'],
|
|
40
84
|
},
|
|
41
85
|
{
|
|
42
86
|
pathPattern: '/me/events',
|
|
43
87
|
method: 'post',
|
|
44
88
|
toolName: 'create-calendar-event',
|
|
89
|
+
scopes: ['Calendars.ReadWrite'],
|
|
45
90
|
},
|
|
46
91
|
{
|
|
47
92
|
pathPattern: '/me/events/{event-id}',
|
|
48
93
|
method: 'patch',
|
|
49
94
|
toolName: 'update-calendar-event',
|
|
95
|
+
scopes: ['Calendars.ReadWrite'],
|
|
50
96
|
},
|
|
51
97
|
{
|
|
52
98
|
pathPattern: '/me/events/{event-id}',
|
|
53
99
|
method: 'delete',
|
|
54
100
|
toolName: 'delete-calendar-event',
|
|
101
|
+
scopes: ['Calendars.ReadWrite'],
|
|
55
102
|
},
|
|
56
103
|
{
|
|
57
104
|
pathPattern: '/me/calendarView',
|
|
58
105
|
method: 'get',
|
|
59
106
|
toolName: 'get-calendar-view',
|
|
107
|
+
scopes: ['Calendars.Read'],
|
|
60
108
|
},
|
|
61
109
|
{
|
|
62
|
-
pathPattern: '/
|
|
110
|
+
pathPattern: '/me/calendars',
|
|
63
111
|
method: 'get',
|
|
64
|
-
toolName: '
|
|
112
|
+
toolName: 'list-calendars',
|
|
113
|
+
scopes: ['Calendars.Read'],
|
|
65
114
|
},
|
|
115
|
+
|
|
66
116
|
{
|
|
67
117
|
pathPattern: '/drives',
|
|
68
118
|
method: 'get',
|
|
69
119
|
toolName: 'list-drives',
|
|
120
|
+
scopes: ['Files.Read'],
|
|
70
121
|
},
|
|
71
122
|
{
|
|
72
123
|
pathPattern: '/drives/{drive-id}/root',
|
|
73
124
|
method: 'get',
|
|
74
125
|
toolName: 'get-drive-root-item',
|
|
126
|
+
scopes: ['Files.Read'],
|
|
75
127
|
},
|
|
76
128
|
{
|
|
77
129
|
pathPattern: '/drives/{drive-id}/root',
|
|
78
130
|
method: 'get',
|
|
79
131
|
toolName: 'get-root-folder',
|
|
132
|
+
scopes: ['Files.Read'],
|
|
80
133
|
},
|
|
81
134
|
{
|
|
82
135
|
pathPattern: '/drives/{drive-id}/items/{driveItem-id}/children',
|
|
83
136
|
method: 'get',
|
|
84
137
|
toolName: 'list-folder-files',
|
|
138
|
+
scopes: ['Files.Read'],
|
|
85
139
|
},
|
|
86
140
|
{
|
|
87
141
|
pathPattern: '/drives/{drive-id}/items/{driveItem-id}/children',
|
|
88
142
|
method: 'post',
|
|
89
143
|
toolName: 'create-item-in-folder',
|
|
144
|
+
scopes: ['Files.ReadWrite'],
|
|
90
145
|
},
|
|
91
146
|
{
|
|
92
147
|
pathPattern: '/drives/{drive-id}/items/{driveItem-id}/children/{driveItem-id1}/content',
|
|
93
148
|
method: 'get',
|
|
94
|
-
toolName: 'download-file-content',
|
|
149
|
+
toolName: 'download-onedrive-file-content',
|
|
150
|
+
scopes: ['Files.Read'],
|
|
95
151
|
},
|
|
96
152
|
{
|
|
97
153
|
pathPattern: '/drives/{drive-id}/items/{driveItem-id}',
|
|
98
154
|
method: 'delete',
|
|
99
|
-
toolName: 'delete-file',
|
|
155
|
+
toolName: 'delete-onedrive-file',
|
|
156
|
+
scopes: ['Files.ReadWrite'],
|
|
100
157
|
},
|
|
101
158
|
{
|
|
102
159
|
pathPattern: '/drives/{drive-id}/items/{driveItem-id}',
|
|
103
160
|
method: 'patch',
|
|
104
|
-
toolName: 'update-file-metadata',
|
|
161
|
+
toolName: 'update-onedrive-file-metadata',
|
|
162
|
+
scopes: ['Files.ReadWrite'],
|
|
105
163
|
},
|
|
164
|
+
|
|
106
165
|
{
|
|
107
166
|
pathPattern:
|
|
108
167
|
'/drives/{drive-id}/items/{driveItem-id}/workbook/worksheets/{workbookWorksheet-id}/charts/add',
|
|
109
168
|
method: 'post',
|
|
110
|
-
toolName: 'create-chart',
|
|
169
|
+
toolName: 'create-excel-chart',
|
|
111
170
|
isExcelOp: true,
|
|
171
|
+
scopes: ['Files.ReadWrite'],
|
|
112
172
|
},
|
|
113
173
|
{
|
|
114
174
|
pathPattern:
|
|
115
175
|
'/drives/{drive-id}/items/{driveItem-id}/workbook/worksheets/{workbookWorksheet-id}/range()/format',
|
|
116
176
|
method: 'patch',
|
|
117
|
-
toolName: 'format-range',
|
|
177
|
+
toolName: 'format-excel-range',
|
|
118
178
|
isExcelOp: true,
|
|
179
|
+
scopes: ['Files.ReadWrite'],
|
|
119
180
|
},
|
|
120
181
|
{
|
|
121
182
|
pathPattern:
|
|
122
183
|
'/drives/{drive-id}/items/{driveItem-id}/workbook/worksheets/{workbookWorksheet-id}/range()/sort',
|
|
123
184
|
method: 'patch',
|
|
124
|
-
toolName: 'sort-range',
|
|
185
|
+
toolName: 'sort-excel-range',
|
|
125
186
|
isExcelOp: true,
|
|
187
|
+
scopes: ['Files.ReadWrite'],
|
|
126
188
|
},
|
|
127
189
|
{
|
|
128
190
|
pathPattern:
|
|
129
191
|
"/drives/{drive-id}/items/{driveItem-id}/workbook/worksheets/{workbookWorksheet-id}/range(address='{address}')",
|
|
130
192
|
method: 'get',
|
|
131
|
-
toolName: 'get-range',
|
|
193
|
+
toolName: 'get-excel-range',
|
|
132
194
|
isExcelOp: true,
|
|
195
|
+
scopes: ['Files.Read'],
|
|
133
196
|
},
|
|
134
197
|
{
|
|
135
198
|
pathPattern: '/drives/{drive-id}/items/{driveItem-id}/workbook/worksheets',
|
|
136
199
|
method: 'get',
|
|
137
|
-
toolName: 'list-worksheets',
|
|
200
|
+
toolName: 'list-excel-worksheets',
|
|
138
201
|
isExcelOp: true,
|
|
202
|
+
scopes: ['Files.Read'],
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
{
|
|
206
|
+
pathPattern: '/me/onenote/notebooks',
|
|
207
|
+
method: 'get',
|
|
208
|
+
toolName: 'list-onenote-notebooks',
|
|
209
|
+
scopes: ['Notes.Read'],
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
pathPattern: '/me/onenote/notebooks/{notebook-id}/sections',
|
|
213
|
+
method: 'get',
|
|
214
|
+
toolName: 'list-onenote-notebook-sections',
|
|
215
|
+
scopes: ['Notes.Read'],
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
pathPattern: '/me/onenote/notebooks/{notebook-id}/sections/{onenoteSection-id}/pages',
|
|
219
|
+
method: 'get',
|
|
220
|
+
toolName: 'list-onenote-section-pages',
|
|
221
|
+
scopes: ['Notes.Read'],
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
pathPattern: '/me/onenote/pages/{onenotePage-id}/content',
|
|
225
|
+
method: 'get',
|
|
226
|
+
toolName: 'get-onenote-page-content',
|
|
227
|
+
scopes: ['Notes.Read'],
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
pathPattern: '/me/onenote/pages',
|
|
231
|
+
method: 'post',
|
|
232
|
+
toolName: 'create-onenote-page',
|
|
233
|
+
scopes: ['Notes.Create'],
|
|
234
|
+
},
|
|
235
|
+
|
|
236
|
+
{
|
|
237
|
+
pathPattern: '/me/todo/lists',
|
|
238
|
+
method: 'get',
|
|
239
|
+
toolName: 'list-todo-task-lists',
|
|
240
|
+
scopes: ['Tasks.Read'],
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
pathPattern: '/me/todo/lists/{todoTaskList-id}/tasks',
|
|
244
|
+
method: 'get',
|
|
245
|
+
toolName: 'list-todo-tasks',
|
|
246
|
+
scopes: ['Tasks.Read'],
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
pathPattern: '/me/todo/lists/{todoTaskList-id}/tasks/{todoTask-id}',
|
|
250
|
+
method: 'get',
|
|
251
|
+
toolName: 'get-todo-task',
|
|
252
|
+
scopes: ['Tasks.Read'],
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
pathPattern: '/me/todo/lists/{todoTaskList-id}/tasks',
|
|
256
|
+
method: 'post',
|
|
257
|
+
toolName: 'create-todo-task',
|
|
258
|
+
scopes: ['Tasks.ReadWrite'],
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
pathPattern: '/me/todo/lists/{todoTaskList-id}/tasks/{todoTask-id}',
|
|
262
|
+
method: 'patch',
|
|
263
|
+
toolName: 'update-todo-task',
|
|
264
|
+
scopes: ['Tasks.ReadWrite'],
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
pathPattern: '/me/todo/lists/{todoTaskList-id}/tasks/{todoTask-id}',
|
|
268
|
+
method: 'delete',
|
|
269
|
+
toolName: 'delete-todo-task',
|
|
270
|
+
scopes: ['Tasks.ReadWrite'],
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
{
|
|
274
|
+
pathPattern: '/me/planner/tasks',
|
|
275
|
+
method: 'get',
|
|
276
|
+
toolName: 'list-planner-tasks',
|
|
277
|
+
scopes: ['Tasks.Read'],
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
pathPattern: '/planner/plans/{plannerPlan-id}',
|
|
281
|
+
method: 'get',
|
|
282
|
+
toolName: 'get-planner-plan',
|
|
283
|
+
scopes: ['Tasks.Read'],
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
pathPattern: '/planner/plans/{plannerPlan-id}/tasks',
|
|
287
|
+
method: 'get',
|
|
288
|
+
toolName: 'list-plan-tasks',
|
|
289
|
+
scopes: ['Tasks.Read'],
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
pathPattern: '/planner/tasks/{plannerTask-id}',
|
|
293
|
+
method: 'get',
|
|
294
|
+
toolName: 'get-planner-task',
|
|
295
|
+
scopes: ['Tasks.Read'],
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
pathPattern: '/planner/tasks',
|
|
299
|
+
method: 'post',
|
|
300
|
+
toolName: 'create-planner-task',
|
|
301
|
+
scopes: ['Tasks.ReadWrite'],
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
{
|
|
305
|
+
pathPattern: '/me/contacts',
|
|
306
|
+
method: 'get',
|
|
307
|
+
toolName: 'list-outlook-contacts',
|
|
308
|
+
scopes: ['Contacts.Read'],
|
|
309
|
+
},
|
|
310
|
+
{
|
|
311
|
+
pathPattern: '/me/contacts/{contact-id}',
|
|
312
|
+
method: 'get',
|
|
313
|
+
toolName: 'get-outlook-contact',
|
|
314
|
+
scopes: ['Contacts.Read'],
|
|
315
|
+
},
|
|
316
|
+
{
|
|
317
|
+
pathPattern: '/me/contacts',
|
|
318
|
+
method: 'post',
|
|
319
|
+
toolName: 'create-outlook-contact',
|
|
320
|
+
scopes: ['Contacts.ReadWrite'],
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
pathPattern: '/me/contacts/{contact-id}',
|
|
324
|
+
method: 'patch',
|
|
325
|
+
toolName: 'update-outlook-contact',
|
|
326
|
+
scopes: ['Contacts.ReadWrite'],
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
pathPattern: '/me/contacts/{contact-id}',
|
|
330
|
+
method: 'delete',
|
|
331
|
+
toolName: 'delete-outlook-contact',
|
|
332
|
+
scopes: ['Contacts.ReadWrite'],
|
|
333
|
+
},
|
|
334
|
+
|
|
335
|
+
{
|
|
336
|
+
pathPattern: '/me',
|
|
337
|
+
method: 'get',
|
|
338
|
+
toolName: 'get-current-user',
|
|
339
|
+
scopes: ['User.Read'],
|
|
139
340
|
},
|
|
140
341
|
];
|
|
141
342
|
|
|
@@ -144,6 +345,16 @@ export async function registerDynamicTools(server, graphClient) {
|
|
|
144
345
|
const openapi = loadOpenApiSpec();
|
|
145
346
|
logger.info('Generating dynamic tools from OpenAPI spec...');
|
|
146
347
|
|
|
348
|
+
const missingEndpoints = validateEndpoints();
|
|
349
|
+
if (missingEndpoints.length > 0) {
|
|
350
|
+
logger.warn('Some endpoints are missing from the OpenAPI spec:');
|
|
351
|
+
missingEndpoints.forEach((endpoint) => {
|
|
352
|
+
logger.warn(
|
|
353
|
+
`- Tool: ${endpoint.toolName}, Path: ${endpoint.pathPattern}, Method: ${endpoint.method}`
|
|
354
|
+
);
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
147
358
|
for (const endpoint of TARGET_ENDPOINTS) {
|
|
148
359
|
const result = findPathAndOperation(openapi, endpoint.pathPattern, endpoint.method);
|
|
149
360
|
if (!result) continue;
|
|
@@ -157,13 +368,13 @@ export async function registerDynamicTools(server, graphClient) {
|
|
|
157
368
|
const paramsSchema = buildParameterSchemas(endpoint, operation);
|
|
158
369
|
|
|
159
370
|
if (endpoint.hasCustomParams) {
|
|
160
|
-
if (endpoint.toolName === 'upload-file') {
|
|
371
|
+
if (endpoint.toolName === 'upload-onedrive-file') {
|
|
161
372
|
paramsSchema.content = z.string().describe('File content to upload');
|
|
162
373
|
paramsSchema.contentType = z
|
|
163
374
|
.string()
|
|
164
375
|
.optional()
|
|
165
376
|
.describe('Content type of the file (e.g., "application/pdf", "image/jpeg")');
|
|
166
|
-
} else if (endpoint.toolName === 'create-folder') {
|
|
377
|
+
} else if (endpoint.toolName === 'create-onedrive-folder') {
|
|
167
378
|
paramsSchema.name = z.string().describe('Name of the folder to create');
|
|
168
379
|
paramsSchema.description = z.string().optional().describe('Description of the folder');
|
|
169
380
|
}
|
|
@@ -193,13 +404,13 @@ export async function registerDynamicTools(server, graphClient) {
|
|
|
193
404
|
options.excelFile = params.filePath;
|
|
194
405
|
}
|
|
195
406
|
|
|
196
|
-
if (endpoint.toolName === 'download-file') {
|
|
407
|
+
if (endpoint.toolName === 'download-onedrive-file-content') {
|
|
197
408
|
options.rawResponse = true;
|
|
198
409
|
}
|
|
199
410
|
|
|
200
411
|
const url = buildRequestUrl(endpoint.pathPattern, params, pathParams, operation.parameters);
|
|
201
412
|
|
|
202
|
-
if (endpoint.toolName === 'upload-file' && params.content) {
|
|
413
|
+
if (endpoint.toolName === 'upload-onedrive-file' && params.content) {
|
|
203
414
|
options.body = params.content;
|
|
204
415
|
options.headers = {
|
|
205
416
|
'Content-Type': params.contentType || 'application/octet-stream',
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { validateEndpoints } from '../src/dynamic-tools.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* This test file ensures that all the mappings in TARGET_ENDPOINTS actually match
|
|
6
|
+
* the endpoints in the OpenAPI spec. It helps catch issues where:
|
|
7
|
+
*
|
|
8
|
+
* 1. An endpoint in TARGET_ENDPOINTS doesn't exist in the OpenAPI spec
|
|
9
|
+
* 2. The method for an endpoint doesn't match what's in the OpenAPI spec
|
|
10
|
+
*
|
|
11
|
+
* This is a more automated approach than manually running the app and tailing logs.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
describe('Mappings Validation', () => {
|
|
15
|
+
it('should verify all TARGET_ENDPOINTS exist in the OpenAPI spec', () => {
|
|
16
|
+
const missingEndpoints = validateEndpoints();
|
|
17
|
+
|
|
18
|
+
if (missingEndpoints.length > 0) {
|
|
19
|
+
console.error('The following endpoints are missing from the OpenAPI spec:');
|
|
20
|
+
missingEndpoints.forEach((endpoint) => {
|
|
21
|
+
console.error(
|
|
22
|
+
`- Tool: ${endpoint.toolName}, Path: ${endpoint.pathPattern}, Method: ${endpoint.method}`
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
expect(missingEndpoints).toEqual([]);
|
|
28
|
+
});
|
|
29
|
+
});
|