m365connector 0.3.1 → 0.3.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # m365connector
2
2
 
3
- MCP server that lets AI agents search and read your Microsoft 365 data (emails, files, Teams chats, calendar events, people, and external connectors).
3
+ MCP server that lets AI agents search and read your Microsoft 365 data (emails, files, Teams chats, calendar events, and external connectors).
4
4
 
5
5
  Works with any MCP-compatible client: Claude Code, Claude Desktop, etc.
6
6
 
@@ -74,7 +74,7 @@ Search across M365 data sources.
74
74
  |-----------|------|----------|---------|-------------|
75
75
  | query | string | yes | | Search query |
76
76
  | conversation_id | string | yes | | Conversation identifier for grouping requests |
77
- | source | string | no | `"all"` | `all`, `email`, `files`, `chat`, `events`, `people`, `external` |
77
+ | source | string | no | `"all"` | `all`, `email`, `files`, `chat`, `events`, `external` |
78
78
  | connector | string | no | | External connector name (when source=`external`) |
79
79
  | size | integer | no | 10 | Results per page (1--25) |
80
80
  | from | integer | no | 0 | Pagination offset |
@@ -90,6 +90,53 @@ Read the content of a search result.
90
90
 
91
91
  Returns content with a `completeness` field: `full`, `partial`, or `snippet`.
92
92
 
93
+ ### whoami
94
+
95
+ Get the current signed-in user's basic profile from Graph.
96
+ When directory scopes are available, response also includes best-effort `manager`, `directReports`, and `directReportsCount` info.
97
+
98
+ | Parameter | Type | Required | Default | Description |
99
+ |-----------|------|----------|---------|-------------|
100
+ | conversation_id | string | yes | | Conversation identifier |
101
+ | direct_reports_size | integer | no | 30 | Maximum number of direct reports to return (1--100) |
102
+
103
+ ### find_people
104
+
105
+ Find people via Graph directory query using free-text and structured filters.
106
+
107
+ | Parameter | Type | Required | Default | Description |
108
+ |-----------|------|----------|---------|-------------|
109
+ | conversation_id | string | yes | | Conversation identifier |
110
+ | query | string | no | | Free-text query matched across person fields |
111
+ | alias | string | no | | Filter by alias (`mailNickname`) |
112
+ | principalName | string | no | | Filter by user principal name |
113
+ | displayName | string | no | | Filter by display name |
114
+ | department | string | no | | Filter by department (prefix match) |
115
+ | office | string | no | | Filter by office location (prefix match) |
116
+ | title | string | no | | Filter by job title (prefix match) |
117
+ | size | integer | no | 20 | Maximum people to return (1--50) |
118
+ | include_direct_reports | boolean | no | false | Include direct reports for each result |
119
+ | direct_reports_size | integer | no | 30 | Maximum direct reports per result (1--100) |
120
+
121
+ At least one of `query`, `alias`, `principalName`, `displayName`, `department`, `office`, or `title` is required.
122
+
123
+ ### count_people
124
+
125
+ Count people matching Graph directory filters without returning the full result list.
126
+
127
+ | Parameter | Type | Required | Default | Description |
128
+ |-----------|------|----------|---------|-------------|
129
+ | conversation_id | string | yes | | Conversation identifier |
130
+ | query | string | no | | Free-text query matched across person fields |
131
+ | alias | string | no | | Filter by alias (`mailNickname`) |
132
+ | principalName | string | no | | Filter by user principal name |
133
+ | displayName | string | no | | Filter by display name |
134
+ | department | string | no | | Filter by department (prefix match) |
135
+ | office | string | no | | Filter by office location (prefix match) |
136
+ | title | string | no | | Filter by job title (prefix match) |
137
+
138
+ At least one of `query`, `alias`, `principalName`, `displayName`, `department`, `office`, or `title` is required.
139
+
93
140
  ## Security
94
141
 
95
142
  - Both servers bind to `127.0.0.1` only (not accessible from the network)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "m365connector",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "description": "MCP server for M365 Connector browser extension",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -1,9 +1,13 @@
1
1
  import crypto from "node:crypto";
2
2
 
