@softeria/ms-365-mcp-server 0.129.0 → 0.130.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/__tests__/graph-tools.test.js +83 -2
- package/dist/endpoints.json +21 -0
- package/dist/generated/client.js +53 -3
- package/dist/graph-client.js +23 -13
- package/dist/graph-tools.js +59 -46
- package/package.json +1 -1
- package/src/endpoints.json +21 -0
|
@@ -62,7 +62,7 @@ function makeConfig(overrides = {}) {
|
|
|
62
62
|
...overrides
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
|
-
function createMockGraphClient(responses) {
|
|
65
|
+
function createMockGraphClient(responses, outputFormat = "json") {
|
|
66
66
|
const responseQueue = [...responses || []];
|
|
67
67
|
return {
|
|
68
68
|
graphRequest: vi.fn().mockImplementation(async () => {
|
|
@@ -72,7 +72,12 @@ function createMockGraphClient(responses) {
|
|
|
72
72
|
return {
|
|
73
73
|
content: [{ type: "text", text: JSON.stringify({ value: [] }) }]
|
|
74
74
|
};
|
|
75
|
-
})
|
|
75
|
+
}),
|
|
76
|
+
// Fake serialize: prefix the JSON in toon mode so a test can tell the merged
|
|
77
|
+
// body went through serialize() and not a plain JSON.stringify.
|
|
78
|
+
serialize: vi.fn().mockImplementation(
|
|
79
|
+
(data) => outputFormat === "toon" ? `TOON:${JSON.stringify(data)}` : JSON.stringify(data)
|
|
80
|
+
)
|
|
76
81
|
};
|
|
77
82
|
}
|
|
78
83
|
async function loadModule() {
|
|
@@ -161,6 +166,61 @@ describe("graph-tools", () => {
|
|
|
161
166
|
expect(parsed.value.map((v) => v.id)).toEqual(["1", "2", "3"]);
|
|
162
167
|
expect(parsed["@odata.nextLink"]).toBeUndefined();
|
|
163
168
|
});
|
|
169
|
+
it("merges all pages under --toon and encodes the combined result once (#560)", async () => {
|
|
170
|
+
const endpoint = makeEndpoint();
|
|
171
|
+
const config = makeConfig();
|
|
172
|
+
mockEndpoints.push(endpoint);
|
|
173
|
+
mockEndpointsJson = [config];
|
|
174
|
+
const graphClient = createMockGraphClient(
|
|
175
|
+
[
|
|
176
|
+
{
|
|
177
|
+
content: [
|
|
178
|
+
{
|
|
179
|
+
type: "text",
|
|
180
|
+
text: JSON.stringify({
|
|
181
|
+
value: [{ id: "1" }, { id: "2" }],
|
|
182
|
+
"@odata.nextLink": "https://graph.microsoft.com/v1.0/me/messages?$skip=2"
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
]
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
content: [{ type: "text", text: JSON.stringify({ value: [{ id: "3" }] }) }]
|
|
189
|
+
}
|
|
190
|
+
],
|
|
191
|
+
"toon"
|
|
192
|
+
);
|
|
193
|
+
const server = createMockServer();
|
|
194
|
+
const { registerGraphTools } = await loadModule();
|
|
195
|
+
registerGraphTools(server, graphClient);
|
|
196
|
+
const tool = server.tools.get("test-tool");
|
|
197
|
+
const result = await tool.handler({ fetchAllPages: true });
|
|
198
|
+
for (const call of graphClient.graphRequest.mock.calls) {
|
|
199
|
+
expect(call[1]?.forceJsonOutput).toBe(true);
|
|
200
|
+
}
|
|
201
|
+
expect(graphClient.serialize).toHaveBeenCalledTimes(1);
|
|
202
|
+
expect(result.content[0].text.startsWith("TOON:")).toBe(true);
|
|
203
|
+
const parsed = JSON.parse(result.content[0].text.slice("TOON:".length));
|
|
204
|
+
expect(parsed.value.map((v) => v.id)).toEqual(["1", "2", "3"]);
|
|
205
|
+
expect(parsed["@odata.nextLink"]).toBeUndefined();
|
|
206
|
+
});
|
|
207
|
+
it("does not inject value:[] when fetchAllPages hits a single-object (non-collection) GET", async () => {
|
|
208
|
+
const endpoint = makeEndpoint();
|
|
209
|
+
const config = makeConfig();
|
|
210
|
+
mockEndpoints.push(endpoint);
|
|
211
|
+
mockEndpointsJson = [config];
|
|
212
|
+
const graphClient = createMockGraphClient([
|
|
213
|
+
{ content: [{ type: "text", text: JSON.stringify({ id: "abc", displayName: "Solo" }) }] }
|
|
214
|
+
]);
|
|
215
|
+
const server = createMockServer();
|
|
216
|
+
const { registerGraphTools } = await loadModule();
|
|
217
|
+
registerGraphTools(server, graphClient);
|
|
218
|
+
const result = await server.tools.get("test-tool").handler({ fetchAllPages: true });
|
|
219
|
+
const parsed = JSON.parse(result.content[0].text);
|
|
220
|
+
expect(parsed).toEqual({ id: "abc", displayName: "Solo" });
|
|
221
|
+
expect(parsed.value).toBeUndefined();
|
|
222
|
+
expect(graphClient.graphRequest).toHaveBeenCalledTimes(1);
|
|
223
|
+
});
|
|
164
224
|
it("should stop at 100 page limit", async () => {
|
|
165
225
|
const endpoint = makeEndpoint();
|
|
166
226
|
const config = makeConfig();
|
|
@@ -712,6 +772,27 @@ describe("graph-tools", () => {
|
|
|
712
772
|
expect(payload.size).toBe(12727);
|
|
713
773
|
expect(payload.contentType).toBe("application/pdf");
|
|
714
774
|
});
|
|
775
|
+
it("forces a JSON body on the metadata request so it works under --toon (#560)", async () => {
|
|
776
|
+
mockEndpoints.length = 0;
|
|
777
|
+
mockEndpointsJson = [];
|
|
778
|
+
const graphClient = {
|
|
779
|
+
graphRequest: vi.fn().mockResolvedValue({
|
|
780
|
+
content: [
|
|
781
|
+
{
|
|
782
|
+
type: "text",
|
|
783
|
+
text: JSON.stringify({ "@microsoft.graph.downloadUrl": "https://dl.example/x" })
|
|
784
|
+
}
|
|
785
|
+
]
|
|
786
|
+
})
|
|
787
|
+
};
|
|
788
|
+
const server = createMockServer();
|
|
789
|
+
const { registerGraphTools } = await loadModule();
|
|
790
|
+
registerGraphTools(server, graphClient);
|
|
791
|
+
const result = await server.tools.get("get-download-url").handler({ target: "/drives/d1/items/item1/content" });
|
|
792
|
+
const [, opts] = graphClient.graphRequest.mock.calls[0];
|
|
793
|
+
expect(opts?.forceJsonOutput).toBe(true);
|
|
794
|
+
expect(JSON.parse(result.content[0].text).downloadUrl).toBe("https://dl.example/x");
|
|
795
|
+
});
|
|
715
796
|
it("rejects query-shaped targets instead of silently changing request semantics", async () => {
|
|
716
797
|
mockEndpoints.length = 0;
|
|
717
798
|
mockEndpointsJson = [];
|
package/dist/endpoints.json
CHANGED
|
@@ -1393,6 +1393,13 @@
|
|
|
1393
1393
|
"presets": ["teams", "work"],
|
|
1394
1394
|
"workScopes": ["ChatMessage.Read"]
|
|
1395
1395
|
},
|
|
1396
|
+
{
|
|
1397
|
+
"pathPattern": "/chats/{chat-id}/messages/{chatMessage-id}",
|
|
1398
|
+
"method": "patch",
|
|
1399
|
+
"toolName": "update-chat-message",
|
|
1400
|
+
"presets": ["teams", "work"],
|
|
1401
|
+
"workScopes": ["Chat.ReadWrite"]
|
|
1402
|
+
},
|
|
1396
1403
|
{
|
|
1397
1404
|
"pathPattern": "/chats/{chat-id}/messages/{chatMessage-id}/hostedContents",
|
|
1398
1405
|
"method": "get",
|
|
@@ -1472,6 +1479,13 @@
|
|
|
1472
1479
|
"presets": ["teams", "work"],
|
|
1473
1480
|
"workScopes": ["ChannelMessage.Read.All"]
|
|
1474
1481
|
},
|
|
1482
|
+
{
|
|
1483
|
+
"pathPattern": "/teams/{team-id}/channels/{channel-id}/messages/{chatMessage-id}",
|
|
1484
|
+
"method": "patch",
|
|
1485
|
+
"toolName": "update-channel-message",
|
|
1486
|
+
"presets": ["teams", "work"],
|
|
1487
|
+
"workScopes": ["ChannelMessage.ReadWrite"]
|
|
1488
|
+
},
|
|
1475
1489
|
{
|
|
1476
1490
|
"pathPattern": "/teams/{team-id}/channels/{channel-id}/messages/{chatMessage-id}/hostedContents",
|
|
1477
1491
|
"method": "get",
|
|
@@ -1503,6 +1517,13 @@
|
|
|
1503
1517
|
"presets": ["teams", "work"],
|
|
1504
1518
|
"workScopes": ["ChannelMessage.Read.All"]
|
|
1505
1519
|
},
|
|
1520
|
+
{
|
|
1521
|
+
"pathPattern": "/teams/{team-id}/channels/{channel-id}/messages/{chatMessage-id}/replies/{chatMessage-id1}",
|
|
1522
|
+
"method": "patch",
|
|
1523
|
+
"toolName": "update-channel-message-reply",
|
|
1524
|
+
"presets": ["teams", "work"],
|
|
1525
|
+
"workScopes": ["ChannelMessage.ReadWrite"]
|
|
1526
|
+
},
|
|
1506
1527
|
{
|
|
1507
1528
|
"pathPattern": "/teams/{team-id}/members",
|
|
1508
1529
|
"method": "post",
|
package/dist/generated/client.js
CHANGED
|
@@ -1613,7 +1613,7 @@ const microsoft_graph_group = z.object({
|
|
|
1613
1613
|
"Indicates if people external to the organization can send messages to the group. The default value is false. Requires $select to retrieve. Supported only on the Get group API (GET /groups/{ID})."
|
|
1614
1614
|
).nullish(),
|
|
1615
1615
|
assignedLabels: z.array(microsoft_graph_assignedLabel).describe(
|
|
1616
|
-
"The list of sensitivity label pairs (label ID, label name) associated with a Microsoft 365 group. Requires $select to retrieve. This property can be updated only in delegated scenarios where the caller requires both the Microsoft Graph permission and a supported administrator role."
|
|
1616
|
+
"The list of sensitivity label pairs (label ID, label name) associated with a Microsoft 365 group or a cloud security group. Requires a Microsoft Entra ID P1 license. Requires $select to retrieve. This property can be specified during group creation or update. However, for cloud security groups, it's immutable once set. This property can be updated only in delegated scenarios where the caller requires both the Microsoft Graph permission and a supported administrator role. See Key differences from Microsoft 365 group labeling to learn more about managing this property for Microsoft 365 vs. cloud security groups."
|
|
1617
1617
|
).optional(),
|
|
1618
1618
|
assignedLicenses: z.array(microsoft_graph_assignedLicense).describe(
|
|
1619
1619
|
"The licenses that are assigned to the group. Requires $select to retrieve. Supports $filter (eq). Read-only."
|
|
@@ -5497,6 +5497,22 @@ const endpoints = makeApi([
|
|
|
5497
5497
|
],
|
|
5498
5498
|
response: z.void()
|
|
5499
5499
|
},
|
|
5500
|
+
{
|
|
5501
|
+
method: "patch",
|
|
5502
|
+
path: "/chats/:chatId/messages/:chatMessageId",
|
|
5503
|
+
alias: "update-chat-message",
|
|
5504
|
+
description: `Update the navigation property messages in chats`,
|
|
5505
|
+
requestFormat: "json",
|
|
5506
|
+
parameters: [
|
|
5507
|
+
{
|
|
5508
|
+
name: "body",
|
|
5509
|
+
description: `New navigation property values`,
|
|
5510
|
+
type: "Body",
|
|
5511
|
+
schema: microsoft_graph_chatMessage
|
|
5512
|
+
}
|
|
5513
|
+
],
|
|
5514
|
+
response: z.void()
|
|
5515
|
+
},
|
|
5500
5516
|
{
|
|
5501
5517
|
method: "get",
|
|
5502
5518
|
path: "/chats/:chatId/messages/:chatMessageId/hostedContents",
|
|
@@ -6871,7 +6887,7 @@ You can search within a folder hierarchy, a whole drive, or files shared with th
|
|
|
6871
6887
|
"Indicates if people external to the organization can send messages to the group. The default value is false. Requires $select to retrieve. Supported only on the Get group API (GET /groups/{ID})."
|
|
6872
6888
|
).nullish(),
|
|
6873
6889
|
assignedLabels: z.array(microsoft_graph_assignedLabel).describe(
|
|
6874
|
-
"The list of sensitivity label pairs (label ID, label name) associated with a Microsoft 365 group. Requires $select to retrieve. This property can be updated only in delegated scenarios where the caller requires both the Microsoft Graph permission and a supported administrator role."
|
|
6890
|
+
"The list of sensitivity label pairs (label ID, label name) associated with a Microsoft 365 group or a cloud security group. Requires a Microsoft Entra ID P1 license. Requires $select to retrieve. This property can be specified during group creation or update. However, for cloud security groups, it's immutable once set. This property can be updated only in delegated scenarios where the caller requires both the Microsoft Graph permission and a supported administrator role. See Key differences from Microsoft 365 group labeling to learn more about managing this property for Microsoft 365 vs. cloud security groups."
|
|
6875
6891
|
).optional(),
|
|
6876
6892
|
assignedLicenses: z.array(microsoft_graph_assignedLicense).describe(
|
|
6877
6893
|
"The licenses that are assigned to the group. Requires $select to retrieve. Supports $filter (eq). Read-only."
|
|
@@ -6982,7 +6998,7 @@ You can create or update the following types of group: By default, this operatio
|
|
|
6982
6998
|
"Indicates if people external to the organization can send messages to the group. The default value is false. Requires $select to retrieve. Supported only on the Get group API (GET /groups/{ID})."
|
|
6983
6999
|
).nullish(),
|
|
6984
7000
|
assignedLabels: z.array(microsoft_graph_assignedLabel).describe(
|
|
6985
|
-
"The list of sensitivity label pairs (label ID, label name) associated with a Microsoft 365 group. Requires $select to retrieve. This property can be updated only in delegated scenarios where the caller requires both the Microsoft Graph permission and a supported administrator role."
|
|
7001
|
+
"The list of sensitivity label pairs (label ID, label name) associated with a Microsoft 365 group or a cloud security group. Requires a Microsoft Entra ID P1 license. Requires $select to retrieve. This property can be specified during group creation or update. However, for cloud security groups, it's immutable once set. This property can be updated only in delegated scenarios where the caller requires both the Microsoft Graph permission and a supported administrator role. See Key differences from Microsoft 365 group labeling to learn more about managing this property for Microsoft 365 vs. cloud security groups."
|
|
6986
7002
|
).optional(),
|
|
6987
7003
|
assignedLicenses: z.array(microsoft_graph_assignedLicense).describe(
|
|
6988
7004
|
"The licenses that are assigned to the group. Requires $select to retrieve. Supports $filter (eq). Read-only."
|
|
@@ -14109,6 +14125,24 @@ To monitor future changes, call the delta API by using the @odata.deltaLink in t
|
|
|
14109
14125
|
],
|
|
14110
14126
|
response: z.void()
|
|
14111
14127
|
},
|
|
14128
|
+
{
|
|
14129
|
+
method: "patch",
|
|
14130
|
+
path: "/teams/:teamId/channels/:channelId/messages/:chatMessageId",
|
|
14131
|
+
alias: "update-channel-message",
|
|
14132
|
+
description: `Update a chatMessage object.
|
|
14133
|
+
Except for the policyViolation property, all properties of a chatMessage can be updated in delegated permissions scenarios.
|
|
14134
|
+
Only the policyViolation property of a chatMessage can be updated in application permissions scenarios. The update only works for chats where members are Microsoft Teams users. If one of the participants is using Skype, the operation fails. This method doesn't support federation. Only the user in the tenant who sent the message can perform data loss prevention (DLP) updates on the specified chat message.`,
|
|
14135
|
+
requestFormat: "json",
|
|
14136
|
+
parameters: [
|
|
14137
|
+
{
|
|
14138
|
+
name: "body",
|
|
14139
|
+
description: `New navigation property values`,
|
|
14140
|
+
type: "Body",
|
|
14141
|
+
schema: microsoft_graph_chatMessage
|
|
14142
|
+
}
|
|
14143
|
+
],
|
|
14144
|
+
response: z.void()
|
|
14145
|
+
},
|
|
14112
14146
|
{
|
|
14113
14147
|
method: "get",
|
|
14114
14148
|
path: "/teams/:teamId/channels/:channelId/messages/:chatMessageId/hostedContents",
|
|
@@ -14225,6 +14259,22 @@ To monitor future changes, call the delta API by using the @odata.deltaLink in t
|
|
|
14225
14259
|
],
|
|
14226
14260
|
response: z.void()
|
|
14227
14261
|
},
|
|
14262
|
+
{
|
|
14263
|
+
method: "patch",
|
|
14264
|
+
path: "/teams/:teamId/channels/:channelId/messages/:chatMessageId/replies/:chatMessageId1",
|
|
14265
|
+
alias: "update-channel-message-reply",
|
|
14266
|
+
description: `Update the navigation property replies in teams`,
|
|
14267
|
+
requestFormat: "json",
|
|
14268
|
+
parameters: [
|
|
14269
|
+
{
|
|
14270
|
+
name: "body",
|
|
14271
|
+
description: `New navigation property values`,
|
|
14272
|
+
type: "Body",
|
|
14273
|
+
schema: microsoft_graph_chatMessage
|
|
14274
|
+
}
|
|
14275
|
+
],
|
|
14276
|
+
response: z.void()
|
|
14277
|
+
},
|
|
14228
14278
|
{
|
|
14229
14279
|
method: "post",
|
|
14230
14280
|
path: "/teams/:teamId/channels/:channelId/messages/:chatMessageId/setReaction",
|
package/dist/graph-client.js
CHANGED
|
@@ -133,11 +133,25 @@ class GraphClient {
|
|
|
133
133
|
}
|
|
134
134
|
return JSON.stringify(data, null, pretty ? 2 : void 0);
|
|
135
135
|
}
|
|
136
|
+
/**
|
|
137
|
+
* Encode a value in the configured format (json/toon). The fetchAllPages merge
|
|
138
|
+
* uses this to encode the combined result once, after parsing pages as JSON (#560).
|
|
139
|
+
* Compact by default like JSON.stringify, so JSON-mode output is byte-identical.
|
|
140
|
+
*/
|
|
141
|
+
serialize(data, pretty = false) {
|
|
142
|
+
return this.serializeData(data, this.outputFormat, pretty);
|
|
143
|
+
}
|
|
136
144
|
async graphRequest(endpoint, options = {}) {
|
|
137
145
|
try {
|
|
138
146
|
logger.info(`Calling ${endpoint} with options: ${JSON.stringify(options)}`);
|
|
139
147
|
const result = await this.makeRequest(endpoint, options);
|
|
140
|
-
|
|
148
|
+
const outputFormat = options.forceJsonOutput ? "json" : this.outputFormat;
|
|
149
|
+
return this.formatJsonResponse(
|
|
150
|
+
result,
|
|
151
|
+
options.rawResponse,
|
|
152
|
+
options.excludeResponse,
|
|
153
|
+
outputFormat
|
|
154
|
+
);
|
|
141
155
|
} catch (error) {
|
|
142
156
|
logger.error(`Error in Graph API request: ${error}`);
|
|
143
157
|
return {
|
|
@@ -146,10 +160,10 @@ class GraphClient {
|
|
|
146
160
|
};
|
|
147
161
|
}
|
|
148
162
|
}
|
|
149
|
-
formatJsonResponse(data, rawResponse = false, excludeResponse = false) {
|
|
163
|
+
formatJsonResponse(data, rawResponse = false, excludeResponse = false, outputFormat = this.outputFormat) {
|
|
150
164
|
if (excludeResponse) {
|
|
151
165
|
return {
|
|
152
|
-
content: [{ type: "text", text: this.serializeData({ success: true },
|
|
166
|
+
content: [{ type: "text", text: this.serializeData({ success: true }, outputFormat) }]
|
|
153
167
|
};
|
|
154
168
|
}
|
|
155
169
|
if (data && typeof data === "object" && "_headers" in data) {
|
|
@@ -163,17 +177,13 @@ class GraphClient {
|
|
|
163
177
|
}
|
|
164
178
|
if (rawResponse) {
|
|
165
179
|
return {
|
|
166
|
-
content: [
|
|
167
|
-
{ type: "text", text: this.serializeData(responseData.data, this.outputFormat) }
|
|
168
|
-
],
|
|
180
|
+
content: [{ type: "text", text: this.serializeData(responseData.data, outputFormat) }],
|
|
169
181
|
_meta: meta
|
|
170
182
|
};
|
|
171
183
|
}
|
|
172
184
|
if (responseData.data === null || responseData.data === void 0) {
|
|
173
185
|
return {
|
|
174
|
-
content: [
|
|
175
|
-
{ type: "text", text: this.serializeData({ success: true }, this.outputFormat) }
|
|
176
|
-
],
|
|
186
|
+
content: [{ type: "text", text: this.serializeData({ success: true }, outputFormat) }],
|
|
177
187
|
_meta: meta
|
|
178
188
|
};
|
|
179
189
|
}
|
|
@@ -191,19 +201,19 @@ class GraphClient {
|
|
|
191
201
|
removeODataProps2(responseData.data);
|
|
192
202
|
return {
|
|
193
203
|
content: [
|
|
194
|
-
{ type: "text", text: this.serializeData(responseData.data,
|
|
204
|
+
{ type: "text", text: this.serializeData(responseData.data, outputFormat, true) }
|
|
195
205
|
],
|
|
196
206
|
_meta: meta
|
|
197
207
|
};
|
|
198
208
|
}
|
|
199
209
|
if (rawResponse) {
|
|
200
210
|
return {
|
|
201
|
-
content: [{ type: "text", text: this.serializeData(data,
|
|
211
|
+
content: [{ type: "text", text: this.serializeData(data, outputFormat) }]
|
|
202
212
|
};
|
|
203
213
|
}
|
|
204
214
|
if (data === null || data === void 0) {
|
|
205
215
|
return {
|
|
206
|
-
content: [{ type: "text", text: this.serializeData({ success: true },
|
|
216
|
+
content: [{ type: "text", text: this.serializeData({ success: true }, outputFormat) }]
|
|
207
217
|
};
|
|
208
218
|
}
|
|
209
219
|
const removeODataProps = (obj) => {
|
|
@@ -219,7 +229,7 @@ class GraphClient {
|
|
|
219
229
|
};
|
|
220
230
|
removeODataProps(data);
|
|
221
231
|
return {
|
|
222
|
-
content: [{ type: "text", text: this.serializeData(data,
|
|
232
|
+
content: [{ type: "text", text: this.serializeData(data, outputFormat, true) }]
|
|
223
233
|
};
|
|
224
234
|
}
|
|
225
235
|
}
|
package/dist/graph-tools.js
CHANGED
|
@@ -328,7 +328,10 @@ const UTILITY_TOOLS = [
|
|
|
328
328
|
accountAccessToken = await authManager.getTokenForAccount(accountParam);
|
|
329
329
|
}
|
|
330
330
|
const response = await graphClient.graphRequest(itemPath, {
|
|
331
|
-
accessToken: accountAccessToken
|
|
331
|
+
accessToken: accountAccessToken,
|
|
332
|
+
// We JSON.parse the metadata below, so force JSON - under --toon it'd be
|
|
333
|
+
// TOON and the parse would fail, masking a real item as "no download url".
|
|
334
|
+
forceJsonOutput: true
|
|
332
335
|
});
|
|
333
336
|
if (response?.isError) {
|
|
334
337
|
return response;
|
|
@@ -612,7 +615,6 @@ async function executeGraphTool(tool, config, graphClient, params, authManager)
|
|
|
612
615
|
logger.info(
|
|
613
616
|
`Making graph request to ${path2} with options: ${JSON.stringify(safeOptions)}${_redacted ? " [accessToken=REDACTED]" : ""}`
|
|
614
617
|
);
|
|
615
|
-
let response = await graphClient.graphRequest(path2, options);
|
|
616
618
|
const fetchAllPages = params.fetchAllPages === true;
|
|
617
619
|
const paginationEnabled = paginationAllowed();
|
|
618
620
|
if (fetchAllPages && !paginationEnabled) {
|
|
@@ -620,58 +622,69 @@ async function executeGraphTool(tool, config, graphClient, params, authManager)
|
|
|
620
622
|
"fetchAllPages requested but MS365_MCP_ALLOW_PAGINATION is disabled; returning first page only"
|
|
621
623
|
);
|
|
622
624
|
}
|
|
623
|
-
|
|
625
|
+
const mergePages = fetchAllPages && paginationEnabled;
|
|
626
|
+
if (mergePages) {
|
|
627
|
+
options.forceJsonOutput = true;
|
|
628
|
+
}
|
|
629
|
+
let response = await graphClient.graphRequest(path2, options);
|
|
630
|
+
if (mergePages && response?.content?.[0]?.text) {
|
|
631
|
+
let combinedResponse;
|
|
624
632
|
try {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
const
|
|
640
|
-
if (
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
633
|
+
combinedResponse = JSON.parse(response.content[0].text);
|
|
634
|
+
const firstValue = combinedResponse.value;
|
|
635
|
+
if (Array.isArray(firstValue)) {
|
|
636
|
+
let allItems = firstValue;
|
|
637
|
+
let nextLink = combinedResponse["@odata.nextLink"];
|
|
638
|
+
let pageCount = 1;
|
|
639
|
+
const maxPages = positiveIntFromEnv("MS365_MCP_MAX_PAGES", DEFAULT_MAX_PAGES);
|
|
640
|
+
const maxItems = positiveIntFromEnv("MS365_MCP_MAX_ITEMS", DEFAULT_MAX_ITEMS);
|
|
641
|
+
let deltaLink = combinedResponse["@odata.deltaLink"];
|
|
642
|
+
while (nextLink && pageCount < maxPages && allItems.length < maxItems) {
|
|
643
|
+
logger.info(`Fetching page ${pageCount + 1} from: ${nextLink}`);
|
|
644
|
+
const url = new URL(nextLink);
|
|
645
|
+
const nextPath = url.pathname.replace(/^\/(v1\.0|beta)/, "") + url.search;
|
|
646
|
+
const nextOptions = { ...options };
|
|
647
|
+
const nextResponse = await graphClient.graphRequest(nextPath, nextOptions);
|
|
648
|
+
if (nextResponse?.content?.[0]?.text) {
|
|
649
|
+
const nextJsonResponse = JSON.parse(nextResponse.content[0].text);
|
|
650
|
+
if (Array.isArray(nextJsonResponse.value)) {
|
|
651
|
+
allItems = allItems.concat(nextJsonResponse.value);
|
|
652
|
+
}
|
|
653
|
+
nextLink = nextJsonResponse["@odata.nextLink"];
|
|
654
|
+
if (nextJsonResponse["@odata.deltaLink"]) {
|
|
655
|
+
deltaLink = nextJsonResponse["@odata.deltaLink"];
|
|
656
|
+
}
|
|
657
|
+
pageCount++;
|
|
658
|
+
} else {
|
|
659
|
+
break;
|
|
646
660
|
}
|
|
647
|
-
pageCount++;
|
|
648
|
-
} else {
|
|
649
|
-
break;
|
|
650
661
|
}
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
662
|
+
if (pageCount >= maxPages) {
|
|
663
|
+
logger.warn(`Reached maximum page limit (${maxPages}) for pagination`);
|
|
664
|
+
}
|
|
665
|
+
if (allItems.length >= maxItems) {
|
|
666
|
+
logger.warn(
|
|
667
|
+
`Reached maximum item limit (${maxItems}) for pagination \u2014 truncated at ${allItems.length} items`
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
combinedResponse.value = allItems;
|
|
671
|
+
if (combinedResponse["@odata.count"]) {
|
|
672
|
+
combinedResponse["@odata.count"] = allItems.length;
|
|
673
|
+
}
|
|
674
|
+
delete combinedResponse["@odata.nextLink"];
|
|
675
|
+
if (deltaLink) {
|
|
676
|
+
combinedResponse["@odata.deltaLink"] = deltaLink;
|
|
677
|
+
}
|
|
678
|
+
logger.info(
|
|
679
|
+
`Pagination complete: collected ${allItems.length} items across ${pageCount} pages`
|
|
658
680
|
);
|
|
659
681
|
}
|
|
660
|
-
combinedResponse.value = allItems;
|
|
661
|
-
if (combinedResponse["@odata.count"]) {
|
|
662
|
-
combinedResponse["@odata.count"] = allItems.length;
|
|
663
|
-
}
|
|
664
|
-
delete combinedResponse["@odata.nextLink"];
|
|
665
|
-
if (deltaLink) {
|
|
666
|
-
combinedResponse["@odata.deltaLink"] = deltaLink;
|
|
667
|
-
}
|
|
668
|
-
response.content[0].text = JSON.stringify(combinedResponse);
|
|
669
|
-
logger.info(
|
|
670
|
-
`Pagination complete: collected ${allItems.length} items across ${pageCount} pages`
|
|
671
|
-
);
|
|
672
682
|
} catch (e) {
|
|
673
683
|
logger.error(`Error during pagination: ${e}`);
|
|
674
684
|
}
|
|
685
|
+
if (combinedResponse !== void 0) {
|
|
686
|
+
response.content[0].text = graphClient.serialize(combinedResponse);
|
|
687
|
+
}
|
|
675
688
|
}
|
|
676
689
|
if (response?.content?.[0]?.text) {
|
|
677
690
|
const responseText = response.content[0].text;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softeria/ms-365-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.130.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
|
@@ -1393,6 +1393,13 @@
|
|
|
1393
1393
|
"presets": ["teams", "work"],
|
|
1394
1394
|
"workScopes": ["ChatMessage.Read"]
|
|
1395
1395
|
},
|
|
1396
|
+
{
|
|
1397
|
+
"pathPattern": "/chats/{chat-id}/messages/{chatMessage-id}",
|
|
1398
|
+
"method": "patch",
|
|
1399
|
+
"toolName": "update-chat-message",
|
|
1400
|
+
"presets": ["teams", "work"],
|
|
1401
|
+
"workScopes": ["Chat.ReadWrite"]
|
|
1402
|
+
},
|
|
1396
1403
|
{
|
|
1397
1404
|
"pathPattern": "/chats/{chat-id}/messages/{chatMessage-id}/hostedContents",
|
|
1398
1405
|
"method": "get",
|
|
@@ -1472,6 +1479,13 @@
|
|
|
1472
1479
|
"presets": ["teams", "work"],
|
|
1473
1480
|
"workScopes": ["ChannelMessage.Read.All"]
|
|
1474
1481
|
},
|
|
1482
|
+
{
|
|
1483
|
+
"pathPattern": "/teams/{team-id}/channels/{channel-id}/messages/{chatMessage-id}",
|
|
1484
|
+
"method": "patch",
|
|
1485
|
+
"toolName": "update-channel-message",
|
|
1486
|
+
"presets": ["teams", "work"],
|
|
1487
|
+
"workScopes": ["ChannelMessage.ReadWrite"]
|
|
1488
|
+
},
|
|
1475
1489
|
{
|
|
1476
1490
|
"pathPattern": "/teams/{team-id}/channels/{channel-id}/messages/{chatMessage-id}/hostedContents",
|
|
1477
1491
|
"method": "get",
|
|
@@ -1503,6 +1517,13 @@
|
|
|
1503
1517
|
"presets": ["teams", "work"],
|
|
1504
1518
|
"workScopes": ["ChannelMessage.Read.All"]
|
|
1505
1519
|
},
|
|
1520
|
+
{
|
|
1521
|
+
"pathPattern": "/teams/{team-id}/channels/{channel-id}/messages/{chatMessage-id}/replies/{chatMessage-id1}",
|
|
1522
|
+
"method": "patch",
|
|
1523
|
+
"toolName": "update-channel-message-reply",
|
|
1524
|
+
"presets": ["teams", "work"],
|
|
1525
|
+
"workScopes": ["ChannelMessage.ReadWrite"]
|
|
1526
|
+
},
|
|
1506
1527
|
{
|
|
1507
1528
|
"pathPattern": "/teams/{team-id}/members",
|
|
1508
1529
|
"method": "post",
|