@softeria/ms-365-mcp-server 0.11.4 → 0.12.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.
@@ -1,509 +1,462 @@
1
- import logger from './logger.js';
2
- import { refreshAccessToken } from './lib/microsoft-auth.js';
1
+ import logger from "./logger.js";
2
+ import { refreshAccessToken } from "./lib/microsoft-auth.js";
3
3
  class GraphClient {
4
- constructor(authManager) {
5
- this.accessToken = null;
6
- this.refreshToken = null;
7
- this.authManager = authManager;
8
- this.sessions = new Map();
4
+ constructor(authManager) {
5
+ // accountId -> (filePath -> sessionId)
6
+ this.accessToken = null;
7
+ this.refreshToken = null;
8
+ this.authManager = authManager;
9
+ this.sessions = /* @__PURE__ */ new Map();
10
+ }
11
+ setOAuthTokens(accessToken, refreshToken) {
12
+ this.accessToken = accessToken;
13
+ this.refreshToken = refreshToken || null;
14
+ }
15
+ async getCurrentAccountId() {
16
+ const currentAccount = await this.authManager.getCurrentAccount();
17
+ return currentAccount?.homeAccountId || null;
18
+ }
19
+ getAccountSessions(accountId) {
20
+ if (!this.sessions.has(accountId)) {
21
+ this.sessions.set(accountId, /* @__PURE__ */ new Map());
9
22
  }
10
- setOAuthTokens(accessToken, refreshToken) {
11
- this.accessToken = accessToken;
12
- this.refreshToken = refreshToken || null;
13
- }
14
- async getCurrentAccountId() {
15
- const currentAccount = await this.authManager.getCurrentAccount();
16
- return currentAccount?.homeAccountId || null;
17
- }
18
- getAccountSessions(accountId) {
19
- if (!this.sessions.has(accountId)) {
20
- this.sessions.set(accountId, new Map());
23
+ return this.sessions.get(accountId);
24
+ }
25
+ async getSessionForFile(filePath) {
26
+ const accountId = await this.getCurrentAccountId();
27
+ if (!accountId) return null;
28
+ const accountSessions = this.getAccountSessions(accountId);
29
+ return accountSessions.get(filePath) || null;
30
+ }
31
+ async setSessionForFile(filePath, sessionId) {
32
+ const accountId = await this.getCurrentAccountId();
33
+ if (!accountId) return;
34
+ const accountSessions = this.getAccountSessions(accountId);
35
+ accountSessions.set(filePath, sessionId);
36
+ }
37
+ async createSession(filePath) {
38
+ try {
39
+ if (!filePath) {
40
+ logger.error("No file path provided for Excel session");
41
+ return null;
42
+ }
43
+ const existingSession = await this.getSessionForFile(filePath);
44
+ if (existingSession) {
45
+ return existingSession;
46
+ }
47
+ logger.info(`Creating new Excel session for file: ${filePath}`);
48
+ const accessToken = await this.authManager.getToken();
49
+ const response = await fetch(
50
+ `https://graph.microsoft.com/v1.0/me/drive/root:${filePath}:/workbook/createSession`,
51
+ {
52
+ method: "POST",
53
+ headers: {
54
+ Authorization: `Bearer ${accessToken}`,
55
+ "Content-Type": "application/json"
56
+ },
57
+ body: JSON.stringify({ persistChanges: true })
21
58
  }
22
- return this.sessions.get(accountId);
23
- }
24
- async getSessionForFile(filePath) {
25
- const accountId = await this.getCurrentAccountId();
26
- if (!accountId)
27
- return null;
28
- const accountSessions = this.getAccountSessions(accountId);
29
- return accountSessions.get(filePath) || null;
30
- }
31
- async setSessionForFile(filePath, sessionId) {
32
- const accountId = await this.getCurrentAccountId();
33
- if (!accountId)
34
- return;
35
- const accountSessions = this.getAccountSessions(accountId);
36
- accountSessions.set(filePath, sessionId);
59
+ );
60
+ if (!response.ok) {
61
+ const errorText = await response.text();
62
+ logger.error(`Failed to create session: ${response.status} - ${errorText}`);
63
+ return null;
64
+ }
65
+ const result = await response.json();
66
+ logger.info(`Session created successfully for file: ${filePath}`);
67
+ await this.setSessionForFile(filePath, result.id);
68
+ return result.id;
69
+ } catch (error) {
70
+ logger.error(`Error creating Excel session: ${error}`);
71
+ return null;
37
72
  }
38
- async createSession(filePath) {
39
- try {
40
- if (!filePath) {
41
- logger.error('No file path provided for Excel session');
42
- return null;
43
- }
44
- const existingSession = await this.getSessionForFile(filePath);
45
- if (existingSession) {
46
- return existingSession;
47
- }
48
- logger.info(`Creating new Excel session for file: ${filePath}`);
49
- const accessToken = await this.authManager.getToken();
50
- const response = await fetch(`https://graph.microsoft.com/v1.0/me/drive/root:${filePath}:/workbook/createSession`, {
51
- method: 'POST',
52
- headers: {
53
- Authorization: `Bearer ${accessToken}`,
54
- 'Content-Type': 'application/json',
55
- },
56
- body: JSON.stringify({ persistChanges: true }),
57
- });
58
- if (!response.ok) {
59
- const errorText = await response.text();
60
- logger.error(`Failed to create session: ${response.status} - ${errorText}`);
61
- return null;
62
- }
63
- const result = await response.json();
64
- logger.info(`Session created successfully for file: ${filePath}`);
65
- await this.setSessionForFile(filePath, result.id);
66
- return result.id;
67
- }
68
- catch (error) {
69
- logger.error(`Error creating Excel session: ${error}`);
70
- return null;
71
- }
73
+ }
74
+ async makeRequest(endpoint, options = {}) {
75
+ let accessToken = options.accessToken || this.accessToken || await this.authManager.getToken();
76
+ let refreshToken = options.refreshToken || this.refreshToken;
77
+ if (!accessToken) {
78
+ throw new Error("No access token available");
72
79
  }
73
- async makeRequest(endpoint, options = {}) {
74
- // Use OAuth tokens if available, otherwise fall back to authManager
75
- let accessToken = options.accessToken || this.accessToken || (await this.authManager.getToken());
76
- let refreshToken = options.refreshToken || this.refreshToken;
80
+ try {
81
+ const response = await this.performRequest(endpoint, accessToken, options);
82
+ if (response.status === 401 && refreshToken) {
83
+ await this.refreshAccessToken(refreshToken);
84
+ accessToken = this.accessToken || accessToken;
77
85
  if (!accessToken) {
78
- throw new Error('No access token available');
86
+ throw new Error("Failed to refresh access token");
79
87
  }
80
- try {
81
- const response = await this.performRequest(endpoint, accessToken, options);
82
- if (response.status === 401 && refreshToken) {
83
- // Token expired, try to refresh
84
- await this.refreshAccessToken(refreshToken);
85
- // Update token for retry
86
- accessToken = this.accessToken || accessToken;
87
- if (!accessToken) {
88
- throw new Error('Failed to refresh access token');
89
- }
90
- // Retry the request with new token
91
- return this.performRequest(endpoint, accessToken, options);
88
+ return this.performRequest(endpoint, accessToken, options);
89
+ }
90
+ if (response.status === 403) {
91
+ const errorText = await response.text();
92
+ if (errorText.includes("scope") || errorText.includes("permission")) {
93
+ const hasWorkPermissions = await this.authManager.hasWorkAccountPermissions();
94
+ if (!hasWorkPermissions) {
95
+ logger.info("403 scope error detected, attempting to expand to work account scopes...");
96
+ const expanded = await this.authManager.expandToWorkAccountScopes();
97
+ if (expanded) {
98
+ const newToken = await this.authManager.getToken();
99
+ if (newToken) {
100
+ logger.info("Retrying request with expanded scopes...");
101
+ return this.performRequest(endpoint, newToken, options);
102
+ }
92
103
  }
93
- if (response.status === 403) {
94
- const errorText = await response.text();
95
- if (errorText.includes('scope') || errorText.includes('permission')) {
96
- const hasWorkPermissions = await this.authManager.hasWorkAccountPermissions();
97
- if (!hasWorkPermissions) {
98
- logger.info('403 scope error detected, attempting to expand to work account scopes...');
99
- const expanded = await this.authManager.expandToWorkAccountScopes();
100
- if (expanded) {
101
- const newToken = await this.authManager.getToken();
102
- if (newToken) {
103
- logger.info('Retrying request with expanded scopes...');
104
- return this.performRequest(endpoint, newToken, options);
105
- }
106
- }
107
- }
108
- }
109
- throw new Error(`Microsoft Graph API scope error: ${response.status} ${response.statusText} - ${errorText}`);
110
- }
111
- if (!response.ok) {
112
- throw new Error(`Microsoft Graph API error: ${response.status} ${response.statusText}`);
113
- }
114
- return response.json();
115
- }
116
- catch (error) {
117
- logger.error('Microsoft Graph API request failed:', error);
118
- throw error;
104
+ }
119
105
  }
106
+ throw new Error(
107
+ `Microsoft Graph API scope error: ${response.status} ${response.statusText} - ${errorText}`
108
+ );
109
+ }
110
+ if (!response.ok) {
111
+ throw new Error(`Microsoft Graph API error: ${response.status} ${response.statusText}`);
112
+ }
113
+ return response.json();
114
+ } catch (error) {
115
+ logger.error("Microsoft Graph API request failed:", error);
116
+ throw error;
120
117
  }
121
- async refreshAccessToken(refreshToken) {
122
- const tenantId = process.env.MS365_MCP_TENANT_ID || 'common';
123
- const clientId = process.env.MS365_MCP_CLIENT_ID || '084a3e9f-a9f4-43f7-89f9-d229cf97853e';
124
- const clientSecret = process.env.MS365_MCP_CLIENT_SECRET;
125
- if (!clientSecret) {
126
- throw new Error('MS365_MCP_CLIENT_SECRET not configured');
127
- }
128
- const response = await refreshAccessToken(refreshToken, clientId, clientSecret, tenantId);
129
- this.accessToken = response.access_token;
130
- if (response.refresh_token) {
131
- this.refreshToken = response.refresh_token;
118
+ }
119
+ async refreshAccessToken(refreshToken) {
120
+ const tenantId = process.env.MS365_MCP_TENANT_ID || "common";
121
+ const clientId = process.env.MS365_MCP_CLIENT_ID || "084a3e9f-a9f4-43f7-89f9-d229cf97853e";
122
+ const clientSecret = process.env.MS365_MCP_CLIENT_SECRET;
123
+ if (!clientSecret) {
124
+ throw new Error("MS365_MCP_CLIENT_SECRET not configured");
125
+ }
126
+ const response = await refreshAccessToken(refreshToken, clientId, clientSecret, tenantId);
127
+ this.accessToken = response.access_token;
128
+ if (response.refresh_token) {
129
+ this.refreshToken = response.refresh_token;
130
+ }
131
+ }
132
+ async performRequest(endpoint, accessToken, options) {
133
+ let url;
134
+ let sessionId = null;
135
+ if (options.excelFile && !endpoint.startsWith("/drive") && !endpoint.startsWith("/users") && !endpoint.startsWith("/me") && !endpoint.startsWith("/teams") && !endpoint.startsWith("/chats") && !endpoint.startsWith("/planner")) {
136
+ sessionId = await this.getSessionForFile(options.excelFile);
137
+ if (!sessionId) {
138
+ sessionId = await this.createSessionWithToken(options.excelFile, accessToken);
139
+ }
140
+ url = `https://graph.microsoft.com/v1.0/me/drive/root:${options.excelFile}:${endpoint}`;
141
+ } else if (endpoint.startsWith("/drive") || endpoint.startsWith("/users") || endpoint.startsWith("/me") || endpoint.startsWith("/teams") || endpoint.startsWith("/chats") || endpoint.startsWith("/planner")) {
142
+ url = `https://graph.microsoft.com/v1.0${endpoint}`;
143
+ } else {
144
+ throw new Error("Excel operation requested without specifying a file");
145
+ }
146
+ const headers = {
147
+ Authorization: `Bearer ${accessToken}`,
148
+ "Content-Type": "application/json",
149
+ ...sessionId && { "workbook-session-id": sessionId },
150
+ ...options.headers
151
+ };
152
+ return fetch(url, {
153
+ method: options.method || "GET",
154
+ headers,
155
+ body: options.body
156
+ });
157
+ }
158
+ async graphRequest(endpoint, options = {}) {
159
+ try {
160
+ logger.info(`Calling ${endpoint} with options: ${JSON.stringify(options)}`);
161
+ const result = await this.makeRequest(endpoint, options);
162
+ return this.formatJsonResponse(result, options.rawResponse);
163
+ } catch (error) {
164
+ logger.error(`Error in Graph API request: ${error}`);
165
+ return {
166
+ content: [{ type: "text", text: JSON.stringify({ error: error.message }) }],
167
+ isError: true
168
+ };
169
+ }
170
+ }
171
+ async createSessionWithToken(filePath, accessToken) {
172
+ try {
173
+ if (!filePath) {
174
+ logger.error("No file path provided for Excel session");
175
+ return null;
176
+ }
177
+ const existingSession = await this.getSessionForFile(filePath);
178
+ if (existingSession) {
179
+ return existingSession;
180
+ }
181
+ logger.info(`Creating new Excel session for file: ${filePath}`);
182
+ const response = await fetch(
183
+ `https://graph.microsoft.com/v1.0/me/drive/root:${filePath}:/workbook/createSession`,
184
+ {
185
+ method: "POST",
186
+ headers: {
187
+ Authorization: `Bearer ${accessToken}`,
188
+ "Content-Type": "application/json"
189
+ },
190
+ body: JSON.stringify({ persistChanges: true })
132
191
  }
192
+ );
193
+ if (!response.ok) {
194
+ const errorText = await response.text();
195
+ logger.error(`Failed to create session: ${response.status} - ${errorText}`);
196
+ return null;
197
+ }
198
+ const result = await response.json();
199
+ logger.info(`Session created successfully for file: ${filePath}`);
200
+ await this.setSessionForFile(filePath, result.id);
201
+ return result.id;
202
+ } catch (error) {
203
+ logger.error(`Error creating Excel session: ${error}`);
204
+ return null;
133
205
  }
134
- async performRequest(endpoint, accessToken, options) {
135
- let url;
136
- let sessionId = null;
137
- if (options.excelFile &&
138
- !endpoint.startsWith('/drive') &&
139
- !endpoint.startsWith('/users') &&
140
- !endpoint.startsWith('/me') &&
141
- !endpoint.startsWith('/teams') &&
142
- !endpoint.startsWith('/chats') &&
143
- !endpoint.startsWith('/planner')) {
144
- sessionId = await this.getSessionForFile(options.excelFile);
145
- if (!sessionId) {
146
- sessionId = await this.createSessionWithToken(options.excelFile, accessToken);
147
- }
148
- url = `https://graph.microsoft.com/v1.0/me/drive/root:${options.excelFile}:${endpoint}`;
206
+ }
207
+ formatJsonResponse(data, rawResponse = false) {
208
+ if (rawResponse) {
209
+ return {
210
+ content: [{ type: "text", text: JSON.stringify(data) }]
211
+ };
212
+ }
213
+ if (data === null || data === void 0) {
214
+ return {
215
+ content: [{ type: "text", text: JSON.stringify({ success: true }) }]
216
+ };
217
+ }
218
+ const removeODataProps = (obj) => {
219
+ if (typeof obj === "object" && obj !== null) {
220
+ Object.keys(obj).forEach((key) => {
221
+ if (key.startsWith("@odata.")) {
222
+ delete obj[key];
223
+ } else if (typeof obj[key] === "object") {
224
+ removeODataProps(obj[key]);
225
+ }
226
+ });
227
+ }
228
+ };
229
+ removeODataProps(data);
230
+ return {
231
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
232
+ };
233
+ }
234
+ async graphRequestOld(endpoint, options = {}) {
235
+ try {
236
+ logger.info(`Calling ${endpoint} with options: ${JSON.stringify(options)}`);
237
+ let accessToken = await this.authManager.getToken();
238
+ let url;
239
+ let sessionId = null;
240
+ if (options.excelFile && !endpoint.startsWith("/drive") && !endpoint.startsWith("/users") && !endpoint.startsWith("/me") && !endpoint.startsWith("/teams") && !endpoint.startsWith("/chats") && !endpoint.startsWith("/planner") && !endpoint.startsWith("/sites")) {
241
+ sessionId = await this.getSessionForFile(options.excelFile);
242
+ if (!sessionId) {
243
+ sessionId = await this.createSession(options.excelFile);
149
244
  }
150
- else if (endpoint.startsWith('/drive') ||
151
- endpoint.startsWith('/users') ||
152
- endpoint.startsWith('/me') ||
153
- endpoint.startsWith('/teams') ||
154
- endpoint.startsWith('/chats') ||
155
- endpoint.startsWith('/planner')) {
156
- url = `https://graph.microsoft.com/v1.0${endpoint}`;
245
+ url = `https://graph.microsoft.com/v1.0/me/drive/root:${options.excelFile}:${endpoint}`;
246
+ } else if (endpoint.startsWith("/drive") || endpoint.startsWith("/users") || endpoint.startsWith("/me") || endpoint.startsWith("/teams") || endpoint.startsWith("/chats") || endpoint.startsWith("/planner") || endpoint.startsWith("/sites")) {
247
+ url = `https://graph.microsoft.com/v1.0${endpoint}`;
248
+ } else {
249
+ logger.error("Excel operation requested without specifying a file");
250
+ return {
251
+ content: [
252
+ {
253
+ type: "text",
254
+ text: JSON.stringify({ error: "No Excel file specified for this operation" })
255
+ }
256
+ ]
257
+ };
258
+ }
259
+ const headers = {
260
+ Authorization: `Bearer ${accessToken}`,
261
+ "Content-Type": "application/json",
262
+ ...sessionId && { "workbook-session-id": sessionId },
263
+ ...options.headers
264
+ };
265
+ delete options.headers;
266
+ logger.info(` ** Making request to ${url} with options: ${JSON.stringify(options)}`);
267
+ const response = await fetch(url, {
268
+ headers,
269
+ ...options
270
+ });
271
+ if (response.status === 401) {
272
+ logger.info("Access token expired, refreshing...");
273
+ const newToken = await this.authManager.getToken(true);
274
+ if (options.excelFile && !endpoint.startsWith("/drive") && !endpoint.startsWith("/users") && !endpoint.startsWith("/me") && !endpoint.startsWith("/teams") && !endpoint.startsWith("/chats") && !endpoint.startsWith("/planner") && !endpoint.startsWith("/sites")) {
275
+ sessionId = await this.createSession(options.excelFile);
157
276
  }
158
- else {
159
- throw new Error('Excel operation requested without specifying a file');
277
+ headers.Authorization = `Bearer ${newToken}`;
278
+ if (sessionId && !endpoint.startsWith("/drive") && !endpoint.startsWith("/users") && !endpoint.startsWith("/me") && !endpoint.startsWith("/teams") && !endpoint.startsWith("/chats") && !endpoint.startsWith("/planner") && !endpoint.startsWith("/sites")) {
279
+ headers["workbook-session-id"] = sessionId;
160
280
  }
161
- const headers = {
162
- Authorization: `Bearer ${accessToken}`,
163
- 'Content-Type': 'application/json',
164
- ...(sessionId && { 'workbook-session-id': sessionId }),
165
- ...options.headers,
166
- };
167
- return fetch(url, {
168
- method: options.method || 'GET',
169
- headers,
170
- body: options.body,
281
+ const retryResponse = await fetch(url, {
282
+ headers,
283
+ ...options
171
284
  });
172
- }
173
- async graphRequest(endpoint, options = {}) {
174
- try {
175
- logger.info(`Calling ${endpoint} with options: ${JSON.stringify(options)}`);
176
- // Use new OAuth-aware request method
177
- const result = await this.makeRequest(endpoint, options);
178
- return this.formatJsonResponse(result, options.rawResponse);
179
- }
180
- catch (error) {
181
- logger.error(`Error in Graph API request: ${error}`);
182
- return {
183
- content: [{ type: 'text', text: JSON.stringify({ error: error.message }) }],
184
- isError: true,
185
- };
285
+ if (!retryResponse.ok) {
286
+ throw new Error(`Graph API error: ${retryResponse.status} ${await retryResponse.text()}`);
186
287
  }
288
+ return this.formatResponse(retryResponse, options.rawResponse);
289
+ }
290
+ if (!response.ok) {
291
+ throw new Error(`Graph API error: ${response.status} ${await response.text()}`);
292
+ }
293
+ return this.formatResponse(response, options.rawResponse);
294
+ } catch (error) {
295
+ logger.error(`Error in Graph API request: ${error}`);
296
+ return {
297
+ content: [{ type: "text", text: JSON.stringify({ error: error.message }) }],
298
+ isError: true
299
+ };
187
300
  }
188
- async createSessionWithToken(filePath, accessToken) {
189
- try {
190
- if (!filePath) {
191
- logger.error('No file path provided for Excel session');
192
- return null;
193
- }
194
- const existingSession = await this.getSessionForFile(filePath);
195
- if (existingSession) {
196
- return existingSession;
197
- }
198
- logger.info(`Creating new Excel session for file: ${filePath}`);
199
- const response = await fetch(`https://graph.microsoft.com/v1.0/me/drive/root:${filePath}:/workbook/createSession`, {
200
- method: 'POST',
201
- headers: {
202
- Authorization: `Bearer ${accessToken}`,
203
- 'Content-Type': 'application/json',
204
- },
205
- body: JSON.stringify({ persistChanges: true }),
206
- });
207
- if (!response.ok) {
208
- const errorText = await response.text();
209
- logger.error(`Failed to create session: ${response.status} - ${errorText}`);
210
- return null;
301
+ }
302
+ async formatResponse(response, rawResponse = false) {
303
+ try {
304
+ if (response.status === 204) {
305
+ return {
306
+ content: [
307
+ {
308
+ type: "text",
309
+ text: JSON.stringify({
310
+ message: "Operation completed successfully"
311
+ })
211
312
  }
212
- const result = await response.json();
213
- logger.info(`Session created successfully for file: ${filePath}`);
214
- await this.setSessionForFile(filePath, result.id);
215
- return result.id;
216
- }
217
- catch (error) {
218
- logger.error(`Error creating Excel session: ${error}`);
219
- return null;
220
- }
221
- }
222
- formatJsonResponse(data, rawResponse = false) {
223
- if (rawResponse) {
224
- return {
225
- content: [{ type: 'text', text: JSON.stringify(data) }],
226
- };
227
- }
228
- if (data === null || data === undefined) {
229
- return {
230
- content: [{ type: 'text', text: JSON.stringify({ success: true }) }],
231
- };
313
+ ]
314
+ };
315
+ }
316
+ if (rawResponse) {
317
+ const contentType2 = response.headers.get("content-type");
318
+ if (contentType2 && contentType2.startsWith("text/")) {
319
+ const text = await response.text();
320
+ return {
321
+ content: [{ type: "text", text }]
322
+ };
232
323
  }
233
- // Remove OData properties
234
- const removeODataProps = (obj) => {
235
- if (typeof obj === 'object' && obj !== null) {
236
- Object.keys(obj).forEach((key) => {
237
- if (key.startsWith('@odata.')) {
238
- delete obj[key];
239
- }
240
- else if (typeof obj[key] === 'object') {
241
- removeODataProps(obj[key]);
242
- }
243
- });
324
+ return {
325
+ content: [
326
+ {
327
+ type: "text",
328
+ text: JSON.stringify({
329
+ message: "Binary file content received",
330
+ contentType: contentType2,
331
+ contentLength: response.headers.get("content-length")
332
+ })
244
333
  }
334
+ ]
245
335
  };
246
- removeODataProps(data);
336
+ }
337
+ const contentType = response.headers.get("content-type");
338
+ if (contentType && !contentType.includes("application/json")) {
339
+ if (contentType.startsWith("text/")) {
340
+ const text = await response.text();
341
+ return {
342
+ content: [{ type: "text", text }]
343
+ };
344
+ }
247
345
  return {
248
- content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
249
- };
250
- }
251
- async graphRequestOld(endpoint, options = {}) {
252
- try {
253
- logger.info(`Calling ${endpoint} with options: ${JSON.stringify(options)}`);
254
- let accessToken = await this.authManager.getToken();
255
- let url;
256
- let sessionId = null;
257
- if (options.excelFile &&
258
- !endpoint.startsWith('/drive') &&
259
- !endpoint.startsWith('/users') &&
260
- !endpoint.startsWith('/me') &&
261
- !endpoint.startsWith('/teams') &&
262
- !endpoint.startsWith('/chats') &&
263
- !endpoint.startsWith('/planner') &&
264
- !endpoint.startsWith('/sites')) {
265
- sessionId = await this.getSessionForFile(options.excelFile);
266
- if (!sessionId) {
267
- sessionId = await this.createSession(options.excelFile);
268
- }
269
- url = `https://graph.microsoft.com/v1.0/me/drive/root:${options.excelFile}:${endpoint}`;
346
+ content: [
347
+ {
348
+ type: "text",
349
+ text: JSON.stringify({
350
+ message: "Binary or non-JSON content received",
351
+ contentType,
352
+ contentLength: response.headers.get("content-length")
353
+ })
270
354
  }
271
- else if (endpoint.startsWith('/drive') ||
272
- endpoint.startsWith('/users') ||
273
- endpoint.startsWith('/me') ||
274
- endpoint.startsWith('/teams') ||
275
- endpoint.startsWith('/chats') ||
276
- endpoint.startsWith('/planner') ||
277
- endpoint.startsWith('/sites')) {
278
- url = `https://graph.microsoft.com/v1.0${endpoint}`;
279
- }
280
- else {
281
- logger.error('Excel operation requested without specifying a file');
282
- return {
283
- content: [
284
- {
285
- type: 'text',
286
- text: JSON.stringify({ error: 'No Excel file specified for this operation' }),
287
- },
288
- ],
289
- };
290
- }
291
- const headers = {
292
- Authorization: `Bearer ${accessToken}`,
293
- 'Content-Type': 'application/json',
294
- ...(sessionId && { 'workbook-session-id': sessionId }),
295
- ...options.headers,
296
- };
297
- delete options.headers;
298
- logger.info(` ** Making request to ${url} with options: ${JSON.stringify(options)}`);
299
- const response = await fetch(url, {
300
- headers,
301
- ...options,
302
- });
303
- if (response.status === 401) {
304
- logger.info('Access token expired, refreshing...');
305
- const newToken = await this.authManager.getToken(true);
306
- if (options.excelFile &&
307
- !endpoint.startsWith('/drive') &&
308
- !endpoint.startsWith('/users') &&
309
- !endpoint.startsWith('/me') &&
310
- !endpoint.startsWith('/teams') &&
311
- !endpoint.startsWith('/chats') &&
312
- !endpoint.startsWith('/planner') &&
313
- !endpoint.startsWith('/sites')) {
314
- sessionId = await this.createSession(options.excelFile);
315
- }
316
- headers.Authorization = `Bearer ${newToken}`;
317
- if (sessionId &&
318
- !endpoint.startsWith('/drive') &&
319
- !endpoint.startsWith('/users') &&
320
- !endpoint.startsWith('/me') &&
321
- !endpoint.startsWith('/teams') &&
322
- !endpoint.startsWith('/chats') &&
323
- !endpoint.startsWith('/planner') &&
324
- !endpoint.startsWith('/sites')) {
325
- headers['workbook-session-id'] = sessionId;
326
- }
327
- const retryResponse = await fetch(url, {
328
- headers,
329
- ...options,
330
- });
331
- if (!retryResponse.ok) {
332
- throw new Error(`Graph API error: ${retryResponse.status} ${await retryResponse.text()}`);
333
- }
334
- return this.formatResponse(retryResponse, options.rawResponse);
335
- }
336
- if (!response.ok) {
337
- throw new Error(`Graph API error: ${response.status} ${await response.text()}`);
355
+ ]
356
+ };
357
+ }
358
+ const result = await response.json();
359
+ const removeODataProps = (obj) => {
360
+ if (!obj || typeof obj !== "object") return;
361
+ if (Array.isArray(obj)) {
362
+ obj.forEach((item) => removeODataProps(item));
363
+ } else {
364
+ Object.keys(obj).forEach((key) => {
365
+ if (key.startsWith("@odata") && !["@odata.nextLink", "@odata.count"].includes(key)) {
366
+ delete obj[key];
367
+ } else if (typeof obj[key] === "object") {
368
+ removeODataProps(obj[key]);
338
369
  }
339
- return this.formatResponse(response, options.rawResponse);
340
- }
341
- catch (error) {
342
- logger.error(`Error in Graph API request: ${error}`);
343
- return {
344
- content: [{ type: 'text', text: JSON.stringify({ error: error.message }) }],
345
- isError: true,
346
- };
370
+ });
347
371
  }
372
+ };
373
+ removeODataProps(result);
374
+ return {
375
+ content: [{ type: "text", text: JSON.stringify(result) }]
376
+ };
377
+ } catch (error) {
378
+ logger.error(`Error formatting response: ${error}`);
379
+ return {
380
+ content: [{ type: "text", text: JSON.stringify({ message: "Success" }) }]
381
+ };
348
382
  }
349
- async formatResponse(response, rawResponse = false) {
350
- try {
351
- if (response.status === 204) {
352
- return {
353
- content: [
354
- {
355
- type: 'text',
356
- text: JSON.stringify({
357
- message: 'Operation completed successfully',
358
- }),
359
- },
360
- ],
361
- };
362
- }
363
- if (rawResponse) {
364
- const contentType = response.headers.get('content-type');
365
- if (contentType && contentType.startsWith('text/')) {
366
- const text = await response.text();
367
- return {
368
- content: [{ type: 'text', text }],
369
- };
370
- }
371
- return {
372
- content: [
373
- {
374
- type: 'text',
375
- text: JSON.stringify({
376
- message: 'Binary file content received',
377
- contentType: contentType,
378
- contentLength: response.headers.get('content-length'),
379
- }),
380
- },
381
- ],
382
- };
383
- }
384
- const contentType = response.headers.get('content-type');
385
- if (contentType && !contentType.includes('application/json')) {
386
- if (contentType.startsWith('text/')) {
387
- const text = await response.text();
388
- return {
389
- content: [{ type: 'text', text }],
390
- };
391
- }
392
- return {
393
- content: [
394
- {
395
- type: 'text',
396
- text: JSON.stringify({
397
- message: 'Binary or non-JSON content received',
398
- contentType: contentType,
399
- contentLength: response.headers.get('content-length'),
400
- }),
401
- },
402
- ],
403
- };
404
- }
405
- const result = await response.json();
406
- const removeODataProps = (obj) => {
407
- if (!obj || typeof obj !== 'object')
408
- return;
409
- if (Array.isArray(obj)) {
410
- obj.forEach((item) => removeODataProps(item));
411
- }
412
- else {
413
- Object.keys(obj).forEach((key) => {
414
- if (key.startsWith('@odata') && !['@odata.nextLink', '@odata.count'].includes(key)) {
415
- delete obj[key];
416
- }
417
- else if (typeof obj[key] === 'object') {
418
- removeODataProps(obj[key]);
419
- }
420
- });
421
- }
422
- };
423
- removeODataProps(result);
424
- return {
425
- content: [{ type: 'text', text: JSON.stringify(result) }],
426
- };
427
- }
428
- catch (error) {
429
- logger.error(`Error formatting response: ${error}`);
430
- return {
431
- content: [{ type: 'text', text: JSON.stringify({ message: 'Success' }) }],
432
- };
433
- }
383
+ }
384
+ async closeSession(filePath) {
385
+ const sessionId = await this.getSessionForFile(filePath);
386
+ if (!filePath || !sessionId) {
387
+ return {
388
+ content: [
389
+ {
390
+ type: "text",
391
+ text: JSON.stringify({ message: "No active session for the specified file" })
392
+ }
393
+ ]
394
+ };
434
395
  }
435
- async closeSession(filePath) {
436
- const sessionId = await this.getSessionForFile(filePath);
437
- if (!filePath || !sessionId) {
438
- return {
439
- content: [
440
- {
441
- type: 'text',
442
- text: JSON.stringify({ message: 'No active session for the specified file' }),
443
- },
444
- ],
445
- };
446
- }
447
- try {
448
- const accessToken = await this.authManager.getToken();
449
- const response = await fetch(`https://graph.microsoft.com/v1.0/me/drive/root:${filePath}:/workbook/closeSession`, {
450
- method: 'POST',
451
- headers: {
452
- Authorization: `Bearer ${accessToken}`,
453
- 'Content-Type': 'application/json',
454
- 'workbook-session-id': sessionId,
455
- },
456
- });
457
- if (response.ok) {
458
- const accountId = await this.getCurrentAccountId();
459
- if (accountId) {
460
- const accountSessions = this.getAccountSessions(accountId);
461
- accountSessions.delete(filePath);
462
- }
463
- return {
464
- content: [
465
- {
466
- type: 'text',
467
- text: JSON.stringify({ message: `Session for ${filePath} closed successfully` }),
468
- },
469
- ],
470
- };
471
- }
472
- else {
473
- throw new Error(`Failed to close session: ${response.status}`);
474
- }
475
- }
476
- catch (error) {
477
- logger.error(`Error closing session: ${error}`);
478
- return {
479
- content: [
480
- {
481
- type: 'text',
482
- text: JSON.stringify({ error: `Failed to close session for ${filePath}` }),
483
- },
484
- ],
485
- isError: true,
486
- };
396
+ try {
397
+ const accessToken = await this.authManager.getToken();
398
+ const response = await fetch(
399
+ `https://graph.microsoft.com/v1.0/me/drive/root:${filePath}:/workbook/closeSession`,
400
+ {
401
+ method: "POST",
402
+ headers: {
403
+ Authorization: `Bearer ${accessToken}`,
404
+ "Content-Type": "application/json",
405
+ "workbook-session-id": sessionId
406
+ }
487
407
  }
488
- }
489
- async closeAllSessions() {
490
- const results = [];
408
+ );
409
+ if (response.ok) {
491
410
  const accountId = await this.getCurrentAccountId();
492
411
  if (accountId) {
493
- const accountSessions = this.getAccountSessions(accountId);
494
- for (const [filePath] of accountSessions) {
495
- const result = await this.closeSession(filePath);
496
- results.push(result);
497
- }
412
+ const accountSessions = this.getAccountSessions(accountId);
413
+ accountSessions.delete(filePath);
498
414
  }
499
415
  return {
500
- content: [
501
- {
502
- type: 'text',
503
- text: JSON.stringify({ message: 'All sessions closed', results }),
504
- },
505
- ],
416
+ content: [
417
+ {
418
+ type: "text",
419
+ text: JSON.stringify({ message: `Session for ${filePath} closed successfully` })
420
+ }
421
+ ]
506
422
  };
423
+ } else {
424
+ throw new Error(`Failed to close session: ${response.status}`);
425
+ }
426
+ } catch (error) {
427
+ logger.error(`Error closing session: ${error}`);
428
+ return {
429
+ content: [
430
+ {
431
+ type: "text",
432
+ text: JSON.stringify({ error: `Failed to close session for ${filePath}` })
433
+ }
434
+ ],
435
+ isError: true
436
+ };
507
437
  }
438
+ }
439
+ async closeAllSessions() {
440
+ const results = [];
441
+ const accountId = await this.getCurrentAccountId();
442
+ if (accountId) {
443
+ const accountSessions = this.getAccountSessions(accountId);
444
+ for (const [filePath] of accountSessions) {
445
+ const result = await this.closeSession(filePath);
446
+ results.push(result);
447
+ }
448
+ }
449
+ return {
450
+ content: [
451
+ {
452
+ type: "text",
453
+ text: JSON.stringify({ message: "All sessions closed", results })
454
+ }
455
+ ]
456
+ };
457
+ }
508
458
  }
509
- export default GraphClient;
459
+ var graph_client_default = GraphClient;
460
+ export {
461
+ graph_client_default as default
462
+ };