@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,238 +1,227 @@
1
- import logger from './logger.js';
2
- import { api } from './generated/client.js';
3
- import { z } from 'zod';
4
- export function registerGraphTools(server, graphClient, readOnly = false, enabledToolsPattern) {
5
- let enabledToolsRegex;
6
- if (enabledToolsPattern) {
7
- try {
8
- enabledToolsRegex = new RegExp(enabledToolsPattern, 'i');
9
- logger.info(`Tool filtering enabled with pattern: ${enabledToolsPattern}`);
10
- }
11
- catch (error) {
12
- logger.error(`Invalid tool filter regex pattern: ${enabledToolsPattern}. Ignoring filter.`);
13
- }
1
+ import logger from "./logger.js";
2
+ import { api } from "./generated/client.js";
3
+ import { z } from "zod";
4
+ function registerGraphTools(server, graphClient, readOnly = false, enabledToolsPattern) {
5
+ let enabledToolsRegex;
6
+ if (enabledToolsPattern) {
7
+ try {
8
+ enabledToolsRegex = new RegExp(enabledToolsPattern, "i");
9
+ logger.info(`Tool filtering enabled with pattern: ${enabledToolsPattern}`);
10
+ } catch (error) {
11
+ logger.error(`Invalid tool filter regex pattern: ${enabledToolsPattern}. Ignoring filter.`);
14
12
  }
15
- for (const tool of api.endpoints) {
16
- if (readOnly && tool.method.toUpperCase() !== 'GET') {
17
- logger.info(`Skipping write operation ${tool.alias} in read-only mode`);
18
- continue;
19
- }
20
- if (enabledToolsRegex && !enabledToolsRegex.test(tool.alias)) {
21
- logger.info(`Skipping tool ${tool.alias} - doesn't match filter pattern`);
22
- continue;
13
+ }
14
+ for (const tool of api.endpoints) {
15
+ if (readOnly && tool.method.toUpperCase() !== "GET") {
16
+ logger.info(`Skipping write operation ${tool.alias} in read-only mode`);
17
+ continue;
18
+ }
19
+ if (enabledToolsRegex && !enabledToolsRegex.test(tool.alias)) {
20
+ logger.info(`Skipping tool ${tool.alias} - doesn't match filter pattern`);
21
+ continue;
22
+ }
23
+ const paramSchema = {};
24
+ if (tool.parameters && tool.parameters.length > 0) {
25
+ for (const param of tool.parameters) {
26
+ if (param.type === "Body" && param.schema) {
27
+ paramSchema[param.name] = z.union([z.string(), param.schema]);
28
+ } else {
29
+ paramSchema[param.name] = param.schema || z.any();
23
30
  }
24
- const paramSchema = {};
25
- if (tool.parameters && tool.parameters.length > 0) {
26
- for (const param of tool.parameters) {
27
- if (param.type === 'Body' && param.schema) {
28
- paramSchema[param.name] = z.union([z.string(), param.schema]);
29
- }
30
- else {
31
- paramSchema[param.name] = param.schema || z.any();
32
- }
31
+ }
32
+ }
33
+ if (tool.method.toUpperCase() === "GET" && tool.path.includes("/")) {
34
+ paramSchema["fetchAllPages"] = z.boolean().describe("Automatically fetch all pages of results").optional();
35
+ }
36
+ server.tool(
37
+ tool.alias,
38
+ tool.description ?? "",
39
+ paramSchema,
40
+ {
41
+ title: tool.alias,
42
+ readOnlyHint: tool.method.toUpperCase() === "GET"
43
+ },
44
+ async (params, extra) => {
45
+ logger.info(`Tool ${tool.alias} called with params: ${JSON.stringify(params)}`);
46
+ try {
47
+ logger.info(`params: ${JSON.stringify(params)}`);
48
+ const parameterDefinitions = tool.parameters || [];
49
+ let path = tool.path;
50
+ const queryParams = {};
51
+ const headers = {};
52
+ let body = null;
53
+ for (let [paramName, paramValue] of Object.entries(params)) {
54
+ if (paramName === "fetchAllPages") {
55
+ continue;
33
56
  }
34
- }
35
- if (tool.method.toUpperCase() === 'GET' && tool.path.includes('/')) {
36
- paramSchema['fetchAllPages'] = z
37
- .boolean()
38
- .describe('Automatically fetch all pages of results')
39
- .optional();
40
- }
41
- server.tool(tool.alias, tool.description ?? '', paramSchema, {
42
- title: tool.alias,
43
- readOnlyHint: tool.method.toUpperCase() === 'GET',
44
- }, async (params, extra) => {
45
- logger.info(`Tool ${tool.alias} called with params: ${JSON.stringify(params)}`);
46
- try {
47
- logger.info(`params: ${JSON.stringify(params)}`);
48
- const parameterDefinitions = tool.parameters || [];
49
- let path = tool.path;
50
- const queryParams = {};
51
- const headers = {};
52
- let body = null;
53
- for (let [paramName, paramValue] of Object.entries(params)) {
54
- // Skip pagination control parameter - it's not part of the Microsoft Graph API - I think 🤷
55
- if (paramName === 'fetchAllPages') {
56
- continue;
57
- }
58
- // Ok, so, MCP clients (such as claude code) doesn't support $ in parameter names,
59
- // and others might not support __, so we strip them in hack.ts and restore them here
60
- const odataParams = [
61
- 'filter',
62
- 'select',
63
- 'expand',
64
- 'orderby',
65
- 'skip',
66
- 'top',
67
- 'count',
68
- 'search',
69
- 'format',
70
- ];
71
- const fixedParamName = odataParams.includes(paramName.toLowerCase())
72
- ? `$${paramName.toLowerCase()}`
73
- : paramName;
74
- const paramDef = parameterDefinitions.find((p) => p.name === paramName);
75
- if (paramDef) {
76
- switch (paramDef.type) {
77
- case 'Path':
78
- path = path
79
- .replace(`{${paramName}}`, encodeURIComponent(paramValue))
80
- .replace(`:${paramName}`, encodeURIComponent(paramValue));
81
- break;
82
- case 'Query':
83
- queryParams[fixedParamName] = `${paramValue}`;
84
- break;
85
- case 'Body':
86
- if (typeof paramValue === 'string') {
87
- try {
88
- body = JSON.parse(paramValue);
89
- }
90
- catch (e) {
91
- body = paramValue;
92
- }
93
- }
94
- else {
95
- body = paramValue;
96
- }
97
- break;
98
- case 'Header':
99
- headers[fixedParamName] = `${paramValue}`;
100
- break;
101
- }
102
- }
103
- else if (paramName === 'body') {
104
- if (typeof paramValue === 'string') {
105
- try {
106
- body = JSON.parse(paramValue);
107
- }
108
- catch (e) {
109
- body = paramValue;
110
- }
111
- }
112
- else {
113
- body = paramValue;
114
- }
115
- logger.info(`Set legacy body param: ${JSON.stringify(body)}`);
116
- }
117
- }
118
- if (Object.keys(queryParams).length > 0) {
119
- const queryString = Object.entries(queryParams)
120
- .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
121
- .join('&');
122
- path = `${path}${path.includes('?') ? '&' : '?'}${queryString}`;
123
- }
124
- const options = {
125
- method: tool.method.toUpperCase(),
126
- headers,
127
- };
128
- if (options.method !== 'GET' && body) {
129
- options.body = typeof body === 'string' ? body : JSON.stringify(body);
130
- }
131
- const isProbablyMediaContent = tool.errors?.some((error) => error.description === 'Retrieved media content') ||
132
- path.endsWith('/content');
133
- if (isProbablyMediaContent) {
134
- options.rawResponse = true;
135
- }
136
- logger.info(`Making graph request to ${path} with options: ${JSON.stringify(options)}`);
137
- let response = await graphClient.graphRequest(path, options);
138
- const fetchAllPages = params.fetchAllPages === true;
139
- if (fetchAllPages && response && response.content && response.content.length > 0) {
57
+ const odataParams = [
58
+ "filter",
59
+ "select",
60
+ "expand",
61
+ "orderby",
62
+ "skip",
63
+ "top",
64
+ "count",
65
+ "search",
66
+ "format"
67
+ ];
68
+ const fixedParamName = odataParams.includes(paramName.toLowerCase()) ? `$${paramName.toLowerCase()}` : paramName;
69
+ const paramDef = parameterDefinitions.find((p) => p.name === paramName);
70
+ if (paramDef) {
71
+ switch (paramDef.type) {
72
+ case "Path":
73
+ path = path.replace(`{${paramName}}`, encodeURIComponent(paramValue)).replace(`:${paramName}`, encodeURIComponent(paramValue));
74
+ break;
75
+ case "Query":
76
+ queryParams[fixedParamName] = `${paramValue}`;
77
+ break;
78
+ case "Body":
79
+ if (typeof paramValue === "string") {
140
80
  try {
141
- let combinedResponse = JSON.parse(response.content[0].text);
142
- let allItems = combinedResponse.value || [];
143
- let nextLink = combinedResponse['@odata.nextLink'];
144
- let pageCount = 1;
145
- while (nextLink) {
146
- logger.info(`Fetching page ${pageCount + 1} from: ${nextLink}`);
147
- const url = new URL(nextLink);
148
- const nextPath = url.pathname.replace('/v1.0', '');
149
- const nextOptions = { ...options };
150
- const nextQueryParams = {};
151
- for (const [key, value] of url.searchParams.entries()) {
152
- nextQueryParams[key] = value;
153
- }
154
- nextOptions.queryParams = nextQueryParams;
155
- const nextResponse = await graphClient.graphRequest(nextPath, nextOptions);
156
- if (nextResponse && nextResponse.content && nextResponse.content.length > 0) {
157
- const nextJsonResponse = JSON.parse(nextResponse.content[0].text);
158
- if (nextJsonResponse.value && Array.isArray(nextJsonResponse.value)) {
159
- allItems = allItems.concat(nextJsonResponse.value);
160
- }
161
- nextLink = nextJsonResponse['@odata.nextLink'];
162
- pageCount++;
163
- if (pageCount > 100) {
164
- logger.warn(`Reached maximum page limit (100) for pagination`);
165
- break;
166
- }
167
- }
168
- else {
169
- break;
170
- }
171
- }
172
- combinedResponse.value = allItems;
173
- if (combinedResponse['@odata.count']) {
174
- combinedResponse['@odata.count'] = allItems.length;
175
- }
176
- delete combinedResponse['@odata.nextLink'];
177
- response.content[0].text = JSON.stringify(combinedResponse);
178
- logger.info(`Pagination complete: collected ${allItems.length} items across ${pageCount} pages`);
179
- }
180
- catch (e) {
181
- logger.error(`Error during pagination: ${e}`);
81
+ body = JSON.parse(paramValue);
82
+ } catch (e) {
83
+ body = paramValue;
182
84
  }
85
+ } else {
86
+ body = paramValue;
87
+ }
88
+ break;
89
+ case "Header":
90
+ headers[fixedParamName] = `${paramValue}`;
91
+ break;
92
+ }
93
+ } else if (paramName === "body") {
94
+ if (typeof paramValue === "string") {
95
+ try {
96
+ body = JSON.parse(paramValue);
97
+ } catch (e) {
98
+ body = paramValue;
183
99
  }
184
- if (response && response.content && response.content.length > 0) {
185
- const responseText = response.content[0].text;
186
- const responseSize = responseText.length;
187
- logger.info(`Response size: ${responseSize} characters`);
188
- try {
189
- const jsonResponse = JSON.parse(responseText);
190
- if (jsonResponse.value && Array.isArray(jsonResponse.value)) {
191
- logger.info(`Response contains ${jsonResponse.value.length} items`);
192
- if (jsonResponse.value.length > 0 && jsonResponse.value[0].body) {
193
- logger.info(`First item has body field with size: ${JSON.stringify(jsonResponse.value[0].body).length} characters`);
194
- }
195
- }
196
- if (jsonResponse['@odata.nextLink']) {
197
- logger.info(`Response has pagination nextLink: ${jsonResponse['@odata.nextLink']}`);
198
- }
199
- const preview = responseText.substring(0, 500);
200
- logger.info(`Response preview: ${preview}${responseText.length > 500 ? '...' : ''}`);
201
- }
202
- catch (e) {
203
- const preview = responseText.substring(0, 500);
204
- logger.info(`Response preview (non-JSON): ${preview}${responseText.length > 500 ? '...' : ''}`);
205
- }
100
+ } else {
101
+ body = paramValue;
102
+ }
103
+ logger.info(`Set legacy body param: ${JSON.stringify(body)}`);
104
+ }
105
+ }
106
+ if (Object.keys(queryParams).length > 0) {
107
+ const queryString = Object.entries(queryParams).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
108
+ path = `${path}${path.includes("?") ? "&" : "?"}${queryString}`;
109
+ }
110
+ const options = {
111
+ method: tool.method.toUpperCase(),
112
+ headers
113
+ };
114
+ if (options.method !== "GET" && body) {
115
+ options.body = typeof body === "string" ? body : JSON.stringify(body);
116
+ }
117
+ const isProbablyMediaContent = tool.errors?.some((error) => error.description === "Retrieved media content") || path.endsWith("/content");
118
+ if (isProbablyMediaContent) {
119
+ options.rawResponse = true;
120
+ }
121
+ logger.info(`Making graph request to ${path} with options: ${JSON.stringify(options)}`);
122
+ let response = await graphClient.graphRequest(path, options);
123
+ const fetchAllPages = params.fetchAllPages === true;
124
+ if (fetchAllPages && response && response.content && response.content.length > 0) {
125
+ try {
126
+ let combinedResponse = JSON.parse(response.content[0].text);
127
+ let allItems = combinedResponse.value || [];
128
+ let nextLink = combinedResponse["@odata.nextLink"];
129
+ let pageCount = 1;
130
+ while (nextLink) {
131
+ logger.info(`Fetching page ${pageCount + 1} from: ${nextLink}`);
132
+ const url = new URL(nextLink);
133
+ const nextPath = url.pathname.replace("/v1.0", "");
134
+ const nextOptions = { ...options };
135
+ const nextQueryParams = {};
136
+ for (const [key, value] of url.searchParams.entries()) {
137
+ nextQueryParams[key] = value;
138
+ }
139
+ nextOptions.queryParams = nextQueryParams;
140
+ const nextResponse = await graphClient.graphRequest(nextPath, nextOptions);
141
+ if (nextResponse && nextResponse.content && nextResponse.content.length > 0) {
142
+ const nextJsonResponse = JSON.parse(nextResponse.content[0].text);
143
+ if (nextJsonResponse.value && Array.isArray(nextJsonResponse.value)) {
144
+ allItems = allItems.concat(nextJsonResponse.value);
145
+ }
146
+ nextLink = nextJsonResponse["@odata.nextLink"];
147
+ pageCount++;
148
+ if (pageCount > 100) {
149
+ logger.warn(`Reached maximum page limit (100) for pagination`);
150
+ break;
151
+ }
152
+ } else {
153
+ break;
206
154
  }
207
- // Convert McpResponse to CallToolResult with the correct structure
208
- const content = response.content.map((item) => {
209
- // GraphClient only returns text content items, so create proper TextContent items
210
- const textContent = {
211
- type: 'text',
212
- text: item.text,
213
- };
214
- return textContent;
215
- });
216
- const result = {
217
- content,
218
- _meta: response._meta,
219
- isError: response.isError,
220
- };
221
- return result;
155
+ }
156
+ combinedResponse.value = allItems;
157
+ if (combinedResponse["@odata.count"]) {
158
+ combinedResponse["@odata.count"] = allItems.length;
159
+ }
160
+ delete combinedResponse["@odata.nextLink"];
161
+ response.content[0].text = JSON.stringify(combinedResponse);
162
+ logger.info(
163
+ `Pagination complete: collected ${allItems.length} items across ${pageCount} pages`
164
+ );
165
+ } catch (e) {
166
+ logger.error(`Error during pagination: ${e}`);
222
167
  }
223
- catch (error) {
224
- logger.error(`Error in tool ${tool.alias}: ${error.message}`);
225
- const errorContent = {
226
- type: 'text',
227
- text: JSON.stringify({
228
- error: `Error in tool ${tool.alias}: ${error.message}`,
229
- }),
230
- };
231
- return {
232
- content: [errorContent],
233
- isError: true,
234
- };
168
+ }
169
+ if (response && response.content && response.content.length > 0) {
170
+ const responseText = response.content[0].text;
171
+ const responseSize = responseText.length;
172
+ logger.info(`Response size: ${responseSize} characters`);
173
+ try {
174
+ const jsonResponse = JSON.parse(responseText);
175
+ if (jsonResponse.value && Array.isArray(jsonResponse.value)) {
176
+ logger.info(`Response contains ${jsonResponse.value.length} items`);
177
+ if (jsonResponse.value.length > 0 && jsonResponse.value[0].body) {
178
+ logger.info(
179
+ `First item has body field with size: ${JSON.stringify(jsonResponse.value[0].body).length} characters`
180
+ );
181
+ }
182
+ }
183
+ if (jsonResponse["@odata.nextLink"]) {
184
+ logger.info(`Response has pagination nextLink: ${jsonResponse["@odata.nextLink"]}`);
185
+ }
186
+ const preview = responseText.substring(0, 500);
187
+ logger.info(`Response preview: ${preview}${responseText.length > 500 ? "..." : ""}`);
188
+ } catch (e) {
189
+ const preview = responseText.substring(0, 500);
190
+ logger.info(
191
+ `Response preview (non-JSON): ${preview}${responseText.length > 500 ? "..." : ""}`
192
+ );
235
193
  }
236
- });
237
- }
194
+ }
195
+ const content = response.content.map((item) => {
196
+ const textContent = {
197
+ type: "text",
198
+ text: item.text
199
+ };
200
+ return textContent;
201
+ });
202
+ const result = {
203
+ content,
204
+ _meta: response._meta,
205
+ isError: response.isError
206
+ };
207
+ return result;
208
+ } catch (error) {
209
+ logger.error(`Error in tool ${tool.alias}: ${error.message}`);
210
+ const errorContent = {
211
+ type: "text",
212
+ text: JSON.stringify({
213
+ error: `Error in tool ${tool.alias}: ${error.message}`
214
+ })
215
+ };
216
+ return {
217
+ content: [errorContent],
218
+ isError: true
219
+ };
220
+ }
221
+ }
222
+ );
223
+ }
238
224
  }
225
+ export {
226
+ registerGraphTools
227
+ };
package/dist/index.js CHANGED
@@ -1,86 +1,83 @@
1
1
  #!/usr/bin/env node
2
- import 'dotenv/config';
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
- import { buildScopesFromEndpoints } from './auth.js';
2
+ import "dotenv/config";
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
+ import { buildScopesFromEndpoints } from "./auth.js";
9
9
  async function main() {
10
- try {
11
- const args = parseArgs();
12
- let includeWorkScopes = args.forceWorkScopes;
13
- if (!includeWorkScopes) {
14
- const tempAuthManager = new AuthManager(undefined, buildScopesFromEndpoints(false));
15
- await tempAuthManager.loadTokenCache();
16
- const hasWorkPermissions = await tempAuthManager.hasWorkAccountPermissions();
17
- if (hasWorkPermissions) {
18
- includeWorkScopes = true;
19
- logger.info('Detected existing work account permissions, including work scopes');
20
- }
21
- }
22
- const scopes = buildScopesFromEndpoints(includeWorkScopes);
23
- const authManager = new AuthManager(undefined, scopes);
24
- await authManager.loadTokenCache();
25
- if (args.login) {
26
- await authManager.acquireTokenByDeviceCode();
27
- logger.info('Login completed, testing connection with Graph API...');
28
- const result = await authManager.testLogin();
29
- console.log(JSON.stringify(result));
30
- process.exit(0);
31
- }
32
- if (args.verifyLogin) {
33
- logger.info('Verifying login...');
34
- const result = await authManager.testLogin();
35
- console.log(JSON.stringify(result));
36
- process.exit(0);
37
- }
38
- if (args.logout) {
39
- await authManager.logout();
40
- console.log(JSON.stringify({ message: 'Logged out successfully' }));
41
- process.exit(0);
42
- }
43
- if (args.listAccounts) {
44
- const accounts = await authManager.listAccounts();
45
- const selectedAccountId = authManager.getSelectedAccountId();
46
- const result = accounts.map(account => ({
47
- id: account.homeAccountId,
48
- username: account.username,
49
- name: account.name,
50
- selected: account.homeAccountId === selectedAccountId
51
- }));
52
- console.log(JSON.stringify({ accounts: result }));
53
- process.exit(0);
54
- }
55
- if (args.selectAccount) {
56
- const success = await authManager.selectAccount(args.selectAccount);
57
- if (success) {
58
- console.log(JSON.stringify({ message: `Selected account: ${args.selectAccount}` }));
59
- }
60
- else {
61
- console.log(JSON.stringify({ error: `Account not found: ${args.selectAccount}` }));
62
- process.exit(1);
63
- }
64
- process.exit(0);
65
- }
66
- if (args.removeAccount) {
67
- const success = await authManager.removeAccount(args.removeAccount);
68
- if (success) {
69
- console.log(JSON.stringify({ message: `Removed account: ${args.removeAccount}` }));
70
- }
71
- else {
72
- console.log(JSON.stringify({ error: `Account not found: ${args.removeAccount}` }));
73
- process.exit(1);
74
- }
75
- process.exit(0);
76
- }
77
- const server = new MicrosoftGraphServer(authManager, args);
78
- await server.initialize(version);
79
- await server.start();
10
+ try {
11
+ const args = parseArgs();
12
+ let includeWorkScopes = args.forceWorkScopes;
13
+ if (!includeWorkScopes) {
14
+ const tempAuthManager = new AuthManager(void 0, buildScopesFromEndpoints(false));
15
+ await tempAuthManager.loadTokenCache();
16
+ const hasWorkPermissions = await tempAuthManager.hasWorkAccountPermissions();
17
+ if (hasWorkPermissions) {
18
+ includeWorkScopes = true;
19
+ logger.info("Detected existing work account permissions, including work scopes");
20
+ }
80
21
  }
81
- catch (error) {
82
- logger.error(`Startup error: ${error}`);
22
+ const scopes = buildScopesFromEndpoints(includeWorkScopes);
23
+ const authManager = new AuthManager(void 0, scopes);
24
+ await authManager.loadTokenCache();
25
+ if (args.login) {
26
+ await authManager.acquireTokenByDeviceCode();
27
+ logger.info("Login completed, testing connection with Graph API...");
28
+ const result = await authManager.testLogin();
29
+ console.log(JSON.stringify(result));
30
+ process.exit(0);
31
+ }
32
+ if (args.verifyLogin) {
33
+ logger.info("Verifying login...");
34
+ const result = await authManager.testLogin();
35
+ console.log(JSON.stringify(result));
36
+ process.exit(0);
37
+ }
38
+ if (args.logout) {
39
+ await authManager.logout();
40
+ console.log(JSON.stringify({ message: "Logged out successfully" }));
41
+ process.exit(0);
42
+ }
43
+ if (args.listAccounts) {
44
+ const accounts = await authManager.listAccounts();
45
+ const selectedAccountId = authManager.getSelectedAccountId();
46
+ const result = accounts.map((account) => ({
47
+ id: account.homeAccountId,
48
+ username: account.username,
49
+ name: account.name,
50
+ selected: account.homeAccountId === selectedAccountId
51
+ }));
52
+ console.log(JSON.stringify({ accounts: result }));
53
+ process.exit(0);
54
+ }
55
+ if (args.selectAccount) {
56
+ const success = await authManager.selectAccount(args.selectAccount);
57
+ if (success) {
58
+ console.log(JSON.stringify({ message: `Selected account: ${args.selectAccount}` }));
59
+ } else {
60
+ console.log(JSON.stringify({ error: `Account not found: ${args.selectAccount}` }));
61
+ process.exit(1);
62
+ }
63
+ process.exit(0);
64
+ }
65
+ if (args.removeAccount) {
66
+ const success = await authManager.removeAccount(args.removeAccount);
67
+ if (success) {
68
+ console.log(JSON.stringify({ message: `Removed account: ${args.removeAccount}` }));
69
+ } else {
70
+ console.log(JSON.stringify({ error: `Account not found: ${args.removeAccount}` }));
83
71
  process.exit(1);
72
+ }
73
+ process.exit(0);
84
74
  }
75
+ const server = new MicrosoftGraphServer(authManager, args);
76
+ await server.initialize(version);
77
+ await server.start();
78
+ } catch (error) {
79
+ logger.error(`Startup error: ${error}`);
80
+ process.exit(1);
81
+ }
85
82
  }
86
83
  main();