ms365-mcp-server 1.1.3 ā 1.1.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/README.md +40 -0
- package/dist/index.js +182 -21
- package/dist/utils/ms365-operations.js +94 -27
- 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.5"
|
|
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).replace(/[^a-zA-Z0-9-_]/g, '_');
|
|
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) {
|
|
@@ -810,33 +810,100 @@ export class MS365Operations {
|
|
|
810
810
|
async searchEmailsToMe(additionalCriteria = {}) {
|
|
811
811
|
try {
|
|
812
812
|
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
|
-
|
|
813
|
+
const graphClient = await this.getGraphClient();
|
|
814
|
+
// Create cache key from criteria
|
|
815
|
+
const cacheKey = JSON.stringify({ ...additionalCriteria, userEmail });
|
|
816
|
+
const cachedResults = this.getCachedResults(cacheKey);
|
|
817
|
+
if (cachedResults) {
|
|
818
|
+
return cachedResults;
|
|
819
|
+
}
|
|
820
|
+
try {
|
|
821
|
+
// Start with a basic query to get emails
|
|
822
|
+
const apiCall = graphClient.api('/me/messages')
|
|
823
|
+
.select('id,subject,from,toRecipients,ccRecipients,receivedDateTime,sentDateTime,bodyPreview,isRead,hasAttachments,importance,conversationId,parentFolderId,webLink');
|
|
824
|
+
// Add search query for user's email in TO or CC using proper syntax
|
|
825
|
+
apiCall.search(`"${userEmail}"`);
|
|
826
|
+
// Add simple filters that are supported by the API
|
|
827
|
+
if (additionalCriteria.after) {
|
|
828
|
+
apiCall.filter(`receivedDateTime ge ${new Date(additionalCriteria.after).toISOString()}`);
|
|
829
|
+
}
|
|
830
|
+
if (additionalCriteria.before) {
|
|
831
|
+
apiCall.filter(`receivedDateTime le ${new Date(additionalCriteria.before).toISOString()}`);
|
|
832
|
+
}
|
|
833
|
+
if (additionalCriteria.hasAttachment !== undefined) {
|
|
834
|
+
apiCall.filter(`hasAttachments eq ${additionalCriteria.hasAttachment}`);
|
|
835
|
+
}
|
|
836
|
+
if (additionalCriteria.isUnread !== undefined) {
|
|
837
|
+
apiCall.filter(`isRead eq ${!additionalCriteria.isUnread}`);
|
|
838
|
+
}
|
|
839
|
+
if (additionalCriteria.importance) {
|
|
840
|
+
apiCall.filter(`importance eq '${additionalCriteria.importance}'`);
|
|
841
|
+
}
|
|
842
|
+
// Set page size
|
|
843
|
+
const pageSize = Math.min(additionalCriteria.maxResults || 100, 100);
|
|
844
|
+
apiCall.top(pageSize);
|
|
845
|
+
const result = await apiCall.get();
|
|
846
|
+
const messages = result.value?.map((email) => ({
|
|
847
|
+
id: email.id,
|
|
848
|
+
subject: email.subject || '',
|
|
849
|
+
from: {
|
|
850
|
+
name: email.from?.emailAddress?.name || '',
|
|
851
|
+
address: email.from?.emailAddress?.address || ''
|
|
852
|
+
},
|
|
853
|
+
toRecipients: email.toRecipients?.map((recipient) => ({
|
|
854
|
+
name: recipient.emailAddress?.name || '',
|
|
855
|
+
address: recipient.emailAddress?.address || ''
|
|
856
|
+
})) || [],
|
|
857
|
+
ccRecipients: email.ccRecipients?.map((recipient) => ({
|
|
858
|
+
name: recipient.emailAddress?.name || '',
|
|
859
|
+
address: recipient.emailAddress?.address || ''
|
|
860
|
+
})) || [],
|
|
861
|
+
receivedDateTime: email.receivedDateTime,
|
|
862
|
+
sentDateTime: email.sentDateTime,
|
|
863
|
+
bodyPreview: email.bodyPreview || '',
|
|
864
|
+
isRead: email.isRead || false,
|
|
865
|
+
hasAttachments: email.hasAttachments || false,
|
|
866
|
+
importance: email.importance || 'normal',
|
|
867
|
+
conversationId: email.conversationId || '',
|
|
868
|
+
parentFolderId: email.parentFolderId || '',
|
|
869
|
+
webLink: email.webLink || '',
|
|
870
|
+
attachments: []
|
|
871
|
+
})) || [];
|
|
872
|
+
// Filter messages to only include those where the user is in TO or CC
|
|
873
|
+
const filteredMessages = messages.filter(message => {
|
|
874
|
+
const isInTo = message.toRecipients.some(recipient => recipient.address.toLowerCase() === userEmail.toLowerCase());
|
|
875
|
+
const isInCc = message.ccRecipients.some(recipient => recipient.address.toLowerCase() === userEmail.toLowerCase());
|
|
876
|
+
return isInTo || isInCc;
|
|
877
|
+
});
|
|
878
|
+
// Sort messages by receivedDateTime in descending order
|
|
879
|
+
filteredMessages.sort((a, b) => new Date(b.receivedDateTime).getTime() - new Date(a.receivedDateTime).getTime());
|
|
880
|
+
// For emails with attachments, get attachment counts
|
|
881
|
+
for (const message of filteredMessages) {
|
|
882
|
+
if (message.hasAttachments) {
|
|
883
|
+
try {
|
|
884
|
+
const attachments = await graphClient
|
|
885
|
+
.api(`/me/messages/${message.id}/attachments`)
|
|
886
|
+
.select('id')
|
|
887
|
+
.get();
|
|
888
|
+
message.attachments = new Array(attachments.value?.length || 0);
|
|
889
|
+
}
|
|
890
|
+
catch (error) {
|
|
891
|
+
logger.error(`Error getting attachment count for message ${message.id}:`, error);
|
|
892
|
+
message.attachments = [];
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
const searchResult = {
|
|
897
|
+
messages: filteredMessages,
|
|
898
|
+
hasMore: !!result['@odata.nextLink']
|
|
899
|
+
};
|
|
900
|
+
this.setCachedResults(cacheKey, searchResult);
|
|
901
|
+
return searchResult;
|
|
902
|
+
}
|
|
903
|
+
catch (error) {
|
|
904
|
+
logger.error('Error in email search:', error);
|
|
905
|
+
throw error;
|
|
906
|
+
}
|
|
840
907
|
}
|
|
841
908
|
catch (error) {
|
|
842
909
|
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.5",
|
|
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
|
},
|