@softeria/ms-365-mcp-server 0.119.0 → 0.120.1
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/README.md +1 -1
- package/bin/modules/simplified-openapi.mjs +14 -0
- package/dist/__tests__/graph-tools.test.js +114 -0
- package/dist/generated/client.js +99 -6
- package/dist/graph-tools.js +87 -0
- package/docs/deployment.md +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -285,7 +285,7 @@ Open WebUI supports MCP servers via HTTP transport with OAuth 2.1.
|
|
|
285
285
|
|
|
286
286
|
3. Click **Register Client**.
|
|
287
287
|
|
|
288
|
-
> **Note**: Dynamic client registration is enabled by default in HTTP mode. Use `--no-dynamic-registration` to disable it. If using a custom Azure Entra app,
|
|
288
|
+
> **Note**: Dynamic client registration is enabled by default in HTTP mode. Use `--no-dynamic-registration` to disable it. If using a custom Azure Entra app, the platform type for your redirect URI depends on whether the app has a client secret: with a secret use "Web", without one use "Mobile and desktop applications" (never "Single-page application").
|
|
289
289
|
|
|
290
290
|
**Quick test setup** using the default Azure app (ID `ms-365` and `localhost:8080` are pre-configured):
|
|
291
291
|
|
|
@@ -508,6 +508,14 @@ function findUsedSchemas(openApiSpec) {
|
|
|
508
508
|
);
|
|
509
509
|
schemasToProcess.push(schemaName);
|
|
510
510
|
}
|
|
511
|
+
// Trace refs nested anywhere in an inline request body (e.g. a property's
|
|
512
|
+
// anyOf: [{$ref}, {nullable object}]). Without this they're pruned as unused,
|
|
513
|
+
// then stripped as a "broken reference", degrading the body to a bare object.
|
|
514
|
+
if (content.schema) {
|
|
515
|
+
findRefsInObject(content.schema, (ref) =>
|
|
516
|
+
schemasToProcess.push(ref.replace('#/components/schemas/', ''))
|
|
517
|
+
);
|
|
518
|
+
}
|
|
511
519
|
});
|
|
512
520
|
}
|
|
513
521
|
|
|
@@ -547,6 +555,12 @@ function findUsedSchemas(openApiSpec) {
|
|
|
547
555
|
}
|
|
548
556
|
});
|
|
549
557
|
}
|
|
558
|
+
// Trace refs nested anywhere in an inline response schema.
|
|
559
|
+
if (content.schema) {
|
|
560
|
+
findRefsInObject(content.schema, (ref) =>
|
|
561
|
+
schemasToProcess.push(ref.replace('#/components/schemas/', ''))
|
|
562
|
+
);
|
|
563
|
+
}
|
|
550
564
|
});
|
|
551
565
|
}
|
|
552
566
|
});
|
|
@@ -914,4 +914,118 @@ describe("graph-tools", () => {
|
|
|
914
914
|
expect(server.tools.has("parse-teams-url")).toBe(true);
|
|
915
915
|
});
|
|
916
916
|
});
|
|
917
|
+
describe("copilot-retrieve", () => {
|
|
918
|
+
const retrievalResponse = {
|
|
919
|
+
content: [
|
|
920
|
+
{
|
|
921
|
+
type: "text",
|
|
922
|
+
text: JSON.stringify({
|
|
923
|
+
retrievalHits: [
|
|
924
|
+
{
|
|
925
|
+
webUrl: "https://contoso.sharepoint.com/sites/HR/VPNAccess.docx",
|
|
926
|
+
extracts: [{ text: "To configure the VPN...", relevanceScore: 0.83 }],
|
|
927
|
+
resourceType: "listItem",
|
|
928
|
+
resourceMetadata: { title: "VPN Access" }
|
|
929
|
+
}
|
|
930
|
+
]
|
|
931
|
+
})
|
|
932
|
+
}
|
|
933
|
+
]
|
|
934
|
+
};
|
|
935
|
+
it("POSTs queryString + dataSource to /copilot/retrieval on v1.0 and returns the hits", async () => {
|
|
936
|
+
mockEndpoints.length = 0;
|
|
937
|
+
mockEndpointsJson = [];
|
|
938
|
+
const graphClient = { graphRequest: vi.fn().mockResolvedValue(retrievalResponse) };
|
|
939
|
+
const server = createMockServer();
|
|
940
|
+
const { registerGraphTools } = await loadModule();
|
|
941
|
+
registerGraphTools(server, graphClient);
|
|
942
|
+
const tool = server.tools.get("copilot-retrieve");
|
|
943
|
+
expect(tool).toBeDefined();
|
|
944
|
+
const result = await tool.handler({
|
|
945
|
+
queryString: "How to setup corporate VPN?",
|
|
946
|
+
dataSource: "sharePoint"
|
|
947
|
+
});
|
|
948
|
+
expect(graphClient.graphRequest).toHaveBeenCalledTimes(1);
|
|
949
|
+
const [endpoint, options] = graphClient.graphRequest.mock.calls[0];
|
|
950
|
+
expect(endpoint).toBe("/copilot/retrieval");
|
|
951
|
+
expect(options.method).toBe("POST");
|
|
952
|
+
expect(options.apiVersion === void 0 || options.apiVersion === "v1.0").toBe(true);
|
|
953
|
+
const body = JSON.parse(options.body);
|
|
954
|
+
expect(body).toEqual({
|
|
955
|
+
queryString: "How to setup corporate VPN?",
|
|
956
|
+
dataSource: "sharePoint"
|
|
957
|
+
});
|
|
958
|
+
const payload = JSON.parse(result.content[0].text);
|
|
959
|
+
expect(payload.retrievalHits[0].webUrl).toBe(
|
|
960
|
+
"https://contoso.sharepoint.com/sites/HR/VPNAccess.docx"
|
|
961
|
+
);
|
|
962
|
+
});
|
|
963
|
+
it("includes optional filterExpression, resourceMetadata, and maximumNumberOfResults only when provided", async () => {
|
|
964
|
+
mockEndpoints.length = 0;
|
|
965
|
+
mockEndpointsJson = [];
|
|
966
|
+
const graphClient = { graphRequest: vi.fn().mockResolvedValue(retrievalResponse) };
|
|
967
|
+
const server = createMockServer();
|
|
968
|
+
const { registerGraphTools } = await loadModule();
|
|
969
|
+
registerGraphTools(server, graphClient);
|
|
970
|
+
const tool = server.tools.get("copilot-retrieve");
|
|
971
|
+
await tool.handler({
|
|
972
|
+
queryString: "corporate VPN",
|
|
973
|
+
dataSource: "sharePoint",
|
|
974
|
+
filterExpression: 'Author:"Megan Bowen"',
|
|
975
|
+
resourceMetadata: ["title", "author"],
|
|
976
|
+
maximumNumberOfResults: 5
|
|
977
|
+
});
|
|
978
|
+
const [, options] = graphClient.graphRequest.mock.calls[0];
|
|
979
|
+
const body = JSON.parse(options.body);
|
|
980
|
+
expect(body.filterExpression).toBe('Author:"Megan Bowen"');
|
|
981
|
+
expect(body.resourceMetadata).toEqual(["title", "author"]);
|
|
982
|
+
expect(body.maximumNumberOfResults).toBe(5);
|
|
983
|
+
});
|
|
984
|
+
it("requires a non-empty queryString", async () => {
|
|
985
|
+
mockEndpoints.length = 0;
|
|
986
|
+
mockEndpointsJson = [];
|
|
987
|
+
const graphClient = { graphRequest: vi.fn() };
|
|
988
|
+
const server = createMockServer();
|
|
989
|
+
const { registerGraphTools } = await loadModule();
|
|
990
|
+
registerGraphTools(server, graphClient);
|
|
991
|
+
const tool = server.tools.get("copilot-retrieve");
|
|
992
|
+
const result = await tool.handler({ queryString: " ", dataSource: "sharePoint" });
|
|
993
|
+
expect(result.isError).toBe(true);
|
|
994
|
+
expect(graphClient.graphRequest).not.toHaveBeenCalled();
|
|
995
|
+
const payload = JSON.parse(result.content[0].text);
|
|
996
|
+
expect(payload.error).toMatch(/queryString/);
|
|
997
|
+
});
|
|
998
|
+
it("rejects an invalid dataSource", async () => {
|
|
999
|
+
mockEndpoints.length = 0;
|
|
1000
|
+
mockEndpointsJson = [];
|
|
1001
|
+
const graphClient = { graphRequest: vi.fn() };
|
|
1002
|
+
const server = createMockServer();
|
|
1003
|
+
const { registerGraphTools } = await loadModule();
|
|
1004
|
+
registerGraphTools(server, graphClient);
|
|
1005
|
+
const tool = server.tools.get("copilot-retrieve");
|
|
1006
|
+
const result = await tool.handler({ queryString: "vpn", dataSource: "mailbox" });
|
|
1007
|
+
expect(result.isError).toBe(true);
|
|
1008
|
+
expect(graphClient.graphRequest).not.toHaveBeenCalled();
|
|
1009
|
+
const payload = JSON.parse(result.content[0].text);
|
|
1010
|
+
expect(payload.error).toMatch(/dataSource/);
|
|
1011
|
+
});
|
|
1012
|
+
it("rejects maximumNumberOfResults outside the 1-25 range", async () => {
|
|
1013
|
+
mockEndpoints.length = 0;
|
|
1014
|
+
mockEndpointsJson = [];
|
|
1015
|
+
const graphClient = { graphRequest: vi.fn() };
|
|
1016
|
+
const server = createMockServer();
|
|
1017
|
+
const { registerGraphTools } = await loadModule();
|
|
1018
|
+
registerGraphTools(server, graphClient);
|
|
1019
|
+
const tool = server.tools.get("copilot-retrieve");
|
|
1020
|
+
const result = await tool.handler({
|
|
1021
|
+
queryString: "vpn",
|
|
1022
|
+
dataSource: "sharePoint",
|
|
1023
|
+
maximumNumberOfResults: 50
|
|
1024
|
+
});
|
|
1025
|
+
expect(result.isError).toBe(true);
|
|
1026
|
+
expect(graphClient.graphRequest).not.toHaveBeenCalled();
|
|
1027
|
+
const payload = JSON.parse(result.content[0].text);
|
|
1028
|
+
expect(payload.error).toMatch(/maximumNumberOfResults/);
|
|
1029
|
+
});
|
|
1030
|
+
});
|
|
917
1031
|
});
|
package/dist/generated/client.js
CHANGED
|
@@ -825,6 +825,15 @@ const copy_drive_item_Body = z.object({
|
|
|
825
825
|
childrenOnly: z.boolean().nullable().default(false),
|
|
826
826
|
includeAllVersionHistory: z.boolean().nullable().default(false)
|
|
827
827
|
}).partial().passthrough();
|
|
828
|
+
const microsoft_graph_driveRecipient = z.object({
|
|
829
|
+
alias: z.string().describe(
|
|
830
|
+
"The alias of the domain object, for cases where an email address is unavailable (for example, security groups)."
|
|
831
|
+
).nullish(),
|
|
832
|
+
email: z.string().describe(
|
|
833
|
+
"The email address for the recipient, if the recipient has an associated email address."
|
|
834
|
+
).nullish(),
|
|
835
|
+
objectId: z.string().describe("The unique identifier for the recipient in the directory.").nullish()
|
|
836
|
+
}).passthrough();
|
|
828
837
|
const create_drive_item_share_link_Body = z.object({
|
|
829
838
|
type: z.string().nullable(),
|
|
830
839
|
scope: z.string().nullable(),
|
|
@@ -833,7 +842,7 @@ const create_drive_item_share_link_Body = z.object({
|
|
|
833
842
|
).datetime({ offset: true }).nullable(),
|
|
834
843
|
password: z.string().nullable(),
|
|
835
844
|
message: z.string().nullable(),
|
|
836
|
-
recipients: z.array(
|
|
845
|
+
recipients: z.array(microsoft_graph_driveRecipient),
|
|
837
846
|
retainInheritedPermissions: z.boolean().nullable().default(false),
|
|
838
847
|
sendNotification: z.boolean().nullable().default(false)
|
|
839
848
|
}).partial().passthrough();
|
|
@@ -917,7 +926,51 @@ const microsoft_graph_permission = z.object({
|
|
|
917
926
|
"A unique token that can be used to access this shared item via the shares API. Read-only."
|
|
918
927
|
).nullish()
|
|
919
928
|
}).passthrough();
|
|
920
|
-
const
|
|
929
|
+
const microsoft_graph_driveItemSourceApplication = z.enum([
|
|
930
|
+
"teams",
|
|
931
|
+
"yammer",
|
|
932
|
+
"sharePoint",
|
|
933
|
+
"oneDrive",
|
|
934
|
+
"stream",
|
|
935
|
+
"powerPoint",
|
|
936
|
+
"office",
|
|
937
|
+
"loki",
|
|
938
|
+
"loop",
|
|
939
|
+
"other",
|
|
940
|
+
"unknownFutureValue"
|
|
941
|
+
]);
|
|
942
|
+
const microsoft_graph_driveItemSource = z.object({
|
|
943
|
+
application: microsoft_graph_driveItemSourceApplication.optional(),
|
|
944
|
+
externalId: z.string().describe("The external identifier for the drive item from the source.").nullish()
|
|
945
|
+
}).passthrough();
|
|
946
|
+
const microsoft_graph_mediaSourceContentCategory = z.enum([
|
|
947
|
+
"meeting",
|
|
948
|
+
"liveStream",
|
|
949
|
+
"presentation",
|
|
950
|
+
"screenRecording",
|
|
951
|
+
"story",
|
|
952
|
+
"profile",
|
|
953
|
+
"chat",
|
|
954
|
+
"note",
|
|
955
|
+
"comment",
|
|
956
|
+
"unknownFutureValue"
|
|
957
|
+
]);
|
|
958
|
+
const microsoft_graph_mediaSource = z.object({ contentCategory: microsoft_graph_mediaSourceContentCategory.optional() }).passthrough();
|
|
959
|
+
const microsoft_graph_driveItemUploadableProperties = z.object({
|
|
960
|
+
description: z.string().describe(
|
|
961
|
+
"Provides a user-visible description of the item. Read-write. Only on OneDrive Personal."
|
|
962
|
+
).nullish(),
|
|
963
|
+
driveItemSource: microsoft_graph_driveItemSource.optional(),
|
|
964
|
+
fileSize: z.number().describe(
|
|
965
|
+
"Provides an expected file size to perform a quota check before uploading. Only on OneDrive Personal."
|
|
966
|
+
).nullish(),
|
|
967
|
+
fileSystemInfo: microsoft_graph_fileSystemInfo.optional(),
|
|
968
|
+
mediaSource: microsoft_graph_mediaSource.optional(),
|
|
969
|
+
name: z.string().describe("The name of the item (filename and extension). Read-write.").nullish()
|
|
970
|
+
}).passthrough();
|
|
971
|
+
const create_upload_session_Body = z.object({
|
|
972
|
+
item: z.union([microsoft_graph_driveItemUploadableProperties, z.object({}).partial().passthrough()])
|
|
973
|
+
}).partial().passthrough();
|
|
921
974
|
const microsoft_graph_uploadSession = z.object({
|
|
922
975
|
expirationDateTime: z.string().regex(
|
|
923
976
|
/^[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])$/
|
|
@@ -951,7 +1004,7 @@ const share_drive_item_Body = z.object({
|
|
|
951
1004
|
roles: z.array(z.string().nullable()),
|
|
952
1005
|
sendInvitation: z.boolean().nullable().default(false),
|
|
953
1006
|
message: z.string().nullable(),
|
|
954
|
-
recipients: z.array(
|
|
1007
|
+
recipients: z.array(microsoft_graph_driveRecipient),
|
|
955
1008
|
retainInheritedPermissions: z.boolean().nullable().default(false),
|
|
956
1009
|
expirationDateTime: z.string().nullable(),
|
|
957
1010
|
password: z.string().nullable()
|
|
@@ -3182,6 +3235,20 @@ const microsoft_graph_attachmentCollectionResponse = z.object({
|
|
|
3182
3235
|
"@odata.nextLink": z.string().nullable(),
|
|
3183
3236
|
value: z.array(microsoft_graph_attachment)
|
|
3184
3237
|
}).partial().passthrough();
|
|
3238
|
+
const microsoft_graph_attachmentType = z.enum(["file", "item", "reference"]);
|
|
3239
|
+
const microsoft_graph_attachmentItem = z.object({
|
|
3240
|
+
attachmentType: microsoft_graph_attachmentType.optional(),
|
|
3241
|
+
contentId: z.string().describe(
|
|
3242
|
+
"The CID or Content-Id of the attachment for referencing for the in-line attachments using the <img src='cid:contentId'> tag in HTML messages. Optional."
|
|
3243
|
+
).nullish(),
|
|
3244
|
+
contentType: z.string().describe("The nature of the data in the attachment. Optional.").nullish(),
|
|
3245
|
+
isInline: z.boolean().describe("true if the attachment is an inline attachment; otherwise, false. Optional.").nullish(),
|
|
3246
|
+
name: z.string().describe(
|
|
3247
|
+
"The display name of the attachment. This can be a descriptive string and doesn't have to be the actual file name. Required."
|
|
3248
|
+
).nullish(),
|
|
3249
|
+
size: z.number().describe("The length of the attachment in bytes. Required.").nullish()
|
|
3250
|
+
}).passthrough();
|
|
3251
|
+
const create_mail_attachment_upload_session_Body = z.object({ AttachmentItem: microsoft_graph_attachmentItem }).partial().passthrough();
|
|
3185
3252
|
const create_forward_draft_Body = z.object({
|
|
3186
3253
|
ToRecipients: z.array(microsoft_graph_recipient),
|
|
3187
3254
|
Message: z.union([microsoft_graph_message, z.object({}).partial().passthrough()]),
|
|
@@ -3875,13 +3942,27 @@ const microsoft_graph_userScopeTeamsAppInstallationCollectionResponse = z.object
|
|
|
3875
3942
|
"@odata.nextLink": z.string().nullable(),
|
|
3876
3943
|
value: z.array(microsoft_graph_userScopeTeamsAppInstallation)
|
|
3877
3944
|
}).partial().passthrough();
|
|
3945
|
+
const microsoft_graph_teamworkActivityTopicSource = z.enum(["entityUrl", "text"]);
|
|
3946
|
+
const microsoft_graph_teamworkActivityTopic = z.object({
|
|
3947
|
+
source: microsoft_graph_teamworkActivityTopicSource.optional(),
|
|
3948
|
+
value: z.string().describe(
|
|
3949
|
+
"The topic value. If the value of the source property is entityUrl, this must be a Microsoft Graph URL. If the value is text, this must be a plain text value."
|
|
3950
|
+
).optional(),
|
|
3951
|
+
webUrl: z.string().describe(
|
|
3952
|
+
"The link the user clicks when they select the notification. Optional when source is entityUrl; required when source is text."
|
|
3953
|
+
).nullish()
|
|
3954
|
+
}).passthrough();
|
|
3955
|
+
const microsoft_graph_keyValuePair = z.object({
|
|
3956
|
+
name: z.string().describe("Name for this key-value pair").optional(),
|
|
3957
|
+
value: z.string().describe("Value for this key-value pair").nullish()
|
|
3958
|
+
}).passthrough();
|
|
3878
3959
|
const send_my_activity_notification_Body = z.object({
|
|
3879
|
-
topic: z.object({}).partial().passthrough(),
|
|
3960
|
+
topic: z.union([microsoft_graph_teamworkActivityTopic, z.object({}).partial().passthrough()]),
|
|
3880
3961
|
activityType: z.string().nullable(),
|
|
3881
3962
|
chainId: z.number().nullable(),
|
|
3882
3963
|
previewText: z.union([microsoft_graph_itemBody, z.object({}).partial().passthrough()]),
|
|
3883
3964
|
teamsAppId: z.string().nullable(),
|
|
3884
|
-
templateParameters: z.array(
|
|
3965
|
+
templateParameters: z.array(microsoft_graph_keyValuePair),
|
|
3885
3966
|
iconId: z.string().nullable()
|
|
3886
3967
|
}).partial().passthrough();
|
|
3887
3968
|
const microsoft_graph_wellknownListName = z.enum([
|
|
@@ -4762,6 +4843,7 @@ const schemas = {
|
|
|
4762
4843
|
microsoft_graph_driveItem,
|
|
4763
4844
|
microsoft_graph_driveItemCollectionResponse,
|
|
4764
4845
|
copy_drive_item_Body,
|
|
4846
|
+
microsoft_graph_driveRecipient,
|
|
4765
4847
|
create_drive_item_share_link_Body,
|
|
4766
4848
|
microsoft_graph_sharePointGroupIdentity,
|
|
4767
4849
|
microsoft_graph_sharePointIdentity,
|
|
@@ -4769,6 +4851,11 @@ const schemas = {
|
|
|
4769
4851
|
microsoft_graph_sharingInvitation,
|
|
4770
4852
|
microsoft_graph_sharingLink,
|
|
4771
4853
|
microsoft_graph_permission,
|
|
4854
|
+
microsoft_graph_driveItemSourceApplication,
|
|
4855
|
+
microsoft_graph_driveItemSource,
|
|
4856
|
+
microsoft_graph_mediaSourceContentCategory,
|
|
4857
|
+
microsoft_graph_mediaSource,
|
|
4858
|
+
microsoft_graph_driveItemUploadableProperties,
|
|
4772
4859
|
create_upload_session_Body,
|
|
4773
4860
|
microsoft_graph_uploadSession,
|
|
4774
4861
|
BaseDeltaFunctionResponse,
|
|
@@ -4993,6 +5080,9 @@ const schemas = {
|
|
|
4993
5080
|
microsoft_graph_messageRuleCollectionResponse,
|
|
4994
5081
|
microsoft_graph_messageCollectionResponse,
|
|
4995
5082
|
microsoft_graph_attachmentCollectionResponse,
|
|
5083
|
+
microsoft_graph_attachmentType,
|
|
5084
|
+
microsoft_graph_attachmentItem,
|
|
5085
|
+
create_mail_attachment_upload_session_Body,
|
|
4996
5086
|
create_forward_draft_Body,
|
|
4997
5087
|
create_reply_draft_Body,
|
|
4998
5088
|
microsoft_graph_attendeeBase,
|
|
@@ -5070,6 +5160,9 @@ const schemas = {
|
|
|
5070
5160
|
microsoft_graph_associatedTeamInfoCollectionResponse,
|
|
5071
5161
|
microsoft_graph_userScopeTeamsAppInstallation,
|
|
5072
5162
|
microsoft_graph_userScopeTeamsAppInstallationCollectionResponse,
|
|
5163
|
+
microsoft_graph_teamworkActivityTopicSource,
|
|
5164
|
+
microsoft_graph_teamworkActivityTopic,
|
|
5165
|
+
microsoft_graph_keyValuePair,
|
|
5073
5166
|
send_my_activity_notification_Body,
|
|
5074
5167
|
microsoft_graph_wellknownListName,
|
|
5075
5168
|
microsoft_graph_taskStatus,
|
|
@@ -10176,7 +10269,7 @@ resource.`,
|
|
|
10176
10269
|
name: "body",
|
|
10177
10270
|
description: `Action parameters`,
|
|
10178
10271
|
type: "Body",
|
|
10179
|
-
schema:
|
|
10272
|
+
schema: create_mail_attachment_upload_session_Body
|
|
10180
10273
|
}
|
|
10181
10274
|
],
|
|
10182
10275
|
response: z.void()
|
package/dist/graph-tools.js
CHANGED
|
@@ -169,6 +169,93 @@ const UTILITY_TOOLS = [
|
|
|
169
169
|
};
|
|
170
170
|
}
|
|
171
171
|
}
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
name: "copilot-retrieve",
|
|
175
|
+
method: "POST",
|
|
176
|
+
path: "tool:copilot-retrieve",
|
|
177
|
+
description: 'Semantic search over Microsoft 365 content via the Copilot Retrieval API (POST /copilot/retrieval). Grounds a natural-language query against the same hybrid index that powers Microsoft 365 Copilot and returns relevant, permission-trimmed text extracts with their source URLs \u2014 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). Returns { retrievalHits: [{ webUrl, extracts: [{ text, relevanceScore }], resourceType, resourceMetadata, sensitivityLabel? }] }. Read-only. Requires delegated Files.Read.All + Sites.Read.All (SharePoint/OneDrive) or ExternalItem.Read.All (Copilot connectors); application-only auth is not supported.',
|
|
178
|
+
readOnlyHint: true,
|
|
179
|
+
openWorldHint: true,
|
|
180
|
+
buildSchema: (ctx) => {
|
|
181
|
+
const schema = {
|
|
182
|
+
queryString: z.string().min(1).max(1500).describe(
|
|
183
|
+
"Natural-language query (a single sentence works best; max 1500 characters). Avoid spelling errors in context-rich keywords."
|
|
184
|
+
),
|
|
185
|
+
dataSource: z.enum(["sharePoint", "oneDriveBusiness", "externalItem"]).describe(
|
|
186
|
+
'Which source to retrieve from \u2014 one at a time (interleaved results are not supported). "sharePoint", "oneDriveBusiness", or "externalItem" (Copilot connectors).'
|
|
187
|
+
),
|
|
188
|
+
filterExpression: z.string().optional().describe(
|
|
189
|
+
'Optional KQL expression to scope the retrieval before the query runs. Supported SharePoint/OneDrive properties: Author, FileExtension, Filename, FileType, InformationProtectionLabelId, LastModifiedTime, ModifiedBy, Path, SiteID, Title. Example: Author:"Megan Bowen" OR Path:"https://contoso.sharepoint.com/sites/HR/". Invalid KQL is ignored (query runs unscoped).'
|
|
190
|
+
),
|
|
191
|
+
resourceMetadata: z.array(z.string()).optional().describe(
|
|
192
|
+
'Optional list of retrievable metadata fields to return per hit (e.g. ["title", "author"]). By default no metadata is returned.'
|
|
193
|
+
),
|
|
194
|
+
maximumNumberOfResults: z.number().int().min(1).max(25).optional().describe(
|
|
195
|
+
"Optional cap on the number of results (1-25). Best practice: leave unset unless your LLM has strict token limits \u2014 results are unordered."
|
|
196
|
+
)
|
|
197
|
+
};
|
|
198
|
+
if (ctx.multiAccount) {
|
|
199
|
+
schema["account"] = z.string().optional().describe(
|
|
200
|
+
"Account to use when multiple Microsoft accounts are configured. Required when multiple accounts exist (see list-accounts)."
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
return schema;
|
|
204
|
+
},
|
|
205
|
+
execute: async (params, { graphClient, authManager }) => {
|
|
206
|
+
const err = (message) => ({
|
|
207
|
+
content: [{ type: "text", text: JSON.stringify({ error: message }) }],
|
|
208
|
+
isError: true
|
|
209
|
+
});
|
|
210
|
+
const queryString = typeof params.queryString === "string" ? params.queryString : "";
|
|
211
|
+
if (queryString.trim().length === 0) {
|
|
212
|
+
return err("queryString is required and must be a non-empty string.");
|
|
213
|
+
}
|
|
214
|
+
if (queryString.length > 1500) {
|
|
215
|
+
return err("queryString must be 1500 characters or fewer.");
|
|
216
|
+
}
|
|
217
|
+
const dataSource = params.dataSource;
|
|
218
|
+
const allowedSources = ["sharePoint", "oneDriveBusiness", "externalItem"];
|
|
219
|
+
if (typeof dataSource !== "string" || !allowedSources.includes(dataSource)) {
|
|
220
|
+
return err(`dataSource is required and must be one of: ${allowedSources.join(", ")}.`);
|
|
221
|
+
}
|
|
222
|
+
const body = { queryString, dataSource };
|
|
223
|
+
if (params.filterExpression !== void 0) {
|
|
224
|
+
if (typeof params.filterExpression !== "string") {
|
|
225
|
+
return err("filterExpression must be a string (KQL expression).");
|
|
226
|
+
}
|
|
227
|
+
body.filterExpression = params.filterExpression;
|
|
228
|
+
}
|
|
229
|
+
if (params.resourceMetadata !== void 0) {
|
|
230
|
+
const rm = params.resourceMetadata;
|
|
231
|
+
if (!Array.isArray(rm) || rm.some((x) => typeof x !== "string")) {
|
|
232
|
+
return err("resourceMetadata must be an array of strings.");
|
|
233
|
+
}
|
|
234
|
+
body.resourceMetadata = rm;
|
|
235
|
+
}
|
|
236
|
+
if (params.maximumNumberOfResults !== void 0) {
|
|
237
|
+
const n = params.maximumNumberOfResults;
|
|
238
|
+
if (typeof n !== "number" || !Number.isInteger(n) || n < 1 || n > 25) {
|
|
239
|
+
return err("maximumNumberOfResults must be an integer between 1 and 25.");
|
|
240
|
+
}
|
|
241
|
+
body.maximumNumberOfResults = n;
|
|
242
|
+
}
|
|
243
|
+
try {
|
|
244
|
+
let accountAccessToken;
|
|
245
|
+
if (authManager && !authManager.isOAuthModeEnabled() && !getRequestTokens()) {
|
|
246
|
+
accountAccessToken = await authManager.getTokenForAccount(
|
|
247
|
+
params.account
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
return await graphClient.graphRequest("/copilot/retrieval", {
|
|
251
|
+
method: "POST",
|
|
252
|
+
body: JSON.stringify(body),
|
|
253
|
+
accessToken: accountAccessToken
|
|
254
|
+
});
|
|
255
|
+
} catch (error) {
|
|
256
|
+
return err(error.message);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
172
259
|
}
|
|
173
260
|
];
|
|
174
261
|
function registerUtilityToolWithMcp(server, utility, ctx) {
|
package/docs/deployment.md
CHANGED
|
@@ -127,7 +127,7 @@ When deploying for an organization, create a dedicated app registration instead
|
|
|
127
127
|
- Claude (claude.ai, Desktop, Cowork): `https://claude.ai/api/mcp/auth_callback`
|
|
128
128
|
- Other clients: check the `redirect_uri` query parameter your client sends to the server's `/authorize` endpoint (visible in the server logs)
|
|
129
129
|
|
|
130
|
-
> **Common pitfall**: registering `https://your-server-domain/callback` here breaks sign-in with `AADSTS50011` (redirect URI mismatch) after the user authenticates. The server has no callback endpoint of its own; the authorization code always goes to the MCP client.
|
|
130
|
+
> **Common pitfall**: registering `https://your-server-domain/callback` here breaks sign-in with `AADSTS50011` (redirect URI mismatch) after the user authenticates. The server has no callback endpoint of its own; the authorization code always goes to the MCP client. Note that platform type **Web** applies because this setup uses a client secret; an app without a secret must register the redirect URI under "Mobile and desktop applications" instead.
|
|
131
131
|
|
|
132
132
|
2. **Add API permissions** > Microsoft Graph > Delegated permissions
|
|
133
133
|
Run `npx @softeria/ms-365-mcp-server --org-mode --list-permissions` to print the exact list of permissions required for your enabled tools.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softeria/ms-365-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.120.1",
|
|
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",
|