@softeria/ms-365-mcp-server 0.95.0 → 0.97.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/bin/modules/generate-mcp-tools.mjs +7 -0
- package/bin/modules/simplified-openapi.mjs +14 -4
- package/dist/__tests__/graph-tools.test.js +202 -0
- package/dist/endpoints.json +60 -53
- package/dist/generated/client.js +164 -93
- package/dist/graph-client.js +1 -0
- package/dist/graph-tools.js +216 -73
- package/dist/lib/tool-schema.js +24 -1
- package/dist/mcp-instructions.js +2 -1
- package/dist/server.js +2 -1
- package/dist/tool-categories.js +4 -4
- package/package.json +1 -1
- package/src/endpoints.json +60 -53
- package/src/generated/endpoint-types.ts +1 -1
package/dist/graph-tools.js
CHANGED
|
@@ -8,7 +8,7 @@ import { TOOL_CATEGORIES } from "./tool-categories.js";
|
|
|
8
8
|
import { getRequestTokens } from "./request-context.js";
|
|
9
9
|
import { parseTeamsUrl } from "./lib/teams-url-parser.js";
|
|
10
10
|
import { buildBM25Index, scoreQuery, tokenize } from "./lib/bm25.js";
|
|
11
|
-
import { describeToolSchema } from "./lib/tool-schema.js";
|
|
11
|
+
import { describeToolSchema, describeUtilityToolSchema } from "./lib/tool-schema.js";
|
|
12
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
13
13
|
const __dirname = path.dirname(__filename);
|
|
14
14
|
const endpointsData = JSON.parse(
|
|
@@ -34,6 +34,111 @@ function clampTopQueryParam(queryParams) {
|
|
|
34
34
|
logger.info(`Clamping $top from ${requested} to ${cap} (MS365_MCP_MAX_TOP)`);
|
|
35
35
|
queryParams["$top"] = String(cap);
|
|
36
36
|
}
|
|
37
|
+
const UTILITY_TOOLS = [
|
|
38
|
+
{
|
|
39
|
+
name: "parse-teams-url",
|
|
40
|
+
method: "POST",
|
|
41
|
+
path: "tool:parse-teams-url",
|
|
42
|
+
description: "Converts any Teams meeting URL format (short /meet/, full /meetup-join/, or recap ?threadId=) into a standard joinWebUrl. Use this before list-online-meetings when the user provides a recap or short URL.",
|
|
43
|
+
readOnlyHint: true,
|
|
44
|
+
openWorldHint: false,
|
|
45
|
+
buildSchema: () => ({
|
|
46
|
+
url: z.string().describe("Teams meeting URL in any format")
|
|
47
|
+
}),
|
|
48
|
+
execute: async (params) => {
|
|
49
|
+
const url = params.url;
|
|
50
|
+
if (typeof url !== "string") {
|
|
51
|
+
return {
|
|
52
|
+
content: [{ type: "text", text: JSON.stringify({ error: "url is required." }) }],
|
|
53
|
+
isError: true
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
const joinWebUrl = parseTeamsUrl(url);
|
|
58
|
+
return { content: [{ type: "text", text: joinWebUrl }] };
|
|
59
|
+
} catch (error) {
|
|
60
|
+
return {
|
|
61
|
+
content: [{ type: "text", text: JSON.stringify({ error: error.message }) }],
|
|
62
|
+
isError: true
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "download-bytes",
|
|
69
|
+
method: "GET",
|
|
70
|
+
path: "tool:download-bytes",
|
|
71
|
+
description: 'Download binary content from Microsoft Graph and return it as base64. Single tool for any binary read: drive file content, mail attachment, profile photo, Teams hosted content, meeting recording. Returns { contentType, encoding: "base64", contentLength, contentBytes }.',
|
|
72
|
+
readOnlyHint: true,
|
|
73
|
+
openWorldHint: true,
|
|
74
|
+
buildSchema: (ctx) => {
|
|
75
|
+
const schema = {
|
|
76
|
+
target: z.string().describe(
|
|
77
|
+
'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 (often large), use get-meeting-recording-content which returns a URL for out-of-band download by the client.'
|
|
78
|
+
)
|
|
79
|
+
};
|
|
80
|
+
if (ctx.multiAccount) {
|
|
81
|
+
schema["account"] = z.string().optional().describe(
|
|
82
|
+
"Account to use when multiple Microsoft accounts are configured. Required when multiple accounts exist (see list-accounts)."
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
return schema;
|
|
86
|
+
},
|
|
87
|
+
execute: async (params, { graphClient, authManager }) => {
|
|
88
|
+
const target = params.target;
|
|
89
|
+
const accountParam = params.account;
|
|
90
|
+
if (typeof target !== "string" || target.length === 0) {
|
|
91
|
+
return {
|
|
92
|
+
content: [
|
|
93
|
+
{
|
|
94
|
+
type: "text",
|
|
95
|
+
text: JSON.stringify({ error: "target is required and must be a non-empty string." })
|
|
96
|
+
}
|
|
97
|
+
],
|
|
98
|
+
isError: true
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (!target.startsWith("/")) {
|
|
102
|
+
return {
|
|
103
|
+
content: [
|
|
104
|
+
{
|
|
105
|
+
type: "text",
|
|
106
|
+
text: JSON.stringify({
|
|
107
|
+
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).'
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
],
|
|
111
|
+
isError: true
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
let accountAccessToken;
|
|
116
|
+
if (authManager && !authManager.isOAuthModeEnabled() && !getRequestTokens()) {
|
|
117
|
+
accountAccessToken = await authManager.getTokenForAccount(accountParam);
|
|
118
|
+
}
|
|
119
|
+
return await graphClient.graphRequest(target, { accessToken: accountAccessToken });
|
|
120
|
+
} catch (error) {
|
|
121
|
+
return {
|
|
122
|
+
content: [{ type: "text", text: JSON.stringify({ error: error.message }) }],
|
|
123
|
+
isError: true
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
];
|
|
129
|
+
function registerUtilityToolWithMcp(server, utility, ctx) {
|
|
130
|
+
server.tool(
|
|
131
|
+
utility.name,
|
|
132
|
+
utility.description,
|
|
133
|
+
utility.buildSchema(ctx),
|
|
134
|
+
{
|
|
135
|
+
title: utility.name,
|
|
136
|
+
readOnlyHint: utility.readOnlyHint ?? true,
|
|
137
|
+
openWorldHint: utility.openWorldHint ?? true
|
|
138
|
+
},
|
|
139
|
+
async (params) => utility.execute(params, ctx)
|
|
140
|
+
);
|
|
141
|
+
}
|
|
37
142
|
async function executeGraphTool(tool, config, graphClient, params, authManager) {
|
|
38
143
|
logger.info(`Tool ${tool.alias} called with params: ${JSON.stringify(params)}`);
|
|
39
144
|
try {
|
|
@@ -174,7 +279,12 @@ async function executeGraphTool(tool, config, graphClient, params, authManager)
|
|
|
174
279
|
headers
|
|
175
280
|
};
|
|
176
281
|
if (options.method !== "GET" && body) {
|
|
177
|
-
if (
|
|
282
|
+
if (tool.requestFormat === "binary" && typeof body === "string") {
|
|
283
|
+
options.body = Buffer.from(body, "base64");
|
|
284
|
+
if (!config?.contentType) {
|
|
285
|
+
headers["Content-Type"] = "application/octet-stream";
|
|
286
|
+
}
|
|
287
|
+
} else if (config?.contentType === "text/html") {
|
|
178
288
|
if (typeof body === "string") {
|
|
179
289
|
options.body = body;
|
|
180
290
|
} else if (typeof body === "object" && "content" in body) {
|
|
@@ -426,36 +536,19 @@ function registerGraphTools(server, graphClient, readOnly = false, enabledToolsP
|
|
|
426
536
|
if (multiAccount) {
|
|
427
537
|
logger.info('Multi-account mode: "account" parameter injected into all tool schemas');
|
|
428
538
|
}
|
|
429
|
-
|
|
539
|
+
const utilityCtx = {
|
|
540
|
+
graphClient,
|
|
541
|
+
authManager,
|
|
542
|
+
multiAccount,
|
|
543
|
+
accountNames
|
|
544
|
+
};
|
|
545
|
+
for (const utility of UTILITY_TOOLS) {
|
|
546
|
+
if (enabledToolsRegex && !enabledToolsRegex.test(utility.name)) continue;
|
|
430
547
|
try {
|
|
431
|
-
server
|
|
432
|
-
"parse-teams-url",
|
|
433
|
-
"Converts any Teams meeting URL format (short /meet/, full /meetup-join/, or recap ?threadId=) into a standard joinWebUrl. Use this before list-online-meetings when the user provides a recap or short URL.",
|
|
434
|
-
{
|
|
435
|
-
url: z.string().describe("Teams meeting URL in any format")
|
|
436
|
-
},
|
|
437
|
-
{
|
|
438
|
-
title: "parse-teams-url",
|
|
439
|
-
readOnlyHint: true,
|
|
440
|
-
openWorldHint: false
|
|
441
|
-
},
|
|
442
|
-
async ({ url }) => {
|
|
443
|
-
try {
|
|
444
|
-
const joinWebUrl = parseTeamsUrl(url);
|
|
445
|
-
return { content: [{ type: "text", text: joinWebUrl }] };
|
|
446
|
-
} catch (error) {
|
|
447
|
-
return {
|
|
448
|
-
content: [
|
|
449
|
-
{ type: "text", text: JSON.stringify({ error: error.message }) }
|
|
450
|
-
],
|
|
451
|
-
isError: true
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
);
|
|
548
|
+
registerUtilityToolWithMcp(server, utility, utilityCtx);
|
|
456
549
|
registeredCount++;
|
|
457
550
|
} catch (error) {
|
|
458
|
-
logger.error(`Failed to register tool
|
|
551
|
+
logger.error(`Failed to register tool ${utility.name}: ${error.message}`);
|
|
459
552
|
failedCount++;
|
|
460
553
|
}
|
|
461
554
|
}
|
|
@@ -481,7 +574,7 @@ function buildToolsRegistry(readOnly, orgMode) {
|
|
|
481
574
|
}
|
|
482
575
|
return toolsMap;
|
|
483
576
|
}
|
|
484
|
-
function buildDiscoverySearchIndex(toolsRegistry) {
|
|
577
|
+
function buildDiscoverySearchIndex(toolsRegistry, utilityTools = []) {
|
|
485
578
|
const TIP_EXCERPT_TOKENS = 12;
|
|
486
579
|
const DESC_CAP_TOKENS = 40;
|
|
487
580
|
const docs = [];
|
|
@@ -505,6 +598,14 @@ function buildDiscoverySearchIndex(toolsRegistry) {
|
|
|
505
598
|
];
|
|
506
599
|
docs.push({ id: name, tokens });
|
|
507
600
|
}
|
|
601
|
+
for (const utility of utilityTools) {
|
|
602
|
+
const nt = tokenize(utility.name);
|
|
603
|
+
nameTokens.set(utility.name, new Set(nt));
|
|
604
|
+
const pathTokens = tokenize(utility.path);
|
|
605
|
+
const descTokens = tokenize(utility.description).slice(0, DESC_CAP_TOKENS);
|
|
606
|
+
const tokens = [...nt, ...nt, ...nt, ...nt, ...nt, ...pathTokens, ...pathTokens, ...descTokens];
|
|
607
|
+
docs.push({ id: utility.name, tokens });
|
|
608
|
+
}
|
|
508
609
|
return { bm25: buildBM25Index(docs), nameTokens };
|
|
509
610
|
}
|
|
510
611
|
function scoreDiscoveryQuery(query, index) {
|
|
@@ -530,29 +631,51 @@ function scoreDiscoveryQuery(query, index) {
|
|
|
530
631
|
ranked.sort((a, b) => b.score - a.score);
|
|
531
632
|
return ranked;
|
|
532
633
|
}
|
|
533
|
-
function registerDiscoveryTools(server, graphClient, readOnly = false, orgMode = false, authManager,
|
|
634
|
+
function registerDiscoveryTools(server, graphClient, readOnly = false, orgMode = false, authManager, multiAccount = false, accountNames = []) {
|
|
534
635
|
const toolsRegistry = buildToolsRegistry(readOnly, orgMode);
|
|
535
|
-
const
|
|
536
|
-
|
|
636
|
+
const utilityTools = UTILITY_TOOLS;
|
|
637
|
+
const searchIndex = buildDiscoverySearchIndex(toolsRegistry, utilityTools);
|
|
638
|
+
const totalCount = toolsRegistry.size + utilityTools.length;
|
|
639
|
+
logger.info(
|
|
640
|
+
`Discovery mode: ${totalCount} tools (${toolsRegistry.size} Graph + ${utilityTools.length} utility)`
|
|
641
|
+
);
|
|
642
|
+
const utilityCtx = {
|
|
643
|
+
graphClient,
|
|
644
|
+
authManager,
|
|
645
|
+
multiAccount,
|
|
646
|
+
accountNames
|
|
647
|
+
};
|
|
648
|
+
const utilityByName = new Map(utilityTools.map((u) => [u.name, u]));
|
|
537
649
|
const categoryNames = Object.keys(TOOL_CATEGORIES).join(", ");
|
|
538
650
|
const toResultEntry = (name) => {
|
|
539
651
|
const entry = toolsRegistry.get(name);
|
|
540
|
-
if (
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
652
|
+
if (entry) {
|
|
653
|
+
const { tool, config } = entry;
|
|
654
|
+
return {
|
|
655
|
+
name,
|
|
656
|
+
method: tool.method.toUpperCase(),
|
|
657
|
+
path: tool.path,
|
|
658
|
+
description: tool.description || `${tool.method.toUpperCase()} ${tool.path}`,
|
|
659
|
+
...config?.llmTip ? { llmTip: config.llmTip } : {}
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
const utility = utilityByName.get(name);
|
|
663
|
+
if (utility) {
|
|
664
|
+
return {
|
|
665
|
+
name: utility.name,
|
|
666
|
+
method: utility.method,
|
|
667
|
+
path: utility.path,
|
|
668
|
+
description: utility.description
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
return null;
|
|
549
672
|
};
|
|
550
673
|
server.tool(
|
|
551
674
|
"search-tools",
|
|
552
|
-
`Search through ${toolsRegistry.size} Microsoft Graph API
|
|
675
|
+
`Search through ${totalCount} tools (${toolsRegistry.size} Microsoft Graph API operations + ${utilityTools.length} server utilities like download-bytes). Ranks results by BM25 over tool name, llmTip, description, and path. After picking a tool, call get-tool-schema for parameters, then execute-tool.`,
|
|
553
676
|
{
|
|
554
677
|
query: z.string().describe(
|
|
555
|
-
'Natural-language query. Tokenized and BM25-ranked. E.g. "send email", "
|
|
678
|
+
'Natural-language query. Tokenized and BM25-ranked. E.g. "send email", "download photo", "list unread messages".'
|
|
556
679
|
).optional(),
|
|
557
680
|
category: z.string().describe(`Optional pre-filter by category: ${categoryNames}`).optional(),
|
|
558
681
|
limit: z.number().describe("Maximum results (default: 10, max: 50)").optional()
|
|
@@ -571,7 +694,9 @@ function registerDiscoveryTools(server, graphClient, readOnly = false, orgMode =
|
|
|
571
694
|
const ranked = scoreDiscoveryQuery(query, searchIndex);
|
|
572
695
|
orderedNames = ranked.map((r) => r.id).filter(categoryFilter);
|
|
573
696
|
} else {
|
|
574
|
-
orderedNames = [...toolsRegistry.keys()].filter(
|
|
697
|
+
orderedNames = [...toolsRegistry.keys(), ...utilityTools.map((u) => u.name)].filter(
|
|
698
|
+
categoryFilter
|
|
699
|
+
);
|
|
575
700
|
}
|
|
576
701
|
const tools = orderedNames.slice(0, maxLimit).map(toResultEntry).filter(Boolean);
|
|
577
702
|
return {
|
|
@@ -581,7 +706,7 @@ function registerDiscoveryTools(server, graphClient, readOnly = false, orgMode =
|
|
|
581
706
|
text: JSON.stringify(
|
|
582
707
|
{
|
|
583
708
|
found: tools.length,
|
|
584
|
-
total:
|
|
709
|
+
total: totalCount,
|
|
585
710
|
tools,
|
|
586
711
|
tip: "Call get-tool-schema(tool_name) to see parameters before invoking execute-tool."
|
|
587
712
|
},
|
|
@@ -606,23 +731,30 @@ function registerDiscoveryTools(server, graphClient, readOnly = false, orgMode =
|
|
|
606
731
|
},
|
|
607
732
|
async ({ tool_name }) => {
|
|
608
733
|
const entry = toolsRegistry.get(tool_name);
|
|
609
|
-
if (
|
|
734
|
+
if (entry) {
|
|
735
|
+
const schema = describeToolSchema(entry.tool, entry.config?.llmTip);
|
|
610
736
|
return {
|
|
611
|
-
content: [
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
],
|
|
620
|
-
isError: true
|
|
737
|
+
content: [{ type: "text", text: JSON.stringify(schema, null, 2) }]
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
const utility = utilityByName.get(tool_name);
|
|
741
|
+
if (utility) {
|
|
742
|
+
const schema = describeUtilityToolSchema(utility, utilityCtx);
|
|
743
|
+
return {
|
|
744
|
+
content: [{ type: "text", text: JSON.stringify(schema, null, 2) }]
|
|
621
745
|
};
|
|
622
746
|
}
|
|
623
|
-
const schema = describeToolSchema(entry.tool, entry.config?.llmTip);
|
|
624
747
|
return {
|
|
625
|
-
content: [
|
|
748
|
+
content: [
|
|
749
|
+
{
|
|
750
|
+
type: "text",
|
|
751
|
+
text: JSON.stringify({
|
|
752
|
+
error: `Tool not found: ${tool_name}`,
|
|
753
|
+
tip: "Use search-tools to find available tools."
|
|
754
|
+
})
|
|
755
|
+
}
|
|
756
|
+
],
|
|
757
|
+
isError: true
|
|
626
758
|
};
|
|
627
759
|
}
|
|
628
760
|
);
|
|
@@ -643,25 +775,36 @@ function registerDiscoveryTools(server, graphClient, readOnly = false, orgMode =
|
|
|
643
775
|
},
|
|
644
776
|
async ({ tool_name, parameters = {} }) => {
|
|
645
777
|
const toolData = toolsRegistry.get(tool_name);
|
|
646
|
-
if (
|
|
647
|
-
return
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
};
|
|
778
|
+
if (toolData) {
|
|
779
|
+
return executeGraphTool(
|
|
780
|
+
toolData.tool,
|
|
781
|
+
toolData.config,
|
|
782
|
+
graphClient,
|
|
783
|
+
parameters,
|
|
784
|
+
authManager
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
const utility = utilityByName.get(tool_name);
|
|
788
|
+
if (utility) {
|
|
789
|
+
return utility.execute(parameters, utilityCtx);
|
|
659
790
|
}
|
|
660
|
-
return
|
|
791
|
+
return {
|
|
792
|
+
content: [
|
|
793
|
+
{
|
|
794
|
+
type: "text",
|
|
795
|
+
text: JSON.stringify({
|
|
796
|
+
error: `Tool not found: ${tool_name}`,
|
|
797
|
+
tip: "Use search-tools to find available tools."
|
|
798
|
+
})
|
|
799
|
+
}
|
|
800
|
+
],
|
|
801
|
+
isError: true
|
|
802
|
+
};
|
|
661
803
|
}
|
|
662
804
|
);
|
|
663
805
|
}
|
|
664
806
|
export {
|
|
807
|
+
UTILITY_TOOLS,
|
|
665
808
|
buildDiscoverySearchIndex,
|
|
666
809
|
buildToolsRegistry,
|
|
667
810
|
registerDiscoveryTools,
|
package/dist/lib/tool-schema.js
CHANGED
|
@@ -30,6 +30,29 @@ function describeToolSchema(tool, llmTip) {
|
|
|
30
30
|
parameters: params
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
|
+
function describeUtilityToolSchema(utility, ctx) {
|
|
34
|
+
const schemaMap = utility.buildSchema(ctx);
|
|
35
|
+
const params = Object.entries(schemaMap).map(([name, zodSchema]) => {
|
|
36
|
+
const { inner, optional } = unwrapOptional(zodSchema);
|
|
37
|
+
const jsonSchema = zodToJsonSchema(inner, { target: "jsonSchema7", $refStrategy: "none" });
|
|
38
|
+
const { $schema: _s, ...schema } = jsonSchema;
|
|
39
|
+
return {
|
|
40
|
+
name,
|
|
41
|
+
in: "Query",
|
|
42
|
+
required: !optional,
|
|
43
|
+
description: zodSchema.description,
|
|
44
|
+
schema
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
return {
|
|
48
|
+
name: utility.name,
|
|
49
|
+
method: utility.method,
|
|
50
|
+
path: utility.path,
|
|
51
|
+
description: utility.description,
|
|
52
|
+
parameters: params
|
|
53
|
+
};
|
|
54
|
+
}
|
|
33
55
|
export {
|
|
34
|
-
describeToolSchema
|
|
56
|
+
describeToolSchema,
|
|
57
|
+
describeUtilityToolSchema
|
|
35
58
|
};
|
package/dist/mcp-instructions.js
CHANGED
|
@@ -5,7 +5,8 @@ function buildGeneralMcpInstructions(opts) {
|
|
|
5
5
|
"Mail and message $search uses KQL; the $search query parameter value must be double-quoted per Graph (see search-query-parameter in Microsoft Graph docs).",
|
|
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
|
-
"Teams chat and channel messages: prefer HTML contentType in the body; plain text is often mangled by Graph."
|
|
8
|
+
"Teams chat and channel messages: prefer HTML contentType in the body; plain text is often mangled by Graph.",
|
|
9
|
+
"Files / binary content: use download-bytes for any binary read (drive file content, mail attachments, profile photos, Teams hosted content, meeting recordings); pass it a Graph path or an absolute @microsoft.graph.downloadUrl from a metadata response. For uploads, upload-file-content takes a base64 string body up to 4MB; use create-upload-session above that."
|
|
9
10
|
];
|
|
10
11
|
if (opts.readOnly) parts.push("This server is read-only; write operations are disabled.");
|
|
11
12
|
if (opts.multiAccount)
|
package/dist/server.js
CHANGED
package/dist/tool-categories.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const TOOL_CATEGORIES = {
|
|
2
2
|
mail: {
|
|
3
3
|
name: "mail",
|
|
4
|
-
pattern: /mail|attachment|draft/i,
|
|
4
|
+
pattern: /mail|attachment|draft|download-bytes/i,
|
|
5
5
|
description: "Email operations (read, send, manage folders, attachments)"
|
|
6
6
|
},
|
|
7
7
|
calendar: {
|
|
@@ -16,12 +16,12 @@ const TOOL_CATEGORIES = {
|
|
|
16
16
|
},
|
|
17
17
|
personal: {
|
|
18
18
|
name: "personal",
|
|
19
|
-
pattern: /mail|calendar|drive|contact|todo|onenote|attachment|draft|event|file|folder|search|query/i,
|
|
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
23
|
name: "work",
|
|
24
|
-
pattern: /team|channel|chat|sharepoint|planner|site|list|shared|search|query/i,
|
|
24
|
+
pattern: /team|channel|chat|sharepoint|planner|site|list|shared|search|query|download-bytes/i,
|
|
25
25
|
description: "Organization/work tools (Teams, SharePoint, shared mailboxes, search)",
|
|
26
26
|
requiresOrgMode: true
|
|
27
27
|
},
|
|
@@ -52,7 +52,7 @@ const TOOL_CATEGORIES = {
|
|
|
52
52
|
},
|
|
53
53
|
users: {
|
|
54
54
|
name: "users",
|
|
55
|
-
pattern: /user|list-users/i,
|
|
55
|
+
pattern: /user|list-users|download-bytes/i,
|
|
56
56
|
description: "User directory access",
|
|
57
57
|
requiresOrgMode: true
|
|
58
58
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softeria/ms-365-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.97.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",
|