@softeria/ms-365-mcp-server 0.133.0 → 0.133.2
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/__tests__/graph-tools.test.js +51 -37
- package/dist/endpoints.json +6 -0
- package/dist/graph-client.js +61 -0
- package/dist/graph-tools.js +4 -24
- package/package.json +1 -1
- package/src/endpoints.json +6 -0
|
@@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import { tmpdir } from "os";
|
|
4
4
|
import { join } from "path";
|
|
5
|
-
import {
|
|
5
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
6
6
|
vi.mock("../logger.js", () => ({
|
|
7
7
|
default: {
|
|
8
8
|
info: vi.fn(),
|
|
@@ -755,16 +755,13 @@ describe("graph-tools", () => {
|
|
|
755
755
|
afterEach(() => {
|
|
756
756
|
rmSync(tmpDir, { recursive: true, force: true });
|
|
757
757
|
});
|
|
758
|
-
it("
|
|
758
|
+
it("streams bytes to the output path and returns metadata", async () => {
|
|
759
759
|
mockEndpoints.length = 0;
|
|
760
760
|
mockEndpointsJson = [];
|
|
761
761
|
const graphClient = {
|
|
762
|
-
|
|
763
|
-
message: "OK!",
|
|
762
|
+
downloadToFile: vi.fn().mockResolvedValue({
|
|
764
763
|
contentType: "image/jpeg",
|
|
765
|
-
|
|
766
|
-
contentLength: 2,
|
|
767
|
-
contentBytes: "aGk="
|
|
764
|
+
contentLength: 2
|
|
768
765
|
})
|
|
769
766
|
};
|
|
770
767
|
const server = createMockServer();
|
|
@@ -774,38 +771,19 @@ describe("graph-tools", () => {
|
|
|
774
771
|
expect(tool).toBeDefined();
|
|
775
772
|
const outputPath = join(tmpDir, "photo.jpg");
|
|
776
773
|
const result = await tool.handler({ target: "/me/photo/$value", outputPath });
|
|
777
|
-
expect(graphClient.
|
|
778
|
-
const [reqPath, options] = graphClient.
|
|
774
|
+
expect(graphClient.downloadToFile).toHaveBeenCalledTimes(1);
|
|
775
|
+
const [reqPath, dest, options] = graphClient.downloadToFile.mock.calls[0];
|
|
779
776
|
expect(reqPath).toBe("/me/photo/$value");
|
|
780
|
-
expect(
|
|
777
|
+
expect(dest).toBe(outputPath);
|
|
778
|
+
expect(options).toStrictEqual({ accessToken: void 0 });
|
|
781
779
|
expect(result.isError).toBeUndefined();
|
|
782
780
|
const payload = JSON.parse(result.content[0].text);
|
|
783
781
|
expect(payload).toEqual({ path: outputPath, contentType: "image/jpeg", bytesWritten: 2 });
|
|
784
|
-
expect(existsSync(outputPath)).toBe(true);
|
|
785
|
-
expect(readFileSync(outputPath).toString("utf8")).toBe("hi");
|
|
786
|
-
});
|
|
787
|
-
it("writes a text/JSON body verbatim from rawResponse", async () => {
|
|
788
|
-
mockEndpoints.length = 0;
|
|
789
|
-
mockEndpointsJson = [];
|
|
790
|
-
const graphClient = {
|
|
791
|
-
makeRequest: vi.fn().mockResolvedValue({
|
|
792
|
-
message: "OK!",
|
|
793
|
-
contentType: "text/plain",
|
|
794
|
-
rawResponse: "line1\nline2\n"
|
|
795
|
-
})
|
|
796
|
-
};
|
|
797
|
-
const server = createMockServer();
|
|
798
|
-
const { registerGraphTools } = await loadModule();
|
|
799
|
-
registerGraphTools(server, graphClient);
|
|
800
|
-
const outputPath = join(tmpDir, "note.txt");
|
|
801
|
-
const result = await server.tools.get("download-bytes-to-file").handler({ target: "/me/drive/root:/note.txt:/content", outputPath });
|
|
802
|
-
expect(result.isError).toBeUndefined();
|
|
803
|
-
expect(readFileSync(outputPath).toString("utf8")).toBe("line1\nline2\n");
|
|
804
782
|
});
|
|
805
783
|
it("rejects a relative outputPath", async () => {
|
|
806
784
|
mockEndpoints.length = 0;
|
|
807
785
|
mockEndpointsJson = [];
|
|
808
|
-
const graphClient = {
|
|
786
|
+
const graphClient = { downloadToFile: vi.fn() };
|
|
809
787
|
const server = createMockServer();
|
|
810
788
|
const { registerGraphTools } = await loadModule();
|
|
811
789
|
registerGraphTools(server, graphClient);
|
|
@@ -813,7 +791,7 @@ describe("graph-tools", () => {
|
|
|
813
791
|
expect(result.isError).toBe(true);
|
|
814
792
|
const payload = JSON.parse(result.content[0].text);
|
|
815
793
|
expect(payload.error).toMatch(/absolute path/);
|
|
816
|
-
expect(graphClient.
|
|
794
|
+
expect(graphClient.downloadToFile).not.toHaveBeenCalled();
|
|
817
795
|
});
|
|
818
796
|
it("rejects absolute URLs in target (Graph paths only)", async () => {
|
|
819
797
|
mockEndpoints.length = 0;
|
|
@@ -834,7 +812,7 @@ describe("graph-tools", () => {
|
|
|
834
812
|
mockEndpointsJson = [];
|
|
835
813
|
const outputPath = join(tmpDir, "existing.bin");
|
|
836
814
|
writeFileSync(outputPath, "original");
|
|
837
|
-
const graphClient = {
|
|
815
|
+
const graphClient = { downloadToFile: vi.fn() };
|
|
838
816
|
const server = createMockServer();
|
|
839
817
|
const { registerGraphTools } = await loadModule();
|
|
840
818
|
registerGraphTools(server, graphClient);
|
|
@@ -842,14 +820,14 @@ describe("graph-tools", () => {
|
|
|
842
820
|
expect(result.isError).toBe(true);
|
|
843
821
|
const payload = JSON.parse(result.content[0].text);
|
|
844
822
|
expect(payload.error).toMatch(/already exists/);
|
|
845
|
-
expect(graphClient.
|
|
823
|
+
expect(graphClient.downloadToFile).not.toHaveBeenCalled();
|
|
846
824
|
expect(readFileSync(outputPath).toString("utf8")).toBe("original");
|
|
847
825
|
});
|
|
848
|
-
it("surfaces a Graph error
|
|
826
|
+
it("surfaces a Graph error when downloadToFile throws", async () => {
|
|
849
827
|
mockEndpoints.length = 0;
|
|
850
828
|
mockEndpointsJson = [];
|
|
851
829
|
const graphClient = {
|
|
852
|
-
|
|
830
|
+
downloadToFile: vi.fn().mockRejectedValue(new Error("Microsoft Graph API error: 404 Not Found"))
|
|
853
831
|
};
|
|
854
832
|
const server = createMockServer();
|
|
855
833
|
const { registerGraphTools } = await loadModule();
|
|
@@ -859,7 +837,43 @@ describe("graph-tools", () => {
|
|
|
859
837
|
expect(result.isError).toBe(true);
|
|
860
838
|
const payload = JSON.parse(result.content[0].text);
|
|
861
839
|
expect(payload.error).toMatch(/404 Not Found/);
|
|
862
|
-
|
|
840
|
+
});
|
|
841
|
+
it("forwards the resolved account token to downloadToFile in multi-account mode", async () => {
|
|
842
|
+
mockEndpoints.length = 0;
|
|
843
|
+
mockEndpointsJson = [];
|
|
844
|
+
const graphClient = {
|
|
845
|
+
downloadToFile: vi.fn().mockResolvedValue({ contentType: "application/pdf", contentLength: 3 })
|
|
846
|
+
};
|
|
847
|
+
const authManager = {
|
|
848
|
+
isOAuthModeEnabled: vi.fn().mockReturnValue(false),
|
|
849
|
+
getToken: vi.fn().mockResolvedValue(null),
|
|
850
|
+
getTokenForAccount: vi.fn().mockResolvedValue("account-2-token")
|
|
851
|
+
};
|
|
852
|
+
const server = createMockServer();
|
|
853
|
+
const { registerGraphTools } = await loadModule();
|
|
854
|
+
registerGraphTools(
|
|
855
|
+
server,
|
|
856
|
+
graphClient,
|
|
857
|
+
false,
|
|
858
|
+
void 0,
|
|
859
|
+
false,
|
|
860
|
+
authManager,
|
|
861
|
+
true,
|
|
862
|
+
["user1@domain.com", "user2@domain.com"]
|
|
863
|
+
);
|
|
864
|
+
const outputPath = join(tmpDir, "invoice.pdf");
|
|
865
|
+
const result = await server.tools.get("download-bytes-to-file").handler({
|
|
866
|
+
target: "/me/messages/m1/attachments/a1/$value",
|
|
867
|
+
outputPath,
|
|
868
|
+
account: "user2@domain.com"
|
|
869
|
+
});
|
|
870
|
+
expect(result.isError).toBeUndefined();
|
|
871
|
+
expect(authManager.getTokenForAccount).toHaveBeenCalledWith("user2@domain.com");
|
|
872
|
+
expect(graphClient.downloadToFile).toHaveBeenCalledWith(
|
|
873
|
+
"/me/messages/m1/attachments/a1/$value",
|
|
874
|
+
outputPath,
|
|
875
|
+
{ accessToken: "account-2-token" }
|
|
876
|
+
);
|
|
863
877
|
});
|
|
864
878
|
it("is registered in stdio mode but hidden in HTTP mode", async () => {
|
|
865
879
|
mockEndpoints.length = 0;
|
package/dist/endpoints.json
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"pathPattern": "/me/messages",
|
|
11
11
|
"method": "get",
|
|
12
12
|
"toolName": "list-mail-messages",
|
|
13
|
+
"descriptionOverride": "List, search, and filter Outlook email messages in the signed-in user's mailbox across all folders. Returns message metadata (subject, from, receivedDateTime, isRead, hasAttachments) plus a body preview. Use $search for keyword queries, $filter to narrow by sender/read state/date, $top to limit page size, and $select to trim fields.",
|
|
13
14
|
"presets": ["mail", "outlook", "personal"],
|
|
14
15
|
"scopes": ["Mail.Read"],
|
|
15
16
|
"llmTip": "List read search my Outlook emails across folders. CRITICAL: When searching emails, the $search parameter value MUST be wrapped in double quotes. Format: $search=\"your search query here\". Use KQL (Keyword Query Language) syntax to search specific properties: 'from:', 'subject:', 'body:', 'to:', 'cc:', 'bcc:', 'attachment:', 'hasAttachments:', 'importance:', 'received:', 'sent:'. Examples: $search=\"from:john@example.com\" | $search=\"subject:meeting AND hasAttachments:true\" | $search=\"body:urgent AND received>=2024-01-01\" | $search=\"from:john AND importance:high\". Remember: ALWAYS wrap the entire search expression in double quotes! Reference: https://learn.microsoft.com/en-us/graph/search-query-parameter IMPORTANT: Always use $select to limit returned fields and reduce response size. Recommended default: $select=id,subject,from,toRecipients,receivedDateTime,bodyPreview,isRead,hasAttachments. Use bodyPreview instead of body for listings. To read the full email body, use get-mail-message with the specific message id."
|
|
@@ -72,6 +73,7 @@
|
|
|
72
73
|
"pathPattern": "/me/messages/{message-id}",
|
|
73
74
|
"method": "get",
|
|
74
75
|
"toolName": "get-mail-message",
|
|
76
|
+
"descriptionOverride": "Get a single Outlook email message by its message ID, including full subject, sender, recipients, body, and attachment flags. Use list-mail-messages first to obtain the message ID.",
|
|
75
77
|
"presets": ["mail", "outlook", "personal"],
|
|
76
78
|
"scopes": ["Mail.Read"]
|
|
77
79
|
},
|
|
@@ -157,6 +159,7 @@
|
|
|
157
159
|
"pathPattern": "/me/messages",
|
|
158
160
|
"method": "post",
|
|
159
161
|
"toolName": "create-draft-email",
|
|
162
|
+
"descriptionOverride": "Create a draft Outlook email message in the signed-in user's Drafts folder. Set subject, body, toRecipients, ccRecipients, and importance. The draft is saved, not sent — use send-mail to send a message directly, or send the draft afterwards.",
|
|
160
163
|
"presets": ["mail", "outlook", "personal"],
|
|
161
164
|
"scopes": ["Mail.ReadWrite"]
|
|
162
165
|
},
|
|
@@ -164,6 +167,7 @@
|
|
|
164
167
|
"pathPattern": "/me/messages/{message-id}",
|
|
165
168
|
"method": "delete",
|
|
166
169
|
"toolName": "delete-mail-message",
|
|
170
|
+
"descriptionOverride": "Delete an Outlook email message by its message ID. This is a soft delete that moves the message to Deleted Items.",
|
|
167
171
|
"presets": ["mail", "outlook", "personal"],
|
|
168
172
|
"scopes": ["Mail.ReadWrite"],
|
|
169
173
|
"llmTip": "Soft delete — moves to Deleted Items. To permanently delete, delete again from Deleted Items."
|
|
@@ -180,6 +184,7 @@
|
|
|
180
184
|
"pathPattern": "/me/messages/{message-id}",
|
|
181
185
|
"method": "patch",
|
|
182
186
|
"toolName": "update-mail-message",
|
|
187
|
+
"descriptionOverride": "Update an existing Outlook email message by its message ID — for example mark it read or unread (isRead), flag it (flag), change its categories, importance, or edit a draft's subject, body, or recipients.",
|
|
183
188
|
"presets": ["mail", "outlook", "personal"],
|
|
184
189
|
"scopes": ["Mail.ReadWrite"]
|
|
185
190
|
},
|
|
@@ -820,6 +825,7 @@
|
|
|
820
825
|
"pathPattern": "/me/messages/{message-id}/$value",
|
|
821
826
|
"method": "get",
|
|
822
827
|
"toolName": "get-mail-message-mime",
|
|
828
|
+
"descriptionOverride": "Download the raw MIME source (RFC 822 .eml content) of an Outlook email message by its message ID. Returns the complete original message including headers and encoded attachments.",
|
|
823
829
|
"presets": ["mail", "outlook", "personal"],
|
|
824
830
|
"scopes": ["Mail.Read"],
|
|
825
831
|
"acceptType": "text/plain",
|
package/dist/graph-client.js
CHANGED
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
getSharedBreaker,
|
|
8
8
|
loadResilienceConfig
|
|
9
9
|
} from "./lib/graph-resilience.js";
|
|
10
|
+
import { open, stat, unlink } from "fs/promises";
|
|
11
|
+
import { pipeline } from "stream/promises";
|
|
10
12
|
function isBinaryContentType(contentType) {
|
|
11
13
|
if (!contentType) return false;
|
|
12
14
|
const lower = contentType.toLowerCase().split(";")[0].trim();
|
|
@@ -100,6 +102,65 @@ class GraphClient {
|
|
|
100
102
|
throw error;
|
|
101
103
|
}
|
|
102
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Stream Graph byte content straight to a file, without holding the whole
|
|
107
|
+
* payload in memory. download-bytes-to-file uses this for big mail attachments
|
|
108
|
+
* and meeting recordings, where makeRequest's base64 buffering would blow up
|
|
109
|
+
* memory or hit V8's max string length. Creates the file with wx + 0o600 (never
|
|
110
|
+
* overwrites) and removes a partial file if the transfer fails.
|
|
111
|
+
*/
|
|
112
|
+
async downloadToFile(endpoint, destinationPath, options = {}) {
|
|
113
|
+
const fileHandle = await open(destinationPath, "wx", 384);
|
|
114
|
+
let completed = false;
|
|
115
|
+
try {
|
|
116
|
+
const contextTokens = getRequestTokens();
|
|
117
|
+
const accessToken = options.accessToken ?? contextTokens?.accessToken ?? await this.authManager.getToken();
|
|
118
|
+
if (!accessToken) {
|
|
119
|
+
throw new Error("No access token available");
|
|
120
|
+
}
|
|
121
|
+
const response = await this.performRequest(endpoint, accessToken, options);
|
|
122
|
+
if (response.status === 403) {
|
|
123
|
+
const errorText = await response.text();
|
|
124
|
+
if (errorText.includes("scope") || errorText.includes("permission")) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
`Microsoft Graph API scope error: ${response.status} ${response.statusText} - ${errorText}. This tool requires organization mode. Please restart with --org-mode flag.`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
throw new Error(
|
|
130
|
+
`Microsoft Graph API error: ${response.status} ${response.statusText} - ${errorText}`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
if (!response.ok) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
`Microsoft Graph API error: ${response.status} ${response.statusText} - ${await response.text()}`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
if (!response.body) {
|
|
139
|
+
throw new Error("Microsoft Graph returned an empty response body");
|
|
140
|
+
}
|
|
141
|
+
await pipeline(response.body, fileHandle.createWriteStream());
|
|
142
|
+
completed = true;
|
|
143
|
+
let contentLength;
|
|
144
|
+
try {
|
|
145
|
+
contentLength = (await stat(destinationPath)).size;
|
|
146
|
+
} catch {
|
|
147
|
+
const header = Number(response.headers.get("content-length"));
|
|
148
|
+
contentLength = Number.isFinite(header) ? header : 0;
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
contentType: response.headers.get("content-type") || "application/octet-stream",
|
|
152
|
+
contentLength
|
|
153
|
+
};
|
|
154
|
+
} catch (error) {
|
|
155
|
+
logger.error("Microsoft Graph file download failed:", error);
|
|
156
|
+
throw error;
|
|
157
|
+
} finally {
|
|
158
|
+
await fileHandle.close().catch(() => void 0);
|
|
159
|
+
if (!completed) {
|
|
160
|
+
await unlink(destinationPath).catch(() => void 0);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
103
164
|
async performRequest(endpoint, accessToken, options) {
|
|
104
165
|
const cloudEndpoints = getCloudEndpoints(this.secrets.cloudType);
|
|
105
166
|
const apiVersion = options.apiVersion || "v1.0";
|
package/dist/graph-tools.js
CHANGED
|
@@ -12,7 +12,7 @@ import { api as betaApi } from "./generated/client-beta.js";
|
|
|
12
12
|
const allEndpoints = [...api.endpoints, ...betaApi.endpoints];
|
|
13
13
|
import { z } from "zod";
|
|
14
14
|
import { readFileSync } from "fs";
|
|
15
|
-
import {
|
|
15
|
+
import { access } from "fs/promises";
|
|
16
16
|
import path from "path";
|
|
17
17
|
import { fileURLToPath } from "url";
|
|
18
18
|
import { TOOL_CATEGORIES } from "./tool-categories.js";
|
|
@@ -299,29 +299,9 @@ const UTILITY_TOOLS = [
|
|
|
299
299
|
if (authManager && !authManager.isOAuthModeEnabled() && !getRequestTokens()) {
|
|
300
300
|
accountAccessToken = await authManager.getTokenForAccount(accountParam);
|
|
301
301
|
}
|
|
302
|
-
const result = await graphClient.
|
|
303
|
-
accessToken: accountAccessToken
|
|
304
|
-
rawResponse: true
|
|
302
|
+
const result = await graphClient.downloadToFile(target, outputPath, {
|
|
303
|
+
accessToken: accountAccessToken
|
|
305
304
|
});
|
|
306
|
-
let buffer;
|
|
307
|
-
if (result.encoding === "base64" && typeof result.contentBytes === "string") {
|
|
308
|
-
buffer = Buffer.from(result.contentBytes, "base64");
|
|
309
|
-
} else if (typeof result.rawResponse === "string") {
|
|
310
|
-
buffer = Buffer.from(result.rawResponse, "utf8");
|
|
311
|
-
} else {
|
|
312
|
-
return {
|
|
313
|
-
content: [
|
|
314
|
-
{
|
|
315
|
-
type: "text",
|
|
316
|
-
text: JSON.stringify({
|
|
317
|
-
error: "Graph response contained no downloadable bytes for this target."
|
|
318
|
-
})
|
|
319
|
-
}
|
|
320
|
-
],
|
|
321
|
-
isError: true
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
await writeFile(outputPath, buffer, { flag: "wx" });
|
|
325
305
|
return {
|
|
326
306
|
content: [
|
|
327
307
|
{
|
|
@@ -329,7 +309,7 @@ const UTILITY_TOOLS = [
|
|
|
329
309
|
text: JSON.stringify({
|
|
330
310
|
path: outputPath,
|
|
331
311
|
contentType: result.contentType,
|
|
332
|
-
bytesWritten:
|
|
312
|
+
bytesWritten: result.contentLength
|
|
333
313
|
})
|
|
334
314
|
}
|
|
335
315
|
]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softeria/ms-365-mcp-server",
|
|
3
|
-
"version": "0.133.
|
|
3
|
+
"version": "0.133.2",
|
|
4
4
|
"description": " A Model Context Protocol (MCP) server for interacting with Microsoft 365 and Office services through the Graph API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
package/src/endpoints.json
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"pathPattern": "/me/messages",
|
|
11
11
|
"method": "get",
|
|
12
12
|
"toolName": "list-mail-messages",
|
|
13
|
+
"descriptionOverride": "List, search, and filter Outlook email messages in the signed-in user's mailbox across all folders. Returns message metadata (subject, from, receivedDateTime, isRead, hasAttachments) plus a body preview. Use $search for keyword queries, $filter to narrow by sender/read state/date, $top to limit page size, and $select to trim fields.",
|
|
13
14
|
"presets": ["mail", "outlook", "personal"],
|
|
14
15
|
"scopes": ["Mail.Read"],
|
|
15
16
|
"llmTip": "List read search my Outlook emails across folders. CRITICAL: When searching emails, the $search parameter value MUST be wrapped in double quotes. Format: $search=\"your search query here\". Use KQL (Keyword Query Language) syntax to search specific properties: 'from:', 'subject:', 'body:', 'to:', 'cc:', 'bcc:', 'attachment:', 'hasAttachments:', 'importance:', 'received:', 'sent:'. Examples: $search=\"from:john@example.com\" | $search=\"subject:meeting AND hasAttachments:true\" | $search=\"body:urgent AND received>=2024-01-01\" | $search=\"from:john AND importance:high\". Remember: ALWAYS wrap the entire search expression in double quotes! Reference: https://learn.microsoft.com/en-us/graph/search-query-parameter IMPORTANT: Always use $select to limit returned fields and reduce response size. Recommended default: $select=id,subject,from,toRecipients,receivedDateTime,bodyPreview,isRead,hasAttachments. Use bodyPreview instead of body for listings. To read the full email body, use get-mail-message with the specific message id."
|
|
@@ -72,6 +73,7 @@
|
|
|
72
73
|
"pathPattern": "/me/messages/{message-id}",
|
|
73
74
|
"method": "get",
|
|
74
75
|
"toolName": "get-mail-message",
|
|
76
|
+
"descriptionOverride": "Get a single Outlook email message by its message ID, including full subject, sender, recipients, body, and attachment flags. Use list-mail-messages first to obtain the message ID.",
|
|
75
77
|
"presets": ["mail", "outlook", "personal"],
|
|
76
78
|
"scopes": ["Mail.Read"]
|
|
77
79
|
},
|
|
@@ -157,6 +159,7 @@
|
|
|
157
159
|
"pathPattern": "/me/messages",
|
|
158
160
|
"method": "post",
|
|
159
161
|
"toolName": "create-draft-email",
|
|
162
|
+
"descriptionOverride": "Create a draft Outlook email message in the signed-in user's Drafts folder. Set subject, body, toRecipients, ccRecipients, and importance. The draft is saved, not sent — use send-mail to send a message directly, or send the draft afterwards.",
|
|
160
163
|
"presets": ["mail", "outlook", "personal"],
|
|
161
164
|
"scopes": ["Mail.ReadWrite"]
|
|
162
165
|
},
|
|
@@ -164,6 +167,7 @@
|
|
|
164
167
|
"pathPattern": "/me/messages/{message-id}",
|
|
165
168
|
"method": "delete",
|
|
166
169
|
"toolName": "delete-mail-message",
|
|
170
|
+
"descriptionOverride": "Delete an Outlook email message by its message ID. This is a soft delete that moves the message to Deleted Items.",
|
|
167
171
|
"presets": ["mail", "outlook", "personal"],
|
|
168
172
|
"scopes": ["Mail.ReadWrite"],
|
|
169
173
|
"llmTip": "Soft delete — moves to Deleted Items. To permanently delete, delete again from Deleted Items."
|
|
@@ -180,6 +184,7 @@
|
|
|
180
184
|
"pathPattern": "/me/messages/{message-id}",
|
|
181
185
|
"method": "patch",
|
|
182
186
|
"toolName": "update-mail-message",
|
|
187
|
+
"descriptionOverride": "Update an existing Outlook email message by its message ID — for example mark it read or unread (isRead), flag it (flag), change its categories, importance, or edit a draft's subject, body, or recipients.",
|
|
183
188
|
"presets": ["mail", "outlook", "personal"],
|
|
184
189
|
"scopes": ["Mail.ReadWrite"]
|
|
185
190
|
},
|
|
@@ -820,6 +825,7 @@
|
|
|
820
825
|
"pathPattern": "/me/messages/{message-id}/$value",
|
|
821
826
|
"method": "get",
|
|
822
827
|
"toolName": "get-mail-message-mime",
|
|
828
|
+
"descriptionOverride": "Download the raw MIME source (RFC 822 .eml content) of an Outlook email message by its message ID. Returns the complete original message including headers and encoded attachments.",
|
|
823
829
|
"presets": ["mail", "outlook", "personal"],
|
|
824
830
|
"scopes": ["Mail.Read"],
|
|
825
831
|
"acceptType": "text/plain",
|