@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/dist/graph-tools.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import logger from './logger.js';
|
|
2
2
|
import { api } from './generated/client.js';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
-
export function registerGraphTools(server, graphClient) {
|
|
4
|
+
export function registerGraphTools(server, graphClient, readOnly = false) {
|
|
5
5
|
for (const tool of api.endpoints) {
|
|
6
|
-
|
|
6
|
+
if (readOnly && tool.method.toUpperCase() !== 'GET') {
|
|
7
|
+
logger.info(`Skipping write operation ${tool.alias} in read-only mode`);
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
7
10
|
const paramSchema = {};
|
|
8
11
|
if (tool.parameters && tool.parameters.length > 0) {
|
|
9
12
|
for (const param of tool.parameters) {
|
package/dist/server.js
CHANGED
|
@@ -17,13 +17,16 @@ class MicrosoftGraphServer {
|
|
|
17
17
|
version,
|
|
18
18
|
});
|
|
19
19
|
registerAuthTools(this.server, this.authManager);
|
|
20
|
-
registerGraphTools(this.server, this.graphClient);
|
|
20
|
+
registerGraphTools(this.server, this.graphClient, this.options.readOnly);
|
|
21
21
|
}
|
|
22
22
|
async start() {
|
|
23
23
|
if (this.options.v) {
|
|
24
24
|
enableConsoleLogging();
|
|
25
25
|
}
|
|
26
26
|
logger.info('Microsoft 365 MCP Server starting...');
|
|
27
|
+
if (this.options.readOnly) {
|
|
28
|
+
logger.info('Server running in READ-ONLY mode. Write operations are disabled.');
|
|
29
|
+
}
|
|
27
30
|
const transport = new StdioServerTransport();
|
|
28
31
|
await this.server.connect(transport);
|
|
29
32
|
logger.info('Server connected to transport');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softeria/ms-365-mcp-server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Microsoft 365 MCP Server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"dev": "tsx src/index.ts",
|
|
15
15
|
"format": "prettier --write \"**/*.{ts,mts,js,mjs,json,md}\"",
|
|
16
16
|
"release": "ts-node --esm bin/release.mts",
|
|
17
|
-
"inspect": "npx @modelcontextprotocol/inspector tsx src/index.ts"
|
|
17
|
+
"inspect": "npx @modelcontextprotocol/inspector tsx src/index.ts",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
18
19
|
},
|
|
19
20
|
"keywords": [
|
|
20
21
|
"microsoft",
|
package/src/endpoints.json
CHANGED
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
]
|
|
105
105
|
},
|
|
106
106
|
{
|
|
107
|
-
"pathPattern": "/drives",
|
|
107
|
+
"pathPattern": "/me/drives",
|
|
108
108
|
"method": "get",
|
|
109
109
|
"toolName": "list-drives",
|
|
110
110
|
"scopes": [
|
|
@@ -196,6 +196,14 @@
|
|
|
196
196
|
"Files.Read"
|
|
197
197
|
]
|
|
198
198
|
},
|
|
199
|
+
{
|
|
200
|
+
"pathPattern": "/drives/{drive-id}/items/{driveItem-id}/children",
|
|
201
|
+
"method": "post",
|
|
202
|
+
"toolName": "create-folder",
|
|
203
|
+
"scopes": [
|
|
204
|
+
"Files.ReadWrite"
|
|
205
|
+
]
|
|
206
|
+
},
|
|
199
207
|
{
|
|
200
208
|
"pathPattern": "/me/onenote/notebooks",
|
|
201
209
|
"method": "get",
|
package/src/generated/README.md
CHANGED
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
This directory contains the generated TypeScript client for the Microsoft 365 API based on the OpenAPI specification.
|
|
4
4
|
|
|
5
|
+
> **Important Note for NPM Package Users**:
|
|
6
|
+
> The source file `client.ts` (approximately 1MB) is excluded from the npm package to reduce package size,
|
|
7
|
+
> but the compiled JavaScript file `client.js` is included. This means the package is fully functional,
|
|
8
|
+
> but you won't see the TypeScript source in the node_modules directory.
|
|
9
|
+
|
|
5
10
|
## The Evolution
|
|
6
11
|
|
|
7
12
|
### Initial Challenge
|
|
8
13
|
|
|
9
|
-
Our initial approach used the full MS 365 OpenAPI specification file directly. This created
|
|
14
|
+
Our initial approach used the full MS 365 OpenAPI specification file directly. This created several significant problems:
|
|
10
15
|
|
|
11
16
|
- The spec file was a whopping 45MB in size
|
|
12
17
|
- It had to be included in the npm package
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
name: Build
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [ main ]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [ main ]
|
|
8
|
-
workflow_dispatch:
|
|
9
|
-
|
|
10
|
-
jobs:
|
|
11
|
-
build:
|
|
12
|
-
runs-on: ubuntu-latest
|
|
13
|
-
|
|
14
|
-
strategy:
|
|
15
|
-
matrix:
|
|
16
|
-
node-version: [ 18.x, 20.x ]
|
|
17
|
-
|
|
18
|
-
steps:
|
|
19
|
-
- uses: actions/checkout@v4
|
|
20
|
-
|
|
21
|
-
- name: Use Node.js ${{ matrix.node-version }}
|
|
22
|
-
uses: actions/setup-node@v4
|
|
23
|
-
with:
|
|
24
|
-
node-version: ${{ matrix.node-version }}
|
|
25
|
-
cache: 'npm'
|
|
26
|
-
|
|
27
|
-
- name: Install dependencies
|
|
28
|
-
run: npm ci
|
|
29
|
-
|
|
30
|
-
- name: Build TypeScript
|
|
31
|
-
run: npm run build
|
|
32
|
-
|
|
33
|
-
- name: Run tests
|
|
34
|
-
run: npm test
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
name: Node.js Package
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
release:
|
|
5
|
-
types: [ created ]
|
|
6
|
-
|
|
7
|
-
jobs:
|
|
8
|
-
build:
|
|
9
|
-
runs-on: ubuntu-latest
|
|
10
|
-
steps:
|
|
11
|
-
- uses: actions/checkout@v4
|
|
12
|
-
- uses: actions/setup-node@v4
|
|
13
|
-
with:
|
|
14
|
-
node-version: 20
|
|
15
|
-
- run: npm ci
|
|
16
|
-
- run: npm run build
|
|
17
|
-
- run: npm test
|
|
18
|
-
|
|
19
|
-
publish-npm:
|
|
20
|
-
needs: build
|
|
21
|
-
runs-on: ubuntu-latest
|
|
22
|
-
steps:
|
|
23
|
-
- uses: actions/checkout@v4
|
|
24
|
-
- uses: actions/setup-node@v4
|
|
25
|
-
with:
|
|
26
|
-
node-version: 20
|
|
27
|
-
registry-url: https://registry.npmjs.org/
|
|
28
|
-
- run: npm ci
|
|
29
|
-
- run: npm run build
|
|
30
|
-
- run: npm publish
|
|
31
|
-
env:
|
|
32
|
-
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
|
package/.prettierrc
DELETED
package/src/auth-tools.ts
DELETED
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
-
import AuthManager from './auth.js';
|
|
4
|
-
|
|
5
|
-
export function registerAuthTools(server: McpServer, authManager: AuthManager): void {
|
|
6
|
-
server.tool(
|
|
7
|
-
'login',
|
|
8
|
-
{
|
|
9
|
-
force: z.boolean().default(false).describe('Force a new login even if already logged in'),
|
|
10
|
-
},
|
|
11
|
-
async ({ force }) => {
|
|
12
|
-
try {
|
|
13
|
-
if (!force) {
|
|
14
|
-
const loginStatus = await authManager.testLogin();
|
|
15
|
-
if (loginStatus.success) {
|
|
16
|
-
return {
|
|
17
|
-
content: [
|
|
18
|
-
{
|
|
19
|
-
type: 'text',
|
|
20
|
-
text: JSON.stringify({
|
|
21
|
-
status: 'Already logged in',
|
|
22
|
-
...loginStatus,
|
|
23
|
-
}),
|
|
24
|
-
},
|
|
25
|
-
],
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
const text = await new Promise<string>((r) => {
|
|
31
|
-
authManager.acquireTokenByDeviceCode(r);
|
|
32
|
-
});
|
|
33
|
-
return {
|
|
34
|
-
content: [
|
|
35
|
-
{
|
|
36
|
-
type: 'text',
|
|
37
|
-
text,
|
|
38
|
-
},
|
|
39
|
-
],
|
|
40
|
-
};
|
|
41
|
-
} catch (error) {
|
|
42
|
-
return {
|
|
43
|
-
content: [
|
|
44
|
-
{
|
|
45
|
-
type: 'text',
|
|
46
|
-
text: JSON.stringify({ error: `Authentication failed: ${(error as Error).message}` }),
|
|
47
|
-
},
|
|
48
|
-
],
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
);
|
|
53
|
-
|
|
54
|
-
server.tool('logout', {}, async () => {
|
|
55
|
-
try {
|
|
56
|
-
await authManager.logout();
|
|
57
|
-
return {
|
|
58
|
-
content: [
|
|
59
|
-
{
|
|
60
|
-
type: 'text',
|
|
61
|
-
text: JSON.stringify({ message: 'Logged out successfully' }),
|
|
62
|
-
},
|
|
63
|
-
],
|
|
64
|
-
};
|
|
65
|
-
} catch (error) {
|
|
66
|
-
return {
|
|
67
|
-
content: [
|
|
68
|
-
{
|
|
69
|
-
type: 'text',
|
|
70
|
-
text: JSON.stringify({ error: 'Logout failed' }),
|
|
71
|
-
},
|
|
72
|
-
],
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
server.tool('verify-login', async () => {
|
|
78
|
-
const testResult = await authManager.testLogin();
|
|
79
|
-
|
|
80
|
-
return {
|
|
81
|
-
content: [
|
|
82
|
-
{
|
|
83
|
-
type: 'text',
|
|
84
|
-
text: JSON.stringify(testResult),
|
|
85
|
-
},
|
|
86
|
-
],
|
|
87
|
-
};
|
|
88
|
-
});
|
|
89
|
-
}
|
package/src/auth.ts
DELETED
|
@@ -1,267 +0,0 @@
|
|
|
1
|
-
import { PublicClientApplication } from '@azure/msal-node';
|
|
2
|
-
import type { Configuration } from '@azure/msal-node';
|
|
3
|
-
import keytar from 'keytar';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
import path from 'path';
|
|
6
|
-
import fs from 'fs';
|
|
7
|
-
import logger from './logger.js';
|
|
8
|
-
|
|
9
|
-
const endpoints = await import('./endpoints.json', {
|
|
10
|
-
with: { type: 'json' },
|
|
11
|
-
});
|
|
12
|
-
|
|
13
|
-
const SERVICE_NAME = 'ms-365-mcp-server';
|
|
14
|
-
const TOKEN_CACHE_ACCOUNT = 'msal-token-cache';
|
|
15
|
-
const FALLBACK_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
-
const FALLBACK_PATH = path.join(FALLBACK_DIR, '..', '.token-cache.json');
|
|
17
|
-
|
|
18
|
-
const DEFAULT_CONFIG: Configuration = {
|
|
19
|
-
auth: {
|
|
20
|
-
clientId: '084a3e9f-a9f4-43f7-89f9-d229cf97853e',
|
|
21
|
-
authority: 'https://login.microsoftonline.com/common',
|
|
22
|
-
},
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
interface ScopeHierarchy {
|
|
26
|
-
[key: string]: string[];
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const SCOPE_HIERARCHY: ScopeHierarchy = {
|
|
30
|
-
'Mail.ReadWrite': ['Mail.Read', 'Mail.Send'],
|
|
31
|
-
'Calendars.ReadWrite': ['Calendars.Read'],
|
|
32
|
-
'Files.ReadWrite': ['Files.Read'],
|
|
33
|
-
'Tasks.ReadWrite': ['Tasks.Read'],
|
|
34
|
-
'Contacts.ReadWrite': ['Contacts.Read'],
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
function buildScopesFromEndpoints(): string[] {
|
|
38
|
-
const scopesSet = new Set<string>();
|
|
39
|
-
|
|
40
|
-
endpoints.default.forEach((endpoint) => {
|
|
41
|
-
if (endpoint.scopes && Array.isArray(endpoint.scopes)) {
|
|
42
|
-
endpoint.scopes.forEach((scope) => scopesSet.add(scope));
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
Object.entries(SCOPE_HIERARCHY).forEach(([higherScope, lowerScopes]) => {
|
|
47
|
-
if (lowerScopes.every((scope) => scopesSet.has(scope))) {
|
|
48
|
-
lowerScopes.forEach((scope) => scopesSet.delete(scope));
|
|
49
|
-
scopesSet.add(higherScope);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
return Array.from(scopesSet);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
interface LoginTestResult {
|
|
57
|
-
success: boolean;
|
|
58
|
-
message: string;
|
|
59
|
-
userData?: {
|
|
60
|
-
displayName: string;
|
|
61
|
-
userPrincipalName: string;
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
class AuthManager {
|
|
66
|
-
private config: Configuration;
|
|
67
|
-
private scopes: string[];
|
|
68
|
-
private msalApp: PublicClientApplication;
|
|
69
|
-
private accessToken: string | null;
|
|
70
|
-
private tokenExpiry: number | null;
|
|
71
|
-
|
|
72
|
-
constructor(
|
|
73
|
-
config: Configuration = DEFAULT_CONFIG,
|
|
74
|
-
scopes: string[] = buildScopesFromEndpoints()
|
|
75
|
-
) {
|
|
76
|
-
logger.info(`And scopes are ${scopes.join(', ')}`, scopes);
|
|
77
|
-
this.config = config;
|
|
78
|
-
this.scopes = scopes;
|
|
79
|
-
this.msalApp = new PublicClientApplication(this.config);
|
|
80
|
-
this.accessToken = null;
|
|
81
|
-
this.tokenExpiry = null;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
async loadTokenCache(): Promise<void> {
|
|
85
|
-
try {
|
|
86
|
-
let cacheData: string | undefined;
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
const cachedData = await keytar.getPassword(SERVICE_NAME, TOKEN_CACHE_ACCOUNT);
|
|
90
|
-
if (cachedData) {
|
|
91
|
-
cacheData = cachedData;
|
|
92
|
-
}
|
|
93
|
-
} catch (keytarError) {
|
|
94
|
-
logger.warn(
|
|
95
|
-
`Keychain access failed, falling back to file storage: ${(keytarError as Error).message}`
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (!cacheData && fs.existsSync(FALLBACK_PATH)) {
|
|
100
|
-
cacheData = fs.readFileSync(FALLBACK_PATH, 'utf8');
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
if (cacheData) {
|
|
104
|
-
this.msalApp.getTokenCache().deserialize(cacheData);
|
|
105
|
-
}
|
|
106
|
-
} catch (error) {
|
|
107
|
-
logger.error(`Error loading token cache: ${(error as Error).message}`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
async saveTokenCache(): Promise<void> {
|
|
112
|
-
try {
|
|
113
|
-
const cacheData = this.msalApp.getTokenCache().serialize();
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
await keytar.setPassword(SERVICE_NAME, TOKEN_CACHE_ACCOUNT, cacheData);
|
|
117
|
-
} catch (keytarError) {
|
|
118
|
-
logger.warn(
|
|
119
|
-
`Keychain save failed, falling back to file storage: ${(keytarError as Error).message}`
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
fs.writeFileSync(FALLBACK_PATH, cacheData);
|
|
123
|
-
}
|
|
124
|
-
} catch (error) {
|
|
125
|
-
logger.error(`Error saving token cache: ${(error as Error).message}`);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
async getToken(forceRefresh = false): Promise<string | null> {
|
|
130
|
-
if (this.accessToken && this.tokenExpiry && this.tokenExpiry > Date.now() && !forceRefresh) {
|
|
131
|
-
return this.accessToken;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const accounts = await this.msalApp.getTokenCache().getAllAccounts();
|
|
135
|
-
|
|
136
|
-
if (accounts.length > 0) {
|
|
137
|
-
const silentRequest = {
|
|
138
|
-
account: accounts[0],
|
|
139
|
-
scopes: this.scopes,
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
try {
|
|
143
|
-
const response = await this.msalApp.acquireTokenSilent(silentRequest);
|
|
144
|
-
this.accessToken = response.accessToken;
|
|
145
|
-
this.tokenExpiry = response.expiresOn ? new Date(response.expiresOn).getTime() : null;
|
|
146
|
-
return this.accessToken;
|
|
147
|
-
} catch (error) {
|
|
148
|
-
logger.info('Silent token acquisition failed, using device code flow');
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
throw new Error('No valid token found');
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async acquireTokenByDeviceCode(hack?: (message: string) => void): Promise<string | null> {
|
|
156
|
-
const deviceCodeRequest = {
|
|
157
|
-
scopes: this.scopes,
|
|
158
|
-
deviceCodeCallback: (response: { message: string }) => {
|
|
159
|
-
const text = ['\n', response.message, '\n'].join('');
|
|
160
|
-
if (hack) {
|
|
161
|
-
hack(text + 'After login run the "verify login" command');
|
|
162
|
-
} else {
|
|
163
|
-
console.log(text);
|
|
164
|
-
}
|
|
165
|
-
logger.info('Device code login initiated');
|
|
166
|
-
},
|
|
167
|
-
};
|
|
168
|
-
|
|
169
|
-
try {
|
|
170
|
-
logger.info('Requesting device code...');
|
|
171
|
-
const response = await this.msalApp.acquireTokenByDeviceCode(deviceCodeRequest);
|
|
172
|
-
logger.info('Device code login successful');
|
|
173
|
-
this.accessToken = response?.accessToken || null;
|
|
174
|
-
this.tokenExpiry = response?.expiresOn ? new Date(response.expiresOn).getTime() : null;
|
|
175
|
-
await this.saveTokenCache();
|
|
176
|
-
return this.accessToken;
|
|
177
|
-
} catch (error) {
|
|
178
|
-
logger.error(`Error in device code flow: ${(error as Error).message}`);
|
|
179
|
-
throw error;
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
async testLogin(): Promise<LoginTestResult> {
|
|
184
|
-
try {
|
|
185
|
-
logger.info('Testing login...');
|
|
186
|
-
const token = await this.getToken();
|
|
187
|
-
|
|
188
|
-
if (!token) {
|
|
189
|
-
logger.error('Login test failed - no token received');
|
|
190
|
-
return {
|
|
191
|
-
success: false,
|
|
192
|
-
message: 'Login failed - no token received',
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
logger.info('Token retrieved successfully, testing Graph API access...');
|
|
197
|
-
|
|
198
|
-
try {
|
|
199
|
-
const response = await fetch('https://graph.microsoft.com/v1.0/me', {
|
|
200
|
-
headers: {
|
|
201
|
-
Authorization: `Bearer ${token}`,
|
|
202
|
-
},
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
if (response.ok) {
|
|
206
|
-
const userData = await response.json();
|
|
207
|
-
logger.info('Graph API user data fetch successful');
|
|
208
|
-
return {
|
|
209
|
-
success: true,
|
|
210
|
-
message: 'Login successful',
|
|
211
|
-
userData: {
|
|
212
|
-
displayName: userData.displayName,
|
|
213
|
-
userPrincipalName: userData.userPrincipalName,
|
|
214
|
-
},
|
|
215
|
-
};
|
|
216
|
-
} else {
|
|
217
|
-
const errorText = await response.text();
|
|
218
|
-
logger.error(`Graph API user data fetch failed: ${response.status} - ${errorText}`);
|
|
219
|
-
return {
|
|
220
|
-
success: false,
|
|
221
|
-
message: `Login successful but Graph API access failed: ${response.status}`,
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
} catch (graphError) {
|
|
225
|
-
logger.error(`Error fetching user data: ${(graphError as Error).message}`);
|
|
226
|
-
return {
|
|
227
|
-
success: false,
|
|
228
|
-
message: `Login successful but Graph API access failed: ${(graphError as Error).message}`,
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
} catch (error) {
|
|
232
|
-
logger.error(`Login test failed: ${(error as Error).message}`);
|
|
233
|
-
return {
|
|
234
|
-
success: false,
|
|
235
|
-
message: `Login failed: ${(error as Error).message}`,
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
async logout(): Promise<boolean> {
|
|
241
|
-
try {
|
|
242
|
-
const accounts = await this.msalApp.getTokenCache().getAllAccounts();
|
|
243
|
-
for (const account of accounts) {
|
|
244
|
-
await this.msalApp.getTokenCache().removeAccount(account);
|
|
245
|
-
}
|
|
246
|
-
this.accessToken = null;
|
|
247
|
-
this.tokenExpiry = null;
|
|
248
|
-
|
|
249
|
-
try {
|
|
250
|
-
await keytar.deletePassword(SERVICE_NAME, TOKEN_CACHE_ACCOUNT);
|
|
251
|
-
} catch (keytarError) {
|
|
252
|
-
logger.warn(`Keychain deletion failed: ${(keytarError as Error).message}`);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (fs.existsSync(FALLBACK_PATH)) {
|
|
256
|
-
fs.unlinkSync(FALLBACK_PATH);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
return true;
|
|
260
|
-
} catch (error) {
|
|
261
|
-
logger.error(`Error during logout: ${(error as Error).message}`);
|
|
262
|
-
throw error;
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
export default AuthManager;
|
package/src/cli.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { Command } from 'commander';
|
|
2
|
-
import { readFileSync } from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
|
|
6
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
-
const packageJsonPath = path.join(__dirname, '..', 'package.json');
|
|
8
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
9
|
-
const version = packageJson.version;
|
|
10
|
-
|
|
11
|
-
const program = new Command();
|
|
12
|
-
|
|
13
|
-
program
|
|
14
|
-
.name('ms-365-mcp-server')
|
|
15
|
-
.description('Microsoft 365 MCP Server')
|
|
16
|
-
.version(version)
|
|
17
|
-
.option('-v', 'Enable verbose logging')
|
|
18
|
-
.option('--login', 'Login using device code flow')
|
|
19
|
-
.option('--logout', 'Log out and clear saved credentials')
|
|
20
|
-
.option('--verify-login', 'Verify login without starting the server');
|
|
21
|
-
|
|
22
|
-
export interface CommandOptions {
|
|
23
|
-
v?: boolean;
|
|
24
|
-
login?: boolean;
|
|
25
|
-
logout?: boolean;
|
|
26
|
-
verifyLogin?: boolean;
|
|
27
|
-
[key: string]: any;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function parseArgs(): CommandOptions {
|
|
31
|
-
program.parse();
|
|
32
|
-
return program.opts();
|
|
33
|
-
}
|