@frustrated/ms-graph-mcp 0.1.7 → 0.1.10
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/AGENTS.MD +137 -0
- package/CONTRIBUTING.md +3 -3
- package/LICENSE +21 -21
- package/README.md +95 -95
- package/bun.lock +183 -1
- package/docs/jsr_mcp_ideation.md +83 -83
- package/docs/ms_graph_mcp_spec.md +212 -212
- package/docs/tools/README.md +13 -13
- package/docs/tools/calendar.md +31 -31
- package/docs/tools/mail.md +26 -26
- package/package.json +49 -45
- package/src/auth.ts +194 -199
- package/src/config.ts +52 -48
- package/src/index.ts +77 -80
- package/src/mcp-interface.ts +72 -122
- package/src/tools/calendar.ts +41 -41
- package/src/tools/index.ts +9 -9
- package/src/tools/mail.ts +34 -34
- package/src/utils.ts +21 -20
- package/tsconfig.json +21 -21
- package/.claude/settings.local.json +0 -8
package/docs/tools/calendar.md
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
# Calendar Tools
|
|
2
|
-
|
|
3
|
-
This document details the sub-tools available under the `calendar` top-level tool. These tools allow interaction with the user's Microsoft 365 calendar.
|
|
4
|
-
|
|
5
|
-
## `calendar.create_event`
|
|
6
|
-
|
|
7
|
-
Creates a new event in the user's calendar.
|
|
8
|
-
|
|
9
|
-
### Parameters
|
|
10
|
-
|
|
11
|
-
* `subject` (string, required): The subject of the event.
|
|
12
|
-
* `start` (object, required): The start date and time of the event.
|
|
13
|
-
* `dateTime` (string, required): The date and time in ISO 8601 format (e.g., `2026-03-20T09:00:00`).
|
|
14
|
-
* `timeZone` (string, required): The time zone of the start time (e.g., `America/Los_Angeles`).
|
|
15
|
-
* `end` (object, required): The end date and time of the event.
|
|
16
|
-
* `dateTime` (string, required): The date and time in ISO 8601 format (e.g., `2026-03-20T10:00:00`).
|
|
17
|
-
* `timeZone` (string, required): The time zone of the end time (e.g., `America/Los_Angeles`).
|
|
18
|
-
* `content` (string, optional): The body content of the event, in HTML format.
|
|
19
|
-
* `attendees` (array of objects, optional): A list of attendees for the event.
|
|
20
|
-
* Each object contains:
|
|
21
|
-
* `emailAddress` (string, required): The email address of the attendee.
|
|
22
|
-
* `type` (string, required): The attendee type, either `required` or `optional`.
|
|
23
|
-
* `location` (string, optional): The display name of the event location.
|
|
24
|
-
|
|
25
|
-
### Returns
|
|
26
|
-
|
|
27
|
-
An object containing:
|
|
28
|
-
* `id` (string): The unique identifier of the created event.
|
|
29
|
-
* `webLink` (string): A URL to open the event in Outlook Web App.
|
|
30
|
-
* `status` (string): The status of the event creation, either `created` or `failed`.
|
|
31
|
-
* `errorMessage` (string, optional): An error message if the event creation failed.
|
|
1
|
+
# Calendar Tools
|
|
2
|
+
|
|
3
|
+
This document details the sub-tools available under the `calendar` top-level tool. These tools allow interaction with the user's Microsoft 365 calendar.
|
|
4
|
+
|
|
5
|
+
## `calendar.create_event`
|
|
6
|
+
|
|
7
|
+
Creates a new event in the user's calendar.
|
|
8
|
+
|
|
9
|
+
### Parameters
|
|
10
|
+
|
|
11
|
+
* `subject` (string, required): The subject of the event.
|
|
12
|
+
* `start` (object, required): The start date and time of the event.
|
|
13
|
+
* `dateTime` (string, required): The date and time in ISO 8601 format (e.g., `2026-03-20T09:00:00`).
|
|
14
|
+
* `timeZone` (string, required): The time zone of the start time (e.g., `America/Los_Angeles`).
|
|
15
|
+
* `end` (object, required): The end date and time of the event.
|
|
16
|
+
* `dateTime` (string, required): The date and time in ISO 8601 format (e.g., `2026-03-20T10:00:00`).
|
|
17
|
+
* `timeZone` (string, required): The time zone of the end time (e.g., `America/Los_Angeles`).
|
|
18
|
+
* `content` (string, optional): The body content of the event, in HTML format.
|
|
19
|
+
* `attendees` (array of objects, optional): A list of attendees for the event.
|
|
20
|
+
* Each object contains:
|
|
21
|
+
* `emailAddress` (string, required): The email address of the attendee.
|
|
22
|
+
* `type` (string, required): The attendee type, either `required` or `optional`.
|
|
23
|
+
* `location` (string, optional): The display name of the event location.
|
|
24
|
+
|
|
25
|
+
### Returns
|
|
26
|
+
|
|
27
|
+
An object containing:
|
|
28
|
+
* `id` (string): The unique identifier of the created event.
|
|
29
|
+
* `webLink` (string): A URL to open the event in Outlook Web App.
|
|
30
|
+
* `status` (string): The status of the event creation, either `created` or `failed`.
|
|
31
|
+
* `errorMessage` (string, optional): An error message if the event creation failed.
|
package/docs/tools/mail.md
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
# Mail Tools
|
|
2
|
-
|
|
3
|
-
This document details the sub-tools available under the `mail` top-level tool. These tools allow interaction with the user's Microsoft 365 mailbox.
|
|
4
|
-
|
|
5
|
-
## `mail.list_messages`
|
|
6
|
-
|
|
7
|
-
Lists messages from the user's mailbox.
|
|
8
|
-
|
|
9
|
-
### Parameters
|
|
10
|
-
|
|
11
|
-
* `folderId` (string, optional): The ID of the mail folder to retrieve messages from. If not provided, defaults to the inbox (`/me/messages`).
|
|
12
|
-
* `top` (number, optional): The maximum number of messages to return in the response.
|
|
13
|
-
* `filter` (string, optional): An OData filter query to apply to the messages (e.g., `isRead eq false`).
|
|
14
|
-
|
|
15
|
-
### Returns
|
|
16
|
-
|
|
17
|
-
An object containing:
|
|
18
|
-
* `messages` (array): A list of message objects, each containing:
|
|
19
|
-
* `id` (string): The unique identifier of the message.
|
|
20
|
-
* `subject` (string): The subject of the message.
|
|
21
|
-
* `from` (object): Information about the sender.
|
|
22
|
-
* `receivedDateTime` (string): The date and time the message was received.
|
|
23
|
-
* `isRead` (boolean): Indicates whether the message has been read.
|
|
24
|
-
* `bodyPreview` (string): A short preview of the message body.
|
|
25
|
-
* `webLink` (string): A URL to open the message in Outlook Web App.
|
|
26
|
-
* `nextLink` (string, optional): A URL to retrieve the next page of messages, if available.
|
|
1
|
+
# Mail Tools
|
|
2
|
+
|
|
3
|
+
This document details the sub-tools available under the `mail` top-level tool. These tools allow interaction with the user's Microsoft 365 mailbox.
|
|
4
|
+
|
|
5
|
+
## `mail.list_messages`
|
|
6
|
+
|
|
7
|
+
Lists messages from the user's mailbox.
|
|
8
|
+
|
|
9
|
+
### Parameters
|
|
10
|
+
|
|
11
|
+
* `folderId` (string, optional): The ID of the mail folder to retrieve messages from. If not provided, defaults to the inbox (`/me/messages`).
|
|
12
|
+
* `top` (number, optional): The maximum number of messages to return in the response.
|
|
13
|
+
* `filter` (string, optional): An OData filter query to apply to the messages (e.g., `isRead eq false`).
|
|
14
|
+
|
|
15
|
+
### Returns
|
|
16
|
+
|
|
17
|
+
An object containing:
|
|
18
|
+
* `messages` (array): A list of message objects, each containing:
|
|
19
|
+
* `id` (string): The unique identifier of the message.
|
|
20
|
+
* `subject` (string): The subject of the message.
|
|
21
|
+
* `from` (object): Information about the sender.
|
|
22
|
+
* `receivedDateTime` (string): The date and time the message was received.
|
|
23
|
+
* `isRead` (boolean): Indicates whether the message has been read.
|
|
24
|
+
* `bodyPreview` (string): A short preview of the message body.
|
|
25
|
+
* `webLink` (string): A URL to open the message in Outlook Web App.
|
|
26
|
+
* `nextLink` (string, optional): A URL to retrieve the next page of messages, if available.
|
package/package.json
CHANGED
|
@@ -1,45 +1,49 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@frustrated/ms-graph-mcp",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "A JSR-based TypeScript MCP package for personal Microsoft Graph access via CLI",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"main": "./src/index.ts",
|
|
7
|
-
"bin": {
|
|
8
|
-
"ms-graph-mcp": "./src/index.ts"
|
|
9
|
-
},
|
|
10
|
-
"exports": {
|
|
11
|
-
".": "./src/index.ts"
|
|
12
|
-
},
|
|
13
|
-
"scripts": {
|
|
14
|
-
"dev": "bun run src/index.ts",
|
|
15
|
-
"test": "bun test",
|
|
16
|
-
"typecheck": "bun run tsc --noEmit"
|
|
17
|
-
},
|
|
18
|
-
"nodeModulesDir": "auto",
|
|
19
|
-
"imports": {
|
|
20
|
-
"@azure/msal-node": "npm:@azure/msal-node@^3.0.0",
|
|
21
|
-
"@microsoft/microsoft-graph-client": "npm:@microsoft/microsoft-graph-client@^3.0.0",
|
|
22
|
-
"@commander-js/extra-typings": "npm:@commander-js/extra-typings@^14.0.0"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"@
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
},
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@frustrated/ms-graph-mcp",
|
|
3
|
+
"version": "0.1.10",
|
|
4
|
+
"description": "A JSR-based TypeScript MCP package for personal Microsoft Graph access via CLI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ms-graph-mcp": "./src/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/index.ts"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"dev": "bun run src/index.ts",
|
|
15
|
+
"test": "bun test",
|
|
16
|
+
"typecheck": "bun run tsc --noEmit"
|
|
17
|
+
},
|
|
18
|
+
"nodeModulesDir": "auto",
|
|
19
|
+
"imports": {
|
|
20
|
+
"@azure/msal-node": "npm:@azure/msal-node@^3.0.0",
|
|
21
|
+
"@microsoft/microsoft-graph-client": "npm:@microsoft/microsoft-graph-client@^3.0.0",
|
|
22
|
+
"@commander-js/extra-typings": "npm:@commander-js/extra-typings@^14.0.0",
|
|
23
|
+
"@modelcontextprotocol/sdk": "npm:@modelcontextprotocol/sdk@^1.28.0",
|
|
24
|
+
"zod": "npm:zod@^3.24.2"
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@azure/msal-node": "^3.0.0",
|
|
28
|
+
"@microsoft/microsoft-graph-client": "^3.0.0",
|
|
29
|
+
"@commander-js/extra-typings": "^14.0.0",
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
31
|
+
"zod": "^3.24.2"
|
|
32
|
+
},
|
|
33
|
+
"publish": {
|
|
34
|
+
"include": [
|
|
35
|
+
"src/",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
]
|
|
39
|
+
},
|
|
40
|
+
"repository": {
|
|
41
|
+
"type": "git",
|
|
42
|
+
"url": "https://github.com/usually-frustrated/ms-graph-mcp.git"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"bun-types": "latest",
|
|
46
|
+
"typescript": "^5.3.0",
|
|
47
|
+
"@types/node": "^20.10.0"
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/auth.ts
CHANGED
|
@@ -1,199 +1,194 @@
|
|
|
1
|
-
import {
|
|
2
|
-
PublicClientApplication,
|
|
3
|
-
Configuration,
|
|
4
|
-
LogLevel,
|
|
5
|
-
CryptoProvider,
|
|
6
|
-
} from "@azure/msal-node";
|
|
7
|
-
import { AddressInfo } from "node:net";
|
|
8
|
-
import { promises as fs } from "node:fs";
|
|
9
|
-
import * as path from "node:path";
|
|
10
|
-
import * as os from "node:os";
|
|
11
|
-
import { createServer } from "node:http";
|
|
12
|
-
|
|
13
|
-
const MSAL_CONFIG: Configuration = {
|
|
14
|
-
auth: {
|
|
15
|
-
clientId: "0a74e52a-4d5b-4005-8dad-6b7cf45ec5fe", // Replace with actual Client ID
|
|
16
|
-
authority: "https://login.microsoftonline.com/common",
|
|
17
|
-
},
|
|
18
|
-
system: {
|
|
19
|
-
loggerOptions: {
|
|
20
|
-
loggerCallback(loglevel, message, containsPii) {
|
|
21
|
-
console.log(message);
|
|
22
|
-
},
|
|
23
|
-
piiLoggingEnabled: false,
|
|
24
|
-
logLevel: LogLevel.Verbose,
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const REDIRECT_URI_PATH = "/auth-callback";
|
|
30
|
-
const TOKEN_CACHE_FILE = path.join(
|
|
31
|
-
os.homedir(),
|
|
32
|
-
".config",
|
|
33
|
-
"ms-graph-mcp",
|
|
34
|
-
"msal_cache.json",
|
|
35
|
-
);
|
|
36
|
-
const SCOPES = [
|
|
37
|
-
"User.Read",
|
|
38
|
-
"Mail.ReadWrite",
|
|
39
|
-
"Calendars.ReadWrite",
|
|
40
|
-
"Files.ReadWrite.All",
|
|
41
|
-
"offline_access",
|
|
42
|
-
];
|
|
43
|
-
|
|
44
|
-
const pca = new PublicClientApplication(MSAL_CONFIG);
|
|
45
|
-
|
|
46
|
-
async function saveTokenCache() {
|
|
47
|
-
const serialized = pca.getTokenCache().serialize();
|
|
48
|
-
await fs.mkdir(path.dirname(TOKEN_CACHE_FILE), { recursive: true });
|
|
49
|
-
await fs.writeFile(TOKEN_CACHE_FILE, serialized, { mode: 0o600 });
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async function loadTokenCache(): Promise<boolean> {
|
|
53
|
-
try {
|
|
54
|
-
const data = await fs.readFile(TOKEN_CACHE_FILE, "utf-8");
|
|
55
|
-
pca.getTokenCache().deserialize(data);
|
|
56
|
-
return true;
|
|
57
|
-
} catch {
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export async function initAuth(): Promise<void> {
|
|
63
|
-
console.log("Initiating Microsoft Graph authentication...");
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
clientId: MSAL_CONFIG.auth.clientId,
|
|
196
|
-
tenantId: authority.split("/").pop() || "common",
|
|
197
|
-
isAuthenticated,
|
|
198
|
-
};
|
|
199
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
PublicClientApplication,
|
|
3
|
+
Configuration,
|
|
4
|
+
LogLevel,
|
|
5
|
+
CryptoProvider,
|
|
6
|
+
} from "@azure/msal-node";
|
|
7
|
+
import { AddressInfo } from "node:net";
|
|
8
|
+
import { promises as fs } from "node:fs";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
import * as os from "node:os";
|
|
11
|
+
import { createServer } from "node:http";
|
|
12
|
+
|
|
13
|
+
const MSAL_CONFIG: Configuration = {
|
|
14
|
+
auth: {
|
|
15
|
+
clientId: "0a74e52a-4d5b-4005-8dad-6b7cf45ec5fe", // Replace with actual Client ID
|
|
16
|
+
authority: "https://login.microsoftonline.com/common",
|
|
17
|
+
},
|
|
18
|
+
system: {
|
|
19
|
+
loggerOptions: {
|
|
20
|
+
loggerCallback(loglevel, message, containsPii) {
|
|
21
|
+
console.log(message);
|
|
22
|
+
},
|
|
23
|
+
piiLoggingEnabled: false,
|
|
24
|
+
logLevel: LogLevel.Verbose,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const REDIRECT_URI_PATH = "/auth-callback";
|
|
30
|
+
const TOKEN_CACHE_FILE = path.join(
|
|
31
|
+
os.homedir(),
|
|
32
|
+
".config",
|
|
33
|
+
"ms-graph-mcp",
|
|
34
|
+
"msal_cache.json",
|
|
35
|
+
);
|
|
36
|
+
const SCOPES = [
|
|
37
|
+
"User.Read",
|
|
38
|
+
"Mail.ReadWrite",
|
|
39
|
+
"Calendars.ReadWrite",
|
|
40
|
+
"Files.ReadWrite.All",
|
|
41
|
+
"offline_access",
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
const pca = new PublicClientApplication(MSAL_CONFIG);
|
|
45
|
+
|
|
46
|
+
async function saveTokenCache() {
|
|
47
|
+
const serialized = pca.getTokenCache().serialize();
|
|
48
|
+
await fs.mkdir(path.dirname(TOKEN_CACHE_FILE), { recursive: true });
|
|
49
|
+
await fs.writeFile(TOKEN_CACHE_FILE, serialized, { mode: 0o600 });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function loadTokenCache(): Promise<boolean> {
|
|
53
|
+
try {
|
|
54
|
+
const data = await fs.readFile(TOKEN_CACHE_FILE, "utf-8");
|
|
55
|
+
pca.getTokenCache().deserialize(data);
|
|
56
|
+
return true;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function initAuth(): Promise<void> {
|
|
63
|
+
console.log("Initiating Microsoft Graph authentication...");
|
|
64
|
+
|
|
65
|
+
const cryptoProvider = new CryptoProvider();
|
|
66
|
+
const pkceCodes = await cryptoProvider.generatePkceCodes();
|
|
67
|
+
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
const server = createServer(async (req, res) => {
|
|
70
|
+
if (req.url?.startsWith(REDIRECT_URI_PATH)) {
|
|
71
|
+
const url = new URL(`http://localhost${req.url}`);
|
|
72
|
+
const code = url.searchParams.get("code");
|
|
73
|
+
|
|
74
|
+
if (code) {
|
|
75
|
+
try {
|
|
76
|
+
const addr = server.address() as AddressInfo;
|
|
77
|
+
const redirectUri = `http://localhost:${addr.port}${REDIRECT_URI_PATH}`;
|
|
78
|
+
const tokenResult = await pca.acquireTokenByCode({
|
|
79
|
+
code,
|
|
80
|
+
scopes: SCOPES,
|
|
81
|
+
redirectUri,
|
|
82
|
+
codeVerifier: pkceCodes.verifier,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (tokenResult) {
|
|
86
|
+
await saveTokenCache();
|
|
87
|
+
res.writeHead(200, { "Content-Type": "text/plain" });
|
|
88
|
+
res.end("Authentication successful! You can close this window.");
|
|
89
|
+
console.log("Authentication successful. Tokens saved securely.");
|
|
90
|
+
server.close(() => resolve());
|
|
91
|
+
} else {
|
|
92
|
+
throw new Error(
|
|
93
|
+
"Authentication failed: no token result received.",
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error("Error acquiring token:", error);
|
|
98
|
+
res.writeHead(500, { "Content-Type": "text/plain" });
|
|
99
|
+
res.end("Authentication failed. Check console for details.");
|
|
100
|
+
server.close(() => reject(error));
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
104
|
+
res.end("Authorization code not found in redirect.");
|
|
105
|
+
server.close(() =>
|
|
106
|
+
reject(new Error("Authorization code not found.")),
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
res.writeHead(404, { "Content-Type": "text/plain" });
|
|
111
|
+
res.end("Not Found");
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
server.listen(0, async () => {
|
|
116
|
+
try {
|
|
117
|
+
const addr = server.address() as AddressInfo;
|
|
118
|
+
const redirectUri = `http://localhost:${addr.port}${REDIRECT_URI_PATH}`;
|
|
119
|
+
const authCodeUrl = await pca.getAuthCodeUrl({
|
|
120
|
+
scopes: SCOPES,
|
|
121
|
+
redirectUri,
|
|
122
|
+
codeChallenge: pkceCodes.challenge,
|
|
123
|
+
codeChallengeMethod: "S256" as const,
|
|
124
|
+
});
|
|
125
|
+
console.log(
|
|
126
|
+
`\nOpen this URL in your browser to authenticate:\n\n ${authCodeUrl}\n`,
|
|
127
|
+
);
|
|
128
|
+
} catch (err) {
|
|
129
|
+
server.close(() => reject(err));
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
server.on("error", (err) => {
|
|
134
|
+
console.error("Server error:", err);
|
|
135
|
+
reject(err);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export async function getAccessToken(): Promise<string> {
|
|
141
|
+
await loadTokenCache();
|
|
142
|
+
|
|
143
|
+
const accounts = await pca.getAllAccounts();
|
|
144
|
+
if (accounts.length === 0) {
|
|
145
|
+
throw new Error("No account found. Please run `ms-graph-mcp init` first.");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const tokenResult = await pca.acquireTokenSilent({
|
|
150
|
+
account: accounts[0],
|
|
151
|
+
scopes: SCOPES,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
if (!tokenResult) {
|
|
155
|
+
throw new Error("Failed to acquire access token silently.");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
await saveTokenCache();
|
|
159
|
+
return tokenResult.accessToken;
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.error("Error refreshing token:", error);
|
|
162
|
+
await fs.unlink(TOKEN_CACHE_FILE).catch(() => {});
|
|
163
|
+
throw new Error(
|
|
164
|
+
"Failed to refresh access token. Please re-authenticate using `ms-graph-mcp init`.",
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export async function revokeAuth(): Promise<void> {
|
|
170
|
+
try {
|
|
171
|
+
await fs.unlink(TOKEN_CACHE_FILE).catch(() => {});
|
|
172
|
+
console.log("Authentication revoked. All tokens cleared.");
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error("Error revoking authentication:", error);
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function getAuthStatus(): Promise<{
|
|
180
|
+
clientId: string;
|
|
181
|
+
tenantId: string;
|
|
182
|
+
isAuthenticated: boolean;
|
|
183
|
+
}> {
|
|
184
|
+
await loadTokenCache();
|
|
185
|
+
const accounts = await pca.getAllAccounts();
|
|
186
|
+
const isAuthenticated = accounts.length > 0;
|
|
187
|
+
const authority =
|
|
188
|
+
MSAL_CONFIG.auth.authority ?? "https://login.microsoftonline.com/common";
|
|
189
|
+
return {
|
|
190
|
+
clientId: MSAL_CONFIG.auth.clientId,
|
|
191
|
+
tenantId: authority.split("/").pop() || "common",
|
|
192
|
+
isAuthenticated,
|
|
193
|
+
};
|
|
194
|
+
}
|