3
+ const WHOAMI_TIMEOUT_MS = 60000;
4
+ const FIND_PEOPLE_TIMEOUT_MS = 90000;
5
+ const COUNT_PEOPLE_TIMEOUT_MS = 120000;
6
+
3
7
  const SEARCH_TOOL = {
4
8
  name: "search",
5
9
  description:
6
- "Search across Microsoft 365 data including emails, files, Teams chats, calendar events, people, and external connectors.",
10
+ "Search across Microsoft 365 data including emails, files, Teams chats, calendar events, and external connectors.",
7
11
  inputSchema: {
8
12
  type: "object",
9
13
  properties: {
@@ -17,7 +21,7 @@ const SEARCH_TOOL = {
17
21
  },
18
22
  source: {
19
23
  type: "string",
20
- enum: ["all", "email", "files", "chat", "events", "people", "external"],
24
+ enum: ["all", "email", "files", "chat", "events", "external"],
21
25
  description: "Data source to search",
22
26
  default: "all"
23
27
  },
@@ -63,6 +67,135 @@ const OPEN_TOOL = {
63
67
  }
64
68
  };
65
69
 
70
+ const WHOAMI_TOOL = {
71
+ name: "whoami",
72
+ description: "Get the current signed-in user's basic profile from Microsoft Graph (/me).",
73
+ inputSchema: {
74
+ type: "object",
75
+ properties: {
76
+ conversation_id: {
77
+ type: "string",
78
+ description: "Client-provided conversation identifier for grouping related requests"
79
+ },
80
+ direct_reports_size: {
81
+ type: "integer",
82
+ minimum: 1,
83
+ maximum: 100,
84
+ description: "Maximum number of direct reports to return",
85
+ default: 30
86
+ }
87
+ },
88
+ required: ["conversation_id"]
89
+ }
90
+ };
91
+
92
+ const FIND_PEOPLE_TOOL = {
93
+ name: "find_people",
94
+ description:
95
+ "Find people from Microsoft Graph directory data using query, alias, principal name, display name, department, office, and title filters.",
96
+ inputSchema: {
97
+ type: "object",
98
+ properties: {
99
+ conversation_id: {
100
+ type: "string",
101
+ description: "Client-provided conversation identifier for grouping related requests"
102
+ },
103
+ query: {
104
+ type: "string",
105
+ description: "Free-text query matched across person fields"
106
+ },
107
+ alias: {
108
+ type: "string",
109
+ description: "Filter by alias (mailNickname)"
110
+ },
111
+ principalName: {
112
+ type: "string",
113
+ description: "Filter by user principal name"
114
+ },
115
+ displayName: {
116
+ type: "string",
117
+ description: "Filter by display name"
118
+ },
119
+ department: {
120
+ type: "string",
121
+ description: "Filter by department (prefix match)"
122
+ },
123
+ office: {
124
+ type: "string",
125
+ description: "Filter by office location (prefix match)"
126
+ },
127
+ title: {
128
+ type: "string",
129
+ description: "Filter by job title (prefix match)"
130
+ },
131
+ size: {
132
+ type: "integer",
133
+ minimum: 1,
134
+ maximum: 50,
135
+ description: "Maximum number of people to return",
136
+ default: 20
137
+ },
138
+ include_direct_reports: {
139
+ type: "boolean",
140
+ description: "Include direct reports for each result",
141
+ default: false
142
+ },
143
+ direct_reports_size: {
144
+ type: "integer",
145
+ minimum: 1,
146
+ maximum: 100,
147
+ description: "Max direct reports per person",
148
+ default: 30
149
+ }
150
+ },
151
+ required: ["conversation_id"]
152
+ }
153
+ };
154
+
155
+ const COUNT_PEOPLE_TOOL = {
156
+ name: "count_people",
157
+ description:
158
+ "Count people matching filters in Microsoft Graph directory data. Returns only the count, not the full results.",
159
+ inputSchema: {
160
+ type: "object",
161
+ properties: {
162
+ conversation_id: {
163
+ type: "string",
164
+ description: "Client-provided conversation identifier for grouping related requests"
165
+ },
166
+ query: {
167
+ type: "string",
168
+ description: "Free-text query matched across person fields"
169
+ },
170
+ alias: {
171
+ type: "string",
172
+ description: "Filter by alias (mailNickname)"
173
+ },
174
+ principalName: {
175
+ type: "string",
176
+ description: "Filter by user principal name"
177
+ },
178
+ displayName: {
179
+ type: "string",
180
+ description: "Filter by display name"
181
+ },
182
+ department: {
183
+ type: "string",
184
+ description: "Filter by department (prefix match)"
185
+ },
186
+ office: {
187
+ type: "string",
188
+ description: "Filter by office location (prefix match)"
189
+ },
190
+ title: {
191
+ type: "string",
192
+ description: "Filter by job title (prefix match)"
193
+ }
194
+ },
195
+ required: ["conversation_id"]
196
+ }
197
+ };
198
+
66
199
  function asObject(value) {
67
200
  if (!value || typeof value !== "object") {
68
201
  return {};
@@ -201,4 +334,77 @@ export function registerTools(registry, context) {
201
334
 
202
335
  return asObject(wsResult);
203
336
  });
337
+
338
+ registry.addTool(WHOAMI_TOOL, async (args) => {
339
+ const conversationId = String(args.conversation_id || "").trim();
340
+ if (!conversationId) {
341
+ throw new Error("conversation_id is required");
342
+ }
343
+
344
+ const wsResult = await context.ws.sendRequest(
345
+ "whoami",
346
+ {
347
+ conversation_id: conversationId,
348
+ direct_reports_size: Number.isInteger(args.direct_reports_size)
349
+ ? args.direct_reports_size
350
+ : 30
351
+ },
352
+ { timeoutMs: WHOAMI_TIMEOUT_MS }
353
+ );
354
+
355
+ return asObject(wsResult);
356
+ });
357
+
358
+ registry.addTool(FIND_PEOPLE_TOOL, async (args) => {
359
+ const conversationId = String(args.conversation_id || "").trim();
360
+ if (!conversationId) {
361
+ throw new Error("conversation_id is required");
362
+ }
363
+
364
+ const requestParams = {
365
+ query: typeof args.query === "string" ? args.query.trim() : "",
366
+ alias: typeof args.alias === "string" ? args.alias.trim() : "",
367
+ principalName: typeof args.principalName === "string" ? args.principalName.trim() : "",
368
+ displayName: typeof args.displayName === "string" ? args.displayName.trim() : "",
369
+ department: typeof args.department === "string" ? args.department.trim() : "",
370
+ office: typeof args.office === "string" ? args.office.trim() : "",
371
+ title: typeof args.title === "string" ? args.title.trim() : "",
372
+ include_direct_reports: Boolean(args.include_direct_reports),
373
+ direct_reports_size: Number.isInteger(args.direct_reports_size) ? args.direct_reports_size : 30
374
+ };
375
+ const size = Number.isInteger(args.size) ? args.size : 20;
376
+
377
+ const wsResult = await context.ws.sendRequest(
378
+ "find_people",
379
+ { conversation_id: conversationId, ...requestParams, size },
380
+ { timeoutMs: FIND_PEOPLE_TIMEOUT_MS }
381
+ );
382
+
383
+ return asObject(wsResult);
384
+ });
385
+
386
+ registry.addTool(COUNT_PEOPLE_TOOL, async (args) => {
387
+ const conversationId = String(args.conversation_id || "").trim();
388
+ if (!conversationId) {
389
+ throw new Error("conversation_id is required");
390
+ }
391
+
392
+ const requestParams = {
393
+ query: typeof args.query === "string" ? args.query.trim() : "",
394
+ alias: typeof args.alias === "string" ? args.alias.trim() : "",
395
+ principalName: typeof args.principalName === "string" ? args.principalName.trim() : "",
396
+ displayName: typeof args.displayName === "string" ? args.displayName.trim() : "",
397
+ department: typeof args.department === "string" ? args.department.trim() : "",
398
+ office: typeof args.office === "string" ? args.office.trim() : "",
399
+ title: typeof args.title === "string" ? args.title.trim() : ""
400
+ };
401
+
402
+ const wsResult = await context.ws.sendRequest(
403
+ "count_people",
404
+ { conversation_id: conversationId, ...requestParams },
405
+ { timeoutMs: COUNT_PEOPLE_TIMEOUT_MS }
406
+ );
407
+
408
+ return asObject(wsResult);
409
+ });
204
410
  }