@softeria/ms-365-mcp-server 0.77.0 → 0.79.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.
@@ -352,6 +352,7 @@
352
352
  "pathPattern": "/me/calendar/getSchedule",
353
353
  "method": "post",
354
354
  "toolName": "get-schedule",
355
+ "readOnly": true,
355
356
  "workScopes": ["Calendars.Read"]
356
357
  },
357
358
  {
@@ -407,6 +408,7 @@
407
408
  "pathPattern": "/me/findMeetingTimes",
408
409
  "method": "post",
409
410
  "toolName": "find-meeting-times",
411
+ "readOnly": true,
410
412
  "workScopes": ["Calendars.Read.Shared"]
411
413
  },
412
414
  {
@@ -944,6 +946,21 @@
944
946
  "toolName": "get-chat-message",
945
947
  "workScopes": ["ChatMessage.Read"]
946
948
  },
949
+ {
950
+ "pathPattern": "/chats/{chat-id}/messages/{chatMessage-id}/hostedContents",
951
+ "method": "get",
952
+ "toolName": "list-chat-message-hosted-contents",
953
+ "workScopes": ["ChatMessage.Read"],
954
+ "llmTip": "Lists hosted-content references (inline images, code snippets) attached to a Teams chat message. Returns an array of { id, contentType }. Use get-chat-message-hosted-content with the id to download the bytes. Hosted content IDs can also be parsed from the <img src> URL in the message body between /hostedContents/ and /$value."
955
+ },
956
+ {
957
+ "pathPattern": "/chats/{chat-id}/messages/{chatMessage-id}/hostedContents/{chatMessageHostedContent-id}/$value",
958
+ "method": "get",
959
+ "toolName": "get-chat-message-hosted-content",
960
+ "workScopes": ["ChatMessage.Read"],
961
+ "returnDownloadUrl": true,
962
+ "llmTip": "Returns raw bytes of a hosted content item (typically image/png or image/jpeg) attached to a Teams 1:1 or group chat message. Use list-chat-message-hosted-contents to discover IDs, or extract the ID from the <img src> URL in the message body."
963
+ },
947
964
  {
948
965
  "pathPattern": "/chats/{chat-id}/messages",
949
966
  "method": "post",
@@ -1005,6 +1022,21 @@
1005
1022
  "toolName": "get-channel-message",
1006
1023
  "workScopes": ["ChannelMessage.Read.All"]
1007
1024
  },
