@softeria/ms-365-mcp-server 0.132.1 → 0.133.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 +142 -0
- package/dist/graph-tools.js +160 -2
- package/dist/mcp-instructions.js +1 -1
- package/dist/server.js +4 -2
- package/dist/tool-categories.js +1 -1
- package/package.json +1 -1
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
2
|
import { z } from "zod";
|
|
3
|
+
import { tmpdir } from "os";
|
|
4
|
+
import { join } from "path";
|
|
5
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
3
6
|
vi.mock("../logger.js", () => ({
|
|
4
7
|
default: {
|
|
5
8
|
info: vi.fn(),
|
|
@@ -744,6 +747,145 @@ describe("graph-tools", () => {
|
|
|
744
747
|
expect(payload.error).toMatch(/relative Microsoft Graph path/);
|
|
745
748
|
});
|
|
746
749
|
});
|
|
750
|
+
describe("download-bytes-to-file", () => {
|
|
751
|
+
let tmpDir;
|
|
752
|
+
beforeEach(() => {
|
|
753
|
+
tmpDir = mkdtempSync(join(tmpdir(), "dbtf-"));
|
|
754
|
+
});
|
|
755
|
+
afterEach(() => {
|
|
756
|
+
rmSync(tmpDir, { recursive: true, force: true });
|
|
757
|
+
});
|
|
758
|
+
it("writes decoded binary bytes to the output path and returns metadata", async () => {
|
|
759
|
+
mockEndpoints.length = 0;
|
|
760
|
+
mockEndpointsJson = [];
|
|
761
|
+
const graphClient = {
|
|
762
|
+
makeRequest: vi.fn().mockResolvedValue({
|
|
763
|
+
message: "OK!",
|
|
764
|
+
contentType: "image/jpeg",
|
|
765
|
+
encoding: "base64",
|
|
766
|
+
contentLength: 2,
|
|
767
|
+
contentBytes: "aGk="
|
|
768
|
+
})
|
|
769
|
+
};
|
|
770
|
+
const server = createMockServer();
|
|
771
|
+
const { registerGraphTools } = await loadModule();
|
|
772
|
+
registerGraphTools(server, graphClient);
|
|
773
|
+
const tool = server.tools.get("download-bytes-to-file");
|
|
774
|
+
expect(tool).toBeDefined();
|
|
775
|
+
const outputPath = join(tmpDir, "photo.jpg");
|
|
776
|
+
const result = await tool.handler({ target: "/me/photo/$value", outputPath });
|
|
777
|
+
expect(graphClient.makeRequest).toHaveBeenCalledTimes(1);
|
|
778
|
+
const [reqPath, options] = graphClient.makeRequest.mock.calls[0];
|
|
779
|
+
expect(reqPath).toBe("/me/photo/$value");
|
|
780
|
+
expect(options.rawResponse).toBe(true);
|
|
781
|
+
expect(result.isError).toBeUndefined();
|
|
782
|
+
const payload = JSON.parse(result.content[0].text);
|
|
783
|
+
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
|
+
});
|
|
805
|
+
it("rejects a relative outputPath", async () => {
|
|
806
|
+
mockEndpoints.length = 0;
|
|
807
|
+
mockEndpointsJson = [];
|
|
808
|
+
const graphClient = { makeRequest: vi.fn() };
|
|
809
|
+
const server = createMockServer();
|
|
810
|
+
const { registerGraphTools } = await loadModule();
|
|
811
|
+
registerGraphTools(server, graphClient);
|
|
812
|
+
const result = await server.tools.get("download-bytes-to-file").handler({ target: "/me/photo/$value", outputPath: "relative/path.jpg" });
|
|
813
|
+
expect(result.isError).toBe(true);
|
|
814
|
+
const payload = JSON.parse(result.content[0].text);
|
|
815
|
+
expect(payload.error).toMatch(/absolute path/);
|
|
816
|
+
expect(graphClient.makeRequest).not.toHaveBeenCalled();
|
|
817
|
+
});
|
|
818
|
+
it("rejects absolute URLs in target (Graph paths only)", async () => {
|
|
819
|
+
mockEndpoints.length = 0;
|
|
820
|
+
mockEndpointsJson = [];
|
|
821
|
+
const server = createMockServer();
|
|
822
|
+
const { registerGraphTools } = await loadModule();
|
|
823
|
+
registerGraphTools(server, {});
|
|
824
|
+
const result = await server.tools.get("download-bytes-to-file").handler({
|
|
825
|
+
target: "https://example.sharepoint.com/d/abc?temp=signed",
|
|
826
|
+
outputPath: join(tmpDir, "x.bin")
|
|
827
|
+
});
|
|
828
|
+
expect(result.isError).toBe(true);
|
|
829
|
+
const payload = JSON.parse(result.content[0].text);
|
|
830
|
+
expect(payload.error).toMatch(/relative Microsoft Graph path/);
|
|
831
|
+
});
|
|
832
|
+
it("refuses to overwrite an existing file and skips the Graph call", async () => {
|
|
833
|
+
mockEndpoints.length = 0;
|
|
834
|
+
mockEndpointsJson = [];
|
|
835
|
+
const outputPath = join(tmpDir, "existing.bin");
|
|
836
|
+
writeFileSync(outputPath, "original");
|
|
837
|
+
const graphClient = { makeRequest: vi.fn() };
|
|
838
|
+
const server = createMockServer();
|
|
839
|
+
const { registerGraphTools } = await loadModule();
|
|
840
|
+
registerGraphTools(server, graphClient);
|
|
841
|
+
const result = await server.tools.get("download-bytes-to-file").handler({ target: "/me/photo/$value", outputPath });
|
|
842
|
+
expect(result.isError).toBe(true);
|
|
843
|
+
const payload = JSON.parse(result.content[0].text);
|
|
844
|
+
expect(payload.error).toMatch(/already exists/);
|
|
845
|
+
expect(graphClient.makeRequest).not.toHaveBeenCalled();
|
|
846
|
+
expect(readFileSync(outputPath).toString("utf8")).toBe("original");
|
|
847
|
+
});
|
|
848
|
+
it("surfaces a Graph error and writes no file when makeRequest throws", async () => {
|
|
849
|
+
mockEndpoints.length = 0;
|
|
850
|
+
mockEndpointsJson = [];
|
|
851
|
+
const graphClient = {
|
|
852
|
+
makeRequest: vi.fn().mockRejectedValue(new Error("Microsoft Graph API error: 404 Not Found"))
|
|
853
|
+
};
|
|
854
|
+
const server = createMockServer();
|
|
855
|
+
const { registerGraphTools } = await loadModule();
|
|
856
|
+
registerGraphTools(server, graphClient);
|
|
857
|
+
const outputPath = join(tmpDir, "missing.bin");
|
|
858
|
+
const result = await server.tools.get("download-bytes-to-file").handler({ target: "/me/messages/abc/attachments/xyz/$value", outputPath });
|
|
859
|
+
expect(result.isError).toBe(true);
|
|
860
|
+
const payload = JSON.parse(result.content[0].text);
|
|
861
|
+
expect(payload.error).toMatch(/404 Not Found/);
|
|
862
|
+
expect(existsSync(outputPath)).toBe(false);
|
|
863
|
+
});
|
|
864
|
+
it("is registered in stdio mode but hidden in HTTP mode", async () => {
|
|
865
|
+
mockEndpoints.length = 0;
|
|
866
|
+
mockEndpointsJson = [];
|
|
867
|
+
const { registerGraphTools } = await loadModule();
|
|
868
|
+
const stdioServer = createMockServer();
|
|
869
|
+
registerGraphTools(stdioServer, {});
|
|
870
|
+
expect(stdioServer.tools.has("download-bytes-to-file")).toBe(true);
|
|
871
|
+
expect(stdioServer.tools.has("download-bytes")).toBe(true);
|
|
872
|
+
const httpServer = createMockServer();
|
|
873
|
+
registerGraphTools(
|
|
874
|
+
httpServer,
|
|
875
|
+
{},
|
|
876
|
+
false,
|
|
877
|
+
void 0,
|
|
878
|
+
false,
|
|
879
|
+
void 0,
|
|
880
|
+
false,
|
|
881
|
+
[],
|
|
882
|
+
void 0,
|
|
883
|
+
true
|
|
884
|
+
);
|
|
885
|
+
expect(httpServer.tools.has("download-bytes-to-file")).toBe(false);
|
|
886
|
+
expect(httpServer.tools.has("download-bytes")).toBe(true);
|
|
887
|
+
});
|
|
888
|
+
});
|
|
747
889
|
describe("get-download-url", () => {
|
|
748
890
|
it("strips /content, fetches item metadata, and returns the pre-authed downloadUrl", async () => {
|
|
749
891
|
mockEndpoints.length = 0;
|
package/dist/graph-tools.js
CHANGED
|
@@ -12,6 +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 { writeFile, access } from "fs/promises";
|
|
15
16
|
import path from "path";
|
|
16
17
|
import { fileURLToPath } from "url";
|
|
17
18
|
import { TOOL_CATEGORIES } from "./tool-categories.js";
|
|
@@ -186,6 +187,161 @@ const UTILITY_TOOLS = [
|
|
|
186
187
|
}
|
|
187
188
|
}
|
|
188
189
|
},
|
|
190
|
+
{
|
|
191
|
+
name: "download-bytes-to-file",
|
|
192
|
+
method: "GET",
|
|
193
|
+
path: "tool:download-bytes-to-file",
|
|
194
|
+
searchKeywords: "save to disk save to file write file to disk save attachment to disk save recording to disk write bytes to local file output path",
|
|
195
|
+
// Front-loaded on purpose: the discovery search index caps a tool's
|
|
196
|
+
// description at ~40 tokens, so the OneDrive/SharePoint guidance below
|
|
197
|
+
// sits past the cap. That keeps the hint for the reading LLM while letting
|
|
198
|
+
// get-download-url own the high-signal "drive"/"sharepoint" search terms.
|
|
199
|
+
description: "Write authenticated Microsoft Graph byte content to a local file on the server, returning { path, contentType, bytesWritten } instead of base64. The only out-of-band way to save mail attachments and meeting recordings, whose bytes are exposed solely through authenticated endpoints. Also handles profile photos and Teams hosted content. Writes to an absolute outputPath and never overwrites an existing file. stdio mode only: not available over HTTP. For OneDrive or SharePoint file content, get-download-url is preferred \u2014 it returns a pre-authenticated URL for fully out-of-band download without the server fetching the bytes.",
|
|
200
|
+
readOnlyHint: true,
|
|
201
|
+
openWorldHint: true,
|
|
202
|
+
stdioOnly: true,
|
|
203
|
+
buildSchema: (ctx) => {
|
|
204
|
+
const schema = {
|
|
205
|
+
target: z.string().describe(
|
|
206
|
+
'Relative Microsoft Graph path starting with "/". Common paths: /drives/{drive-id}/items/{driveItem-id}/content (drive file content); /me/messages/{message-id}/attachments/{attachment-id}/$value (mail attachment, list-mail-attachments returns the IDs); /me/photo/$value or /users/{user-id}/photo/$value (profile photo); /chats/{chat-id}/messages/{chatMessage-id}/hostedContents/{chatMessageHostedContent-id}/$value (Teams chat hosted content, list-chat-message-hosted-contents returns the IDs); /teams/{team-id}/channels/{channel-id}/messages/{chatMessage-id}/hostedContents/{chatMessageHostedContent-id}/$value (Teams channel hosted content). For meeting recordings, use get-meeting-recording-content where available; Microsoft Graph returns authenticated recording bytes, not a pre-authenticated download URL.'
|
|
207
|
+
),
|
|
208
|
+
outputPath: z.string().describe(
|
|
209
|
+
"Absolute path on the server's filesystem where the bytes are written, e.g. /Users/me/downloads/invoice.pdf. Must be absolute; relative paths are rejected. The parent directory must already exist, and an existing file is never overwritten (the call errors if outputPath already exists)."
|
|
210
|
+
)
|
|
211
|
+
};
|
|
212
|
+
if (ctx.multiAccount) {
|
|
213
|
+
schema["account"] = z.string().optional().describe(
|
|
214
|
+
"Account to use when multiple Microsoft accounts are configured. Required when multiple accounts exist (see list-accounts)."
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
return schema;
|
|
218
|
+
},
|
|
219
|
+
execute: async (params, { graphClient, authManager }) => {
|
|
220
|
+
const target = params.target;
|
|
221
|
+
const outputPath = params.outputPath;
|
|
222
|
+
const accountParam = params.account;
|
|
223
|
+
if (typeof target !== "string" || target.length === 0) {
|
|
224
|
+
return {
|
|
225
|
+
content: [
|
|
226
|
+
{
|
|
227
|
+
type: "text",
|
|
228
|
+
text: JSON.stringify({ error: "target is required and must be a non-empty string." })
|
|
229
|
+
}
|
|
230
|
+
],
|
|
231
|
+
isError: true
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
if (!target.startsWith("/")) {
|
|
235
|
+
return {
|
|
236
|
+
content: [
|
|
237
|
+
{
|
|
238
|
+
type: "text",
|
|
239
|
+
text: JSON.stringify({
|
|
240
|
+
error: 'target must be a relative Microsoft Graph path starting with "/", e.g. /me/photo/$value or /drives/{drive-id}/items/{driveItem-id}/content. Absolute URLs are not accepted; if you have an @microsoft.graph.downloadUrl, use the equivalent /content or /$value path instead (Graph 302-redirects to the same bytes).'
|
|
241
|
+
})
|
|
242
|
+
}
|
|
243
|
+
],
|
|
244
|
+
isError: true
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
if (typeof outputPath !== "string" || outputPath.length === 0) {
|
|
248
|
+
return {
|
|
249
|
+
content: [
|
|
250
|
+
{
|
|
251
|
+
type: "text",
|
|
252
|
+
text: JSON.stringify({
|
|
253
|
+
error: "outputPath is required and must be a non-empty string."
|
|
254
|
+
})
|
|
255
|
+
}
|
|
256
|
+
],
|
|
257
|
+
isError: true
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
if (!path.isAbsolute(outputPath)) {
|
|
261
|
+
return {
|
|
262
|
+
content: [
|
|
263
|
+
{
|
|
264
|
+
type: "text",
|
|
265
|
+
text: JSON.stringify({
|
|
266
|
+
error: `outputPath must be an absolute path, e.g. /Users/me/downloads/file.ext. Received: ${outputPath}`
|
|
267
|
+
})
|
|
268
|
+
}
|
|
269
|
+
],
|
|
270
|
+
isError: true
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
let fileExists = false;
|
|
274
|
+
try {
|
|
275
|
+
await access(outputPath);
|
|
276
|
+
fileExists = true;
|
|
277
|
+
} catch {
|
|
278
|
+
}
|
|
279
|
+
if (fileExists) {
|
|
280
|
+
return {
|
|
281
|
+
content: [
|
|
282
|
+
{
|
|
283
|
+
type: "text",
|
|
284
|
+
text: JSON.stringify({ error: `file already exists at ${outputPath}` })
|
|
285
|
+
}
|
|
286
|
+
],
|
|
287
|
+
isError: true
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
try {
|
|
291
|
+
const accountModeError = await checkAccountParamInBearerMode(accountParam, authManager);
|
|
292
|
+
if (accountModeError) {
|
|
293
|
+
return {
|
|
294
|
+
content: [{ type: "text", text: JSON.stringify({ error: accountModeError }) }],
|
|
295
|
+
isError: true
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
let accountAccessToken;
|
|
299
|
+
if (authManager && !authManager.isOAuthModeEnabled() && !getRequestTokens()) {
|
|
300
|
+
accountAccessToken = await authManager.getTokenForAccount(accountParam);
|
|
301
|
+
}
|
|
302
|
+
const result = await graphClient.makeRequest(target, {
|
|
303
|
+
accessToken: accountAccessToken,
|
|
304
|
+
rawResponse: true
|
|
305
|
+
});
|
|
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
|
+
return {
|
|
326
|
+
content: [
|
|
327
|
+
{
|
|
328
|
+
type: "text",
|
|
329
|
+
text: JSON.stringify({
|
|
330
|
+
path: outputPath,
|
|
331
|
+
contentType: result.contentType,
|
|
332
|
+
bytesWritten: buffer.byteLength
|
|
333
|
+
})
|
|
334
|
+
}
|
|
335
|
+
]
|
|
336
|
+
};
|
|
337
|
+
} catch (error) {
|
|
338
|
+
return {
|
|
339
|
+
content: [{ type: "text", text: JSON.stringify({ error: error.message }) }],
|
|
340
|
+
isError: true
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
},
|
|
189
345
|
{
|
|
190
346
|
name: "get-download-url",
|
|
191
347
|
method: "GET",
|
|
@@ -810,7 +966,7 @@ async function executeGraphTool(tool, config, graphClient, params, authManager)
|
|
|
810
966
|
};
|
|
811
967
|
}
|
|
812
968
|
}
|
|
813
|
-
function registerGraphTools(server, graphClient, readOnly = false, enabledToolsPattern, orgMode = false, authManager, multiAccount = false, accountNames = [], allowedScopesValue) {
|
|
969
|
+
function registerGraphTools(server, graphClient, readOnly = false, enabledToolsPattern, orgMode = false, authManager, multiAccount = false, accountNames = [], allowedScopesValue, httpMode = false) {
|
|
814
970
|
let enabledToolsRegex;
|
|
815
971
|
if (enabledToolsPattern) {
|
|
816
972
|
try {
|
|
@@ -983,6 +1139,7 @@ function registerGraphTools(server, graphClient, readOnly = false, enabledToolsP
|
|
|
983
1139
|
};
|
|
984
1140
|
for (const utility of UTILITY_TOOLS) {
|
|
985
1141
|
if (readOnly && !utility.readOnlyHint) continue;
|
|
1142
|
+
if (httpMode && utility.stdioOnly) continue;
|
|
986
1143
|
if (enabledToolsRegex && !enabledToolsRegex.test(utility.name)) continue;
|
|
987
1144
|
try {
|
|
988
1145
|
registerUtilityToolWithMcp(server, utility, utilityCtx);
|
|
@@ -1098,7 +1255,7 @@ function scoreDiscoveryQuery(query, index) {
|
|
|
1098
1255
|
ranked.sort((a, b) => b.score - a.score);
|
|
1099
1256
|
return ranked;
|
|
1100
1257
|
}
|
|
1101
|
-
function registerDiscoveryTools(server, graphClient, readOnly = false, orgMode = false, authManager, multiAccount = false, accountNames = [], enabledTools, allowedScopesValue) {
|
|
1258
|
+
function registerDiscoveryTools(server, graphClient, readOnly = false, orgMode = false, authManager, multiAccount = false, accountNames = [], enabledTools, allowedScopesValue, httpMode = false) {
|
|
1102
1259
|
let enabledToolsRegex;
|
|
1103
1260
|
if (enabledTools) {
|
|
1104
1261
|
try {
|
|
@@ -1125,6 +1282,7 @@ function registerDiscoveryTools(server, graphClient, readOnly = false, orgMode =
|
|
|
1125
1282
|
}
|
|
1126
1283
|
const utilityTools = UTILITY_TOOLS.filter((u) => {
|
|
1127
1284
|
if (readOnly && !u.readOnlyHint) return false;
|
|
1285
|
+
if (httpMode && u.stdioOnly) return false;
|
|
1128
1286
|
if (enabledToolsRegex && !enabledToolsRegex.test(u.name)) return false;
|
|
1129
1287
|
return true;
|
|
1130
1288
|
});
|
package/dist/mcp-instructions.js
CHANGED
|
@@ -6,7 +6,7 @@ function buildGeneralMcpInstructions(opts) {
|
|
|
6
6
|
"When you need an organizational user or recipient address, resolve it with list-users (or another directory tool); do not invent SMTP addresses.",
|
|
7
7
|
"Directory $search on collections such as /users or /groups requires ConsistencyLevel: eventual when the tool exposes that header.",
|
|
8
8
|
"Teams chat and channel messages: prefer HTML contentType in the body; plain text is often mangled by Graph.",
|
|
9
|
-
"Files / binary content: for large drive/SharePoint file content, prefer get-download-url to resolve a pre-authenticated URL for out-of-band download. Use download-bytes for authenticated byte reads such as mail attachments, profile photos, Teams hosted content, and meeting recordings.
|
|
9
|
+
"Files / binary content: for large drive/SharePoint file content, prefer get-download-url to resolve a pre-authenticated URL for out-of-band download. Use download-bytes for authenticated byte reads such as mail attachments, profile photos, Teams hosted content, and meeting recordings. In stdio mode, download-bytes-to-file writes those same authenticated bytes straight to a local absolute path instead of returning base64 \u2014 the only out-of-band option for large mail attachments and meeting recordings, which get-download-url cannot handle. These tools take relative Microsoft Graph paths, not absolute URLs. For uploads, upload-file-content takes a base64 string body up to 4MB; use create-upload-session above that."
|
|
10
10
|
];
|
|
11
11
|
if (opts.readOnly) parts.push("This server is read-only; write operations are disabled.");
|
|
12
12
|
if (opts.multiAccount)
|
package/dist/server.js
CHANGED
|
@@ -88,7 +88,8 @@ class MicrosoftGraphServer {
|
|
|
88
88
|
this.multiAccount,
|
|
89
89
|
this.accountNames,
|
|
90
90
|
this.options.enabledTools,
|
|
91
|
-
this.options.allowedScopes
|
|
91
|
+
this.options.allowedScopes,
|
|
92
|
+
Boolean(this.options.http)
|
|
92
93
|
);
|
|
93
94
|
} else {
|
|
94
95
|
registerGraphTools(
|
|
@@ -100,7 +101,8 @@ class MicrosoftGraphServer {
|
|
|
100
101
|
this.authManager,
|
|
101
102
|
this.multiAccount,
|
|
102
103
|
this.accountNames,
|
|
103
|
-
this.options.allowedScopes
|
|
104
|
+
this.options.allowedScopes,
|
|
105
|
+
Boolean(this.options.http)
|
|
104
106
|
);
|
|
105
107
|
}
|
|
106
108
|
installToolSchemaRefNormalization(server);
|
package/dist/tool-categories.js
CHANGED
|
@@ -53,7 +53,7 @@ const PRESET_META = {
|
|
|
53
53
|
requiresOrgMode: true
|
|
54
54
|
}
|
|
55
55
|
};
|
|
56
|
-
const UNIVERSAL_UTILITY_TOOLS = ["download-bytes"];
|
|
56
|
+
const UNIVERSAL_UTILITY_TOOLS = ["download-bytes", "download-bytes-to-file"];
|
|
57
57
|
const SCOPED_UTILITY_TOOLS = {
|
|
58
58
|
"get-download-url": ["files", "onedrive", "personal", "work", "search"],
|
|
59
59
|
"parse-teams-url": ["teams", "work"]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softeria/ms-365-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.133.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",
|