m365connector 0.3.0 → 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 +49 -2
- package/package.json +1 -1
- package/src/index.js +7 -5
- package/src/tools/search.js +208 -2
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,
|
|
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`, `
|
|
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
package/src/index.js
CHANGED
|
@@ -8,9 +8,12 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
|
|
|
8
8
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
9
9
|
import { WsServer } from "./ws-server.js";
|
|
10
10
|
|
|
11
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const PKG = JSON.parse(fs.readFileSync(path.resolve(__dirname, "..", "package.json"), "utf-8"));
|
|
13
|
+
|
|
11
14
|
const SERVER_INFO = {
|
|
12
|
-
name:
|
|
13
|
-
version:
|
|
15
|
+
name: PKG.name,
|
|
16
|
+
version: PKG.version
|
|
14
17
|
};
|
|
15
18
|
|
|
16
19
|
const DEFAULT_MCP_PORT = 52366;
|
|
@@ -63,8 +66,7 @@ function createReadHandleCache() {
|
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
async function loadToolModules(toolRegistry, context) {
|
|
66
|
-
const
|
|
67
|
-
const toolsDir = path.resolve(currentDir, "tools");
|
|
69
|
+
const toolsDir = path.resolve(__dirname, "tools");
|
|
68
70
|
const files = fs
|
|
69
71
|
.readdirSync(toolsDir)
|
|
70
72
|
.filter((name) => name.endsWith(".js"))
|
|
@@ -261,7 +263,7 @@ async function main() {
|
|
|
261
263
|
server.once("error", reject);
|
|
262
264
|
});
|
|
263
265
|
|
|
264
|
-
stderrLogger.error(`[m365connector] MCP HTTP listening on http://127.0.0.1:${mcpPort}/mcp`);
|
|
266
|
+
stderrLogger.error(`[m365connector] MCP HTTP listening on http://127.0.0.1:${mcpPort}/mcp (v${PKG.version})`);
|
|
265
267
|
|
|
266
268
|
let shuttingDown = false;
|
|
267
269
|
const shutdown = async () => {
|
package/src/tools/search.js
CHANGED
|
@@ -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,
|
|
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", "
|
|
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
|
}
|