@dashflow/ms365-mcp-server 1.0.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.
- package/.env.example +54 -0
- package/.releaserc.json +9 -0
- package/Dockerfile +22 -0
- package/LICENSE +21 -0
- package/README.md +590 -0
- package/bin/generate-graph-client.mjs +59 -0
- package/bin/modules/download-openapi.mjs +40 -0
- package/bin/modules/extract-descriptions.mjs +48 -0
- package/bin/modules/generate-mcp-tools.mjs +46 -0
- package/bin/modules/simplified-openapi.mjs +694 -0
- package/dist/auth-tools.js +202 -0
- package/dist/auth.js +422 -0
- package/dist/cli.js +78 -0
- package/dist/cloud-config.js +49 -0
- package/dist/endpoints.json +596 -0
- package/dist/generated/endpoint-types.js +0 -0
- package/dist/generated/hack.js +42 -0
- package/dist/graph-client.js +208 -0
- package/dist/graph-tools.js +401 -0
- package/dist/index.js +76 -0
- package/dist/lib/microsoft-auth.js +73 -0
- package/dist/logger.js +42 -0
- package/dist/oauth-provider.js +51 -0
- package/dist/request-context.js +9 -0
- package/dist/secrets.js +68 -0
- package/dist/server.js +387 -0
- package/dist/tool-categories.js +93 -0
- package/dist/version.js +10 -0
- package/eslint.config.js +43 -0
- package/glama.json +4 -0
- package/package.json +79 -0
- package/remove-recursive-refs.js +294 -0
- package/src/endpoints.json +596 -0
- package/src/generated/README.md +56 -0
- package/src/generated/endpoint-types.ts +27 -0
- package/src/generated/hack.ts +49 -0
- package/test-calendar-fix.js +62 -0
- package/test-real-calendar.js +96 -0
- package/tsup.config.ts +30 -0
package/dist/server.js
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
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 logger, { enableConsoleLogging } from "./logger.js";
|
|
7
|
+
import { registerAuthTools } from "./auth-tools.js";
|
|
8
|
+
import { registerGraphTools, registerDiscoveryTools } from "./graph-tools.js";
|
|
9
|
+
import GraphClient from "./graph-client.js";
|
|
10
|
+
import { buildScopesFromEndpoints } from "./auth.js";
|
|
11
|
+
import { MicrosoftOAuthProvider } from "./oauth-provider.js";
|
|
12
|
+
import {
|
|
13
|
+
exchangeCodeForToken,
|
|
14
|
+
microsoftBearerTokenAuthMiddleware,
|
|
15
|
+
refreshAccessToken
|
|
16
|
+
} from "./lib/microsoft-auth.js";
|
|
17
|
+
import { getSecrets } from "./secrets.js";
|
|
18
|
+
import { getCloudEndpoints } from "./cloud-config.js";
|
|
19
|
+
import { requestContext } from "./request-context.js";
|
|
20
|
+
function parseHttpOption(httpOption) {
|
|
21
|
+
if (typeof httpOption === "boolean") {
|
|
22
|
+
return { host: void 0, port: 3e3 };
|
|
23
|
+
}
|
|
24
|
+
const httpString = httpOption.trim();
|
|
25
|
+
if (httpString.includes(":")) {
|
|
26
|
+
const [hostPart, portPart] = httpString.split(":");
|
|
27
|
+
const host = hostPart || void 0;
|
|
28
|
+
const port2 = parseInt(portPart) || 3e3;
|
|
29
|
+
return { host, port: port2 };
|
|
30
|
+
}
|
|
31
|
+
const port = parseInt(httpString) || 3e3;
|
|
32
|
+
return { host: void 0, port };
|
|
33
|
+
}
|
|
34
|
+
class MicrosoftGraphServer {
|
|
35
|
+
constructor(authManager, options = {}) {
|
|
36
|
+
this.authManager = authManager;
|
|
37
|
+
this.options = options;
|
|
38
|
+
this.graphClient = null;
|
|
39
|
+
this.server = null;
|
|
40
|
+
this.secrets = null;
|
|
41
|
+
}
|
|
42
|
+
async initialize(version) {
|
|
43
|
+
this.secrets = await getSecrets();
|
|
44
|
+
const outputFormat = this.options.toon ? "toon" : "json";
|
|
45
|
+
this.graphClient = new GraphClient(this.authManager, this.secrets, outputFormat);
|
|
46
|
+
this.server = new McpServer({
|
|
47
|
+
name: "Microsoft365MCP",
|
|
48
|
+
version
|
|
49
|
+
});
|
|
50
|
+
const shouldRegisterAuthTools = !this.options.http || this.options.enableAuthTools;
|
|
51
|
+
if (shouldRegisterAuthTools) {
|
|
52
|
+
registerAuthTools(this.server, this.authManager);
|
|
53
|
+
}
|
|
54
|
+
if (this.options.discovery) {
|
|
55
|
+
logger.info("Discovery mode enabled (experimental) - registering discovery tool only");
|
|
56
|
+
registerDiscoveryTools(
|
|
57
|
+
this.server,
|
|
58
|
+
this.graphClient,
|
|
59
|
+
this.options.readOnly,
|
|
60
|
+
this.options.orgMode
|
|
61
|
+
);
|
|
62
|
+
} else {
|
|
63
|
+
registerGraphTools(
|
|
64
|
+
this.server,
|
|
65
|
+
this.graphClient,
|
|
66
|
+
this.options.readOnly,
|
|
67
|
+
this.options.enabledTools,
|
|
68
|
+
this.options.orgMode
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
async start() {
|
|
73
|
+
if (this.options.v) {
|
|
74
|
+
enableConsoleLogging();
|
|
75
|
+
}
|
|
76
|
+
logger.info("Microsoft 365 MCP Server starting...");
|
|
77
|
+
logger.info("Secrets Check:", {
|
|
78
|
+
CLIENT_ID: this.secrets?.clientId ? `${this.secrets.clientId.substring(0, 8)}...` : "NOT SET",
|
|
79
|
+
CLIENT_SECRET: this.secrets?.clientSecret ? "SET" : "NOT SET",
|
|
80
|
+
TENANT_ID: this.secrets?.tenantId || "NOT SET",
|
|
81
|
+
NODE_ENV: process.env.NODE_ENV || "NOT SET"
|
|
82
|
+
});
|
|
83
|
+
if (this.options.readOnly) {
|
|
84
|
+
logger.info("Server running in READ-ONLY mode. Write operations are disabled.");
|
|
85
|
+
}
|
|
86
|
+
if (this.options.http) {
|
|
87
|
+
const { host, port } = parseHttpOption(this.options.http);
|
|
88
|
+
const app = express();
|
|
89
|
+
app.set("trust proxy", true);
|
|
90
|
+
app.use(express.json());
|
|
91
|
+
app.use(express.urlencoded({ extended: true }));
|
|
92
|
+
const corsOrigin = process.env.MS365_MCP_CORS_ORIGIN || "*";
|
|
93
|
+
app.use((req, res, next) => {
|
|
94
|
+
res.header("Access-Control-Allow-Origin", corsOrigin);
|
|
95
|
+
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
|
96
|
+
res.header(
|
|
97
|
+
"Access-Control-Allow-Headers",
|
|
98
|
+
"Origin, X-Requested-With, Content-Type, Accept, Authorization, mcp-protocol-version"
|
|
99
|
+
);
|
|
100
|
+
if (req.method === "OPTIONS") {
|
|
101
|
+
res.sendStatus(200);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
next();
|
|
105
|
+
});
|
|
106
|
+
const oauthProvider = new MicrosoftOAuthProvider(this.authManager, this.secrets);
|
|
107
|
+
app.get("/.well-known/oauth-authorization-server", async (req, res) => {
|
|
108
|
+
const protocol = req.secure ? "https" : "http";
|
|
109
|
+
const url = new URL(`${protocol}://${req.get("host")}`);
|
|
110
|
+
const scopes = buildScopesFromEndpoints(this.options.orgMode, this.options.enabledTools);
|
|
111
|
+
const metadata = {
|
|
112
|
+
issuer: url.origin,
|
|
113
|
+
authorization_endpoint: `${url.origin}/authorize`,
|
|
114
|
+
token_endpoint: `${url.origin}/token`,
|
|
115
|
+
response_types_supported: ["code"],
|
|
116
|
+
response_modes_supported: ["query"],
|
|
117
|
+
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
118
|
+
token_endpoint_auth_methods_supported: ["none"],
|
|
119
|
+
code_challenge_methods_supported: ["S256"],
|
|
120
|
+
scopes_supported: scopes
|
|
121
|
+
};
|
|
122
|
+
if (this.options.enableDynamicRegistration) {
|
|
123
|
+
metadata.registration_endpoint = `${url.origin}/register`;
|
|
124
|
+
}
|
|
125
|
+
res.json(metadata);
|
|
126
|
+
});
|
|
127
|
+
app.get("/.well-known/oauth-protected-resource", async (req, res) => {
|
|
128
|
+
const protocol = req.secure ? "https" : "http";
|
|
129
|
+
const url = new URL(`${protocol}://${req.get("host")}`);
|
|
130
|
+
const scopes = buildScopesFromEndpoints(this.options.orgMode, this.options.enabledTools);
|
|
131
|
+
res.json({
|
|
132
|
+
resource: `${url.origin}/mcp`,
|
|
133
|
+
authorization_servers: [url.origin],
|
|
134
|
+
scopes_supported: scopes,
|
|
135
|
+
bearer_methods_supported: ["header"],
|
|
136
|
+
resource_documentation: `${url.origin}`
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
if (this.options.enableDynamicRegistration) {
|
|
140
|
+
app.post("/register", async (req, res) => {
|
|
141
|
+
const body = req.body;
|
|
142
|
+
logger.info("Client registration request", { body });
|
|
143
|
+
const clientId = `mcp-client-${Date.now()}`;
|
|
144
|
+
res.status(201).json({
|
|
145
|
+
client_id: clientId,
|
|
146
|
+
client_id_issued_at: Math.floor(Date.now() / 1e3),
|
|
147
|
+
redirect_uris: body.redirect_uris || [],
|
|
148
|
+
grant_types: body.grant_types || ["authorization_code", "refresh_token"],
|
|
149
|
+
response_types: body.response_types || ["code"],
|
|
150
|
+
token_endpoint_auth_method: body.token_endpoint_auth_method || "none",
|
|
151
|
+
client_name: body.client_name || "MCP Client"
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
app.get("/authorize", async (req, res) => {
|
|
156
|
+
const url = new URL(req.url, `${req.protocol}://${req.get("host")}`);
|
|
157
|
+
const tenantId = this.secrets?.tenantId || "common";
|
|
158
|
+
const clientId = this.secrets.clientId;
|
|
159
|
+
const cloudEndpoints = getCloudEndpoints(this.secrets.cloudType);
|
|
160
|
+
const microsoftAuthUrl = new URL(
|
|
161
|
+
`${cloudEndpoints.authority}/${tenantId}/oauth2/v2.0/authorize`
|
|
162
|
+
);
|
|
163
|
+
const allowedParams = [
|
|
164
|
+
"response_type",
|
|
165
|
+
"redirect_uri",
|
|
166
|
+
"scope",
|
|
167
|
+
"state",
|
|
168
|
+
"response_mode",
|
|
169
|
+
"code_challenge",
|
|
170
|
+
"code_challenge_method",
|
|
171
|
+
"prompt",
|
|
172
|
+
"login_hint",
|
|
173
|
+
"domain_hint"
|
|
174
|
+
];
|
|
175
|
+
allowedParams.forEach((param) => {
|
|
176
|
+
const value = url.searchParams.get(param);
|
|
177
|
+
if (value) {
|
|
178
|
+
microsoftAuthUrl.searchParams.set(param, value);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
microsoftAuthUrl.searchParams.set("client_id", clientId);
|
|
182
|
+
if (!microsoftAuthUrl.searchParams.get("scope")) {
|
|
183
|
+
microsoftAuthUrl.searchParams.set("scope", "User.Read Files.Read Mail.Read");
|
|
184
|
+
}
|
|
185
|
+
res.redirect(microsoftAuthUrl.toString());
|
|
186
|
+
});
|
|
187
|
+
app.post("/token", async (req, res) => {
|
|
188
|
+
try {
|
|
189
|
+
logger.info("Token endpoint called", {
|
|
190
|
+
method: req.method,
|
|
191
|
+
url: req.url,
|
|
192
|
+
contentType: req.get("Content-Type"),
|
|
193
|
+
grant_type: req.body?.grant_type
|
|
194
|
+
});
|
|
195
|
+
const body = req.body;
|
|
196
|
+
if (!body) {
|
|
197
|
+
logger.error("Token endpoint: Request body is undefined");
|
|
198
|
+
res.status(400).json({
|
|
199
|
+
error: "invalid_request",
|
|
200
|
+
error_description: "Request body is required"
|
|
201
|
+
});
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (!body.grant_type) {
|
|
205
|
+
logger.error("Token endpoint: grant_type is missing", { body });
|
|
206
|
+
res.status(400).json({
|
|
207
|
+
error: "invalid_request",
|
|
208
|
+
error_description: "grant_type parameter is required"
|
|
209
|
+
});
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (body.grant_type === "authorization_code") {
|
|
213
|
+
const tenantId = this.secrets?.tenantId || "common";
|
|
214
|
+
const clientId = this.secrets.clientId;
|
|
215
|
+
const clientSecret = this.secrets?.clientSecret;
|
|
216
|
+
logger.info("Token endpoint: authorization_code exchange", {
|
|
217
|
+
redirect_uri: body.redirect_uri,
|
|
218
|
+
has_code: !!body.code,
|
|
219
|
+
has_code_verifier: !!body.code_verifier,
|
|
220
|
+
clientId,
|
|
221
|
+
tenantId,
|
|
222
|
+
hasClientSecret: !!clientSecret
|
|
223
|
+
});
|
|
224
|
+
const result = await exchangeCodeForToken(
|
|
225
|
+
body.code,
|
|
226
|
+
body.redirect_uri,
|
|
227
|
+
clientId,
|
|
228
|
+
clientSecret,
|
|
229
|
+
tenantId,
|
|
230
|
+
body.code_verifier,
|
|
231
|
+
this.secrets.cloudType
|
|
232
|
+
);
|
|
233
|
+
res.json(result);
|
|
234
|
+
} else if (body.grant_type === "refresh_token") {
|
|
235
|
+
const tenantId = this.secrets?.tenantId || "common";
|
|
236
|
+
const clientId = this.secrets.clientId;
|
|
237
|
+
const clientSecret = this.secrets?.clientSecret;
|
|
238
|
+
if (clientSecret) {
|
|
239
|
+
logger.info("Refresh endpoint: Using confidential client with client_secret");
|
|
240
|
+
} else {
|
|
241
|
+
logger.info("Refresh endpoint: Using public client without client_secret");
|
|
242
|
+
}
|
|
243
|
+
const result = await refreshAccessToken(
|
|
244
|
+
body.refresh_token,
|
|
245
|
+
clientId,
|
|
246
|
+
clientSecret,
|
|
247
|
+
tenantId,
|
|
248
|
+
this.secrets.cloudType
|
|
249
|
+
);
|
|
250
|
+
res.json(result);
|
|
251
|
+
} else {
|
|
252
|
+
res.status(400).json({
|
|
253
|
+
error: "unsupported_grant_type",
|
|
254
|
+
error_description: `Grant type '${body.grant_type}' is not supported`
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
} catch (error) {
|
|
258
|
+
logger.error("Token endpoint error:", error);
|
|
259
|
+
res.status(500).json({
|
|
260
|
+
error: "server_error",
|
|
261
|
+
error_description: "Internal server error during token exchange"
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
app.use(
|
|
266
|
+
mcpAuthRouter({
|
|
267
|
+
provider: oauthProvider,
|
|
268
|
+
issuerUrl: new URL(`http://localhost:${port}`)
|
|
269
|
+
})
|
|
270
|
+
);
|
|
271
|
+
app.get(
|
|
272
|
+
"/mcp",
|
|
273
|
+
microsoftBearerTokenAuthMiddleware,
|
|
274
|
+
async (req, res) => {
|
|
275
|
+
const handler = async () => {
|
|
276
|
+
const transport = new StreamableHTTPServerTransport({
|
|
277
|
+
sessionIdGenerator: void 0
|
|
278
|
+
// Stateless mode
|
|
279
|
+
});
|
|
280
|
+
res.on("close", () => {
|
|
281
|
+
transport.close();
|
|
282
|
+
});
|
|
283
|
+
await this.server.connect(transport);
|
|
284
|
+
await transport.handleRequest(req, res, void 0);
|
|
285
|
+
};
|
|
286
|
+
try {
|
|
287
|
+
if (req.microsoftAuth) {
|
|
288
|
+
await requestContext.run(
|
|
289
|
+
{
|
|
290
|
+
accessToken: req.microsoftAuth.accessToken,
|
|
291
|
+
refreshToken: req.microsoftAuth.refreshToken
|
|
292
|
+
},
|
|
293
|
+
handler
|
|
294
|
+
);
|
|
295
|
+
} else {
|
|
296
|
+
await handler();
|
|
297
|
+
}
|
|
298
|
+
} catch (error) {
|
|
299
|
+
logger.error("Error handling MCP GET request:", error);
|
|
300
|
+
if (!res.headersSent) {
|
|
301
|
+
res.status(500).json({
|
|
302
|
+
jsonrpc: "2.0",
|
|
303
|
+
error: {
|
|
304
|
+
code: -32603,
|
|
305
|
+
message: "Internal server error"
|
|
306
|
+
},
|
|
307
|
+
id: null
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
);
|
|
313
|
+
app.post(
|
|
314
|
+
"/mcp",
|
|
315
|
+
microsoftBearerTokenAuthMiddleware,
|
|
316
|
+
async (req, res) => {
|
|
317
|
+
const handler = async () => {
|
|
318
|
+
const transport = new StreamableHTTPServerTransport({
|
|
319
|
+
sessionIdGenerator: void 0
|
|
320
|
+
// Stateless mode
|
|
321
|
+
});
|
|
322
|
+
res.on("close", () => {
|
|
323
|
+
transport.close();
|
|
324
|
+
});
|
|
325
|
+
await this.server.connect(transport);
|
|
326
|
+
await transport.handleRequest(req, res, req.body);
|
|
327
|
+
};
|
|
328
|
+
try {
|
|
329
|
+
if (req.microsoftAuth) {
|
|
330
|
+
await requestContext.run(
|
|
331
|
+
{
|
|
332
|
+
accessToken: req.microsoftAuth.accessToken,
|
|
333
|
+
refreshToken: req.microsoftAuth.refreshToken
|
|
334
|
+
},
|
|
335
|
+
handler
|
|
336
|
+
);
|
|
337
|
+
} else {
|
|
338
|
+
await handler();
|
|
339
|
+
}
|
|
340
|
+
} catch (error) {
|
|
341
|
+
logger.error("Error handling MCP POST request:", error);
|
|
342
|
+
if (!res.headersSent) {
|
|
343
|
+
res.status(500).json({
|
|
344
|
+
jsonrpc: "2.0",
|
|
345
|
+
error: {
|
|
346
|
+
code: -32603,
|
|
347
|
+
message: "Internal server error"
|
|
348
|
+
},
|
|
349
|
+
id: null
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
);
|
|
355
|
+
app.get("/", (req, res) => {
|
|
356
|
+
res.send("Microsoft 365 MCP Server is running");
|
|
357
|
+
});
|
|
358
|
+
if (host) {
|
|
359
|
+
app.listen(port, host, () => {
|
|
360
|
+
logger.info(`Server listening on ${host}:${port}`);
|
|
361
|
+
logger.info(` - MCP endpoint: http://${host}:${port}/mcp`);
|
|
362
|
+
logger.info(` - OAuth endpoints: http://${host}:${port}/auth/*`);
|
|
363
|
+
logger.info(
|
|
364
|
+
` - OAuth discovery: http://${host}:${port}/.well-known/oauth-authorization-server`
|
|
365
|
+
);
|
|
366
|
+
});
|
|
367
|
+
} else {
|
|
368
|
+
app.listen(port, () => {
|
|
369
|
+
logger.info(`Server listening on all interfaces (0.0.0.0:${port})`);
|
|
370
|
+
logger.info(` - MCP endpoint: http://localhost:${port}/mcp`);
|
|
371
|
+
logger.info(` - OAuth endpoints: http://localhost:${port}/auth/*`);
|
|
372
|
+
logger.info(
|
|
373
|
+
` - OAuth discovery: http://localhost:${port}/.well-known/oauth-authorization-server`
|
|
374
|
+
);
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
} else {
|
|
378
|
+
const transport = new StdioServerTransport();
|
|
379
|
+
await this.server.connect(transport);
|
|
380
|
+
logger.info("Server connected to stdio transport");
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
var server_default = MicrosoftGraphServer;
|
|
385
|
+
export {
|
|
386
|
+
server_default as default
|
|
387
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const TOOL_CATEGORIES = {
|
|
2
|
+
mail: {
|
|
3
|
+
name: "mail",
|
|
4
|
+
pattern: /mail|attachment|draft/i,
|
|
5
|
+
description: "Email operations (read, send, manage folders, attachments)"
|
|
6
|
+
},
|
|
7
|
+
calendar: {
|
|
8
|
+
name: "calendar",
|
|
9
|
+
pattern: /calendar|event/i,
|
|
10
|
+
description: "Calendar and event management"
|
|
11
|
+
},
|
|
12
|
+
files: {
|
|
13
|
+
name: "files",
|
|
14
|
+
pattern: /drive|file|upload|download|folder|item/i,
|
|
15
|
+
description: "OneDrive file and folder operations"
|
|
16
|
+
},
|
|
17
|
+
personal: {
|
|
18
|
+
name: "personal",
|
|
19
|
+
pattern: /mail|calendar|drive|contact|todo|onenote|attachment|draft|event|file|folder/i,
|
|
20
|
+
description: "Personal productivity tools (mail, calendar, files, contacts, tasks, notes)"
|
|
21
|
+
},
|
|
22
|
+
work: {
|
|
23
|
+
name: "work",
|
|
24
|
+
pattern: /team|channel|chat|sharepoint|planner|site|list|shared/i,
|
|
25
|
+
description: "Organization/work tools (Teams, SharePoint, shared mailboxes)",
|
|
26
|
+
requiresOrgMode: true
|
|
27
|
+
},
|
|
28
|
+
excel: {
|
|
29
|
+
name: "excel",
|
|
30
|
+
pattern: /excel|worksheet|workbook|range|chart/i,
|
|
31
|
+
description: "Excel spreadsheet operations"
|
|
32
|
+
},
|
|
33
|
+
contacts: {
|
|
34
|
+
name: "contacts",
|
|
35
|
+
pattern: /contact/i,
|
|
36
|
+
description: "Outlook contacts management"
|
|
37
|
+
},
|
|
38
|
+
tasks: {
|
|
39
|
+
name: "tasks",
|
|
40
|
+
pattern: /todo|planner|task/i,
|
|
41
|
+
description: "Task and planning tools (To Do, Planner)"
|
|
42
|
+
},
|
|
43
|
+
onenote: {
|
|
44
|
+
name: "onenote",
|
|
45
|
+
pattern: /onenote|notebook|section|page/i,
|
|
46
|
+
description: "OneNote notebook operations"
|
|
47
|
+
},
|
|
48
|
+
search: {
|
|
49
|
+
name: "search",
|
|
50
|
+
pattern: /search|query/i,
|
|
51
|
+
description: "Microsoft Search capabilities"
|
|
52
|
+
},
|
|
53
|
+
users: {
|
|
54
|
+
name: "users",
|
|
55
|
+
pattern: /user|list-users/i,
|
|
56
|
+
description: "User directory access",
|
|
57
|
+
requiresOrgMode: true
|
|
58
|
+
},
|
|
59
|
+
all: {
|
|
60
|
+
name: "all",
|
|
61
|
+
pattern: /.*/,
|
|
62
|
+
description: "All available tools"
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
function getCombinedPresetPattern(presets) {
|
|
66
|
+
const patterns = presets.map((preset) => {
|
|
67
|
+
const category = TOOL_CATEGORIES[preset];
|
|
68
|
+
if (!category) {
|
|
69
|
+
throw new Error(
|
|
70
|
+
`Unknown preset: ${preset}. Available presets: ${Object.keys(TOOL_CATEGORIES).join(", ")}`
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
return category.pattern.source;
|
|
74
|
+
});
|
|
75
|
+
return patterns.join("|");
|
|
76
|
+
}
|
|
77
|
+
function listPresets() {
|
|
78
|
+
return Object.values(TOOL_CATEGORIES).map((category) => ({
|
|
79
|
+
name: category.name,
|
|
80
|
+
description: category.description,
|
|
81
|
+
requiresOrgMode: category.requiresOrgMode
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
function presetRequiresOrgMode(preset) {
|
|
85
|
+
const category = TOOL_CATEGORIES[preset];
|
|
86
|
+
return category?.requiresOrgMode || false;
|
|
87
|
+
}
|
|
88
|
+
export {
|
|
89
|
+
TOOL_CATEGORIES,
|
|
90
|
+
getCombinedPresetPattern,
|
|
91
|
+
listPresets,
|
|
92
|
+
presetRequiresOrgMode
|
|
93
|
+
};
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
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/eslint.config.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import js from '@eslint/js';
|
|
2
|
+
import globals from 'globals';
|
|
3
|
+
import tseslint from '@typescript-eslint/eslint-plugin';
|
|
4
|
+
import tsparser from '@typescript-eslint/parser';
|
|
5
|
+
|
|
6
|
+
export default [
|
|
7
|
+
js.configs.recommended,
|
|
8
|
+
{
|
|
9
|
+
files: ['**/*.{ts,tsx,js,mjs}'],
|
|
10
|
+
languageOptions: {
|
|
11
|
+
parser: tsparser,
|
|
12
|
+
parserOptions: {
|
|
13
|
+
ecmaVersion: 2022,
|
|
14
|
+
sourceType: 'module',
|
|
15
|
+
},
|
|
16
|
+
globals: {
|
|
17
|
+
...globals.node,
|
|
18
|
+
...globals.vitest,
|
|
19
|
+
...globals.jest,
|
|
20
|
+
fs: 'readonly',
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
plugins: {
|
|
24
|
+
'@typescript-eslint': tseslint,
|
|
25
|
+
},
|
|
26
|
+
rules: {
|
|
27
|
+
...tseslint.configs.recommended.rules,
|
|
28
|
+
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
|
29
|
+
'@typescript-eslint/no-explicit-any': 'warn',
|
|
30
|
+
'no-console': 'off',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
ignores: [
|
|
35
|
+
'node_modules/**',
|
|
36
|
+
'dist/**',
|
|
37
|
+
'coverage/**',
|
|
38
|
+
'bin/**',
|
|
39
|
+
'src/generated/**',
|
|
40
|
+
'.venv/**',
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
];
|
package/glama.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dashflow/ms365-mcp-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A Model Context Protocol (MCP) server for interacting with Microsoft 365 and Office services through the Graph API",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"ms365-mcp-server": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"generate": "node bin/generate-graph-client.mjs",
|
|
12
|
+
"build": "tsup",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"test:watch": "vitest",
|
|
15
|
+
"dev": "tsx src/index.ts",
|
|
16
|
+
"dev:http": "tsx --watch src/index.ts --http 127.0.0.1:3000 -v",
|
|
17
|
+
"format": "prettier --write \"**/*.{ts,mts,js,mjs,json,md}\"",
|
|
18
|
+
"format:check": "prettier --check \"**/*.{ts,mts,js,mjs,json,md}\"",
|
|
19
|
+
"lint": "eslint .",
|
|
20
|
+
"lint:fix": "eslint . --fix",
|
|
21
|
+
"verify": "npm run generate && npm run lint && npm run format:check && npm run build && npm run test",
|
|
22
|
+
"inspector": "npx @modelcontextprotocol/inspector tsx src/index.ts"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"microsoft",
|
|
26
|
+
"365",
|
|
27
|
+
"mcp",
|
|
28
|
+
"server",
|
|
29
|
+
"dashflow"
|
|
30
|
+
],
|
|
31
|
+
"author": "Dashflow",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@azure/msal-node": "^3.8.0",
|
|
38
|
+
"@modelcontextprotocol/sdk": "^1.25.0",
|
|
39
|
+
"@toon-format/toon": "^0.8.0",
|
|
40
|
+
"commander": "^11.1.0",
|
|
41
|
+
"dotenv": "^17.0.1",
|
|
42
|
+
"express": "^5.2.1",
|
|
43
|
+
"js-yaml": "^4.1.1",
|
|
44
|
+
"winston": "^3.17.0",
|
|
45
|
+
"zod": "^3.24.2"
|
|
46
|
+
},
|
|
47
|
+
"optionalDependencies": {
|
|
48
|
+
"@azure/identity": "^4.5.0",
|
|
49
|
+
"@azure/keyvault-secrets": "^4.9.0",
|
|
50
|
+
"keytar": "^7.9.0"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@redocly/cli": "^2.11.1",
|
|
54
|
+
"@semantic-release/exec": "^7.1.0",
|
|
55
|
+
"@semantic-release/git": "^10.0.1",
|
|
56
|
+
"@semantic-release/github": "^11.0.3",
|
|
57
|
+
"@semantic-release/npm": "^13.1.3",
|
|
58
|
+
"@types/express": "^5.0.3",
|
|
59
|
+
"@types/node": "^22.15.15",
|
|
60
|
+
"@typescript-eslint/eslint-plugin": "^8.38.0",
|
|
61
|
+
"@typescript-eslint/parser": "^8.38.0",
|
|
62
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
63
|
+
"eslint": "^9.31.0",
|
|
64
|
+
"globals": "^16.3.0",
|
|
65
|
+
"prettier": "^3.5.3",
|
|
66
|
+
"semantic-release": "^25.0.2",
|
|
67
|
+
"tsup": "^8.5.1",
|
|
68
|
+
"tsx": "^4.19.4",
|
|
69
|
+
"typescript": "^5.8.3",
|
|
70
|
+
"vitest": "^3.1.1"
|
|
71
|
+
},
|
|
72
|
+
"engines": {
|
|
73
|
+
"node": ">=18"
|
|
74
|
+
},
|
|
75
|
+
"repository": {
|
|
76
|
+
"type": "git",
|
|
77
|
+
"url": "https://github.com/dashflow/ms365-mcp-server.git"
|
|
78
|
+
}
|
|
79
|
+
}
|