@softeria/ms-365-mcp-server 0.11.3 → 0.11.5
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/.releaserc.json +12 -0
- package/README.md +2 -1
- package/dist/auth-tools.js +181 -173
- package/dist/auth.js +402 -415
- package/dist/cli.js +35 -36
- package/dist/endpoints.json +490 -0
- package/dist/generated/client.js +10355 -13943
- package/dist/generated/endpoint-types.js +0 -1
- package/dist/generated/hack.js +39 -33
- package/dist/graph-client.js +426 -473
- package/dist/graph-tools.js +217 -228
- package/dist/index.js +76 -79
- package/dist/lib/microsoft-auth.js +62 -72
- package/dist/logger.js +36 -27
- package/dist/oauth-provider.js +48 -47
- package/dist/server.js +277 -264
- package/dist/version.js +9 -6
- package/package.json +11 -3
- package/tsup.config.ts +30 -0
- package/bin/release.mjs +0 -69
package/dist/server.js
CHANGED
|
@@ -1,277 +1,290 @@
|
|
|
1
|
-
import { McpServer } from
|
|
2
|
-
import { StdioServerTransport } from
|
|
3
|
-
import { StreamableHTTPServerTransport } from
|
|
4
|
-
import { mcpAuthRouter } from
|
|
5
|
-
import express from
|
|
6
|
-
import crypto from
|
|
7
|
-
import logger, { enableConsoleLogging } from
|
|
8
|
-
import { registerAuthTools } from
|
|
9
|
-
import { registerGraphTools } from
|
|
10
|
-
import GraphClient from
|
|
11
|
-
import { MicrosoftOAuthProvider } from
|
|
12
|
-
import { microsoftBearerTokenAuthMiddleware, exchangeCodeForToken, refreshAccessToken } from
|
|
13
|
-
const registeredClients = new Map();
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4
|
+
import { mcpAuthRouter } from "@modelcontextprotocol/sdk/server/auth/router.js";
|
|
5
|
+
import express from "express";
|
|
6
|
+
import crypto from "crypto";
|
|
7
|
+
import logger, { enableConsoleLogging } from "./logger.js";
|
|
8
|
+
import { registerAuthTools } from "./auth-tools.js";
|
|
9
|
+
import { registerGraphTools } from "./graph-tools.js";
|
|
10
|
+
import GraphClient from "./graph-client.js";
|
|
11
|
+
import { MicrosoftOAuthProvider } from "./oauth-provider.js";
|
|
12
|
+
import { microsoftBearerTokenAuthMiddleware, exchangeCodeForToken, refreshAccessToken } from "./lib/microsoft-auth.js";
|
|
13
|
+
const registeredClients = /* @__PURE__ */ new Map();
|
|
14
14
|
class MicrosoftGraphServer {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
constructor(authManager, options = {}) {
|
|
16
|
+
this.authManager = authManager;
|
|
17
|
+
this.options = options;
|
|
18
|
+
this.graphClient = new GraphClient(authManager);
|
|
19
|
+
this.server = null;
|
|
20
|
+
}
|
|
21
|
+
async initialize(version) {
|
|
22
|
+
this.server = new McpServer({
|
|
23
|
+
name: "Microsoft365MCP",
|
|
24
|
+
version
|
|
25
|
+
});
|
|
26
|
+
const shouldRegisterAuthTools = !this.options.http || this.options.enableAuthTools;
|
|
27
|
+
if (shouldRegisterAuthTools) {
|
|
28
|
+
registerAuthTools(this.server, this.authManager);
|
|
20
29
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
registerGraphTools(
|
|
31
|
+
this.server,
|
|
32
|
+
this.graphClient,
|
|
33
|
+
this.options.readOnly,
|
|
34
|
+
this.options.enabledTools
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
async start() {
|
|
38
|
+
if (this.options.v) {
|
|
39
|
+
enableConsoleLogging();
|
|
31
40
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
logger.info("Microsoft 365 MCP Server starting...");
|
|
42
|
+
logger.info("Environment Variables Check:", {
|
|
43
|
+
CLIENT_ID: process.env.MS365_MCP_CLIENT_ID ? `${process.env.MS365_MCP_CLIENT_ID.substring(0, 8)}...` : "NOT SET",
|
|
44
|
+
CLIENT_SECRET: process.env.MS365_MCP_CLIENT_SECRET ? `${process.env.MS365_MCP_CLIENT_SECRET.substring(0, 8)}...` : "NOT SET",
|
|
45
|
+
TENANT_ID: process.env.MS365_MCP_TENANT_ID || "NOT SET",
|
|
46
|
+
NODE_ENV: process.env.NODE_ENV || "NOT SET"
|
|
47
|
+
});
|
|
48
|
+
if (this.options.readOnly) {
|
|
49
|
+
logger.info("Server running in READ-ONLY mode. Write operations are disabled.");
|
|
50
|
+
}
|
|
51
|
+
if (this.options.http) {
|
|
52
|
+
const port = typeof this.options.http === "string" ? parseInt(this.options.http) : 3e3;
|
|
53
|
+
const app = express();
|
|
54
|
+
app.use(express.json());
|
|
55
|
+
app.use(express.urlencoded({ extended: true }));
|
|
56
|
+
app.use((req, res, next) => {
|
|
57
|
+
res.header("Access-Control-Allow-Origin", "*");
|
|
58
|
+
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
|
59
|
+
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization, mcp-protocol-version");
|
|
60
|
+
if (req.method === "OPTIONS") {
|
|
61
|
+
res.sendStatus(200);
|
|
62
|
+
return;
|
|
35
63
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
64
|
+
next();
|
|
65
|
+
});
|
|
66
|
+
const oauthProvider = new MicrosoftOAuthProvider(this.authManager);
|
|
67
|
+
app.get("/.well-known/oauth-authorization-server", async (req, res) => {
|
|
68
|
+
const url = new URL(`${req.protocol}://${req.get("host")}`);
|
|
69
|
+
res.json({
|
|
70
|
+
issuer: url.origin,
|
|
71
|
+
authorization_endpoint: `${url.origin}/authorize`,
|
|
72
|
+
token_endpoint: `${url.origin}/token`,
|
|
73
|
+
registration_endpoint: `${url.origin}/register`,
|
|
74
|
+
response_types_supported: ["code"],
|
|
75
|
+
response_modes_supported: ["query"],
|
|
76
|
+
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
77
|
+
token_endpoint_auth_methods_supported: ["none"],
|
|
78
|
+
code_challenge_methods_supported: ["S256"],
|
|
79
|
+
scopes_supported: [
|
|
80
|
+
"User.Read",
|
|
81
|
+
"Files.Read",
|
|
82
|
+
"Mail.Read"
|
|
83
|
+
]
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
app.get("/.well-known/oauth-protected-resource", async (req, res) => {
|
|
87
|
+
const url = new URL(`${req.protocol}://${req.get("host")}`);
|
|
88
|
+
res.json({
|
|
89
|
+
resource: `${url.origin}/mcp`,
|
|
90
|
+
authorization_servers: [url.origin],
|
|
91
|
+
scopes_supported: [
|
|
92
|
+
"User.Read",
|
|
93
|
+
"Files.Read",
|
|
94
|
+
"Mail.Read"
|
|
95
|
+
],
|
|
96
|
+
bearer_methods_supported: ["header"],
|
|
97
|
+
resource_documentation: `${url.origin}`
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
app.post("/register", async (req, res) => {
|
|
101
|
+
const body = req.body;
|
|
102
|
+
const clientId = crypto.randomUUID();
|
|
103
|
+
registeredClients.set(clientId, {
|
|
104
|
+
client_id: clientId,
|
|
105
|
+
client_name: body.client_name || "MCP Client",
|
|
106
|
+
redirect_uris: body.redirect_uris || [],
|
|
107
|
+
grant_types: body.grant_types || ["authorization_code", "refresh_token"],
|
|
108
|
+
response_types: body.response_types || ["code"],
|
|
109
|
+
scope: body.scope,
|
|
110
|
+
token_endpoint_auth_method: "none",
|
|
111
|
+
created_at: Date.now()
|
|
112
|
+
});
|
|
113
|
+
res.status(201).json({
|
|
114
|
+
client_id: clientId,
|
|
115
|
+
client_name: body.client_name || "MCP Client",
|
|
116
|
+
redirect_uris: body.redirect_uris || [],
|
|
117
|
+
grant_types: body.grant_types || ["authorization_code", "refresh_token"],
|
|
118
|
+
response_types: body.response_types || ["code"],
|
|
119
|
+
scope: body.scope,
|
|
120
|
+
token_endpoint_auth_method: "none"
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
app.get("/authorize", async (req, res) => {
|
|
124
|
+
const url = new URL(req.url, `${req.protocol}://${req.get("host")}`);
|
|
125
|
+
const tenantId = process.env.MS365_MCP_TENANT_ID || "common";
|
|
126
|
+
const clientId = process.env.MS365_MCP_CLIENT_ID || "084a3e9f-a9f4-43f7-89f9-d229cf97853e";
|
|
127
|
+
const microsoftAuthUrl = new URL(`https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize`);
|
|
128
|
+
const allowedParams = [
|
|
129
|
+
"response_type",
|
|
130
|
+
"redirect_uri",
|
|
131
|
+
"scope",
|
|
132
|
+
"state",
|
|
133
|
+
"response_mode",
|
|
134
|
+
"code_challenge",
|
|
135
|
+
"code_challenge_method",
|
|
136
|
+
"prompt",
|
|
137
|
+
"login_hint",
|
|
138
|
+
"domain_hint"
|
|
139
|
+
];
|
|
140
|
+
allowedParams.forEach((param) => {
|
|
141
|
+
const value = url.searchParams.get(param);
|
|
142
|
+
if (value) {
|
|
143
|
+
microsoftAuthUrl.searchParams.set(param, value);
|
|
144
|
+
}
|
|
43
145
|
});
|
|
44
|
-
|
|
45
|
-
|
|
146
|
+
microsoftAuthUrl.searchParams.set("client_id", clientId);
|
|
147
|
+
if (!microsoftAuthUrl.searchParams.get("scope")) {
|
|
148
|
+
microsoftAuthUrl.searchParams.set("scope", "User.Read Files.Read Mail.Read");
|
|
46
149
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
150
|
+
res.redirect(microsoftAuthUrl.toString());
|
|
151
|
+
});
|
|
152
|
+
app.post("/token", async (req, res) => {
|
|
153
|
+
try {
|
|
154
|
+
logger.info("Token endpoint called", {
|
|
155
|
+
method: req.method,
|
|
156
|
+
url: req.url,
|
|
157
|
+
headers: req.headers,
|
|
158
|
+
bodyType: typeof req.body,
|
|
159
|
+
body: req.body,
|
|
160
|
+
rawBody: JSON.stringify(req.body),
|
|
161
|
+
contentType: req.get("Content-Type")
|
|
162
|
+
});
|
|
163
|
+
const body = req.body;
|
|
164
|
+
if (!body) {
|
|
165
|
+
logger.error("Token endpoint: Request body is undefined");
|
|
166
|
+
res.status(400).json({
|
|
167
|
+
error: "invalid_request",
|
|
168
|
+
error_description: "Request body is required"
|
|
63
169
|
});
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
token_endpoint: `${url.origin}/token`,
|
|
72
|
-
registration_endpoint: `${url.origin}/register`,
|
|
73
|
-
response_types_supported: ['code'],
|
|
74
|
-
response_modes_supported: ['query'],
|
|
75
|
-
grant_types_supported: ['authorization_code', 'refresh_token'],
|
|
76
|
-
token_endpoint_auth_methods_supported: ['none'],
|
|
77
|
-
code_challenge_methods_supported: ['S256'],
|
|
78
|
-
scopes_supported: [
|
|
79
|
-
'User.Read', 'Files.Read', 'Mail.Read'
|
|
80
|
-
],
|
|
81
|
-
});
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
if (!body.grant_type) {
|
|
173
|
+
logger.error("Token endpoint: grant_type is missing", { body });
|
|
174
|
+
res.status(400).json({
|
|
175
|
+
error: "invalid_request",
|
|
176
|
+
error_description: "grant_type parameter is required"
|
|
82
177
|
});
|
|
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
|
-
// Only forward parameters that Microsoft OAuth 2.0 v2.0 supports
|
|
130
|
-
const allowedParams = [
|
|
131
|
-
'response_type', 'redirect_uri', 'scope', 'state', 'response_mode',
|
|
132
|
-
'code_challenge', 'code_challenge_method', 'prompt', 'login_hint', 'domain_hint'
|
|
133
|
-
];
|
|
134
|
-
allowedParams.forEach(param => {
|
|
135
|
-
const value = url.searchParams.get(param);
|
|
136
|
-
if (value) {
|
|
137
|
-
microsoftAuthUrl.searchParams.set(param, value);
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
// Use our Microsoft app's client_id
|
|
141
|
-
microsoftAuthUrl.searchParams.set('client_id', clientId);
|
|
142
|
-
// Ensure we have the minimal required scopes if none provided
|
|
143
|
-
if (!microsoftAuthUrl.searchParams.get('scope')) {
|
|
144
|
-
microsoftAuthUrl.searchParams.set('scope', 'User.Read Files.Read Mail.Read');
|
|
145
|
-
}
|
|
146
|
-
// Redirect to Microsoft's authorization page
|
|
147
|
-
res.redirect(microsoftAuthUrl.toString());
|
|
148
|
-
});
|
|
149
|
-
// Token exchange endpoint
|
|
150
|
-
app.post('/token', async (req, res) => {
|
|
151
|
-
try {
|
|
152
|
-
// Comprehensive debugging
|
|
153
|
-
logger.info('Token endpoint called', {
|
|
154
|
-
method: req.method,
|
|
155
|
-
url: req.url,
|
|
156
|
-
headers: req.headers,
|
|
157
|
-
bodyType: typeof req.body,
|
|
158
|
-
body: req.body,
|
|
159
|
-
rawBody: JSON.stringify(req.body),
|
|
160
|
-
contentType: req.get('Content-Type')
|
|
161
|
-
});
|
|
162
|
-
const body = req.body;
|
|
163
|
-
// Add debugging and validation
|
|
164
|
-
if (!body) {
|
|
165
|
-
logger.error('Token endpoint: Request body is undefined');
|
|
166
|
-
res.status(400).json({
|
|
167
|
-
error: 'invalid_request',
|
|
168
|
-
error_description: 'Request body is required'
|
|
169
|
-
});
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
if (!body.grant_type) {
|
|
173
|
-
logger.error('Token endpoint: grant_type is missing', { body });
|
|
174
|
-
res.status(400).json({
|
|
175
|
-
error: 'invalid_request',
|
|
176
|
-
error_description: 'grant_type parameter is required'
|
|
177
|
-
});
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
if (body.grant_type === 'authorization_code') {
|
|
181
|
-
const tenantId = process.env.MS365_MCP_TENANT_ID || 'common';
|
|
182
|
-
const clientId = process.env.MS365_MCP_CLIENT_ID || '084a3e9f-a9f4-43f7-89f9-d229cf97853e';
|
|
183
|
-
const clientSecret = process.env.MS365_MCP_CLIENT_SECRET;
|
|
184
|
-
if (!clientSecret) {
|
|
185
|
-
logger.error('Token endpoint: MS365_MCP_CLIENT_SECRET is not configured');
|
|
186
|
-
res.status(500).json({
|
|
187
|
-
error: 'server_error',
|
|
188
|
-
error_description: 'Server configuration error'
|
|
189
|
-
});
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
const result = await exchangeCodeForToken(body.code, body.redirect_uri, clientId, clientSecret, tenantId, body.code_verifier);
|
|
193
|
-
res.json(result);
|
|
194
|
-
}
|
|
195
|
-
else if (body.grant_type === 'refresh_token') {
|
|
196
|
-
const tenantId = process.env.MS365_MCP_TENANT_ID || 'common';
|
|
197
|
-
const clientId = process.env.MS365_MCP_CLIENT_ID || '084a3e9f-a9f4-43f7-89f9-d229cf97853e';
|
|
198
|
-
const clientSecret = process.env.MS365_MCP_CLIENT_SECRET;
|
|
199
|
-
if (!clientSecret) {
|
|
200
|
-
logger.error('Token endpoint: MS365_MCP_CLIENT_SECRET is not configured');
|
|
201
|
-
res.status(500).json({
|
|
202
|
-
error: 'server_error',
|
|
203
|
-
error_description: 'Server configuration error'
|
|
204
|
-
});
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
const result = await refreshAccessToken(body.refresh_token, clientId, clientSecret, tenantId);
|
|
208
|
-
res.json(result);
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
res.status(400).json({
|
|
212
|
-
error: 'unsupported_grant_type',
|
|
213
|
-
error_description: `Grant type '${body.grant_type}' is not supported`
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
catch (error) {
|
|
218
|
-
logger.error('Token endpoint error:', error);
|
|
219
|
-
res.status(500).json({
|
|
220
|
-
error: 'server_error',
|
|
221
|
-
error_description: 'Internal server error during token exchange'
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
});
|
|
225
|
-
app.use(mcpAuthRouter({
|
|
226
|
-
provider: oauthProvider,
|
|
227
|
-
issuerUrl: new URL(`http://localhost:${port}`),
|
|
228
|
-
}));
|
|
229
|
-
// Microsoft Graph MCP endpoints with bearer token auth
|
|
230
|
-
app.post('/mcp', microsoftBearerTokenAuthMiddleware, async (req, res) => {
|
|
231
|
-
try {
|
|
232
|
-
// Set OAuth tokens in the GraphClient if available
|
|
233
|
-
if (req.microsoftAuth) {
|
|
234
|
-
this.graphClient.setOAuthTokens(req.microsoftAuth.accessToken, req.microsoftAuth.refreshToken);
|
|
235
|
-
}
|
|
236
|
-
const transport = new StreamableHTTPServerTransport({
|
|
237
|
-
sessionIdGenerator: undefined, // Stateless mode
|
|
238
|
-
});
|
|
239
|
-
res.on('close', () => {
|
|
240
|
-
transport.close();
|
|
241
|
-
});
|
|
242
|
-
await this.server.connect(transport);
|
|
243
|
-
await transport.handleRequest(req, res, req.body);
|
|
244
|
-
}
|
|
245
|
-
catch (error) {
|
|
246
|
-
logger.error('Error handling MCP request:', error);
|
|
247
|
-
if (!res.headersSent) {
|
|
248
|
-
res.status(500).json({
|
|
249
|
-
jsonrpc: '2.0',
|
|
250
|
-
error: {
|
|
251
|
-
code: -32603,
|
|
252
|
-
message: 'Internal server error',
|
|
253
|
-
},
|
|
254
|
-
id: null,
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
});
|
|
259
|
-
// Health check endpoint
|
|
260
|
-
app.get('/', (req, res) => {
|
|
261
|
-
res.send('Microsoft 365 MCP Server is running');
|
|
262
|
-
});
|
|
263
|
-
app.listen(port, () => {
|
|
264
|
-
logger.info(`Server listening on HTTP port ${port}`);
|
|
265
|
-
logger.info(` - MCP endpoint: http://localhost:${port}/mcp`);
|
|
266
|
-
logger.info(` - OAuth endpoints: http://localhost:${port}/auth/*`);
|
|
267
|
-
logger.info(` - OAuth discovery: http://localhost:${port}/.well-known/oauth-authorization-server`);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (body.grant_type === "authorization_code") {
|
|
181
|
+
const tenantId = process.env.MS365_MCP_TENANT_ID || "common";
|
|
182
|
+
const clientId = process.env.MS365_MCP_CLIENT_ID || "084a3e9f-a9f4-43f7-89f9-d229cf97853e";
|
|
183
|
+
const clientSecret = process.env.MS365_MCP_CLIENT_SECRET;
|
|
184
|
+
if (!clientSecret) {
|
|
185
|
+
logger.error("Token endpoint: MS365_MCP_CLIENT_SECRET is not configured");
|
|
186
|
+
res.status(500).json({
|
|
187
|
+
error: "server_error",
|
|
188
|
+
error_description: "Server configuration error"
|
|
189
|
+
});
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const result = await exchangeCodeForToken(
|
|
193
|
+
body.code,
|
|
194
|
+
body.redirect_uri,
|
|
195
|
+
clientId,
|
|
196
|
+
clientSecret,
|
|
197
|
+
tenantId,
|
|
198
|
+
body.code_verifier
|
|
199
|
+
);
|
|
200
|
+
res.json(result);
|
|
201
|
+
} else if (body.grant_type === "refresh_token") {
|
|
202
|
+
const tenantId = process.env.MS365_MCP_TENANT_ID || "common";
|
|
203
|
+
const clientId = process.env.MS365_MCP_CLIENT_ID || "084a3e9f-a9f4-43f7-89f9-d229cf97853e";
|
|
204
|
+
const clientSecret = process.env.MS365_MCP_CLIENT_SECRET;
|
|
205
|
+
if (!clientSecret) {
|
|
206
|
+
logger.error("Token endpoint: MS365_MCP_CLIENT_SECRET is not configured");
|
|
207
|
+
res.status(500).json({
|
|
208
|
+
error: "server_error",
|
|
209
|
+
error_description: "Server configuration error"
|
|
210
|
+
});
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const result = await refreshAccessToken(
|
|
214
|
+
body.refresh_token,
|
|
215
|
+
clientId,
|
|
216
|
+
clientSecret,
|
|
217
|
+
tenantId
|
|
218
|
+
);
|
|
219
|
+
res.json(result);
|
|
220
|
+
} else {
|
|
221
|
+
res.status(400).json({
|
|
222
|
+
error: "unsupported_grant_type",
|
|
223
|
+
error_description: `Grant type '${body.grant_type}' is not supported`
|
|
268
224
|
});
|
|
225
|
+
}
|
|
226
|
+
} catch (error) {
|
|
227
|
+
logger.error("Token endpoint error:", error);
|
|
228
|
+
res.status(500).json({
|
|
229
|
+
error: "server_error",
|
|
230
|
+
error_description: "Internal server error during token exchange"
|
|
231
|
+
});
|
|
269
232
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
233
|
+
});
|
|
234
|
+
app.use(
|
|
235
|
+
mcpAuthRouter({
|
|
236
|
+
provider: oauthProvider,
|
|
237
|
+
issuerUrl: new URL(`http://localhost:${port}`)
|
|
238
|
+
})
|
|
239
|
+
);
|
|
240
|
+
app.post("/mcp", microsoftBearerTokenAuthMiddleware, async (req, res) => {
|
|
241
|
+
try {
|
|
242
|
+
if (req.microsoftAuth) {
|
|
243
|
+
this.graphClient.setOAuthTokens(
|
|
244
|
+
req.microsoftAuth.accessToken,
|
|
245
|
+
req.microsoftAuth.refreshToken
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
const transport = new StreamableHTTPServerTransport({
|
|
249
|
+
sessionIdGenerator: void 0
|
|
250
|
+
// Stateless mode
|
|
251
|
+
});
|
|
252
|
+
res.on("close", () => {
|
|
253
|
+
transport.close();
|
|
254
|
+
});
|
|
255
|
+
await this.server.connect(transport);
|
|
256
|
+
await transport.handleRequest(req, res, req.body);
|
|
257
|
+
} catch (error) {
|
|
258
|
+
logger.error("Error handling MCP request:", error);
|
|
259
|
+
if (!res.headersSent) {
|
|
260
|
+
res.status(500).json({
|
|
261
|
+
jsonrpc: "2.0",
|
|
262
|
+
error: {
|
|
263
|
+
code: -32603,
|
|
264
|
+
message: "Internal server error"
|
|
265
|
+
},
|
|
266
|
+
id: null
|
|
267
|
+
});
|
|
268
|
+
}
|
|
274
269
|
}
|
|
270
|
+
});
|
|
271
|
+
app.get("/", (req, res) => {
|
|
272
|
+
res.send("Microsoft 365 MCP Server is running");
|
|
273
|
+
});
|
|
274
|
+
app.listen(port, () => {
|
|
275
|
+
logger.info(`Server listening on HTTP port ${port}`);
|
|
276
|
+
logger.info(` - MCP endpoint: http://localhost:${port}/mcp`);
|
|
277
|
+
logger.info(` - OAuth endpoints: http://localhost:${port}/auth/*`);
|
|
278
|
+
logger.info(` - OAuth discovery: http://localhost:${port}/.well-known/oauth-authorization-server`);
|
|
279
|
+
});
|
|
280
|
+
} else {
|
|
281
|
+
const transport = new StdioServerTransport();
|
|
282
|
+
await this.server.connect(transport);
|
|
283
|
+
logger.info("Server connected to stdio transport");
|
|
275
284
|
}
|
|
285
|
+
}
|
|
276
286
|
}
|
|
277
|
-
|
|
287
|
+
var server_default = MicrosoftGraphServer;
|
|
288
|
+
export {
|
|
289
|
+
server_default as default
|
|
290
|
+
};
|
package/dist/version.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
-
import { readFileSync } from
|
|
2
|
-
import path from
|
|
3
|
-
import { fileURLToPath } from
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
4
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
5
|
-
const packageJsonPath = path.join(__dirname,
|
|
6
|
-
const packageJson = JSON.parse(readFileSync(packageJsonPath,
|
|
7
|
-
|
|
5
|
+
const packageJsonPath = path.join(__dirname, "..", "package.json");
|
|
6
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
|
|
7
|
+
const version = packageJson.version;
|
|
8
|
+
export {
|
|
9
|
+
version
|
|
10
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softeria/ms-365-mcp-server",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.5",
|
|
4
4
|
"description": "Microsoft 365 MCP Server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -8,13 +8,12 @@
|
|
|
8
8
|
"ms-365-mcp-server": "dist/index.js"
|
|
9
9
|
},
|
|
10
10
|
"scripts": {
|
|
11
|
-
"build": "
|
|
11
|
+
"build": "tsup",
|
|
12
12
|
"test": "vitest run",
|
|
13
13
|
"test:watch": "vitest",
|
|
14
14
|
"dev": "tsx src/index.ts",
|
|
15
15
|
"dev:http": "tsx --watch src/index.ts --http 3000 -v",
|
|
16
16
|
"format": "prettier --write \"**/*.{ts,mts,js,mjs,json,md}\"",
|
|
17
|
-
"release": "ts-node --esm bin/release.mts",
|
|
18
17
|
"inspect": "npx @modelcontextprotocol/inspector tsx src/index.ts",
|
|
19
18
|
"prepublishOnly": "npm run build"
|
|
20
19
|
},
|
|
@@ -42,13 +41,22 @@
|
|
|
42
41
|
},
|
|
43
42
|
"devDependencies": {
|
|
44
43
|
"@redocly/cli": "^1.34.3",
|
|
44
|
+
"@semantic-release/exec": "^7.1.0",
|
|
45
|
+
"@semantic-release/git": "^10.0.1",
|
|
46
|
+
"@semantic-release/github": "^11.0.3",
|
|
47
|
+
"@semantic-release/npm": "^12.0.2",
|
|
45
48
|
"@types/express": "^5.0.3",
|
|
46
49
|
"@types/node": "^22.15.15",
|
|
47
50
|
"prettier": "^3.5.3",
|
|
51
|
+
"semantic-release": "^24.2.7",
|
|
52
|
+
"tsup": "^8.5.0",
|
|
48
53
|
"tsx": "^4.19.4",
|
|
49
54
|
"typescript": "^5.8.3",
|
|
50
55
|
"vitest": "^3.1.1"
|
|
51
56
|
},
|
|
57
|
+
"engines": {
|
|
58
|
+
"node": ">=18"
|
|
59
|
+
},
|
|
52
60
|
"repository": {
|
|
53
61
|
"type": "git",
|
|
54
62
|
"url": "https://github.com/softeria/ms-365-mcp-server.git"
|
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { defineConfig } from 'tsup';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: ['src/**/*.ts', 'src/endpoints.json'],
|
|
5
|
+
format: ['esm'],
|
|
6
|
+
target: 'es2020',
|
|
7
|
+
outDir: 'dist',
|
|
8
|
+
clean: true,
|
|
9
|
+
bundle: false,
|
|
10
|
+
splitting: false,
|
|
11
|
+
sourcemap: false,
|
|
12
|
+
dts: false,
|
|
13
|
+
publicDir: false,
|
|
14
|
+
onSuccess: 'chmod +x dist/index.js',
|
|
15
|
+
loader: {
|
|
16
|
+
'.json': 'copy',
|
|
17
|
+
},
|
|
18
|
+
noExternal: [],
|
|
19
|
+
external: [
|
|
20
|
+
'@azure/msal-node',
|
|
21
|
+
'@modelcontextprotocol/sdk',
|
|
22
|
+
'commander',
|
|
23
|
+
'dotenv',
|
|
24
|
+
'express',
|
|
25
|
+
'js-yaml',
|
|
26
|
+
'keytar',
|
|
27
|
+
'winston',
|
|
28
|
+
'zod',
|
|
29
|
+
],
|
|
30
|
+
});
|