ms365-mcp-server 1.1.3 ā 1.1.4
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 +40 -0
- package/dist/index.js +182 -21
- package/package.json +8 -6
package/README.md
CHANGED
|
@@ -252,6 +252,46 @@ ms365-mcp-server --login
|
|
|
252
252
|
}
|
|
253
253
|
```
|
|
254
254
|
|
|
255
|
+
### Attachment Management
|
|
256
|
+
```javascript
|
|
257
|
+
// Download a specific attachment
|
|
258
|
+
{
|
|
259
|
+
"tool": "get_attachment",
|
|
260
|
+
"arguments": {
|
|
261
|
+
"messageId": "email_id",
|
|
262
|
+
"attachmentId": "attachment_id"
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Download all attachments from an email
|
|
267
|
+
{
|
|
268
|
+
"tool": "get_attachment",
|
|
269
|
+
"arguments": {
|
|
270
|
+
"messageId": "email_id"
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### File Serving
|
|
276
|
+
The server automatically serves downloaded attachments. By default, files are served at `http://localhost:55000/attachments/`. For production environments, configure the server URL using the `SERVER_URL` environment variable:
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
# Local development (default)
|
|
280
|
+
SERVER_URL=http://localhost:55000 ms365-mcp-server
|
|
281
|
+
|
|
282
|
+
# Production environment
|
|
283
|
+
SERVER_URL=https://your-domain.com ms365-mcp-server
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Files are:
|
|
287
|
+
- Stored with unique names to prevent collisions
|
|
288
|
+
- Available for 24 hours
|
|
289
|
+
- Served with proper content types
|
|
290
|
+
- Accessible via the configured server URL
|
|
291
|
+
|
|
292
|
+
### Cleanup
|
|
293
|
+
Attachments are automatically cleaned up after 24 hours. You can also manually delete files from the `public/attachments` directory.
|
|
294
|
+
|
|
255
295
|
## šÆ Command Line Options
|
|
256
296
|
|
|
257
297
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,11 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
6
6
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
7
|
+
import express from 'express';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
import fs from 'fs/promises';
|
|
11
|
+
import crypto from 'crypto';
|
|
7
12
|
import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
8
13
|
import { logger } from './utils/api.js';
|
|
9
14
|
import { MS365Operations } from './utils/ms365-operations.js';
|
|
@@ -55,7 +60,7 @@ function parseArgs() {
|
|
|
55
60
|
}
|
|
56
61
|
const server = new Server({
|
|
57
62
|
name: "ms365-mcp-server",
|
|
58
|
-
version: "1.1.
|
|
63
|
+
version: "1.1.4"
|
|
59
64
|
}, {
|
|
60
65
|
capabilities: {
|
|
61
66
|
resources: {
|
|
@@ -835,8 +840,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
835
840
|
const graphClient = await enhancedMS365Auth.getGraphClient();
|
|
836
841
|
ms365Ops.setGraphClient(graphClient);
|
|
837
842
|
}
|
|
838
|
-
if (!args?.messageId
|
|
839
|
-
throw new Error("
|
|
843
|
+
if (!args?.messageId) {
|
|
844
|
+
throw new Error("messageId is required");
|
|
840
845
|
}
|
|
841
846
|
try {
|
|
842
847
|
// First verify the email exists and has attachments
|
|
@@ -847,25 +852,47 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
847
852
|
if (!email.attachments || email.attachments.length === 0) {
|
|
848
853
|
throw new Error("No attachment information available for this email");
|
|
849
854
|
}
|
|
850
|
-
//
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
855
|
+
// If specific attachmentId is provided, get that attachment
|
|
856
|
+
if (args?.attachmentId) {
|
|
857
|
+
const attachmentExists = email.attachments.some(att => att.id === args.attachmentId);
|
|
858
|
+
if (!attachmentExists) {
|
|
859
|
+
throw new Error(`Attachment ID ${args.attachmentId} not found in this email. Available attachments:\n${email.attachments.map(att => `- ${att.name} (ID: ${att.id})`).join('\n')}`);
|
|
860
|
+
}
|
|
861
|
+
const attachment = await ms365Ops.getAttachment(args.messageId, args.attachmentId);
|
|
862
|
+
// Save the attachment to file
|
|
863
|
+
const savedFilename = await saveBase64ToFile(attachment.contentBytes, attachment.name);
|
|
864
|
+
const fileUrl = `${SERVER_URL}/attachments/${savedFilename}`;
|
|
865
|
+
attachment.fileUrl = fileUrl;
|
|
866
|
+
const artifacts = getListOfArtifacts("get_attachment", [attachment]);
|
|
867
|
+
const content = {
|
|
868
|
+
type: "text",
|
|
869
|
+
text: `š Attachment Downloaded\n\nš Name: ${attachment.name}\nš¾ Size: ${attachment.size} bytes\nšļø Type: ${attachment.contentType}\n\nš Download URL: ${fileUrl}\n\nš” The file will be available for 24 hours.`
|
|
870
|
+
};
|
|
871
|
+
return {
|
|
872
|
+
content: [content, ...artifacts]
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
else {
|
|
876
|
+
// Get all attachments
|
|
877
|
+
const attachments = await Promise.all(email.attachments.map(async (att) => {
|
|
878
|
+
const attachment = await ms365Ops.getAttachment(args.messageId, att.id);
|
|
879
|
+
const savedFilename = await saveBase64ToFile(attachment.contentBytes, attachment.name);
|
|
880
|
+
return {
|
|
881
|
+
name: attachment.name,
|
|
882
|
+
contentType: attachment.contentType,
|
|
883
|
+
size: attachment.size,
|
|
884
|
+
fileUrl: `${SERVER_URL}/attachments/${savedFilename}`
|
|
885
|
+
};
|
|
886
|
+
}));
|
|
887
|
+
const artifacts = getListOfArtifacts("get_attachment", attachments);
|
|
888
|
+
const content = {
|
|
889
|
+
type: "text",
|
|
890
|
+
text: `š Downloaded ${attachments.length} Attachments\n\n${attachments.map((att, index) => `${index + 1}. š ${att.name}\n š¾ Size: ${att.size} bytes\n šļø Type: ${att.contentType}\n š Download URL: ${att.fileUrl}\n`).join('\n')}\n\nš” Files will be available for 24 hours.`
|
|
891
|
+
};
|
|
892
|
+
return {
|
|
893
|
+
content: [content, ...artifacts]
|
|
894
|
+
};
|
|
854
895
|
}
|
|
855
|
-
// Now get the attachment
|
|
856
|
-
const attachment = await ms365Ops.getAttachment(args.messageId, args.attachmentId);
|
|
857
|
-
return {
|
|
858
|
-
content: [
|
|
859
|
-
{
|
|
860
|
-
type: "text",
|
|
861
|
-
text: `š Attachment Downloaded\n\nš Name: ${attachment.name}\nš¾ Size: ${attachment.size} bytes\nšļø Type: ${attachment.contentType}\n\nBelow is the base64-encoded content. You can save this to a file using a script or tool that decodes base64.\n\n---BASE64 START---\n${attachment.contentBytes}\n---BASE64 END---`
|
|
862
|
-
}
|
|
863
|
-
],
|
|
864
|
-
base64Content: attachment.contentBytes,
|
|
865
|
-
filename: attachment.name,
|
|
866
|
-
contentType: attachment.contentType,
|
|
867
|
-
size: attachment.size
|
|
868
|
-
};
|
|
869
896
|
}
|
|
870
897
|
catch (error) {
|
|
871
898
|
if (error.message.includes("not found in the store")) {
|
|
@@ -942,6 +969,140 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
942
969
|
};
|
|
943
970
|
}
|
|
944
971
|
});
|
|
972
|
+
function generateAlphaNumericId(alphaSize = 3, numericSize = 3) {
|
|
973
|
+
const letters = 'abcdefghijklmnopqrstuvwxyz';
|
|
974
|
+
const digits = '0123456789';
|
|
975
|
+
let alphaPart = '';
|
|
976
|
+
let numericPart = '';
|
|
977
|
+
for (let i = 0; i < alphaSize; i++) {
|
|
978
|
+
alphaPart += letters.charAt(Math.floor(Math.random() * letters.length));
|
|
979
|
+
}
|
|
980
|
+
for (let i = 0; i < numericSize; i++) {
|
|
981
|
+
numericPart += digits.charAt(Math.floor(Math.random() * digits.length));
|
|
982
|
+
}
|
|
983
|
+
return alphaPart + numericPart;
|
|
984
|
+
}
|
|
985
|
+
function getListOfArtifacts(functionName, attachments) {
|
|
986
|
+
const artifacts = [];
|
|
987
|
+
attachments.forEach((attachment, i) => {
|
|
988
|
+
const url = attachment?.fileUrl || '';
|
|
989
|
+
const fileName = attachment?.name || '';
|
|
990
|
+
if (url) {
|
|
991
|
+
const timestamp = Math.floor(Date.now() / 1000); // seconds
|
|
992
|
+
const visitTimestamp = Date.now(); // milliseconds
|
|
993
|
+
const artifactData = {
|
|
994
|
+
id: `msg_browser_${generateAlphaNumericId()}`,
|
|
995
|
+
parentTaskId: `task_attachment_${generateAlphaNumericId()}`,
|
|
996
|
+
timestamp: timestamp,
|
|
997
|
+
agent: {
|
|
998
|
+
id: "agent_siya_browser",
|
|
999
|
+
name: "SIYA",
|
|
1000
|
+
type: "qna"
|
|
1001
|
+
},
|
|
1002
|
+
messageType: "action",
|
|
1003
|
+
action: {
|
|
1004
|
+
tool: "browser",
|
|
1005
|
+
operation: "browsing",
|
|
1006
|
+
params: {
|
|
1007
|
+
url: `Filename: ${fileName}`,
|
|
1008
|
+
pageTitle: `Tool response for ${functionName}`,
|
|
1009
|
+
visual: {
|
|
1010
|
+
icon: "browser",
|
|
1011
|
+
color: "#2D8CFF"
|
|
1012
|
+
},
|
|
1013
|
+
stream: {
|
|
1014
|
+
type: "vnc",
|
|
1015
|
+
streamId: "stream_browser_1",
|
|
1016
|
+
target: "browser"
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
},
|
|
1020
|
+
content: `Viewed page: ${functionName}`,
|
|
1021
|
+
artifacts: [
|
|
1022
|
+
{
|
|
1023
|
+
id: "artifact_webpage_1746018877304_994",
|
|
1024
|
+
type: "browser_view",
|
|
1025
|
+
content: {
|
|
1026
|
+
url: url,
|
|
1027
|
+
title: functionName,
|
|
1028
|
+
screenshot: "",
|
|
1029
|
+
textContent: `Observed output of cmd \`${functionName}\` executed:`,
|
|
1030
|
+
extractedInfo: {}
|
|
1031
|
+
},
|
|
1032
|
+
metadata: {
|
|
1033
|
+
domainName: "example.com",
|
|
1034
|
+
visitTimestamp: visitTimestamp,
|
|
1035
|
+
category: "web_page"
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
],
|
|
1039
|
+
status: "completed"
|
|
1040
|
+
};
|
|
1041
|
+
const artifact = {
|
|
1042
|
+
type: "text",
|
|
1043
|
+
text: JSON.stringify(artifactData, null, 2),
|
|
1044
|
+
title: `Filename: ${fileName}`,
|
|
1045
|
+
format: "json"
|
|
1046
|
+
};
|
|
1047
|
+
artifacts.push(artifact);
|
|
1048
|
+
}
|
|
1049
|
+
});
|
|
1050
|
+
return artifacts;
|
|
1051
|
+
}
|
|
1052
|
+
// Set up Express server for file serving
|
|
1053
|
+
const app = express();
|
|
1054
|
+
const PORT = process.env.PORT || 55000;
|
|
1055
|
+
const SERVER_URL = process.env.SERVER_URL || `http://localhost:${PORT}`;
|
|
1056
|
+
// Get the directory name in ESM
|
|
1057
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
1058
|
+
const __dirname = path.dirname(__filename);
|
|
1059
|
+
// Serve static files from public directory
|
|
1060
|
+
app.use('/attachments', express.static(path.join(__dirname, '../public/attachments')));
|
|
1061
|
+
// Start Express server
|
|
1062
|
+
app.listen(PORT, () => {
|
|
1063
|
+
logger.log(`File server running on ${SERVER_URL}`);
|
|
1064
|
+
});
|
|
1065
|
+
// Helper function to generate unique filenames
|
|
1066
|
+
function generateUniqueFilename(originalName) {
|
|
1067
|
+
const timestamp = Date.now();
|
|
1068
|
+
const randomString = crypto.randomBytes(8).toString('hex');
|
|
1069
|
+
const extension = path.extname(originalName);
|
|
1070
|
+
const baseName = path.basename(originalName, extension);
|
|
1071
|
+
return `${baseName}-${timestamp}-${randomString}${extension}`;
|
|
1072
|
+
}
|
|
1073
|
+
// Helper function to save base64 content to file
|
|
1074
|
+
async function saveBase64ToFile(base64Content, filename) {
|
|
1075
|
+
const uniqueFilename = generateUniqueFilename(filename);
|
|
1076
|
+
const filePath = path.join(__dirname, '../public/attachments', uniqueFilename);
|
|
1077
|
+
const buffer = Buffer.from(base64Content, 'base64');
|
|
1078
|
+
await fs.writeFile(filePath, buffer);
|
|
1079
|
+
return uniqueFilename;
|
|
1080
|
+
}
|
|
1081
|
+
// Helper function to clean up old attachments
|
|
1082
|
+
async function cleanupOldAttachments() {
|
|
1083
|
+
const attachmentsDir = path.join(__dirname, '../public/attachments');
|
|
1084
|
+
try {
|
|
1085
|
+
const files = await fs.readdir(attachmentsDir);
|
|
1086
|
+
const now = Date.now();
|
|
1087
|
+
for (const file of files) {
|
|
1088
|
+
const filePath = path.join(attachmentsDir, file);
|
|
1089
|
+
const stats = await fs.stat(filePath);
|
|
1090
|
+
const fileAge = now - stats.mtimeMs;
|
|
1091
|
+
// Remove files older than 24 hours
|
|
1092
|
+
if (fileAge > 24 * 60 * 60 * 1000) {
|
|
1093
|
+
await fs.unlink(filePath);
|
|
1094
|
+
logger.log(`Cleaned up old attachment: ${file}`);
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
catch (error) {
|
|
1099
|
+
logger.error('Error cleaning up attachments:', error);
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
// Set up cleanup interval (run every hour)
|
|
1103
|
+
setInterval(cleanupOldAttachments, 60 * 60 * 1000);
|
|
1104
|
+
// Run initial cleanup
|
|
1105
|
+
cleanupOldAttachments();
|
|
945
1106
|
async function main() {
|
|
946
1107
|
ms365Config = parseArgs();
|
|
947
1108
|
if (ms365Config.setupAuth) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ms365-mcp-server",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "Microsoft 365 MCP Server for managing Microsoft 365 email through natural language interactions with full OAuth2 authentication support",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -30,19 +30,21 @@
|
|
|
30
30
|
],
|
|
31
31
|
"license": "MIT",
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@modelcontextprotocol/sdk": "^1.10.1",
|
|
34
33
|
"@azure/msal-node": "^2.6.6",
|
|
35
34
|
"@microsoft/microsoft-graph-client": "^3.0.7",
|
|
35
|
+
"@modelcontextprotocol/sdk": "^1.10.1",
|
|
36
|
+
"express": "^5.1.0",
|
|
36
37
|
"isomorphic-fetch": "^3.0.0",
|
|
37
|
-
"open": "^10.1.0",
|
|
38
|
-
"mime-types": "^2.1.35",
|
|
39
38
|
"keytar": "^7.9.0",
|
|
39
|
+
"mime-types": "^2.1.35",
|
|
40
|
+
"open": "^10.1.0",
|
|
40
41
|
"typescript": "^5.0.4"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
|
-
"@types/
|
|
44
|
-
"@types/mime-types": "^2.1.4",
|
|
44
|
+
"@types/express": "^5.0.3",
|
|
45
45
|
"@types/isomorphic-fetch": "^0.0.36",
|
|
46
|
+
"@types/mime-types": "^2.1.4",
|
|
47
|
+
"@types/node": "^20.2.3",
|
|
46
48
|
"ts-node": "^10.9.1",
|
|
47
49
|
"tsx": "^4.19.4"
|
|
48
50
|
},
|