@softeria/ms-365-mcp-server 0.106.2 → 0.107.1
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/endpoints.json +21 -0
- package/dist/generated/client.js +60 -0
- package/dist/lib/redirect-uri-validation.js +33 -0
- package/dist/server.js +15 -0
- package/docs/deployment.md +22 -0
- package/package.json +1 -1
- package/src/endpoints.json +21 -0
package/dist/endpoints.json
CHANGED
|
@@ -106,6 +106,27 @@
|
|
|
106
106
|
"toolName": "create-shared-mailbox-draft",
|
|
107
107
|
"workScopes": ["Mail.ReadWrite.Shared"]
|
|
108
108
|
},
|
|
109
|
+
{
|
|
110
|
+
"pathPattern": "/users/{user-id}/messages/{message-id}/reply",
|
|
111
|
+
"method": "post",
|
|
112
|
+
"toolName": "reply-shared-mailbox-mail",
|
|
113
|
+
"workScopes": ["Mail.Send.Shared"],
|
|
114
|
+
"llmTip": "Reply to a message from a shared mailbox preserving full HTML formatting and conversation thread. The 'user-id' is the shared mailbox email address (e.g. support@contoso.com). The 'comment' field is your reply text. Do NOT reconstruct the email manually. Requires Send As permission on the shared mailbox in Exchange."
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"pathPattern": "/users/{user-id}/messages/{message-id}/replyAll",
|
|
118
|
+
"method": "post",
|
|
119
|
+
"toolName": "reply-all-shared-mailbox-mail",
|
|
120
|
+
"workScopes": ["Mail.Send.Shared"],
|
|
121
|
+
"llmTip": "Reply-all to a message from a shared mailbox preserving full HTML formatting and conversation thread. The 'user-id' is the shared mailbox email address. The 'comment' field is your reply text. Requires Send As permission on the shared mailbox in Exchange."
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"pathPattern": "/users/{user-id}/messages/{message-id}/forward",
|
|
125
|
+
"method": "post",
|
|
126
|
+
"toolName": "forward-shared-mailbox-mail",
|
|
127
|
+
"workScopes": ["Mail.Send.Shared"],
|
|
128
|
+
"llmTip": "Forward a message from a shared mailbox preserving full HTML formatting and attachments. The 'user-id' is the shared mailbox email address. 'toRecipients' is required. The 'comment' field adds text above the forwarded content. Requires Send As permission on the shared mailbox in Exchange."
|
|
129
|
+
},
|
|
109
130
|
{
|
|
110
131
|
"pathPattern": "/users",
|
|
111
132
|
"method": "get",
|
package/dist/generated/client.js
CHANGED
|
@@ -14465,6 +14465,66 @@ To monitor future changes, call the delta API by using the @odata.deltaLink in t
|
|
|
14465
14465
|
],
|
|
14466
14466
|
response: z.void()
|
|
14467
14467
|
},
|
|
14468
|
+
{
|
|
14469
|
+
method: "post",
|
|
14470
|
+
path: "/users/:userId/messages/:messageId/forward",
|
|
14471
|
+
alias: "forward-shared-mailbox-mail",
|
|
14472
|
+
description: `Forward a message using either JSON or MIME format. When using JSON format, you can:
|
|
14473
|
+
- Specify either a comment or the body property of the message parameter. Specifying both will return an HTTP 400 Bad Request error.
|
|
14474
|
+
- Specify either the toRecipients parameter or the toRecipients property of the message parameter. Specifying both or specifying neither will return an HTTP 400 Bad Request error. When using MIME format:
|
|
14475
|
+
- Provide the applicable Internet message headers and the MIME content, all encoded in base64 format in the request body.
|
|
14476
|
+
- Add any attachments and S/MIME properties to the MIME content. This method saves the message in the Sent Items folder. Alternatively, create a draft to forward a message, and send it later.`,
|
|
14477
|
+
requestFormat: "json",
|
|
14478
|
+
parameters: [
|
|
14479
|
+
{
|
|
14480
|
+
name: "body",
|
|
14481
|
+
description: `Action parameters`,
|
|
14482
|
+
type: "Body",
|
|
14483
|
+
schema: create_forward_draft_Body
|
|
14484
|
+
}
|
|
14485
|
+
],
|
|
14486
|
+
response: z.void()
|
|
14487
|
+
},
|
|
14488
|
+
{
|
|
14489
|
+
method: "post",
|
|
14490
|
+
path: "/users/:userId/messages/:messageId/reply",
|
|
14491
|
+
alias: "reply-shared-mailbox-mail",
|
|
14492
|
+
description: `Reply to the sender of a message using either JSON or MIME format. When using JSON format:
|
|
14493
|
+
* Specify either a comment or the body property of the message parameter. Specifying both will return an HTTP 400 Bad Request error.
|
|
14494
|
+
* If the original message specifies a recipient in the replyTo property, per Internet Message Format (RFC 2822), send the reply to the recipients in replyTo and not the recipient in the from property. When using MIME format:
|
|
14495
|
+
- Provide the applicable Internet message headers and the MIME content, all encoded in base64 format in the request body.
|
|
14496
|
+
- Add any attachments and S/MIME properties to the MIME content. This method saves the message in the Sent Items folder. Alternatively, create a draft to reply to an existing message and send it later.`,
|
|
14497
|
+
requestFormat: "json",
|
|
14498
|
+
parameters: [
|
|
14499
|
+
{
|
|
14500
|
+
name: "body",
|
|
14501
|
+
description: `Action parameters`,
|
|
14502
|
+
type: "Body",
|
|
14503
|
+
schema: create_reply_draft_Body
|
|
14504
|
+
}
|
|
14505
|
+
],
|
|
14506
|
+
response: z.void()
|
|
14507
|
+
},
|
|
14508
|
+
{
|
|
14509
|
+
method: "post",
|
|
14510
|
+
path: "/users/:userId/messages/:messageId/replyAll",
|
|
14511
|
+
alias: "reply-all-shared-mailbox-mail",
|
|
14512
|
+
description: `Reply to all recipients of a message using either JSON or MIME format. When using JSON format:
|
|
14513
|
+
- Specify either a comment or the body property of the message parameter. Specifying both will return an HTTP 400 Bad Request error.
|
|
14514
|
+
- If the original message specifies a recipient in the replyTo property, per Internet Message Format (RFC 2822), send the reply to the recipients in replyTo and not the recipient in the from property. When using MIME format:
|
|
14515
|
+
- Provide the applicable Internet message headers and the MIME content, all encoded in base64 format in the request body.
|
|
14516
|
+
- Add any attachments and S/MIME properties to the MIME content. This method saves the message in the Sent Items folder. Alternatively, create a draft to reply-all to a message and send it later.`,
|
|
14517
|
+
requestFormat: "json",
|
|
14518
|
+
parameters: [
|
|
14519
|
+
{
|
|
14520
|
+
name: "body",
|
|
14521
|
+
description: `Action parameters`,
|
|
14522
|
+
type: "Body",
|
|
14523
|
+
schema: create_reply_draft_Body
|
|
14524
|
+
}
|
|
14525
|
+
],
|
|
14526
|
+
response: z.void()
|
|
14527
|
+
},
|
|
14468
14528
|
{
|
|
14469
14529
|
method: "get",
|
|
14470
14530
|
path: "/users/:userId/presence",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]", "::1"]);
|
|
2
|
+
function isLoopbackHost(host) {
|
|
3
|
+
return LOOPBACK_HOSTS.has(host.toLowerCase());
|
|
4
|
+
}
|
|
5
|
+
function parseAllowlist(raw) {
|
|
6
|
+
if (!raw) return null;
|
|
7
|
+
const list = raw.split(",").map((s) => s.trim()).filter(Boolean);
|
|
8
|
+
return list.length > 0 ? list : null;
|
|
9
|
+
}
|
|
10
|
+
function isAllowedRedirectUri(value, allowlist) {
|
|
11
|
+
if (typeof value !== "string" || value.length === 0) return false;
|
|
12
|
+
let url;
|
|
13
|
+
try {
|
|
14
|
+
url = new URL(value);
|
|
15
|
+
} catch {
|
|
16
|
+
return false;
|
|
17
|
+
}
|
|
18
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
if (allowlist && allowlist.length > 0) {
|
|
22
|
+
return allowlist.includes(value);
|
|
23
|
+
}
|
|
24
|
+
if (url.protocol === "http:") {
|
|
25
|
+
return isLoopbackHost(url.hostname);
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
export {
|
|
30
|
+
isAllowedRedirectUri,
|
|
31
|
+
isLoopbackHost,
|
|
32
|
+
parseAllowlist
|
|
33
|
+
};
|
package/dist/server.js
CHANGED
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
microsoftBearerTokenAuthMiddleware,
|
|
16
16
|
refreshAccessToken
|
|
17
17
|
} from "./lib/microsoft-auth.js";
|
|
18
|
+
import { isAllowedRedirectUri, parseAllowlist } from "./lib/redirect-uri-validation.js";
|
|
18
19
|
import { getSecrets } from "./secrets.js";
|
|
19
20
|
import { getCloudEndpoints } from "./cloud-config.js";
|
|
20
21
|
import { requestContext } from "./request-context.js";
|
|
@@ -226,6 +227,20 @@ class MicrosoftGraphServer {
|
|
|
226
227
|
const clientCodeChallenge = url.searchParams.get("code_challenge");
|
|
227
228
|
const clientCodeChallengeMethod = url.searchParams.get("code_challenge_method");
|
|
228
229
|
const state = url.searchParams.get("state");
|
|
230
|
+
const redirectUriParam = url.searchParams.get("redirect_uri");
|
|
231
|
+
if (redirectUriParam) {
|
|
232
|
+
const allowlist = parseAllowlist(process.env.MS365_MCP_ALLOWED_REDIRECT_URIS);
|
|
233
|
+
if (!isAllowedRedirectUri(redirectUriParam, allowlist)) {
|
|
234
|
+
logger.warn("Rejected /authorize request with disallowed redirect_uri", {
|
|
235
|
+
redirect_uri: redirectUriParam
|
|
236
|
+
});
|
|
237
|
+
res.status(400).json({
|
|
238
|
+
error: "invalid_request",
|
|
239
|
+
error_description: "redirect_uri is not allowed"
|
|
240
|
+
});
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
229
244
|
const allowedParams = [
|
|
230
245
|
"response_type",
|
|
231
246
|
"redirect_uri",
|
package/docs/deployment.md
CHANGED
|
@@ -130,6 +130,28 @@ When deploying for an organization, create a dedicated app registration instead
|
|
|
130
130
|
|
|
131
131
|
5. **Store credentials** in Key Vault (see [Azure Key Vault Integration](../README.md#azure-key-vault-integration))
|
|
132
132
|
|
|
133
|
+
## Redirect URI Validation
|
|
134
|
+
|
|
135
|
+
The /authorize endpoint defensively validates client-supplied `redirect_uri` values before forwarding them to Microsoft Entra (CWE-601, Open Redirect). Microsoft Entra also validates the URI against your app registration, but this server-side check rejects obviously dangerous schemes (`javascript:`, `data:`, `file:`, …) and arbitrary remote `http://` origins before the request leaves the server.
|
|
136
|
+
|
|
137
|
+
Default behaviour (no explicit allowlist):
|
|
138
|
+
|
|
139
|
+
- Only `http:` and `https:` schemes are accepted.
|
|
140
|
+
- `http:` is only allowed for loopback hosts (`localhost`, `127.0.0.1`, `::1`).
|
|
141
|
+
- All other `https://` origins are accepted (Entra still has the final say).
|
|
142
|
+
|
|
143
|
+
For production deployments, configure an explicit allowlist via the `MS365_MCP_ALLOWED_REDIRECT_URIS` environment variable. It takes a comma-separated list of exact URIs; only exact string matches pass validation:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# Single redirect URI
|
|
147
|
+
MS365_MCP_ALLOWED_REDIRECT_URIS=https://mcp.example.com/auth/callback
|
|
148
|
+
|
|
149
|
+
# Multiple URIs (comma-separated, no spaces required)
|
|
150
|
+
MS365_MCP_ALLOWED_REDIRECT_URIS=https://mcp.example.com/auth/callback,https://staging.example.com/auth/callback
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The list should mirror the redirect URIs registered on your Azure AD app registration. Leaving the variable unset falls back to the default behaviour above, which is appropriate for local development but not recommended for shared/production deployments.
|
|
154
|
+
|
|
133
155
|
## Reverse Proxy / Custom Domain
|
|
134
156
|
|
|
135
157
|
When running behind a reverse proxy, set `MS365_MCP_PUBLIC_URL` so that the OAuth authorize URL handed back to the user's browser is resolvable from outside the server's network:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softeria/ms-365-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.107.1",
|
|
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
|
@@ -106,6 +106,27 @@
|
|
|
106
106
|
"toolName": "create-shared-mailbox-draft",
|
|
107
107
|
"workScopes": ["Mail.ReadWrite.Shared"]
|
|
108
108
|
},
|
|
109
|
+
{
|
|
110
|
+
"pathPattern": "/users/{user-id}/messages/{message-id}/reply",
|
|
111
|
+
"method": "post",
|
|
112
|
+
"toolName": "reply-shared-mailbox-mail",
|
|
113
|
+
"workScopes": ["Mail.Send.Shared"],
|
|
114
|
+
"llmTip": "Reply to a message from a shared mailbox preserving full HTML formatting and conversation thread. The 'user-id' is the shared mailbox email address (e.g. support@contoso.com). The 'comment' field is your reply text. Do NOT reconstruct the email manually. Requires Send As permission on the shared mailbox in Exchange."
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"pathPattern": "/users/{user-id}/messages/{message-id}/replyAll",
|
|
118
|
+
"method": "post",
|
|
119
|
+
"toolName": "reply-all-shared-mailbox-mail",
|
|
120
|
+
"workScopes": ["Mail.Send.Shared"],
|
|
121
|
+
"llmTip": "Reply-all to a message from a shared mailbox preserving full HTML formatting and conversation thread. The 'user-id' is the shared mailbox email address. The 'comment' field is your reply text. Requires Send As permission on the shared mailbox in Exchange."
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"pathPattern": "/users/{user-id}/messages/{message-id}/forward",
|
|
125
|
+
"method": "post",
|
|
126
|
+
"toolName": "forward-shared-mailbox-mail",
|
|
127
|
+
"workScopes": ["Mail.Send.Shared"],
|
|
128
|
+
"llmTip": "Forward a message from a shared mailbox preserving full HTML formatting and attachments. The 'user-id' is the shared mailbox email address. 'toRecipients' is required. The 'comment' field adds text above the forwarded content. Requires Send As permission on the shared mailbox in Exchange."
|
|
129
|
+
},
|
|
109
130
|
{
|
|
110
131
|
"pathPattern": "/users",
|
|
111
132
|
"method": "get",
|