@softeria/ms-365-mcp-server 0.123.0 → 0.124.0
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/auth.js +53 -12
- package/dist/endpoints.json +2 -2
- package/dist/graph-tools.js +8 -6
- package/package.json +1 -1
- package/src/endpoints.json +2 -2
package/dist/auth.js
CHANGED
|
@@ -49,13 +49,47 @@ function getEndpointRequiredScopes(endpoint, includeWorkAccountScopes = false) {
|
|
|
49
49
|
return [];
|
|
50
50
|
}
|
|
51
51
|
const scopes = /* @__PURE__ */ new Set();
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
getEndpointScopeGroups(endpoint, includeWorkAccountScopes).forEach(
|
|
53
|
+
(group) => group.forEach((scope) => scopes.add(scope))
|
|
54
|
+
);
|
|
55
|
+
return Array.from(scopes);
|
|
56
|
+
}
|
|
57
|
+
function toScopeGroups(value) {
|
|
58
|
+
if (!value || value.length === 0) {
|
|
59
|
+
return [];
|
|
54
60
|
}
|
|
55
|
-
|
|
56
|
-
|
|
61
|
+
return Array.isArray(value[0]) ? value : [value];
|
|
62
|
+
}
|
|
63
|
+
function getEndpointScopeGroups(endpoint, includeWorkAccountScopes = false) {
|
|
64
|
+
if (!endpoint) {
|
|
65
|
+
return [];
|
|
57
66
|
}
|
|
58
|
-
|
|
67
|
+
const groups = [...toScopeGroups(endpoint.scopes)];
|
|
68
|
+
if (includeWorkAccountScopes) {
|
|
69
|
+
groups.push(...toScopeGroups(endpoint.workScopes));
|
|
70
|
+
}
|
|
71
|
+
return groups;
|
|
72
|
+
}
|
|
73
|
+
function getEndpointLoginScopes(endpoint, includeWorkAccountScopes = false) {
|
|
74
|
+
const groups = getEndpointScopeGroups(endpoint, includeWorkAccountScopes);
|
|
75
|
+
return groups.length > 0 ? groups[0] : [];
|
|
76
|
+
}
|
|
77
|
+
function getMissingAllowedScopesForGroups(scopeGroups, allowedScopes) {
|
|
78
|
+
if (allowedScopes === void 0 || scopeGroups.length === 0) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
const coveredAllowedScopes = new Set(collapseScopeHierarchy(allowedScopes));
|
|
82
|
+
let closest;
|
|
83
|
+
for (const group of scopeGroups) {
|
|
84
|
+
const missing = group.filter((scope) => !coveredAllowedScopes.has(scope));
|
|
85
|
+
if (missing.length === 0) {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
if (!closest || missing.length < closest.length) {
|
|
89
|
+
closest = missing;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return closest ?? [];
|
|
59
93
|
}
|
|
60
94
|
function collapseRedundantScopes(scopes) {
|
|
61
95
|
const scopesSet = new Set(scopes);
|
|
@@ -91,7 +125,7 @@ function buildScopesFromEndpoints(includeWorkAccountScopes = false, enabledTools
|
|
|
91
125
|
if (!includeWorkAccountScopes && !endpoint.scopes && endpoint.workScopes) {
|
|
92
126
|
return;
|
|
93
127
|
}
|
|
94
|
-
|
|
128
|
+
getEndpointLoginScopes(endpoint, includeWorkAccountScopes).forEach(
|
|
95
129
|
(scope) => scopesSet.add(scope)
|
|
96
130
|
);
|
|
97
131
|
});
|
|
@@ -173,6 +207,7 @@ function buildAllowedScopeDiagnostics(options = {}) {
|
|
|
173
207
|
}
|
|
174
208
|
const normalToolScopes = /* @__PURE__ */ new Set();
|
|
175
209
|
const effectiveToolScopes = /* @__PURE__ */ new Set();
|
|
210
|
+
const effectiveToolScopesAllGroups = /* @__PURE__ */ new Set();
|
|
176
211
|
const disabledTools = [];
|
|
177
212
|
for (const endpoint of endpoints.default) {
|
|
178
213
|
if (!endpointMatchesNormalToolSurface(
|
|
@@ -183,18 +218,21 @@ function buildAllowedScopeDiagnostics(options = {}) {
|
|
|
183
218
|
)) {
|
|
184
219
|
continue;
|
|
185
220
|
}
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
const
|
|
221
|
+
const scopeGroups = getEndpointScopeGroups(endpoint, Boolean(options.orgMode));
|
|
222
|
+
const loginScopes = getEndpointLoginScopes(endpoint, Boolean(options.orgMode));
|
|
223
|
+
const allScopes = getEndpointRequiredScopes(endpoint, Boolean(options.orgMode));
|
|
224
|
+
loginScopes.forEach((scope) => normalToolScopes.add(scope));
|
|
225
|
+
const missingScopes = getMissingAllowedScopesForGroups(scopeGroups, allowedScopes);
|
|
189
226
|
if (missingScopes.length > 0) {
|
|
190
227
|
disabledTools.push({
|
|
191
228
|
toolName: endpoint.toolName,
|
|
192
|
-
requiredScopes:
|
|
229
|
+
requiredScopes: allScopes.sort((a, b) => a.localeCompare(b)),
|
|
193
230
|
missingScopes: missingScopes.sort((a, b) => a.localeCompare(b))
|
|
194
231
|
});
|
|
195
232
|
continue;
|
|
196
233
|
}
|
|
197
|
-
|
|
234
|
+
loginScopes.forEach((scope) => effectiveToolScopes.add(scope));
|
|
235
|
+
allScopes.forEach((scope) => effectiveToolScopesAllGroups.add(scope));
|
|
198
236
|
}
|
|
199
237
|
const toolPermissions = collapseRedundantScopes(Array.from(normalToolScopes)).sort(
|
|
200
238
|
(a, b) => a.localeCompare(b)
|
|
@@ -206,7 +244,8 @@ function buildAllowedScopeDiagnostics(options = {}) {
|
|
|
206
244
|
const missingAllowedScopesForTools = Array.from(
|
|
207
245
|
new Set(disabledTools.flatMap((tool) => tool.missingScopes))
|
|
208
246
|
).sort((a, b) => a.localeCompare(b));
|
|
209
|
-
const
|
|
247
|
+
const allEffectiveToolScopes = Array.from(effectiveToolScopesAllGroups);
|
|
248
|
+
const extraAllowedScopesNotUsedByTools = sortedAllowedScopes?.filter((scope) => !isScopeUsedByTools(scope, allEffectiveToolScopes)) ?? [];
|
|
210
249
|
return {
|
|
211
250
|
permissions: effectivePermissions,
|
|
212
251
|
toolPermissions,
|
|
@@ -811,7 +850,9 @@ export {
|
|
|
811
850
|
auth_default as default,
|
|
812
851
|
describeAuthError,
|
|
813
852
|
getEndpointRequiredScopes,
|
|
853
|
+
getEndpointScopeGroups,
|
|
814
854
|
getMissingAllowedScopes,
|
|
855
|
+
getMissingAllowedScopesForGroups,
|
|
815
856
|
getSelectedAccountPath,
|
|
816
857
|
getTokenCachePath,
|
|
817
858
|
parseAllowedScopes,
|
package/dist/endpoints.json
CHANGED
|
@@ -1748,7 +1748,7 @@
|
|
|
1748
1748
|
"toolName": "copilot-retrieve",
|
|
1749
1749
|
"presets": ["search", "work"],
|
|
1750
1750
|
"readOnly": true,
|
|
1751
|
-
"workScopes": ["Files.Read.All", "Sites.Read.All", "ExternalItem.Read.All"],
|
|
1751
|
+
"workScopes": [["Files.Read.All", "Sites.Read.All"], ["ExternalItem.Read.All"]],
|
|
1752
1752
|
"requestBodySchema": {
|
|
1753
1753
|
"type": "object",
|
|
1754
1754
|
"required": ["queryString", "dataSource"],
|
|
@@ -1782,7 +1782,7 @@
|
|
|
1782
1782
|
}
|
|
1783
1783
|
}
|
|
1784
1784
|
},
|
|
1785
|
-
"llmTip": "Semantic/hybrid search over Microsoft 365 content via the Copilot Retrieval API. Grounds a natural-language query against the same hybrid index that powers Microsoft 365 Copilot and returns permission-trimmed text extracts with their source URLs — unlike search-query/search-onedrive-files/search-sharepoint-sites, which are lexical KQL. Prefer this for paraphrase/intent queries (e.g. resolving an informal project nickname, or matching 'packaging requirements' against text that never uses that phrase). Pass the request fields nested under a 'body' object: { queryString, dataSource, filterExpression?, resourceMetadata?, maximumNumberOfResults? }. Returns { retrievalHits: [{ webUrl, extracts: [{ text, relevanceScore }], resourceType, resourceMetadata, sensitivityLabel? }] }. Work/school accounts only (personal accounts are not supported)
|
|
1785
|
+
"llmTip": "Semantic/hybrid search over Microsoft 365 content via the Copilot Retrieval API. Grounds a natural-language query against the same hybrid index that powers Microsoft 365 Copilot and returns permission-trimmed text extracts with their source URLs — unlike search-query/search-onedrive-files/search-sharepoint-sites, which are lexical KQL. Prefer this for paraphrase/intent queries (e.g. resolving an informal project nickname, or matching 'packaging requirements' against text that never uses that phrase). Pass the request fields nested under a 'body' object: { queryString, dataSource, filterExpression?, resourceMetadata?, maximumNumberOfResults? }. Returns { retrievalHits: [{ webUrl, extracts: [{ text, relevanceScore }], resourceType, resourceMetadata, sensitivityLabel? }] }. Work/school accounts only (personal accounts are not supported). dataSource 'sharePoint'/'oneDriveBusiness' need delegated Files.Read.All + Sites.Read.All (requested by default); dataSource 'externalItem' (Copilot connectors) needs ExternalItem.Read.All, which is not requested by default - add it with --extra-scopes ExternalItem.Read.All."
|
|
1786
1786
|
},
|
|
1787
1787
|
{
|
|
1788
1788
|
"pathPattern": "/me/onlineMeetings",
|
package/dist/graph-tools.js
CHANGED
|
@@ -2,8 +2,8 @@ import { randomUUID } from "crypto";
|
|
|
2
2
|
import logger from "./logger.js";
|
|
3
3
|
import { auditLog, getUserIdentityForAudit } from "./audit-log.js";
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
getEndpointScopeGroups,
|
|
6
|
+
getMissingAllowedScopesForGroups,
|
|
7
7
|
parseAllowedScopes
|
|
8
8
|
} from "./auth.js";
|
|
9
9
|
import { api } from "./generated/client.js";
|
|
@@ -522,8 +522,10 @@ function registerGraphTools(server, graphClient, readOnly = false, enabledToolsP
|
|
|
522
522
|
skippedCount++;
|
|
523
523
|
continue;
|
|
524
524
|
}
|
|
525
|
-
const
|
|
526
|
-
|
|
525
|
+
const missingScopes = allowedScopes !== void 0 && !endpointConfig ? ["endpoint scope metadata"] : getMissingAllowedScopesForGroups(
|
|
526
|
+
getEndpointScopeGroups(endpointConfig, orgMode),
|
|
527
|
+
allowedScopes
|
|
528
|
+
);
|
|
527
529
|
if (missingScopes.length > 0) {
|
|
528
530
|
disabledByAllowedScopes.push({ toolName: tool.alias, missingScopes });
|
|
529
531
|
skippedCount++;
|
|
@@ -674,8 +676,8 @@ function buildToolsRegistry(readOnly, orgMode, enabledToolsRegex, allowedScopesV
|
|
|
674
676
|
if (enabledToolsRegex && !enabledToolsRegex.test(tool.alias)) {
|
|
675
677
|
continue;
|
|
676
678
|
}
|
|
677
|
-
const missingScopes = allowedScopes !== void 0 && !endpointConfig ? ["endpoint scope metadata"] :
|
|
678
|
-
|
|
679
|
+
const missingScopes = allowedScopes !== void 0 && !endpointConfig ? ["endpoint scope metadata"] : getMissingAllowedScopesForGroups(
|
|
680
|
+
getEndpointScopeGroups(endpointConfig, orgMode),
|
|
679
681
|
allowedScopes
|
|
680
682
|
);
|
|
681
683
|
if (missingScopes.length > 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softeria/ms-365-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.124.0",
|
|
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
|
@@ -1748,7 +1748,7 @@
|
|
|
1748
1748
|
"toolName": "copilot-retrieve",
|
|
1749
1749
|
"presets": ["search", "work"],
|
|
1750
1750
|
"readOnly": true,
|
|
1751
|
-
"workScopes": ["Files.Read.All", "Sites.Read.All", "ExternalItem.Read.All"],
|
|
1751
|
+
"workScopes": [["Files.Read.All", "Sites.Read.All"], ["ExternalItem.Read.All"]],
|
|
1752
1752
|
"requestBodySchema": {
|
|
1753
1753
|
"type": "object",
|
|
1754
1754
|
"required": ["queryString", "dataSource"],
|
|
@@ -1782,7 +1782,7 @@
|
|
|
1782
1782
|
}
|
|
1783
1783
|
}
|
|
1784
1784
|
},
|
|
1785
|
-
"llmTip": "Semantic/hybrid search over Microsoft 365 content via the Copilot Retrieval API. Grounds a natural-language query against the same hybrid index that powers Microsoft 365 Copilot and returns permission-trimmed text extracts with their source URLs — unlike search-query/search-onedrive-files/search-sharepoint-sites, which are lexical KQL. Prefer this for paraphrase/intent queries (e.g. resolving an informal project nickname, or matching 'packaging requirements' against text that never uses that phrase). Pass the request fields nested under a 'body' object: { queryString, dataSource, filterExpression?, resourceMetadata?, maximumNumberOfResults? }. Returns { retrievalHits: [{ webUrl, extracts: [{ text, relevanceScore }], resourceType, resourceMetadata, sensitivityLabel? }] }. Work/school accounts only (personal accounts are not supported)
|
|
1785
|
+
"llmTip": "Semantic/hybrid search over Microsoft 365 content via the Copilot Retrieval API. Grounds a natural-language query against the same hybrid index that powers Microsoft 365 Copilot and returns permission-trimmed text extracts with their source URLs — unlike search-query/search-onedrive-files/search-sharepoint-sites, which are lexical KQL. Prefer this for paraphrase/intent queries (e.g. resolving an informal project nickname, or matching 'packaging requirements' against text that never uses that phrase). Pass the request fields nested under a 'body' object: { queryString, dataSource, filterExpression?, resourceMetadata?, maximumNumberOfResults? }. Returns { retrievalHits: [{ webUrl, extracts: [{ text, relevanceScore }], resourceType, resourceMetadata, sensitivityLabel? }] }. Work/school accounts only (personal accounts are not supported). dataSource 'sharePoint'/'oneDriveBusiness' need delegated Files.Read.All + Sites.Read.All (requested by default); dataSource 'externalItem' (Copilot connectors) needs ExternalItem.Read.All, which is not requested by default - add it with --extra-scopes ExternalItem.Read.All."
|
|
1786
1786
|
},
|
|
1787
1787
|
{
|
|
1788
1788
|
"pathPattern": "/me/onlineMeetings",
|