@softeria/ms-365-mcp-server 0.97.0 → 0.99.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 +82 -0
- package/dist/endpoints.json +6 -0
- package/dist/generated/client.js +62 -0
- package/dist/graph-tools.js +23 -4
- package/dist/server.js +2 -1
- package/package.json +1 -1
- package/src/endpoints.json +6 -0
|
@@ -643,4 +643,86 @@ describe("graph-tools", () => {
|
|
|
643
643
|
expect(payload.error).toMatch(/not found/i);
|
|
644
644
|
});
|
|
645
645
|
});
|
|
646
|
+
describe("discovery mode: --enabled-tools filter", () => {
|
|
647
|
+
it("search-tools only surfaces Graph tools matching the regex", async () => {
|
|
648
|
+
mockEndpoints.push(
|
|
649
|
+
{
|
|
650
|
+
alias: "list-mail-messages",
|
|
651
|
+
method: "get",
|
|
652
|
+
path: "/me/messages",
|
|
653
|
+
description: "List mail",
|
|
654
|
+
parameters: []
|
|
655
|
+
},
|
|
656
|
+
{
|
|
657
|
+
alias: "list-calendar-events",
|
|
658
|
+
method: "get",
|
|
659
|
+
path: "/me/events",
|
|
660
|
+
description: "List events",
|
|
661
|
+
parameters: []
|
|
662
|
+
}
|
|
663
|
+
);
|
|
664
|
+
mockEndpointsJson = [
|
|
665
|
+
{ toolName: "list-mail-messages", method: "get", pathPattern: "/me/messages" },
|
|
666
|
+
{ toolName: "list-calendar-events", method: "get", pathPattern: "/me/events" }
|
|
667
|
+
];
|
|
668
|
+
const server = createMockServer();
|
|
669
|
+
const { registerDiscoveryTools } = await loadModule();
|
|
670
|
+
registerDiscoveryTools(server, {}, false, false, void 0, false, [], "mail");
|
|
671
|
+
const result = await server.tools.get("search-tools").handler({ limit: 50 });
|
|
672
|
+
const found = JSON.parse(result.content[0].text).tools.map((t) => t.name);
|
|
673
|
+
expect(found).toContain("list-mail-messages");
|
|
674
|
+
expect(found).not.toContain("list-calendar-events");
|
|
675
|
+
});
|
|
676
|
+
it("utility tools obey the regex too", async () => {
|
|
677
|
+
mockEndpoints.length = 0;
|
|
678
|
+
mockEndpointsJson = [];
|
|
679
|
+
const server = createMockServer();
|
|
680
|
+
const { registerDiscoveryTools } = await loadModule();
|
|
681
|
+
registerDiscoveryTools(
|
|
682
|
+
server,
|
|
683
|
+
{},
|
|
684
|
+
false,
|
|
685
|
+
false,
|
|
686
|
+
void 0,
|
|
687
|
+
false,
|
|
688
|
+
[],
|
|
689
|
+
"^download-bytes$"
|
|
690
|
+
);
|
|
691
|
+
const result = await server.tools.get("search-tools").handler({ limit: 50 });
|
|
692
|
+
const found = JSON.parse(result.content[0].text).tools.map((t) => t.name);
|
|
693
|
+
expect(found).toContain("download-bytes");
|
|
694
|
+
expect(found).not.toContain("parse-teams-url");
|
|
695
|
+
});
|
|
696
|
+
it("invalid regex pattern is ignored, all tools surface", async () => {
|
|
697
|
+
mockEndpoints.length = 0;
|
|
698
|
+
mockEndpointsJson = [];
|
|
699
|
+
const server = createMockServer();
|
|
700
|
+
const { registerDiscoveryTools } = await loadModule();
|
|
701
|
+
registerDiscoveryTools(
|
|
702
|
+
server,
|
|
703
|
+
{},
|
|
704
|
+
false,
|
|
705
|
+
false,
|
|
706
|
+
void 0,
|
|
707
|
+
false,
|
|
708
|
+
[],
|
|
709
|
+
"[invalid"
|
|
710
|
+
);
|
|
711
|
+
const result = await server.tools.get("search-tools").handler({ limit: 50 });
|
|
712
|
+
const found = JSON.parse(result.content[0].text).tools.map((t) => t.name);
|
|
713
|
+
expect(found).toContain("download-bytes");
|
|
714
|
+
expect(found).toContain("parse-teams-url");
|
|
715
|
+
});
|
|
716
|
+
});
|
|
717
|
+
describe("utility tools in read-only mode", () => {
|
|
718
|
+
it("skips utility tools whose readOnlyHint is not true", async () => {
|
|
719
|
+
mockEndpoints.length = 0;
|
|
720
|
+
mockEndpointsJson = [];
|
|
721
|
+
const server = createMockServer();
|
|
722
|
+
const { registerGraphTools } = await loadModule();
|
|
723
|
+
registerGraphTools(server, {}, true);
|
|
724
|
+
expect(server.tools.has("download-bytes")).toBe(true);
|
|
725
|
+
expect(server.tools.has("parse-teams-url")).toBe(true);
|
|
726
|
+
});
|
|
727
|
+
});
|
|
646
728
|
});
|
package/dist/endpoints.json
CHANGED
|
@@ -100,6 +100,12 @@
|
|
|
100
100
|
"workScopes": ["Mail.Send.Shared"],
|
|
101
101
|
"llmTip": "CRITICAL: Do not try to guess the email address of the recipients. Use the list-users tool to find the email address of the recipients."
|
|
102
102
|
},
|
|
103
|
+
{
|
|
104
|
+
"pathPattern": "/users/{user-id}/messages",
|
|
105
|
+
"method": "post",
|
|
106
|
+
"toolName": "create-shared-mailbox-draft",
|
|
107
|
+
"workScopes": ["Mail.ReadWrite.Shared"]
|
|
108
|
+
},
|
|
103
109
|
{
|
|
104
110
|
"pathPattern": "/users",
|
|
105
111
|
"method": "get",
|
package/dist/generated/client.js
CHANGED
|
@@ -13409,6 +13409,68 @@ To monitor future changes, call the delta API by using the @odata.deltaLink in t
|
|
|
13409
13409
|
],
|
|
13410
13410
|
response: z.void()
|
|
13411
13411
|
},
|
|
13412
|
+
{
|
|
13413
|
+
method: "post",
|
|
13414
|
+
path: "/users/:userId/messages",
|
|
13415
|
+
alias: "create-shared-mailbox-draft",
|
|
13416
|
+
description: `Create new navigation property to messages for users`,
|
|
13417
|
+
requestFormat: "json",
|
|
13418
|
+
parameters: [
|
|
13419
|
+
{
|
|
13420
|
+
name: "body",
|
|
13421
|
+
description: `New navigation property`,
|
|
13422
|
+
type: "Body",
|
|
13423
|
+
schema: z.object({
|
|
13424
|
+
id: z.string().describe("The unique identifier for an entity. Read-only.").optional(),
|
|
13425
|
+
createdDateTime: z.string().regex(
|
|
13426
|
+
/^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$/
|
|
13427
|
+
).datetime({ offset: true }).describe(
|
|
13428
|
+
"The Timestamp type represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 is 2014-01-01T00:00:00Z"
|
|
13429
|
+
).nullish(),
|
|
13430
|
+
lastModifiedDateTime: z.string().regex(
|
|
13431
|
+
/^[0-9]{4,}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]([.][0-9]{1,12})?(Z|[+-][0-9][0-9]:[0-9][0-9])$/
|
|
13432
|
+
).datetime({ offset: true }).describe(
|
|
13433
|
+
"The Timestamp type represents date and time information using ISO 8601 format and is always in UTC time. For example, midnight UTC on Jan 1, 2014 is 2014-01-01T00:00:00Z"
|
|
13434
|
+
).nullish(),
|
|
13435
|
+
body: microsoft_graph_itemBody.optional(),
|
|
13436
|
+
subject: z.string().describe("The subject of the message.").nullish(),
|
|
13437
|
+
attachments: z.array(microsoft_graph_attachment).describe("The fileAttachment and itemAttachment attachments for the message.").optional(),
|
|
13438
|
+
singleValueExtendedProperties: z.array(microsoft_graph_singleValueLegacyExtendedProperty).describe(
|
|
13439
|
+
"The collection of single-value extended properties defined for the message. Nullable."
|
|
13440
|
+
).optional(),
|
|
13441
|
+
multiValueExtendedProperties: z.array(microsoft_graph_multiValueLegacyExtendedProperty).describe(
|
|
13442
|
+
"The collection of multi-value extended properties defined for the message. Nullable."
|
|
13443
|
+
).optional(),
|
|
13444
|
+
importance: microsoft_graph_importance.optional(),
|
|
13445
|
+
from: microsoft_graph_recipient.optional(),
|
|
13446
|
+
toRecipients: z.array(microsoft_graph_recipient).describe("The To: recipients for the message.").optional(),
|
|
13447
|
+
categories: z.array(z.string().nullable()).describe("The categories associated with the item").optional(),
|
|
13448
|
+
changeKey: z.string().describe(
|
|
13449
|
+
"Identifies the version of the item. Every time the item is changed, changeKey changes as well. This allows Exchange to apply changes to the correct version of the object. Read-only."
|
|
13450
|
+
).nullish(),
|
|
13451
|
+
bccRecipients: z.array(microsoft_graph_recipient).describe("The Bcc: recipients for the message.").optional(),
|
|
13452
|
+
bodyPreview: z.string().describe("The first 255 characters of the message body. It is in text format.").nullish(),
|
|
13453
|
+
ccRecipients: z.array(microsoft_graph_recipient).describe("The Cc: recipients for the message.").optional(),
|
|
13454
|
+
conversationId: z.string().describe("The ID of the conversation the email belongs to.").nullish(),
|
|
13455
|
+
conversationIndex: z.string().describe("Indicates the position of the message within the conversation.").nullish(),
|
|
13456
|
+
flag: microsoft_graph_followupFlag.optional(),
|
|
13457
|
+
hasAttachments: z.boolean().describe(
|
|
13458
|
+
"Indicates whether the message has attachments. This property doesn't include inline attachments, so if a message contains only inline attachments, this property is false. To verify the existence of inline attachments, parse the body property to look for a src attribute, such as <IMG src='cid:image001.jpg@01D26CD8.6C05F070'>."
|
|
13459
|
+
).nullish(),
|
|
13460
|
+
inferenceClassification: microsoft_graph_inferenceClassificationType.optional(),
|
|
13461
|
+
internetMessageHeaders: z.array(microsoft_graph_internetMessageHeader).describe(
|
|
13462
|
+
"A collection of message headers defined by RFC5322. The set includes message headers indicating the network path taken by a message from the sender to the recipient. It can also contain custom message headers that hold app data for the message. Requires $select to retrieve. Read-only."
|
|
13463
|
+
).optional(),
|
|
13464
|
+
internetMessageId: z.string().describe("The message ID in the format specified by RFC2822.").nullish(),
|
|
13465
|
+
isDeliveryReceiptRequested: z.boolean().describe("Indicates whether a read receipt is requested for the message.").nullish(),
|
|
13466
|
+
isDraft: z.boolean().describe(
|
|
13467
|
+
"Indicates whether the message is a draft. A message is a draft if it hasn't been sent yet."
|
|
13468
|
+
).nullish()
|
|
13469
|
+
}).passthrough().passthrough()
|
|
13470
|
+
}
|
|
13471
|
+
],
|
|
13472
|
+
response: z.void()
|
|
13473
|
+
},
|
|
13412
13474
|
{
|
|
13413
13475
|
method: "get",
|
|
13414
13476
|
path: "/users/:userId/messages/:messageId",
|
package/dist/graph-tools.js
CHANGED
|
@@ -543,6 +543,7 @@ function registerGraphTools(server, graphClient, readOnly = false, enabledToolsP
|
|
|
543
543
|
accountNames
|
|
544
544
|
};
|
|
545
545
|
for (const utility of UTILITY_TOOLS) {
|
|
546
|
+
if (readOnly && !utility.readOnlyHint) continue;
|
|
546
547
|
if (enabledToolsRegex && !enabledToolsRegex.test(utility.name)) continue;
|
|
547
548
|
try {
|
|
548
549
|
registerUtilityToolWithMcp(server, utility, utilityCtx);
|
|
@@ -557,7 +558,7 @@ function registerGraphTools(server, graphClient, readOnly = false, enabledToolsP
|
|
|
557
558
|
);
|
|
558
559
|
return registeredCount;
|
|
559
560
|
}
|
|
560
|
-
function buildToolsRegistry(readOnly, orgMode) {
|
|
561
|
+
function buildToolsRegistry(readOnly, orgMode, enabledToolsRegex) {
|
|
561
562
|
const toolsMap = /* @__PURE__ */ new Map();
|
|
562
563
|
for (const tool of api.endpoints) {
|
|
563
564
|
const endpointConfig = endpointsData.find((e) => e.toolName === tool.alias);
|
|
@@ -570,6 +571,9 @@ function buildToolsRegistry(readOnly, orgMode) {
|
|
|
570
571
|
continue;
|
|
571
572
|
}
|
|
572
573
|
}
|
|
574
|
+
if (enabledToolsRegex && !enabledToolsRegex.test(tool.alias)) {
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
573
577
|
toolsMap.set(tool.alias, { tool, config: endpointConfig });
|
|
574
578
|
}
|
|
575
579
|
return toolsMap;
|
|
@@ -631,9 +635,24 @@ function scoreDiscoveryQuery(query, index) {
|
|
|
631
635
|
ranked.sort((a, b) => b.score - a.score);
|
|
632
636
|
return ranked;
|
|
633
637
|
}
|
|
634
|
-
function registerDiscoveryTools(server, graphClient, readOnly = false, orgMode = false, authManager, multiAccount = false, accountNames = []) {
|
|
635
|
-
|
|
636
|
-
|
|
638
|
+
function registerDiscoveryTools(server, graphClient, readOnly = false, orgMode = false, authManager, multiAccount = false, accountNames = [], enabledTools) {
|
|
639
|
+
let enabledToolsRegex;
|
|
640
|
+
if (enabledTools) {
|
|
641
|
+
try {
|
|
642
|
+
enabledToolsRegex = new RegExp(enabledTools, "i");
|
|
643
|
+
logger.info(`Discovery mode: filtering tools with pattern ${enabledTools}`);
|
|
644
|
+
} catch (error) {
|
|
645
|
+
logger.error(
|
|
646
|
+
`Invalid --enabled-tools regex ${JSON.stringify(enabledTools)} \u2014 ignoring filter: ${error.message}`
|
|
647
|
+
);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
const toolsRegistry = buildToolsRegistry(readOnly, orgMode, enabledToolsRegex);
|
|
651
|
+
const utilityTools = UTILITY_TOOLS.filter((u) => {
|
|
652
|
+
if (readOnly && !u.readOnlyHint) return false;
|
|
653
|
+
if (enabledToolsRegex && !enabledToolsRegex.test(u.name)) return false;
|
|
654
|
+
return true;
|
|
655
|
+
});
|
|
637
656
|
const searchIndex = buildDiscoverySearchIndex(toolsRegistry, utilityTools);
|
|
638
657
|
const totalCount = toolsRegistry.size + utilityTools.length;
|
|
639
658
|
logger.info(
|
package/dist/server.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softeria/ms-365-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.99.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
|
@@ -100,6 +100,12 @@
|
|
|
100
100
|
"workScopes": ["Mail.Send.Shared"],
|
|
101
101
|
"llmTip": "CRITICAL: Do not try to guess the email address of the recipients. Use the list-users tool to find the email address of the recipients."
|
|
102
102
|
},
|
|
103
|
+
{
|
|
104
|
+
"pathPattern": "/users/{user-id}/messages",
|
|
105
|
+
"method": "post",
|
|
106
|
+
"toolName": "create-shared-mailbox-draft",
|
|
107
|
+
"workScopes": ["Mail.ReadWrite.Shared"]
|
|
108
|
+
},
|
|
103
109
|
{
|
|
104
110
|
"pathPattern": "/users",
|
|
105
111
|
"method": "get",
|