@softeria/ms-365-mcp-server 0.4.1 → 0.4.3
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 +25 -3
- package/bin/modules/simplified-openapi.mjs +7 -0
- package/dist/auth.js +1 -0
- package/dist/cli.js +7 -2
- package/dist/endpoints.json +9 -1
- package/dist/generated/client.js +7339 -7295
- package/dist/graph-tools.js +5 -2
- package/dist/server.js +4 -1
- package/package.json +3 -2
- package/src/endpoints.json +9 -1
- package/src/generated/README.md +6 -1
- package/.github/workflows/build.yml +0 -34
- package/.github/workflows/npm-publish.yml +0 -32
- package/.prettierrc +0 -7
- package/src/auth-tools.ts +0 -89
- package/src/auth.ts +0 -267
- package/src/cli.ts +0 -33
- package/src/generated/client.ts +0 -24916
- package/src/graph-client.ts +0 -328
- package/src/graph-tools.ts +0 -174
- package/src/index.ts +0 -46
- package/src/logger.ts +0 -43
- package/src/server.ts +0 -46
- package/src/version.ts +0 -9
- package/test/auth-tools.test.ts +0 -97
- package/test/cli.test.ts +0 -45
- package/test/graph-api.test.ts +0 -89
- package/test/test-hack.ts +0 -17
- package/tsconfig.json +0 -16
- package/vitest.config.js +0 -8
package/src/graph-client.ts
DELETED
|
@@ -1,328 +0,0 @@
|
|
|
1
|
-
import logger from './logger.js';
|
|
2
|
-
import AuthManager from './auth.js';
|
|
3
|
-
|
|
4
|
-
interface GraphRequestOptions {
|
|
5
|
-
excelFile?: string;
|
|
6
|
-
headers?: Record<string, string>;
|
|
7
|
-
method?: string;
|
|
8
|
-
body?: string;
|
|
9
|
-
rawResponse?: boolean;
|
|
10
|
-
|
|
11
|
-
[key: string]: any;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface ContentItem {
|
|
15
|
-
type: 'text';
|
|
16
|
-
text: string;
|
|
17
|
-
|
|
18
|
-
[key: string]: unknown;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface McpResponse {
|
|
22
|
-
content: ContentItem[];
|
|
23
|
-
_meta?: Record<string, unknown>;
|
|
24
|
-
isError?: boolean;
|
|
25
|
-
|
|
26
|
-
[key: string]: unknown;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
class GraphClient {
|
|
30
|
-
private authManager: AuthManager;
|
|
31
|
-
private sessions: Map<string, string>;
|
|
32
|
-
|
|
33
|
-
constructor(authManager: AuthManager) {
|
|
34
|
-
this.authManager = authManager;
|
|
35
|
-
this.sessions = new Map();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async createSession(filePath: string): Promise<string | null> {
|
|
39
|
-
try {
|
|
40
|
-
if (!filePath) {
|
|
41
|
-
logger.error('No file path provided for Excel session');
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (this.sessions.has(filePath)) {
|
|
46
|
-
return this.sessions.get(filePath) || null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
logger.info(`Creating new Excel session for file: ${filePath}`);
|
|
50
|
-
const accessToken = await this.authManager.getToken();
|
|
51
|
-
|
|
52
|
-
const response = await fetch(
|
|
53
|
-
`https://graph.microsoft.com/v1.0/me/drive/root:${filePath}:/workbook/createSession`,
|
|
54
|
-
{
|
|
55
|
-
method: 'POST',
|
|
56
|
-
headers: {
|
|
57
|
-
Authorization: `Bearer ${accessToken}`,
|
|
58
|
-
'Content-Type': 'application/json',
|
|
59
|
-
},
|
|
60
|
-
body: JSON.stringify({ persistChanges: true }),
|
|
61
|
-
}
|
|
62
|
-
);
|
|
63
|
-
|
|
64
|
-
if (!response.ok) {
|
|
65
|
-
const errorText = await response.text();
|
|
66
|
-
logger.error(`Failed to create session: ${response.status} - ${errorText}`);
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const result = await response.json();
|
|
71
|
-
logger.info(`Session created successfully for file: ${filePath}`);
|
|
72
|
-
|
|
73
|
-
this.sessions.set(filePath, result.id);
|
|
74
|
-
return result.id;
|
|
75
|
-
} catch (error) {
|
|
76
|
-
logger.error(`Error creating Excel session: ${error}`);
|
|
77
|
-
return null;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async graphRequest(endpoint: string, options: GraphRequestOptions = {}): Promise<McpResponse> {
|
|
82
|
-
try {
|
|
83
|
-
logger.info(`Calling ${endpoint} with options: ${JSON.stringify(options)}`);
|
|
84
|
-
let accessToken = await this.authManager.getToken();
|
|
85
|
-
|
|
86
|
-
let url: string;
|
|
87
|
-
let sessionId: string | null = null;
|
|
88
|
-
|
|
89
|
-
if (
|
|
90
|
-
options.excelFile &&
|
|
91
|
-
!endpoint.startsWith('/drive') &&
|
|
92
|
-
!endpoint.startsWith('/users') &&
|
|
93
|
-
!endpoint.startsWith('/me')
|
|
94
|
-
) {
|
|
95
|
-
sessionId = this.sessions.get(options.excelFile) || null;
|
|
96
|
-
|
|
97
|
-
if (!sessionId) {
|
|
98
|
-
sessionId = await this.createSession(options.excelFile);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
url = `https://graph.microsoft.com/v1.0/me/drive/root:${options.excelFile}:${endpoint}`;
|
|
102
|
-
} else if (
|
|
103
|
-
endpoint.startsWith('/drive') ||
|
|
104
|
-
endpoint.startsWith('/users') ||
|
|
105
|
-
endpoint.startsWith('/me')
|
|
106
|
-
) {
|
|
107
|
-
url = `https://graph.microsoft.com/v1.0${endpoint}`;
|
|
108
|
-
} else {
|
|
109
|
-
logger.error('Excel operation requested without specifying a file');
|
|
110
|
-
return {
|
|
111
|
-
content: [
|
|
112
|
-
{
|
|
113
|
-
type: 'text',
|
|
114
|
-
text: JSON.stringify({ error: 'No Excel file specified for this operation' }),
|
|
115
|
-
},
|
|
116
|
-
],
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const headers: Record<string, string> = {
|
|
121
|
-
...options.headers,
|
|
122
|
-
Authorization: `Bearer ${accessToken}`,
|
|
123
|
-
'Content-Type': 'application/json',
|
|
124
|
-
...(sessionId && { 'workbook-session-id': sessionId }),
|
|
125
|
-
};
|
|
126
|
-
delete options.headers;
|
|
127
|
-
|
|
128
|
-
logger.info(` ** Making request to ${url} with options: ${JSON.stringify(options)}`);
|
|
129
|
-
|
|
130
|
-
const response = await fetch(url, {
|
|
131
|
-
headers,
|
|
132
|
-
...options,
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
if (response.status === 401) {
|
|
136
|
-
logger.info('Access token expired, refreshing...');
|
|
137
|
-
const newToken = await this.authManager.getToken(true);
|
|
138
|
-
|
|
139
|
-
if (
|
|
140
|
-
options.excelFile &&
|
|
141
|
-
!endpoint.startsWith('/drive') &&
|
|
142
|
-
!endpoint.startsWith('/users') &&
|
|
143
|
-
!endpoint.startsWith('/me')
|
|
144
|
-
) {
|
|
145
|
-
sessionId = await this.createSession(options.excelFile);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
headers.Authorization = `Bearer ${newToken}`;
|
|
149
|
-
if (
|
|
150
|
-
sessionId &&
|
|
151
|
-
!endpoint.startsWith('/drive') &&
|
|
152
|
-
!endpoint.startsWith('/users') &&
|
|
153
|
-
!endpoint.startsWith('/me')
|
|
154
|
-
) {
|
|
155
|
-
headers['workbook-session-id'] = sessionId;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const retryResponse = await fetch(url, {
|
|
159
|
-
headers,
|
|
160
|
-
...options,
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
if (!retryResponse.ok) {
|
|
164
|
-
throw new Error(`Graph API error: ${retryResponse.status} ${await retryResponse.text()}`);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return this.formatResponse(retryResponse, options.rawResponse);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (!response.ok) {
|
|
171
|
-
throw new Error(`Graph API error: ${response.status} ${await response.text()}`);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
return this.formatResponse(response, options.rawResponse);
|
|
175
|
-
} catch (error) {
|
|
176
|
-
logger.error(`Error in Graph API request: ${error}`);
|
|
177
|
-
return {
|
|
178
|
-
content: [{ type: 'text', text: JSON.stringify({ error: (error as Error).message }) }],
|
|
179
|
-
isError: true,
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
async formatResponse(response: Response, rawResponse = false): Promise<McpResponse> {
|
|
185
|
-
try {
|
|
186
|
-
if (response.status === 204) {
|
|
187
|
-
return {
|
|
188
|
-
content: [
|
|
189
|
-
{
|
|
190
|
-
type: 'text',
|
|
191
|
-
text: JSON.stringify({
|
|
192
|
-
message: 'Operation completed successfully',
|
|
193
|
-
}),
|
|
194
|
-
},
|
|
195
|
-
],
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
if (rawResponse) {
|
|
200
|
-
const contentType = response.headers.get('content-type');
|
|
201
|
-
|
|
202
|
-
if (contentType && contentType.startsWith('text/')) {
|
|
203
|
-
const text = await response.text();
|
|
204
|
-
return {
|
|
205
|
-
content: [{ type: 'text', text }],
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
return {
|
|
210
|
-
content: [
|
|
211
|
-
{
|
|
212
|
-
type: 'text',
|
|
213
|
-
text: JSON.stringify({
|
|
214
|
-
message: 'Binary file content received',
|
|
215
|
-
contentType: contentType,
|
|
216
|
-
contentLength: response.headers.get('content-length'),
|
|
217
|
-
}),
|
|
218
|
-
},
|
|
219
|
-
],
|
|
220
|
-
};
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const result = await response.json();
|
|
224
|
-
|
|
225
|
-
const removeODataProps = (obj: any): void => {
|
|
226
|
-
if (!obj || typeof obj !== 'object') return;
|
|
227
|
-
|
|
228
|
-
if (Array.isArray(obj)) {
|
|
229
|
-
obj.forEach((item) => removeODataProps(item));
|
|
230
|
-
} else {
|
|
231
|
-
Object.keys(obj).forEach((key) => {
|
|
232
|
-
if (key.startsWith('@odata')) {
|
|
233
|
-
delete obj[key];
|
|
234
|
-
} else if (typeof obj[key] === 'object') {
|
|
235
|
-
removeODataProps(obj[key]);
|
|
236
|
-
}
|
|
237
|
-
});
|
|
238
|
-
}
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
removeODataProps(result);
|
|
242
|
-
|
|
243
|
-
return {
|
|
244
|
-
content: [{ type: 'text', text: JSON.stringify(result) }],
|
|
245
|
-
};
|
|
246
|
-
} catch (error) {
|
|
247
|
-
logger.error(`Error formatting response: ${error}`);
|
|
248
|
-
return {
|
|
249
|
-
content: [{ type: 'text', text: JSON.stringify({ message: 'Success' }) }],
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
async closeSession(filePath: string): Promise<McpResponse> {
|
|
255
|
-
if (!filePath || !this.sessions.has(filePath)) {
|
|
256
|
-
return {
|
|
257
|
-
content: [
|
|
258
|
-
{
|
|
259
|
-
type: 'text',
|
|
260
|
-
text: JSON.stringify({ message: 'No active session for the specified file' }),
|
|
261
|
-
},
|
|
262
|
-
],
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
const sessionId = this.sessions.get(filePath);
|
|
267
|
-
|
|
268
|
-
try {
|
|
269
|
-
const accessToken = await this.authManager.getToken();
|
|
270
|
-
const response = await fetch(
|
|
271
|
-
`https://graph.microsoft.com/v1.0/me/drive/root:${filePath}:/workbook/closeSession`,
|
|
272
|
-
{
|
|
273
|
-
method: 'POST',
|
|
274
|
-
headers: {
|
|
275
|
-
Authorization: `Bearer ${accessToken}`,
|
|
276
|
-
'Content-Type': 'application/json',
|
|
277
|
-
'workbook-session-id': sessionId!,
|
|
278
|
-
},
|
|
279
|
-
}
|
|
280
|
-
);
|
|
281
|
-
|
|
282
|
-
if (response.ok) {
|
|
283
|
-
this.sessions.delete(filePath);
|
|
284
|
-
return {
|
|
285
|
-
content: [
|
|
286
|
-
{
|
|
287
|
-
type: 'text',
|
|
288
|
-
text: JSON.stringify({ message: `Session for ${filePath} closed successfully` }),
|
|
289
|
-
},
|
|
290
|
-
],
|
|
291
|
-
};
|
|
292
|
-
} else {
|
|
293
|
-
throw new Error(`Failed to close session: ${response.status}`);
|
|
294
|
-
}
|
|
295
|
-
} catch (error) {
|
|
296
|
-
logger.error(`Error closing session: ${error}`);
|
|
297
|
-
return {
|
|
298
|
-
content: [
|
|
299
|
-
{
|
|
300
|
-
type: 'text',
|
|
301
|
-
text: JSON.stringify({ error: `Failed to close session for ${filePath}` }),
|
|
302
|
-
},
|
|
303
|
-
],
|
|
304
|
-
isError: true,
|
|
305
|
-
};
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
async closeAllSessions(): Promise<McpResponse> {
|
|
310
|
-
const results: McpResponse[] = [];
|
|
311
|
-
|
|
312
|
-
for (const [filePath] of this.sessions) {
|
|
313
|
-
const result = await this.closeSession(filePath);
|
|
314
|
-
results.push(result);
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
return {
|
|
318
|
-
content: [
|
|
319
|
-
{
|
|
320
|
-
type: 'text',
|
|
321
|
-
text: JSON.stringify({ message: 'All sessions closed', results }),
|
|
322
|
-
},
|
|
323
|
-
],
|
|
324
|
-
};
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
export default GraphClient;
|
package/src/graph-tools.ts
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import logger from './logger.js';
|
|
3
|
-
import GraphClient from './graph-client.js';
|
|
4
|
-
import { api } from './generated/client.js';
|
|
5
|
-
import { z } from 'zod';
|
|
6
|
-
|
|
7
|
-
type TextContent = {
|
|
8
|
-
type: 'text';
|
|
9
|
-
text: string;
|
|
10
|
-
[key: string]: unknown;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
type ImageContent = {
|
|
14
|
-
type: 'image';
|
|
15
|
-
data: string;
|
|
16
|
-
mimeType: string;
|
|
17
|
-
[key: string]: unknown;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
type AudioContent = {
|
|
21
|
-
type: 'audio';
|
|
22
|
-
data: string;
|
|
23
|
-
mimeType: string;
|
|
24
|
-
[key: string]: unknown;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
type ResourceTextContent = {
|
|
28
|
-
type: 'resource';
|
|
29
|
-
resource: {
|
|
30
|
-
text: string;
|
|
31
|
-
uri: string;
|
|
32
|
-
mimeType?: string;
|
|
33
|
-
[key: string]: unknown;
|
|
34
|
-
};
|
|
35
|
-
[key: string]: unknown;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
type ResourceBlobContent = {
|
|
39
|
-
type: 'resource';
|
|
40
|
-
resource: {
|
|
41
|
-
blob: string;
|
|
42
|
-
uri: string;
|
|
43
|
-
mimeType?: string;
|
|
44
|
-
[key: string]: unknown;
|
|
45
|
-
};
|
|
46
|
-
[key: string]: unknown;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
type ResourceContent = ResourceTextContent | ResourceBlobContent;
|
|
50
|
-
|
|
51
|
-
type ContentItem = TextContent | ImageContent | AudioContent | ResourceContent;
|
|
52
|
-
|
|
53
|
-
interface CallToolResult {
|
|
54
|
-
content: ContentItem[];
|
|
55
|
-
_meta?: Record<string, unknown>;
|
|
56
|
-
isError?: boolean;
|
|
57
|
-
|
|
58
|
-
[key: string]: unknown;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
export function registerGraphTools(server: McpServer, graphClient: GraphClient): void {
|
|
62
|
-
for (const tool of api.endpoints) {
|
|
63
|
-
// Create a zod schema for the parameters
|
|
64
|
-
const paramSchema: Record<string, any> = {};
|
|
65
|
-
if (tool.parameters && tool.parameters.length > 0) {
|
|
66
|
-
for (const param of tool.parameters) {
|
|
67
|
-
// Use z.any() as a fallback schema if the parameter schema is not specified
|
|
68
|
-
paramSchema[param.name] = param.schema || z.any();
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
server.tool(
|
|
73
|
-
tool.alias,
|
|
74
|
-
tool.description ?? '',
|
|
75
|
-
paramSchema,
|
|
76
|
-
{
|
|
77
|
-
title: tool.alias,
|
|
78
|
-
readOnlyHint: tool.method.toUpperCase() === 'GET',
|
|
79
|
-
},
|
|
80
|
-
async (params, extra) => {
|
|
81
|
-
logger.info(`Tool ${tool.alias} called with params: ${JSON.stringify(params)}`);
|
|
82
|
-
try {
|
|
83
|
-
logger.info(`params: ${JSON.stringify(params)}`);
|
|
84
|
-
|
|
85
|
-
const parameterDefinitions = tool.parameters || [];
|
|
86
|
-
|
|
87
|
-
let path = tool.path;
|
|
88
|
-
const queryParams: Record<string, string> = {};
|
|
89
|
-
const headers: Record<string, string> = {};
|
|
90
|
-
let body: any = null;
|
|
91
|
-
for (let [paramName, paramValue] of Object.entries(params)) {
|
|
92
|
-
const fixedParamName = paramName.replace(/__/g, '$');
|
|
93
|
-
const paramDef = parameterDefinitions.find((p) => p.name === paramName);
|
|
94
|
-
|
|
95
|
-
if (paramDef) {
|
|
96
|
-
switch (paramDef.type) {
|
|
97
|
-
case 'Path':
|
|
98
|
-
path = path
|
|
99
|
-
.replace(`{${paramName}}`, encodeURIComponent(paramValue as string))
|
|
100
|
-
.replace(`:${paramName}`, encodeURIComponent(paramValue as string));
|
|
101
|
-
break;
|
|
102
|
-
|
|
103
|
-
case 'Query':
|
|
104
|
-
queryParams[fixedParamName] = `${paramValue}`;
|
|
105
|
-
break;
|
|
106
|
-
|
|
107
|
-
case 'Body':
|
|
108
|
-
body = paramValue;
|
|
109
|
-
break;
|
|
110
|
-
|
|
111
|
-
case 'Header':
|
|
112
|
-
headers[fixedParamName] = `${paramValue}`;
|
|
113
|
-
break;
|
|
114
|
-
}
|
|
115
|
-
} else if (paramName === 'body') {
|
|
116
|
-
body = paramValue;
|
|
117
|
-
logger.info(`Set legacy body param: ${JSON.stringify(body)}`);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (Object.keys(queryParams).length > 0) {
|
|
122
|
-
const queryString = Object.entries(queryParams)
|
|
123
|
-
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
|
|
124
|
-
.join('&');
|
|
125
|
-
path = `${path}${path.includes('?') ? '&' : '?'}${queryString}`;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const options: any = {
|
|
129
|
-
method: tool.method.toUpperCase(),
|
|
130
|
-
headers,
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
if (options.method !== 'GET' && body) {
|
|
134
|
-
options.body = JSON.stringify(body);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
logger.info(`Making graph request to ${path} with options: ${JSON.stringify(options)}`);
|
|
138
|
-
const response = await graphClient.graphRequest(path, options);
|
|
139
|
-
|
|
140
|
-
// Convert McpResponse to CallToolResult with the correct structure
|
|
141
|
-
const content: ContentItem[] = response.content.map((item) => {
|
|
142
|
-
// GraphClient only returns text content items, so create proper TextContent items
|
|
143
|
-
const textContent: TextContent = {
|
|
144
|
-
type: 'text',
|
|
145
|
-
text: item.text,
|
|
146
|
-
};
|
|
147
|
-
return textContent;
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
const result: CallToolResult = {
|
|
151
|
-
content,
|
|
152
|
-
_meta: response._meta,
|
|
153
|
-
isError: response.isError,
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
return result;
|
|
157
|
-
} catch (error) {
|
|
158
|
-
logger.error(`Error in tool ${tool.alias}: ${(error as Error).message}`);
|
|
159
|
-
const errorContent: TextContent = {
|
|
160
|
-
type: 'text',
|
|
161
|
-
text: JSON.stringify({
|
|
162
|
-
error: `Error in tool ${tool.alias}: ${(error as Error).message}`,
|
|
163
|
-
}),
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
return {
|
|
167
|
-
content: [errorContent],
|
|
168
|
-
isError: true,
|
|
169
|
-
};
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { parseArgs } from './cli.js';
|
|
4
|
-
import logger from './logger.js';
|
|
5
|
-
import AuthManager from './auth.js';
|
|
6
|
-
import MicrosoftGraphServer from './server.js';
|
|
7
|
-
import { version } from './version.js';
|
|
8
|
-
|
|
9
|
-
async function main(): Promise<void> {
|
|
10
|
-
try {
|
|
11
|
-
const args = parseArgs();
|
|
12
|
-
|
|
13
|
-
const authManager = new AuthManager();
|
|
14
|
-
await authManager.loadTokenCache();
|
|
15
|
-
|
|
16
|
-
if (args.login) {
|
|
17
|
-
await authManager.acquireTokenByDeviceCode();
|
|
18
|
-
logger.info('Login completed, testing connection with Graph API...');
|
|
19
|
-
const result = await authManager.testLogin();
|
|
20
|
-
console.log(JSON.stringify(result));
|
|
21
|
-
process.exit(0);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
if (args.verifyLogin) {
|
|
25
|
-
logger.info('Verifying login...');
|
|
26
|
-
const result = await authManager.testLogin();
|
|
27
|
-
console.log(JSON.stringify(result));
|
|
28
|
-
process.exit(0);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (args.logout) {
|
|
32
|
-
await authManager.logout();
|
|
33
|
-
console.log(JSON.stringify({ message: 'Logged out successfully' }));
|
|
34
|
-
process.exit(0);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const server = new MicrosoftGraphServer(authManager, args);
|
|
38
|
-
await server.initialize(version);
|
|
39
|
-
await server.start();
|
|
40
|
-
} catch (error) {
|
|
41
|
-
logger.error(`Startup error: ${error}`);
|
|
42
|
-
process.exit(1);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
main();
|
package/src/logger.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import winston from 'winston';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { fileURLToPath } from 'url';
|
|
4
|
-
import fs from 'fs';
|
|
5
|
-
|
|
6
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const logsDir = path.join(__dirname, '..', 'logs');
|
|
8
|
-
|
|
9
|
-
if (!fs.existsSync(logsDir)) {
|
|
10
|
-
fs.mkdirSync(logsDir);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const logger = winston.createLogger({
|
|
14
|
-
level: process.env.LOG_LEVEL || 'info',
|
|
15
|
-
format: winston.format.combine(
|
|
16
|
-
winston.format.timestamp({
|
|
17
|
-
format: 'YYYY-MM-DD HH:mm:ss',
|
|
18
|
-
}),
|
|
19
|
-
winston.format.printf(({ level, message, timestamp }) => {
|
|
20
|
-
return `${timestamp} ${level.toUpperCase()}: ${message}`;
|
|
21
|
-
})
|
|
22
|
-
),
|
|
23
|
-
transports: [
|
|
24
|
-
new winston.transports.File({
|
|
25
|
-
filename: path.join(logsDir, 'error.log'),
|
|
26
|
-
level: 'error',
|
|
27
|
-
}),
|
|
28
|
-
new winston.transports.File({
|
|
29
|
-
filename: path.join(logsDir, 'mcp-server.log'),
|
|
30
|
-
}),
|
|
31
|
-
],
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
export const enableConsoleLogging = (): void => {
|
|
35
|
-
logger.add(
|
|
36
|
-
new winston.transports.Console({
|
|
37
|
-
format: winston.format.combine(winston.format.colorize(), winston.format.simple()),
|
|
38
|
-
silent: process.env.SILENT === 'true',
|
|
39
|
-
})
|
|
40
|
-
);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
export default logger;
|
package/src/server.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
-
import logger, { enableConsoleLogging } from './logger.js';
|
|
4
|
-
import { registerAuthTools } from './auth-tools.js';
|
|
5
|
-
import { registerGraphTools } from './graph-tools.js';
|
|
6
|
-
import GraphClient from './graph-client.js';
|
|
7
|
-
import AuthManager from './auth.js';
|
|
8
|
-
import type { CommandOptions } from './cli.ts';
|
|
9
|
-
|
|
10
|
-
class MicrosoftGraphServer {
|
|
11
|
-
private authManager: AuthManager;
|
|
12
|
-
private options: CommandOptions;
|
|
13
|
-
private graphClient: GraphClient;
|
|
14
|
-
private server: McpServer | null;
|
|
15
|
-
|
|
16
|
-
constructor(authManager: AuthManager, options: CommandOptions = {}) {
|
|
17
|
-
this.authManager = authManager;
|
|
18
|
-
this.options = options;
|
|
19
|
-
this.graphClient = new GraphClient(authManager);
|
|
20
|
-
this.server = null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async initialize(version: string): Promise<void> {
|
|
24
|
-
this.server = new McpServer({
|
|
25
|
-
name: 'Microsoft365MCP',
|
|
26
|
-
version,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
registerAuthTools(this.server, this.authManager);
|
|
30
|
-
registerGraphTools(this.server, this.graphClient);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async start(): Promise<void> {
|
|
34
|
-
if (this.options.v) {
|
|
35
|
-
enableConsoleLogging();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
logger.info('Microsoft 365 MCP Server starting...');
|
|
39
|
-
|
|
40
|
-
const transport = new StdioServerTransport();
|
|
41
|
-
await this.server!.connect(transport);
|
|
42
|
-
logger.info('Server connected to transport');
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export default MicrosoftGraphServer;
|
package/src/version.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'fs';
|
|
2
|
-
import path from 'path';
|
|
3
|
-
import { fileURLToPath } from 'url';
|
|
4
|
-
|
|
5
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
|
-
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
|
7
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
8
|
-
|
|
9
|
-
export const version: string = packageJson.version;
|