1025
+ {
1026
+ "pathPattern": "/teams/{team-id}/channels/{channel-id}/messages/{chatMessage-id}/hostedContents",
1027
+ "method": "get",
1028
+ "toolName": "list-channel-message-hosted-contents",
1029
+ "workScopes": ["ChannelMessage.Read.All"],
1030
+ "llmTip": "Lists hosted-content references (inline images, code snippets) attached to a Teams channel message. Returns an array of { id, contentType }. Use get-channel-message-hosted-content with the id to download bytes."
1031
+ },
1032
+ {
1033
+ "pathPattern": "/teams/{team-id}/channels/{channel-id}/messages/{chatMessage-id}/hostedContents/{chatMessageHostedContent-id}/$value",
1034
+ "method": "get",
1035
+ "toolName": "get-channel-message-hosted-content",
1036
+ "workScopes": ["ChannelMessage.Read.All"],
1037
+ "returnDownloadUrl": true,
1038
+ "llmTip": "Returns raw bytes of a hosted content item attached to a Teams channel message. Use list-channel-message-hosted-contents to discover IDs."
1039
+ },
1008
1040
  {
1009
1041
  "pathPattern": "/teams/{team-id}/channels/{channel-id}/messages",
1010
1042
  "method": "post",
@@ -422,6 +422,11 @@ const microsoft_graph_chatMessageCollectionResponse = z.object({
422
422
  "@odata.nextLink": z.string().nullable(),
423
423
  value: z.array(microsoft_graph_chatMessage)
424
424
  }).partial().passthrough();
425
+ const microsoft_graph_chatMessageHostedContentCollectionResponse = z.object({
426
+ "@odata.count": z.number().int().nullable(),
427
+ "@odata.nextLink": z.string().nullable(),
428
+ value: z.array(microsoft_graph_chatMessageHostedContent)
429
+ }).partial().passthrough();
425
430
  const microsoft_graph_pinnedChatMessageInfoCollectionResponse = z.object({
426
431
  "@odata.count": z.number().int().nullable(),
427
432
  "@odata.nextLink": z.string().nullable(),
@@ -4303,6 +4308,7 @@ const schemas = {
4303
4308
  microsoft_graph_ODataErrors_ODataError,
4304
4309
  microsoft_graph_conversationMemberCollectionResponse,
4305
4310
  microsoft_graph_chatMessageCollectionResponse,
4311
+ microsoft_graph_chatMessageHostedContentCollectionResponse,
4306
4312
  microsoft_graph_pinnedChatMessageInfoCollectionResponse,
4307
4313
  microsoft_graph_outOfOfficeSettings,
4308
4314
  microsoft_graph_dateTimeTimeZone,
@@ -4840,6 +4846,64 @@ const endpoints = makeApi([
4840
4846
  ],
4841
4847
  response: z.void()
4842
4848
  },
4849
+ {
4850
+ method: "get",
4851
+ path: "/chats/:chatId/messages/:chatMessageId/hostedContents",
4852
+ alias: "list-chat-message-hosted-contents",
4853
+ description: `Retrieve the list of chatMessageHostedContent objects from a message. This API only lists the hosted content objects. To get the content bytes, see get chatmessage hosted content.`,
4854
+ requestFormat: "json",
4855
+ parameters: [
4856
+ {
4857
+ name: "$top",
4858
+ type: "Query",
4859
+ schema: z.number().int().gte(0).describe("Show only the first n items").optional()
4860
+ },
4861
+ {
4862
+ name: "$skip",
4863
+ type: "Query",
4864
+ schema: z.number().int().gte(0).describe("Skip the first n items").optional()
4865
+ },
4866
+ {
4867
+ name: "$search",
4868
+ type: "Query",
4869
+ schema: z.string().describe("Search items by search phrases").optional()
4870
+ },
4871
+ {
4872
+ name: "$filter",
4873
+ type: "Query",
4874
+ schema: z.string().describe("Filter items by property values").optional()
4875
+ },
4876
+ {
4877
+ name: "$count",
4878
+ type: "Query",
4879
+ schema: z.boolean().describe("Include count of items").optional()
4880
+ },
4881
+ {
4882
+ name: "$orderby",
4883
+ type: "Query",
4884
+ schema: z.array(z.string()).describe("Order items by property values").optional()
4885
+ },
4886
+ {
4887
+ name: "$select",
4888
+ type: "Query",
4889
+ schema: z.array(z.string()).describe("Select properties to be returned").optional()
4890
+ },
4891
+ {
4892
+ name: "$expand",
4893
+ type: "Query",
4894
+ schema: z.array(z.string()).describe("Expand related entities").optional()
4895
+ }
4896
+ ],
4897
+ response: z.void()
4898
+ },
4899
+ {
4900
+ method: "get",
4901
+ path: "/chats/:chatId/messages/:chatMessageId/hostedContents/:chatMessageHostedContentId/$value",
4902
+ alias: "get-chat-message-hosted-content",
4903
+ description: `Retrieve the list of chatMessageHostedContent objects from a message. This API only lists the hosted content objects. To get the content bytes, see get chatmessage hosted content.`,
4904
+ requestFormat: "json",
4905
+ response: z.void()
4906
+ },
4843
4907
  {
4844
4908
  method: "get",
4845
4909
  path: "/chats/:chatId/messages/:chatMessageId/replies",
@@ -10608,6 +10672,64 @@ To monitor future changes, call the delta API by using the @odata.deltaLink in t
10608
10672
  ],
10609
10673
  response: z.void()
10610
10674
  },
10675
+ {
10676
+ method: "get",
10677
+ path: "/teams/:teamId/channels/:channelId/messages/:chatMessageId/hostedContents",
10678
+ alias: "list-channel-message-hosted-contents",
10679
+ description: `Retrieve the list of chatMessageHostedContent objects from a message. This API only lists the hosted content objects. To get the content bytes, see get chatmessage hosted content.`,
10680
+ requestFormat: "json",
10681
+ parameters: [
10682
+ {
10683
+ name: "$top",
10684
+ type: "Query",
10685
+ schema: z.number().int().gte(0).describe("Show only the first n items").optional()
10686
+ },
10687
+ {
10688
+ name: "$skip",
10689
+ type: "Query",
10690
+ schema: z.number().int().gte(0).describe("Skip the first n items").optional()
10691
+ },
10692
+ {
10693
+ name: "$search",
10694
+ type: "Query",
10695
+ schema: z.string().describe("Search items by search phrases").optional()
10696
+ },
10697
+ {
10698
+ name: "$filter",
10699
+ type: "Query",
10700
+ schema: z.string().describe("Filter items by property values").optional()
10701
+ },
10702
+ {
10703
+ name: "$count",
10704
+ type: "Query",
10705
+ schema: z.boolean().describe("Include count of items").optional()
10706
+ },
10707
+ {
10708
+ name: "$orderby",
10709
+ type: "Query",
10710
+ schema: z.array(z.string()).describe("Order items by property values").optional()
10711
+ },
10712
+ {
10713
+ name: "$select",
10714
+ type: "Query",
10715
+ schema: z.array(z.string()).describe("Select properties to be returned").optional()
10716
+ },
10717
+ {
10718
+ name: "$expand",
10719
+ type: "Query",
10720
+ schema: z.array(z.string()).describe("Expand related entities").optional()
10721
+ }
10722
+ ],
10723
+ response: z.void()
10724
+ },
10725
+ {
10726
+ method: "get",
10727
+ path: "/teams/:teamId/channels/:channelId/messages/:chatMessageId/hostedContents/:chatMessageHostedContentId/$value",
10728
+ alias: "get-channel-message-hosted-content",
10729
+ description: `Retrieve the list of chatMessageHostedContent objects from a message. This API only lists the hosted content objects. To get the content bytes, see get chatmessage hosted content.`,
10730
+ requestFormat: "json",
10731
+ response: z.void()
10732
+ },
10611
10733
  {
10612
10734
  method: "get",
10613
10735
  path: "/teams/:teamId/channels/:channelId/messages/:chatMessageId/replies",
@@ -3,6 +3,27 @@ import { refreshAccessToken } from "./lib/microsoft-auth.js";
3
3
  import { encode as toonEncode } from "@toon-format/toon";
4
4
  import { getCloudEndpoints } from "./cloud-config.js";
5
5
  import { getRequestTokens } from "./request-context.js";
6
+ function isBinaryContentType(contentType) {
7
+ if (!contentType) return false;
8
+ const lower = contentType.toLowerCase().split(";")[0].trim();
9
+ if (!lower) return false;
10
+ if (lower.startsWith("image/") || lower.startsWith("video/") || lower.startsWith("audio/") || lower.startsWith("font/")) {
11
+ return true;
12
+ }
13
+ if (lower === "application/octet-stream" || lower === "application/pdf") {
14
+ return true;
15
+ }
16
+ if (lower.startsWith("application/zip") || lower.startsWith("application/x-zip")) {
17
+ return true;
18
+ }
19
+ if (lower.startsWith("application/vnd.") || lower.startsWith("application/x-")) {
20
+ if (lower.endsWith("+json") || lower.endsWith("+xml") || lower.endsWith("+text")) {
21
+ return false;
22
+ }
23
+ return true;
24
+ }
25
+ return false;
26
+ }
6
27
  class GraphClient {
7
28
  constructor(authManager, secrets, outputFormat = "json") {
8
29
  this.outputFormat = "json";
@@ -40,15 +61,28 @@ class GraphClient {
40
61
  `Microsoft Graph API error: ${response.status} ${response.statusText} - ${await response.text()}`
41
62
  );
42
63
  }
43
- const text = await response.text();
64
+ const contentTypeHeader = response.headers?.get?.("content-type") || "";
65
+ const isBinaryResponse = isBinaryContentType(contentTypeHeader);
44
66
  let result;
45
- if (text === "") {
46
- result = { message: "OK!" };
67
+ if (isBinaryResponse) {
68
+ const buffer = Buffer.from(await response.arrayBuffer());
69
+ result = {
70
+ message: "OK!",
71
+ contentType: contentTypeHeader,
72
+ encoding: "base64",
73
+ contentLength: buffer.byteLength,
74
+ contentBytes: buffer.toString("base64")
75
+ };
47
76
  } else {
48
- try {
49
- result = JSON.parse(text);
50
- } catch {
51
- result = { message: "OK!", rawResponse: text };
77
+ const text = await response.text();
78
+ if (text === "") {
79
+ result = { message: "OK!" };
80
+ } else {
81
+ try {
82
+ result = JSON.parse(text);
83
+ } catch {
84
+ result = { message: "OK!", rawResponse: text };
85
+ }
52
86
  }
53
87
  }
54
88
  if (options.includeHeaders) {
@@ -205,5 +239,6 @@ class GraphClient {
205
239
  }
206
240
  var graph_client_default = GraphClient;
207
241
  export {
208
- graph_client_default as default
242
+ graph_client_default as default,
243
+ isBinaryContentType
209
244
  };
@@ -310,10 +310,13 @@ function registerGraphTools(server, graphClient, readOnly = false, enabledToolsP
310
310
  skippedCount++;
311
311
  continue;
312
312
  }
313
- if (readOnly && tool.method.toUpperCase() !== "GET") {
314
- logger.info(`Skipping write operation ${tool.alias} in read-only mode`);
315
- skippedCount++;
316
- continue;
313
+ const method = tool.method.toUpperCase();
314
+ if (readOnly && method !== "GET") {
315
+ if (!(method === "POST" && endpointConfig?.readOnly)) {
316
+ logger.info(`Skipping write operation ${tool.alias} in read-only mode`);
317
+ skippedCount++;
318
+ continue;
319
+ }
317
320
  }
318
321
  if (enabledToolsRegex && !enabledToolsRegex.test(tool.alias)) {
319
322
  logger.info(`Skipping tool ${tool.alias} - doesn't match filter pattern`);
@@ -464,8 +467,11 @@ function buildToolsRegistry(readOnly, orgMode) {
464
467
  if (!orgMode && endpointConfig && !endpointConfig.scopes && endpointConfig.workScopes) {
465
468
  continue;
466
469
  }
467
- if (readOnly && tool.method.toUpperCase() !== "GET") {
468
- continue;
470
+ const method = tool.method.toUpperCase();
471
+ if (readOnly && method !== "GET") {
472
+ if (!(method === "POST" && endpointConfig?.readOnly)) {
473
+ continue;
474
+ }
469
475
  }
470
476
  toolsMap.set(tool.alias, { tool, config: endpointConfig });
471
477
  }
@@ -0,0 +1,25 @@
1
+ function buildGeneralMcpInstructions(opts) {
2
+ const parts = [
3
+ "Microsoft 365 MCP exposes Microsoft Graph through MCP tools. Use each tool name, description, and parameter schema as the source of truth.",
4
+ "Microsoft Graph OData: do not combine $filter with $search on the same request. For lists, prefer modest $top (or top) and $select; avoid very large pages unless the user needs them.",
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
+ "When you need an organizational user or recipient address, resolve it with list-users (or another directory tool); do not invent SMTP addresses.",
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."
9
+ ];
10
+ if (opts.readOnly) parts.push("This server is read-only; write operations are disabled.");
11
+ if (opts.multiAccount)
12
+ parts.push("Multiple accounts: pass the account parameter when required (see list-accounts).");
13
+ if (!opts.orgMode)
14
+ parts.push("Work/school-only tools require starting the server with --org-mode.");
15
+ return parts.join(" ");
16
+ }
17
+ const DISCOVERY_MODE_INSTRUCTIONS_ADDON = "DISCOVERY MODE ADD-ON: Graph is reached via search-tools then execute-tool (plus auth helpers). Call search-tools with short keywords, then execute-tool with tool_name exactly as returned; put Graph parameters in the parameters object. If search-tools returns no matches, retry with shorter or different keywords.";
18
+ function buildMcpServerInstructions(opts) {
19
+ const general = buildGeneralMcpInstructions(opts);
20
+ if (!opts.discovery) return general;
21
+ return `${general} ${DISCOVERY_MODE_INSTRUCTIONS_ADDON}`;
22
+ }
23
+ export {
24
+ buildMcpServerInstructions
25
+ };
package/dist/server.js CHANGED
@@ -6,6 +6,7 @@ import express from "express";
6
6
  import logger, { enableConsoleLogging } from "./logger.js";
7
7
  import { registerAuthTools } from "./auth-tools.js";
8
8
  import { registerGraphTools, registerDiscoveryTools } from "./graph-tools.js";
9
+ import { buildMcpServerInstructions } from "./mcp-instructions.js";
9
10
  import GraphClient from "./graph-client.js";
10
11
  import { buildScopesFromEndpoints } from "./auth.js";
11
12
  import { MicrosoftOAuthProvider } from "./oauth-provider.js";
@@ -46,10 +47,20 @@ class MicrosoftGraphServer {
46
47
  this.secrets = null;
47
48
  }
48
49
  createMcpServer() {
49
- const server = new McpServer({
50
- name: "Microsoft365MCP",
51
- version: this.version
52
- });
50
+ const server = new McpServer(
51
+ {
52
+ name: "Microsoft365MCP",
53
+ version: this.version
54
+ },
55
+ {
56
+ instructions: buildMcpServerInstructions({
57
+ discovery: Boolean(this.options.discovery),
58
+ orgMode: Boolean(this.options.orgMode),
59
+ readOnly: Boolean(this.options.readOnly),
60
+ multiAccount: this.multiAccount
61
+ })
62
+ }
63
+ );
53
64
  const shouldRegisterAuthTools = !this.options.http || this.options.enableAuthTools;
54
65
  if (shouldRegisterAuthTools) {
55
66
  registerAuthTools(server, this.authManager);
@@ -1,10 +1,11 @@
1
- 2026-04-14 20:25:35 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
2
- 2026-04-14 20:25:35 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
3
- 2026-04-14 20:25:35 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
4
- 2026-04-14 20:25:35 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me/messages
5
- 2026-04-14 20:25:35 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me/calendar
6
- 2026-04-14 20:25:37 INFO: Using environment variables for secrets
7
- 2026-04-14 20:25:37 INFO: Using environment variables for secrets
8
- 2026-04-14 20:25:37 INFO: Using environment variables for secrets
9
- 2026-04-14 20:25:37 INFO: Using environment variables for secrets
10
- 2026-04-14 20:25:37 INFO: Using environment variables for secrets
1
+ 2026-04-14 21:28:26 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
2
+ 2026-04-14 21:28:26 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
3
+ 2026-04-14 21:28:26 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me
4
+ 2026-04-14 21:28:26 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me/messages
5
+ 2026-04-14 21:28:26 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me/calendar
6
+ 2026-04-14 21:28:26 INFO: [GRAPH CLIENT] Final URL being sent to Microsoft: https://graph.microsoft.com/v1.0/me/photo/$value
7
+ 2026-04-14 21:28:27 INFO: Using environment variables for secrets
8
+ 2026-04-14 21:28:27 INFO: Using environment variables for secrets
9
+ 2026-04-14 21:28:27 INFO: Using environment variables for secrets
10
+ 2026-04-14 21:28:27 INFO: Using environment variables for secrets
11
+ 2026-04-14 21:28:27 INFO: Using environment variables for secrets
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@softeria/ms-365-mcp-server",
3
- "version": "0.77.0",
3
+ "version": "0.79.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",
@@ -352,6 +352,7 @@
352
352
  "pathPattern": "/me/calendar/getSchedule",
353
353
  "method": "post",
354
354
  "toolName": "get-schedule",
355
+ "readOnly": true,
355
356
  "workScopes": ["Calendars.Read"]
356
357
  },
357
358
  {
@@ -407,6 +408,7 @@
407
408
  "pathPattern": "/me/findMeetingTimes",
408
409
  "method": "post",
409
410
  "toolName": "find-meeting-times",
411
+ "readOnly": true,
410
412
  "workScopes": ["Calendars.Read.Shared"]
411
413
  },
412
414
  {
@@ -944,6 +946,21 @@
944
946
  "toolName": "get-chat-message",
945
947
  "workScopes": ["ChatMessage.Read"]
946
948
  },
949
+ {
950
+ "pathPattern": "/chats/{chat-id}/messages/{chatMessage-id}/hostedContents",
951
+ "method": "get",
952
+ "toolName": "list-chat-message-hosted-contents",
953
+ "workScopes": ["ChatMessage.Read"],
954
+ "llmTip": "Lists hosted-content references (inline images, code snippets) attached to a Teams chat message. Returns an array of { id, contentType }. Use get-chat-message-hosted-content with the id to download the bytes. Hosted content IDs can also be parsed from the <img src> URL in the message body between /hostedContents/ and /$value."
955
+ },
956
+ {
957
+ "pathPattern": "/chats/{chat-id}/messages/{chatMessage-id}/hostedContents/{chatMessageHostedContent-id}/$value",
958
+ "method": "get",
959
+ "toolName": "get-chat-message-hosted-content",
960
+ "workScopes": ["ChatMessage.Read"],
961
+ "returnDownloadUrl": true,
962
+ "llmTip": "Returns raw bytes of a hosted content item (typically image/png or image/jpeg) attached to a Teams 1:1 or group chat message. Use list-chat-message-hosted-contents to discover IDs, or extract the ID from the <img src> URL in the message body."
963
+ },
947
964
  {
948
965
  "pathPattern": "/chats/{chat-id}/messages",
949
966
  "method": "post",
@@ -1005,6 +1022,21 @@
1005
1022
  "toolName": "get-channel-message",
1006
1023
  "workScopes": ["ChannelMessage.Read.All"]
1007
1024
  },
1025
+ {
1026
+ "pathPattern": "/teams/{team-id}/channels/{channel-id}/messages/{chatMessage-id}/hostedContents",
1027
+ "method": "get",
1028
+ "toolName": "list-channel-message-hosted-contents",
1029
+ "workScopes": ["ChannelMessage.Read.All"],
1030
+ "llmTip": "Lists hosted-content references (inline images, code snippets) attached to a Teams channel message. Returns an array of { id, contentType }. Use get-channel-message-hosted-content with the id to download bytes."
1031
+ },
1032
+ {
1033
+ "pathPattern": "/teams/{team-id}/channels/{channel-id}/messages/{chatMessage-id}/hostedContents/{chatMessageHostedContent-id}/$value",
1034
+ "method": "get",
1035
+ "toolName": "get-channel-message-hosted-content",
1036
+ "workScopes": ["ChannelMessage.Read.All"],
1037
+ "returnDownloadUrl": true,
1038
+ "llmTip": "Returns raw bytes of a hosted content item attached to a Teams channel message. Use list-channel-message-hosted-contents to discover IDs."
1039
+ },
1008
1040
  {
1009
1041
  "pathPattern": "/teams/{team-id}/channels/{channel-id}/messages",
1010
1042
  "method": "post",