@softeria/ms-365-mcp-server 0.114.2 → 0.114.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/Dockerfile +1 -1
- package/README.md +13 -1
- package/dist/auth-tools.js +3 -1
- package/dist/auth.js +5 -0
- package/dist/endpoints.json +296 -0
- package/dist/graph-tools.js +24 -1
- package/dist/server.js +17 -10
- package/dist/tool-categories.js +36 -23
- package/docs/deployment.md +5 -1
- package/package.json +1 -1
- package/src/endpoints.json +296 -0
package/dist/graph-tools.js
CHANGED
|
@@ -46,6 +46,15 @@ function formatDisabledToolsForLog(disabledTools) {
|
|
|
46
46
|
const suffix = disabledTools.length > shown.length ? `, ... +${disabledTools.length - shown.length} more` : "";
|
|
47
47
|
return `${shown.join("; ")}${suffix}`;
|
|
48
48
|
}
|
|
49
|
+
async function checkAccountParamInBearerMode(accountParam, authManager) {
|
|
50
|
+
if (!accountParam || !authManager) return null;
|
|
51
|
+
const contextToken = getRequestTokens()?.accessToken;
|
|
52
|
+
if (!contextToken && !authManager.isOAuthModeEnabled()) return null;
|
|
53
|
+
const bearerToken = contextToken ?? await authManager.getToken().catch(() => null) ?? void 0;
|
|
54
|
+
const bearerIdentity = getUserIdentityForAudit(bearerToken);
|
|
55
|
+
if (bearerIdentity && bearerIdentity.toLowerCase() === accountParam.toLowerCase()) return null;
|
|
56
|
+
return `The 'account' parameter is not supported in HTTP/OAuth mode: every request uses the identity of the connecting client's bearer token` + (bearerIdentity ? ` ('${bearerIdentity}')` : "") + `, so account switching is not possible. To act as '${accountParam}', reconnect the MCP client authenticated as that account, or run the server in stdio mode (or HTTP with --trust-proxy-auth) where cached accounts are available.`;
|
|
57
|
+
}
|
|
49
58
|
const UTILITY_TOOLS = [
|
|
50
59
|
{
|
|
51
60
|
name: "parse-teams-url",
|
|
@@ -124,6 +133,13 @@ const UTILITY_TOOLS = [
|
|
|
124
133
|
};
|
|
125
134
|
}
|
|
126
135
|
try {
|
|
136
|
+
const accountModeError = await checkAccountParamInBearerMode(accountParam, authManager);
|
|
137
|
+
if (accountModeError) {
|
|
138
|
+
return {
|
|
139
|
+
content: [{ type: "text", text: JSON.stringify({ error: accountModeError }) }],
|
|
140
|
+
isError: true
|
|
141
|
+
};
|
|
142
|
+
}
|
|
127
143
|
let accountAccessToken;
|
|
128
144
|
if (authManager && !authManager.isOAuthModeEnabled() && !getRequestTokens()) {
|
|
129
145
|
accountAccessToken = await authManager.getTokenForAccount(accountParam);
|
|
@@ -158,9 +174,16 @@ async function executeGraphTool(tool, config, graphClient, params, authManager)
|
|
|
158
174
|
const upn = getUserIdentityForAudit(getRequestTokens()?.accessToken);
|
|
159
175
|
const httpMethod = tool.method.toUpperCase();
|
|
160
176
|
try {
|
|
177
|
+
const accountParam = params.account;
|
|
178
|
+
const accountModeError = await checkAccountParamInBearerMode(accountParam, authManager);
|
|
179
|
+
if (accountModeError) {
|
|
180
|
+
return {
|
|
181
|
+
content: [{ type: "text", text: JSON.stringify({ error: accountModeError }) }],
|
|
182
|
+
isError: true
|
|
183
|
+
};
|
|
184
|
+
}
|
|
161
185
|
let accountAccessToken;
|
|
162
186
|
if (authManager && !authManager.isOAuthModeEnabled() && !getRequestTokens()) {
|
|
163
|
-
const accountParam = params.account;
|
|
164
187
|
try {
|
|
165
188
|
accountAccessToken = await authManager.getTokenForAccount(accountParam);
|
|
166
189
|
} catch (err) {
|
package/dist/server.js
CHANGED
|
@@ -105,17 +105,24 @@ class MicrosoftGraphServer {
|
|
|
105
105
|
async initialize(version) {
|
|
106
106
|
this.secrets = await getSecrets();
|
|
107
107
|
this.version = version;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
108
|
+
const accountRoutingAvailable = (!this.options.http || this.options.trustProxyAuth) && !this.authManager.isOAuthModeEnabled();
|
|
109
|
+
if (accountRoutingAvailable) {
|
|
110
|
+
try {
|
|
111
|
+
this.multiAccount = await this.authManager.isMultiAccount();
|
|
112
|
+
if (this.multiAccount) {
|
|
113
|
+
const accounts = await this.authManager.listAccounts();
|
|
114
|
+
this.accountNames = accounts.map((a) => a.username).filter((u) => !!u);
|
|
115
|
+
logger.info(
|
|
116
|
+
`Multi-account mode detected (${this.accountNames.length} accounts): "account" parameter will be injected into all tool schemas`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
} catch (err) {
|
|
120
|
+
logger.warn(`Failed to detect multi-account mode: ${err.message}`);
|
|
116
121
|
}
|
|
117
|
-
}
|
|
118
|
-
logger.
|
|
122
|
+
} else {
|
|
123
|
+
logger.info(
|
|
124
|
+
'Account routing disabled: requests use the OAuth bearer identity, so the "account" parameter is not injected into tool schemas'
|
|
125
|
+
);
|
|
119
126
|
}
|
|
120
127
|
if (this.options.obo) {
|
|
121
128
|
if (!this.options.http) {
|
package/dist/tool-categories.js
CHANGED
|
@@ -1,61 +1,74 @@
|
|
|
1
|
-
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import path from "path";
|
|
4
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
5
|
+
const __dirname = path.dirname(__filename);
|
|
6
|
+
const endpointEntries = JSON.parse(
|
|
7
|
+
readFileSync(path.join(__dirname, "endpoints.json"), "utf8")
|
|
8
|
+
);
|
|
9
|
+
const PRESET_META = {
|
|
2
10
|
mail: {
|
|
3
|
-
name: "mail",
|
|
4
|
-
pattern: /mail|attachment|draft|download-bytes/i,
|
|
5
11
|
description: "Email operations (read, send, manage folders, attachments)"
|
|
6
12
|
},
|
|
7
13
|
calendar: {
|
|
8
|
-
name: "calendar",
|
|
9
|
-
pattern: /calendar|event|schedule|meeting/i,
|
|
10
14
|
description: "Calendar and event management"
|
|
11
15
|
},
|
|
12
16
|
files: {
|
|
13
|
-
name: "files",
|
|
14
|
-
pattern: /drive|file|upload|download|folder|item/i,
|
|
15
17
|
description: "OneDrive file and folder operations"
|
|
16
18
|
},
|
|
17
19
|
personal: {
|
|
18
|
-
name: "personal",
|
|
19
|
-
pattern: /mail|calendar|drive|contact|todo|onenote|attachment|draft|event|file|folder|search|query|download-bytes|parse-teams-url/i,
|
|
20
20
|
description: "Personal productivity tools (mail, calendar, files, contacts, tasks, notes, search)"
|
|
21
21
|
},
|
|
22
22
|
work: {
|
|
23
|
-
name: "work",
|
|
24
|
-
pattern: /team|channel|chat|sharepoint|planner|site|list|shared|search|query|download-bytes|schedule|meeting/i,
|
|
25
23
|
description: "Organization/work tools (Teams, SharePoint, shared mailboxes, search)",
|
|
26
24
|
requiresOrgMode: true
|
|
27
25
|
},
|
|
28
26
|
excel: {
|
|
29
|
-
name: "excel",
|
|
30
|
-
pattern: /excel|worksheet|workbook|range|chart/i,
|
|
31
27
|
description: "Excel spreadsheet operations"
|
|
32
28
|
},
|
|
33
29
|
contacts: {
|
|
34
|
-
name: "contacts",
|
|
35
|
-
pattern: /contact/i,
|
|
36
30
|
description: "Outlook contacts management"
|
|
37
31
|
},
|
|
38
32
|
tasks: {
|
|
39
|
-
name: "tasks",
|
|
40
|
-
pattern: /todo|planner|task/i,
|
|
41
33
|
description: "Task and planning tools (To Do, Planner)"
|
|
42
34
|
},
|
|
43
35
|
onenote: {
|
|
44
|
-
name: "onenote",
|
|
45
|
-
pattern: /onenote|notebook|section|page/i,
|
|
46
36
|
description: "OneNote notebook operations"
|
|
47
37
|
},
|
|
48
38
|
search: {
|
|
49
|
-
name: "search",
|
|
50
|
-
pattern: /search|query/i,
|
|
51
39
|
description: "Microsoft Search capabilities"
|
|
52
40
|
},
|
|
53
41
|
users: {
|
|
54
|
-
name: "users",
|
|
55
|
-
pattern: /user|list-users|download-bytes/i,
|
|
56
42
|
description: "User directory access",
|
|
57
43
|
requiresOrgMode: true
|
|
58
44
|
},
|
|
45
|
+
outlook: {
|
|
46
|
+
description: "Outlook app only: mail, calendar and contacts"
|
|
47
|
+
},
|
|
48
|
+
onedrive: {
|
|
49
|
+
description: "OneDrive app only: drive and file operations, excluding Excel"
|
|
50
|
+
},
|
|
51
|
+
teams: {
|
|
52
|
+
description: "Teams app only: chats, channels, meetings and presence",
|
|
53
|
+
requiresOrgMode: true
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
function presetPattern(preset) {
|
|
57
|
+
const names = [
|
|
58
|
+
...new Set(endpointEntries.filter((e) => e.presets?.includes(preset)).map((e) => e.toolName))
|
|
59
|
+
];
|
|
60
|
+
if (names.length === 0) {
|
|
61
|
+
throw new Error(`Preset "${preset}" matches no endpoints in endpoints.json`);
|
|
62
|
+
}
|
|
63
|
+
return new RegExp(`^(?:${names.join("|")})$`);
|
|
64
|
+
}
|
|
65
|
+
const TOOL_CATEGORIES = {
|
|
66
|
+
...Object.fromEntries(
|
|
67
|
+
Object.entries(PRESET_META).map(([name, meta]) => [
|
|
68
|
+
name,
|
|
69
|
+
{ name, pattern: presetPattern(name), ...meta }
|
|
70
|
+
])
|
|
71
|
+
),
|
|
59
72
|
all: {
|
|
60
73
|
name: "all",
|
|
61
74
|
pattern: /.*/,
|
package/docs/deployment.md
CHANGED
|
@@ -123,7 +123,11 @@ When deploying for an organization, create a dedicated app registration instead
|
|
|
123
123
|
1. **Create the app** in [Azure Portal](https://portal.azure.com) > App registrations > New registration
|
|
124
124
|
- Name: `MS365 MCP Server`
|
|
125
125
|
- Supported account types: **Accounts in this organizational directory only** (single tenant)
|
|
126
|
-
- Redirect URI:
|
|
126
|
+
- Redirect URI (platform type **Web**): the **MCP client's** OAuth callback URL, not the server's own domain. The server proxies the OAuth flow and forwards the client's `redirect_uri` to Microsoft Entra, so Entra delivers the authorization code directly to the client. Register one redirect URI per MCP client you want to support, for example:
|
|
127
|
+
- Claude (claude.ai, Desktop, Cowork): `https://claude.ai/api/mcp/auth_callback`
|
|
128
|
+
- Other clients: check the `redirect_uri` query parameter your client sends to the server's `/authorize` endpoint (visible in the server logs)
|
|
129
|
+
|
|
130
|
+
> **Common pitfall**: registering `https://your-server-domain/callback` here breaks sign-in with `AADSTS50011` (redirect URI mismatch) after the user authenticates. The server has no callback endpoint of its own; the authorization code always goes to the MCP client.
|
|
127
131
|
|
|
128
132
|
2. **Add API permissions** > Microsoft Graph > Delegated permissions
|
|
129
133
|
Run `npx @softeria/ms-365-mcp-server --org-mode --list-permissions` to print the exact list of permissions required for your enabled tools.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softeria/ms-365-mcp-server",
|
|
3
|
-
"version": "0.114.
|
|
3
|
+
"version": "0.114.4",
|
|
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",
|