ms365-mcp-server 1.1.4 → 1.1.6
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/dist/index.js +2 -2
- package/dist/utils/ms365-operations.js +129 -32
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -60,7 +60,7 @@ function parseArgs() {
|
|
|
60
60
|
}
|
|
61
61
|
const server = new Server({
|
|
62
62
|
name: "ms365-mcp-server",
|
|
63
|
-
version: "1.1.
|
|
63
|
+
version: "1.1.6"
|
|
64
64
|
}, {
|
|
65
65
|
capabilities: {
|
|
66
66
|
resources: {
|
|
@@ -1067,7 +1067,7 @@ function generateUniqueFilename(originalName) {
|
|
|
1067
1067
|
const timestamp = Date.now();
|
|
1068
1068
|
const randomString = crypto.randomBytes(8).toString('hex');
|
|
1069
1069
|
const extension = path.extname(originalName);
|
|
1070
|
-
const baseName = path.basename(originalName, extension);
|
|
1070
|
+
const baseName = path.basename(originalName, extension).replace(/[^a-zA-Z0-9-_]/g, '_');
|
|
1071
1071
|
return `${baseName}-${timestamp}-${randomString}${extension}`;
|
|
1072
1072
|
}
|
|
1073
1073
|
// Helper function to save base64 content to file
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { logger } from './api.js';
|
|
2
|
+
import mimeTypes from 'mime-types';
|
|
2
3
|
/**
|
|
3
4
|
* Microsoft 365 operations manager class
|
|
4
5
|
*/
|
|
@@ -686,19 +687,48 @@ export class MS365Operations {
|
|
|
686
687
|
async getAttachment(messageId, attachmentId) {
|
|
687
688
|
try {
|
|
688
689
|
const graphClient = await this.getGraphClient();
|
|
690
|
+
logger.log(`Fetching attachment ${attachmentId} from message ${messageId}...`);
|
|
691
|
+
// First get attachment metadata
|
|
689
692
|
const attachment = await graphClient
|
|
690
693
|
.api(`/me/messages/${messageId}/attachments/${attachmentId}`)
|
|
691
694
|
.get();
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
695
|
+
logger.log(`Retrieved attachment metadata: ${attachment.name} (${attachment.contentType}, ${attachment.size} bytes)`);
|
|
696
|
+
// Determine content type if not provided
|
|
697
|
+
let contentType = attachment.contentType;
|
|
698
|
+
if (!contentType && attachment.name) {
|
|
699
|
+
contentType = mimeTypes.lookup(attachment.name) || 'application/octet-stream';
|
|
700
|
+
logger.log(`Inferred content type for ${attachment.name}: ${contentType}`);
|
|
701
|
+
}
|
|
702
|
+
// Validate content bytes
|
|
703
|
+
if (!attachment.contentBytes) {
|
|
704
|
+
logger.error('No content bytes found in attachment');
|
|
705
|
+
throw new Error('Attachment content is empty');
|
|
706
|
+
}
|
|
707
|
+
// Return the attachment data
|
|
708
|
+
const result = {
|
|
709
|
+
name: attachment.name || 'unnamed_attachment',
|
|
710
|
+
contentType: contentType || 'application/octet-stream',
|
|
711
|
+
contentBytes: attachment.contentBytes,
|
|
696
712
|
size: attachment.size || 0
|
|
697
713
|
};
|
|
714
|
+
logger.log(`Successfully processed attachment: ${result.name} (${result.contentType}, ${result.size} bytes)`);
|
|
715
|
+
return result;
|
|
698
716
|
}
|
|
699
717
|
catch (error) {
|
|
700
718
|
logger.error('Error getting attachment:', error);
|
|
701
|
-
|
|
719
|
+
// Provide more specific error messages
|
|
720
|
+
if (error.status === 404) {
|
|
721
|
+
throw new Error(`Attachment not found: ${error.message}`);
|
|
722
|
+
}
|
|
723
|
+
else if (error.status === 401) {
|
|
724
|
+
throw new Error('Authentication failed. Please re-authenticate.');
|
|
725
|
+
}
|
|
726
|
+
else if (error.status === 403) {
|
|
727
|
+
throw new Error('Permission denied to access this attachment.');
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
throw new Error(`Failed to get attachment: ${error.message}`);
|
|
731
|
+
}
|
|
702
732
|
}
|
|
703
733
|
}
|
|
704
734
|
/**
|
|
@@ -810,33 +840,100 @@ export class MS365Operations {
|
|
|
810
840
|
async searchEmailsToMe(additionalCriteria = {}) {
|
|
811
841
|
try {
|
|
812
842
|
const userEmail = await this.getCurrentUserEmail();
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
843
|
+
const graphClient = await this.getGraphClient();
|
|
844
|
+
// Create cache key from criteria
|
|
845
|
+
const cacheKey = JSON.stringify({ ...additionalCriteria, userEmail });
|
|
846
|
+
const cachedResults = this.getCachedResults(cacheKey);
|
|
847
|
+
if (cachedResults) {
|
|
848
|
+
return cachedResults;
|
|
849
|
+
}
|
|
850
|
+
try {
|
|
851
|
+
// Start with a basic query to get emails
|
|
852
|
+
const apiCall = graphClient.api('/me/messages')
|
|
853
|
+
.select('id,subject,from,toRecipients,ccRecipients,receivedDateTime,sentDateTime,bodyPreview,isRead,hasAttachments,importance,conversationId,parentFolderId,webLink');
|
|
854
|
+
// Add search query for user's email in TO or CC using proper syntax
|
|
855
|
+
apiCall.search(`"${userEmail}"`);
|
|
856
|
+
// Add simple filters that are supported by the API
|
|
857
|
+
if (additionalCriteria.after) {
|
|
858
|
+
apiCall.filter(`receivedDateTime ge ${new Date(additionalCriteria.after).toISOString()}`);
|
|
859
|
+
}
|
|
860
|
+
if (additionalCriteria.before) {
|
|
861
|
+
apiCall.filter(`receivedDateTime le ${new Date(additionalCriteria.before).toISOString()}`);
|
|
862
|
+
}
|
|
863
|
+
if (additionalCriteria.hasAttachment !== undefined) {
|
|
864
|
+
apiCall.filter(`hasAttachments eq ${additionalCriteria.hasAttachment}`);
|
|
865
|
+
}
|
|
866
|
+
if (additionalCriteria.isUnread !== undefined) {
|
|
867
|
+
apiCall.filter(`isRead eq ${!additionalCriteria.isUnread}`);
|
|
868
|
+
}
|
|
869
|
+
if (additionalCriteria.importance) {
|
|
870
|
+
apiCall.filter(`importance eq '${additionalCriteria.importance}'`);
|
|
871
|
+
}
|
|
872
|
+
// Set page size
|
|
873
|
+
const pageSize = Math.min(additionalCriteria.maxResults || 100, 100);
|
|
874
|
+
apiCall.top(pageSize);
|
|
875
|
+
const result = await apiCall.get();
|
|
876
|
+
const messages = result.value?.map((email) => ({
|
|
877
|
+
id: email.id,
|
|
878
|
+
subject: email.subject || '',
|
|
879
|
+
from: {
|
|
880
|
+
name: email.from?.emailAddress?.name || '',
|
|
881
|
+
address: email.from?.emailAddress?.address || ''
|
|
882
|
+
},
|
|
883
|
+
toRecipients: email.toRecipients?.map((recipient) => ({
|
|
884
|
+
name: recipient.emailAddress?.name || '',
|
|
885
|
+
address: recipient.emailAddress?.address || ''
|
|
886
|
+
})) || [],
|
|
887
|
+
ccRecipients: email.ccRecipients?.map((recipient) => ({
|
|
888
|
+
name: recipient.emailAddress?.name || '',
|
|
889
|
+
address: recipient.emailAddress?.address || ''
|
|
890
|
+
})) || [],
|
|
891
|
+
receivedDateTime: email.receivedDateTime,
|
|
892
|
+
sentDateTime: email.sentDateTime,
|
|
893
|
+
bodyPreview: email.bodyPreview || '',
|
|
894
|
+
isRead: email.isRead || false,
|
|
895
|
+
hasAttachments: email.hasAttachments || false,
|
|
896
|
+
importance: email.importance || 'normal',
|
|
897
|
+
conversationId: email.conversationId || '',
|
|
898
|
+
parentFolderId: email.parentFolderId || '',
|
|
899
|
+
webLink: email.webLink || '',
|
|
900
|
+
attachments: []
|
|
901
|
+
})) || [];
|
|
902
|
+
// Filter messages to only include those where the user is in TO or CC
|
|
903
|
+
const filteredMessages = messages.filter(message => {
|
|
904
|
+
const isInTo = message.toRecipients.some(recipient => recipient.address.toLowerCase() === userEmail.toLowerCase());
|
|
905
|
+
const isInCc = message.ccRecipients.some(recipient => recipient.address.toLowerCase() === userEmail.toLowerCase());
|
|
906
|
+
return isInTo || isInCc;
|
|
907
|
+
});
|
|
908
|
+
// Sort messages by receivedDateTime in descending order
|
|
909
|
+
filteredMessages.sort((a, b) => new Date(b.receivedDateTime).getTime() - new Date(a.receivedDateTime).getTime());
|
|
910
|
+
// For emails with attachments, get attachment counts
|
|
911
|
+
for (const message of filteredMessages) {
|
|
912
|
+
if (message.hasAttachments) {
|
|
913
|
+
try {
|
|
914
|
+
const attachments = await graphClient
|
|
915
|
+
.api(`/me/messages/${message.id}/attachments`)
|
|
916
|
+
.select('id')
|
|
917
|
+
.get();
|
|
918
|
+
message.attachments = new Array(attachments.value?.length || 0);
|
|
919
|
+
}
|
|
920
|
+
catch (error) {
|
|
921
|
+
logger.error(`Error getting attachment count for message ${message.id}:`, error);
|
|
922
|
+
message.attachments = [];
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
const searchResult = {
|
|
927
|
+
messages: filteredMessages,
|
|
928
|
+
hasMore: !!result['@odata.nextLink']
|
|
929
|
+
};
|
|
930
|
+
this.setCachedResults(cacheKey, searchResult);
|
|
931
|
+
return searchResult;
|
|
932
|
+
}
|
|
933
|
+
catch (error) {
|
|
934
|
+
logger.error('Error in email search:', error);
|
|
935
|
+
throw error;
|
|
936
|
+
}
|
|
840
937
|
}
|
|
841
938
|
catch (error) {
|
|
842
939
|
logger.error('Error searching emails addressed to me:', error);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ms365-mcp-server",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
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",
|