@softeria/ms-365-mcp-server 0.92.0 → 0.94.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/cli.js +6 -0
- package/dist/endpoints.json +28 -0
- package/dist/generated/client.js +113 -2
- package/dist/obo-client.js +42 -0
- package/dist/server.js +25 -3
- package/package.json +1 -1
- package/src/endpoints.json +28 -0
package/dist/cli.js
CHANGED
|
@@ -35,6 +35,9 @@ program.name("ms-365-mcp-server").description("Microsoft 365 MCP Server").versio
|
|
|
35
35
|
).option(
|
|
36
36
|
"--public-url <url>",
|
|
37
37
|
"Public base URL (e.g. https://mcp.example.com) used in browser-facing OAuth redirects when running behind a reverse proxy. Server-to-server endpoints (token, register) stay on the request host."
|
|
38
|
+
).option(
|
|
39
|
+
"--obo",
|
|
40
|
+
"Enable On-Behalf-Of token exchange in HTTP mode. Exchanges the incoming bearer token for a Graph API token using the OBO flow. Requires MS365_MCP_CLIENT_SECRET."
|
|
38
41
|
).addOption(
|
|
39
42
|
// DEPRECATED: kept only so existing deployments that set --base-url or
|
|
40
43
|
// MS365_MCP_BASE_URL do not crash at startup. Use --public-url /
|
|
@@ -99,6 +102,9 @@ function parseArgs() {
|
|
|
99
102
|
options.enableDynamicRegistration = true;
|
|
100
103
|
}
|
|
101
104
|
}
|
|
105
|
+
if (process.env.MS365_MCP_OBO === "true" || process.env.MS365_MCP_OBO === "1") {
|
|
106
|
+
options.obo = true;
|
|
107
|
+
}
|
|
102
108
|
if (options.cloud) {
|
|
103
109
|
process.env.MS365_MCP_CLOUD_TYPE = options.cloud;
|
|
104
110
|
}
|
package/dist/endpoints.json
CHANGED
|
@@ -235,6 +235,34 @@
|
|
|
235
235
|
"scopes": ["MailboxSettings.ReadWrite"],
|
|
236
236
|
"llmTip": "Deletes a message rule permanently. Use the Inbox folder ID (get it from list-mail-folders) for inbox rules."
|
|
237
237
|
},
|
|
238
|
+
{
|
|
239
|
+
"pathPattern": "/me/inferenceClassification/overrides",
|
|
240
|
+
"method": "get",
|
|
241
|
+
"toolName": "list-focused-inbox-overrides",
|
|
242
|
+
"scopes": ["Mail.Read"],
|
|
243
|
+
"llmTip": "Lists Focused Inbox classification overrides — explicit rules that force messages from a given sender (by SMTP address) into either the Focused or Other tab, regardless of what the Outlook ML classifier would predict. Each override has id, classifyAs ('focused' or 'other'), and senderEmailAddress {name, address}. Returns an empty collection if the user has never set an override."
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"pathPattern": "/me/inferenceClassification/overrides",
|
|
247
|
+
"method": "post",
|
|
248
|
+
"toolName": "create-focused-inbox-override",
|
|
249
|
+
"scopes": ["Mail.ReadWrite"],
|
|
250
|
+
"llmTip": "Creates a Focused Inbox override for a sender. Body: { classifyAs: 'focused', senderEmailAddress: { name: 'Display Name', address: 'sender@example.com' } }. classifyAs must be 'focused' or 'other'. If an override already exists for that SMTP address, POST updates the existing override's name and classifyAs (use this to rename a sender). Resolve the sender's address with list-users or by reading a recent mail header — do not invent SMTP addresses."
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"pathPattern": "/me/inferenceClassification/overrides/{inferenceClassificationOverride-id}",
|
|
254
|
+
"method": "patch",
|
|
255
|
+
"toolName": "update-focused-inbox-override",
|
|
256
|
+
"scopes": ["Mail.ReadWrite"],
|
|
257
|
+
"llmTip": "Updates the classifyAs field of an existing override. Body: { classifyAs: 'focused' } or { classifyAs: 'other' }. Per Graph API, PATCH cannot change senderEmailAddress — to change the SMTP address, delete and recreate the override. To rename the display name only, POST a new override with the same SMTP address (it will overwrite the name)."
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"pathPattern": "/me/inferenceClassification/overrides/{inferenceClassificationOverride-id}",
|
|
261
|
+
"method": "delete",
|
|
262
|
+
"toolName": "delete-focused-inbox-override",
|
|
263
|
+
"scopes": ["Mail.ReadWrite"],
|
|
264
|
+
"llmTip": "Deletes a Focused Inbox override. Future messages from that sender revert to the Outlook ML classifier's default behavior. Use list-focused-inbox-overrides to find the ID first."
|
|
265
|
+
},
|
|
238
266
|
{
|
|
239
267
|
"pathPattern": "/me/events",
|
|
240
268
|
"method": "get",
|
package/dist/generated/client.js
CHANGED
|
@@ -2585,6 +2585,17 @@ const decline_calendar_event_Body = z.object({
|
|
|
2585
2585
|
}).partial().passthrough();
|
|
2586
2586
|
const forward_calendar_event_Body = z.object({ ToRecipients: z.array(microsoft_graph_recipient), Comment: z.string().nullable() }).partial().passthrough();
|
|
2587
2587
|
const snooze_calendar_event_reminder_Body = z.object({ NewReminderTime: microsoft_graph_dateTimeTimeZone }).partial().passthrough();
|
|
2588
|
+
const microsoft_graph_inferenceClassificationType = z.enum(["focused", "other"]);
|
|
2589
|
+
const microsoft_graph_inferenceClassificationOverride = z.object({
|
|
2590
|
+
id: z.string().describe("The unique identifier for an entity. Read-only.").optional(),
|
|
2591
|
+
classifyAs: microsoft_graph_inferenceClassificationType.optional(),
|
|
2592
|
+
senderEmailAddress: microsoft_graph_emailAddress.optional()
|
|
2593
|
+
}).passthrough();
|
|
2594
|
+
const microsoft_graph_inferenceClassificationOverrideCollectionResponse = z.object({
|
|
2595
|
+
"@odata.count": z.number().int().nullable(),
|
|
2596
|
+
"@odata.nextLink": z.string().nullable(),
|
|
2597
|
+
value: z.array(microsoft_graph_inferenceClassificationOverride)
|
|
2598
|
+
}).partial().passthrough();
|
|
2588
2599
|
const microsoft_graph_resourceReference = z.object({
|
|
2589
2600
|
id: z.string().describe("The item's unique identifier.").nullish(),
|
|
2590
2601
|
type: z.string().describe(
|
|
@@ -2996,7 +3007,6 @@ const microsoft_graph_followupFlag = z.object({
|
|
|
2996
3007
|
flagStatus: microsoft_graph_followupFlagStatus.optional(),
|
|
2997
3008
|
startDateTime: microsoft_graph_dateTimeTimeZone.optional()
|
|
2998
3009
|
}).passthrough();
|
|
2999
|
-
const microsoft_graph_inferenceClassificationType = z.enum(["focused", "other"]);
|
|
3000
3010
|
const microsoft_graph_internetMessageHeader = z.object({
|
|
3001
3011
|
name: z.string().describe("Represents the key in a key-value pair.").nullish(),
|
|
3002
3012
|
value: z.string().describe("The value in a key-value pair.").nullish()
|
|
@@ -4749,6 +4759,9 @@ const schemas = {
|
|
|
4749
4759
|
decline_calendar_event_Body,
|
|
4750
4760
|
forward_calendar_event_Body,
|
|
4751
4761
|
snooze_calendar_event_reminder_Body,
|
|
4762
|
+
microsoft_graph_inferenceClassificationType,
|
|
4763
|
+
microsoft_graph_inferenceClassificationOverride,
|
|
4764
|
+
microsoft_graph_inferenceClassificationOverrideCollectionResponse,
|
|
4752
4765
|
microsoft_graph_resourceReference,
|
|
4753
4766
|
microsoft_graph_resourceVisualization,
|
|
4754
4767
|
microsoft_graph_entity,
|
|
@@ -4786,7 +4799,6 @@ const schemas = {
|
|
|
4786
4799
|
microsoft_graph_messageRule,
|
|
4787
4800
|
microsoft_graph_followupFlagStatus,
|
|
4788
4801
|
microsoft_graph_followupFlag,
|
|
4789
|
-
microsoft_graph_inferenceClassificationType,
|
|
4790
4802
|
microsoft_graph_internetMessageHeader,
|
|
4791
4803
|
microsoft_graph_message,
|
|
4792
4804
|
microsoft_graph_mailFolder,
|
|
@@ -8453,6 +8465,105 @@ Based on this value, you can better adjust the parameters and call findMeetingTi
|
|
|
8453
8465
|
],
|
|
8454
8466
|
response: z.void()
|
|
8455
8467
|
},
|
|
8468
|
+
{
|
|
8469
|
+
method: "get",
|
|
8470
|
+
path: "/me/inferenceClassification/overrides",
|
|
8471
|
+
alias: "list-focused-inbox-overrides",
|
|
8472
|
+
description: `Get the overrides that a user has set up to always classify messages from certain senders in specific ways. Each override corresponds to an SMTP address of a sender. Initially, a user does not have any overrides.`,
|
|
8473
|
+
requestFormat: "json",
|
|
8474
|
+
parameters: [
|
|
8475
|
+
{
|
|
8476
|
+
name: "$top",
|
|
8477
|
+
type: "Query",
|
|
8478
|
+
schema: z.number().int().gte(0).describe("Show only the first n items").optional()
|
|
8479
|
+
},
|
|
8480
|
+
{
|
|
8481
|
+
name: "$skip",
|
|
8482
|
+
type: "Query",
|
|
8483
|
+
schema: z.number().int().gte(0).describe("Skip the first n items").optional()
|
|
8484
|
+
},
|
|
8485
|
+
{
|
|
8486
|
+
name: "$search",
|
|
8487
|
+
type: "Query",
|
|
8488
|
+
schema: z.string().describe("Search items by search phrases").optional()
|
|
8489
|
+
},
|
|
8490
|
+
{
|
|
8491
|
+
name: "$filter",
|
|
8492
|
+
type: "Query",
|
|
8493
|
+
schema: z.string().describe("Filter items by property values").optional()
|
|
8494
|
+
},
|
|
8495
|
+
{
|
|
8496
|
+
name: "$count",
|
|
8497
|
+
type: "Query",
|
|
8498
|
+
schema: z.boolean().describe("Include count of items").optional()
|
|
8499
|
+
},
|
|
8500
|
+
{
|
|
8501
|
+
name: "$orderby",
|
|
8502
|
+
type: "Query",
|
|
8503
|
+
schema: z.array(z.string()).describe("Order items by property values").optional()
|
|
8504
|
+
},
|
|
8505
|
+
{
|
|
8506
|
+
name: "$select",
|
|
8507
|
+
type: "Query",
|
|
8508
|
+
schema: z.array(z.string()).describe("Select properties to be returned").optional()
|
|
8509
|
+
},
|
|
8510
|
+
{
|
|
8511
|
+
name: "$expand",
|
|
8512
|
+
type: "Query",
|
|
8513
|
+
schema: z.array(z.string()).describe("Expand related entities").optional()
|
|
8514
|
+
}
|
|
8515
|
+
],
|
|
8516
|
+
response: z.void()
|
|
8517
|
+
},
|
|
8518
|
+
{
|
|
8519
|
+
method: "post",
|
|
8520
|
+
path: "/me/inferenceClassification/overrides",
|
|
8521
|
+
alias: "create-focused-inbox-override",
|
|
8522
|
+
description: `Create an override for a sender identified by an SMTP address. Future messages from that SMTP address will be consistently classified
|
|
8523
|
+
as specified in the override. Note`,
|
|
8524
|
+
requestFormat: "json",
|
|
8525
|
+
parameters: [
|
|
8526
|
+
{
|
|
8527
|
+
name: "body",
|
|
8528
|
+
description: `New navigation property`,
|
|
8529
|
+
type: "Body",
|
|
8530
|
+
schema: microsoft_graph_inferenceClassificationOverride
|
|
8531
|
+
}
|
|
8532
|
+
],
|
|
8533
|
+
response: z.void()
|
|
8534
|
+
},
|
|
8535
|
+
{
|
|
8536
|
+
method: "patch",
|
|
8537
|
+
path: "/me/inferenceClassification/overrides/:inferenceClassificationOverrideId",
|
|
8538
|
+
alias: "update-focused-inbox-override",
|
|
8539
|
+
description: `Change the classifyAs field of an override as specified. You cannot use PATCH to change any other fields in an inferenceClassificationOverride instance. If an override exists for a sender and the sender changes his/her display name, you can use POST to force an update to the name field in the existing override. If an override exists for a sender and the sender changes his/her SMTP address, deleting the existing override and creating a new one with
|
|
8540
|
+
the new SMTP address is the only way to 'update' the override for this sender.`,
|
|
8541
|
+
requestFormat: "json",
|
|
8542
|
+
parameters: [
|
|
8543
|
+
{
|
|
8544
|
+
name: "body",
|
|
8545
|
+
description: `New navigation property values`,
|
|
8546
|
+
type: "Body",
|
|
8547
|
+
schema: microsoft_graph_inferenceClassificationOverride
|
|
8548
|
+
}
|
|
8549
|
+
],
|
|
8550
|
+
response: z.void()
|
|
8551
|
+
},
|
|
8552
|
+
{
|
|
8553
|
+
method: "delete",
|
|
8554
|
+
path: "/me/inferenceClassification/overrides/:inferenceClassificationOverrideId",
|
|
8555
|
+
alias: "delete-focused-inbox-override",
|
|
8556
|
+
description: `Delete an override specified by its ID.`,
|
|
8557
|
+
requestFormat: "json",
|
|
8558
|
+
parameters: [
|
|
8559
|
+
{
|
|
8560
|
+
name: "If-Match",
|
|
8561
|
+
type: "Header",
|
|
8562
|
+
schema: z.string().describe("ETag").optional()
|
|
8563
|
+
}
|
|
8564
|
+
],
|
|
8565
|
+
response: z.void()
|
|
8566
|
+
},
|
|
8456
8567
|
{
|
|
8457
8568
|
method: "get",
|
|
8458
8569
|
path: "/me/insights/trending",
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ConfidentialClientApplication } from "@azure/msal-node";
|
|
2
|
+
import logger from "./logger.js";
|
|
3
|
+
import { getCloudEndpoints } from "./cloud-config.js";
|
|
4
|
+
class OboClient {
|
|
5
|
+
constructor(secrets) {
|
|
6
|
+
if (!secrets.clientSecret) {
|
|
7
|
+
throw new Error(
|
|
8
|
+
"On-Behalf-Of flow requires MS365_MCP_CLIENT_SECRET to be set (confidential client)."
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
const cloudEndpoints = getCloudEndpoints(secrets.cloudType);
|
|
12
|
+
this.cca = new ConfidentialClientApplication({
|
|
13
|
+
auth: {
|
|
14
|
+
clientId: secrets.clientId,
|
|
15
|
+
clientSecret: secrets.clientSecret,
|
|
16
|
+
authority: `${cloudEndpoints.authority}/${secrets.tenantId || "common"}`
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
const graphBase = cloudEndpoints.graphApi.replace(/\/$/, "");
|
|
20
|
+
this.graphScopes = [`${graphBase}/.default`];
|
|
21
|
+
}
|
|
22
|
+
async exchangeToken(userAssertion) {
|
|
23
|
+
try {
|
|
24
|
+
const result = await this.cca.acquireTokenOnBehalfOf({
|
|
25
|
+
oboAssertion: userAssertion,
|
|
26
|
+
scopes: this.graphScopes
|
|
27
|
+
});
|
|
28
|
+
if (!result?.accessToken) {
|
|
29
|
+
throw new Error("OBO token exchange returned no access token");
|
|
30
|
+
}
|
|
31
|
+
logger.info("OBO token exchange successful");
|
|
32
|
+
return result.accessToken;
|
|
33
|
+
} catch (error) {
|
|
34
|
+
logger.error(`OBO token exchange failed: ${error.message}`);
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
var obo_client_default = OboClient;
|
|
40
|
+
export {
|
|
41
|
+
obo_client_default as default
|
|
42
|
+
};
|
package/dist/server.js
CHANGED
|
@@ -19,6 +19,7 @@ import { getSecrets } from "./secrets.js";
|
|
|
19
19
|
import { getCloudEndpoints } from "./cloud-config.js";
|
|
20
20
|
import { requestContext } from "./request-context.js";
|
|
21
21
|
import crypto from "node:crypto";
|
|
22
|
+
import OboClient from "./obo-client.js";
|
|
22
23
|
function parseHttpOption(httpOption) {
|
|
23
24
|
if (typeof httpOption === "boolean") {
|
|
24
25
|
return { host: void 0, port: 3e3 };
|
|
@@ -45,6 +46,7 @@ class MicrosoftGraphServer {
|
|
|
45
46
|
this.graphClient = null;
|
|
46
47
|
this.server = null;
|
|
47
48
|
this.secrets = null;
|
|
49
|
+
this.oboClient = null;
|
|
48
50
|
}
|
|
49
51
|
createMcpServer() {
|
|
50
52
|
const server = new McpServer(
|
|
@@ -103,6 +105,18 @@ class MicrosoftGraphServer {
|
|
|
103
105
|
} catch (err) {
|
|
104
106
|
logger.warn(`Failed to detect multi-account mode: ${err.message}`);
|
|
105
107
|
}
|
|
108
|
+
if (this.options.obo) {
|
|
109
|
+
if (!this.options.http) {
|
|
110
|
+
throw new Error("--obo requires --http (On-Behalf-Of flow only works in HTTP mode).");
|
|
111
|
+
}
|
|
112
|
+
if (!this.secrets.clientSecret) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
"--obo requires MS365_MCP_CLIENT_SECRET to be set (confidential client required for On-Behalf-Of flow)."
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
this.oboClient = new OboClient(this.secrets);
|
|
118
|
+
logger.info("On-Behalf-Of (OBO) flow enabled");
|
|
119
|
+
}
|
|
106
120
|
const outputFormat = this.options.toon ? "toon" : "json";
|
|
107
121
|
this.graphClient = new GraphClient(this.authManager, this.secrets, outputFormat);
|
|
108
122
|
if (!this.options.http) {
|
|
@@ -174,7 +188,7 @@ class MicrosoftGraphServer {
|
|
|
174
188
|
const protocol = req.secure ? "https" : "http";
|
|
175
189
|
const requestOrigin = `${protocol}://${req.get("host")}`;
|
|
176
190
|
const browserBase = publicBase ?? requestOrigin;
|
|
177
|
-
const scopes = buildScopesFromEndpoints(this.options.orgMode, this.options.enabledTools);
|
|
191
|
+
const scopes = this.options.obo ? [`api://${this.secrets.clientId}/access_as_user`] : buildScopesFromEndpoints(this.options.orgMode, this.options.enabledTools);
|
|
178
192
|
res.json({
|
|
179
193
|
resource: `${requestOrigin}/mcp`,
|
|
180
194
|
authorization_servers: [browserBase],
|
|
@@ -398,7 +412,11 @@ class MicrosoftGraphServer {
|
|
|
398
412
|
};
|
|
399
413
|
try {
|
|
400
414
|
if (req.microsoftAuth) {
|
|
401
|
-
|
|
415
|
+
let accessToken = req.microsoftAuth.accessToken;
|
|
416
|
+
if (this.oboClient) {
|
|
417
|
+
accessToken = await this.oboClient.exchangeToken(accessToken);
|
|
418
|
+
}
|
|
419
|
+
await requestContext.run({ accessToken }, handler);
|
|
402
420
|
} else {
|
|
403
421
|
await handler();
|
|
404
422
|
}
|
|
@@ -436,7 +454,11 @@ class MicrosoftGraphServer {
|
|
|
436
454
|
};
|
|
437
455
|
try {
|
|
438
456
|
if (req.microsoftAuth) {
|
|
439
|
-
|
|
457
|
+
let accessToken = req.microsoftAuth.accessToken;
|
|
458
|
+
if (this.oboClient) {
|
|
459
|
+
accessToken = await this.oboClient.exchangeToken(accessToken);
|
|
460
|
+
}
|
|
461
|
+
await requestContext.run({ accessToken }, handler);
|
|
440
462
|
} else {
|
|
441
463
|
await handler();
|
|
442
464
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@softeria/ms-365-mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.94.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
|
@@ -235,6 +235,34 @@
|
|
|
235
235
|
"scopes": ["MailboxSettings.ReadWrite"],
|
|
236
236
|
"llmTip": "Deletes a message rule permanently. Use the Inbox folder ID (get it from list-mail-folders) for inbox rules."
|
|
237
237
|
},
|
|
238
|
+
{
|
|
239
|
+
"pathPattern": "/me/inferenceClassification/overrides",
|
|
240
|
+
"method": "get",
|
|
241
|
+
"toolName": "list-focused-inbox-overrides",
|
|
242
|
+
"scopes": ["Mail.Read"],
|
|
243
|
+
"llmTip": "Lists Focused Inbox classification overrides — explicit rules that force messages from a given sender (by SMTP address) into either the Focused or Other tab, regardless of what the Outlook ML classifier would predict. Each override has id, classifyAs ('focused' or 'other'), and senderEmailAddress {name, address}. Returns an empty collection if the user has never set an override."
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
"pathPattern": "/me/inferenceClassification/overrides",
|
|
247
|
+
"method": "post",
|
|
248
|
+
"toolName": "create-focused-inbox-override",
|
|
249
|
+
"scopes": ["Mail.ReadWrite"],
|
|
250
|
+
"llmTip": "Creates a Focused Inbox override for a sender. Body: { classifyAs: 'focused', senderEmailAddress: { name: 'Display Name', address: 'sender@example.com' } }. classifyAs must be 'focused' or 'other'. If an override already exists for that SMTP address, POST updates the existing override's name and classifyAs (use this to rename a sender). Resolve the sender's address with list-users or by reading a recent mail header — do not invent SMTP addresses."
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
"pathPattern": "/me/inferenceClassification/overrides/{inferenceClassificationOverride-id}",
|
|
254
|
+
"method": "patch",
|
|
255
|
+
"toolName": "update-focused-inbox-override",
|
|
256
|
+
"scopes": ["Mail.ReadWrite"],
|
|
257
|
+
"llmTip": "Updates the classifyAs field of an existing override. Body: { classifyAs: 'focused' } or { classifyAs: 'other' }. Per Graph API, PATCH cannot change senderEmailAddress — to change the SMTP address, delete and recreate the override. To rename the display name only, POST a new override with the same SMTP address (it will overwrite the name)."
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
"pathPattern": "/me/inferenceClassification/overrides/{inferenceClassificationOverride-id}",
|
|
261
|
+
"method": "delete",
|
|
262
|
+
"toolName": "delete-focused-inbox-override",
|
|
263
|
+
"scopes": ["Mail.ReadWrite"],
|
|
264
|
+
"llmTip": "Deletes a Focused Inbox override. Future messages from that sender revert to the Outlook ML classifier's default behavior. Use list-focused-inbox-overrides to find the ID first."
|
|
265
|
+
},
|
|
238
266
|
{
|
|
239
267
|
"pathPattern": "/me/events",
|
|
240
268
|
"method": "get",
|