@softeria/ms-365-mcp-server 0.26.0 → 0.27.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/README.md +16 -0
- package/bin/modules/generate-mcp-tools.mjs +4 -0
- package/dist/cli.js +25 -1
- package/dist/generated/client.js +124 -2728
- package/dist/graph-tools.js +311 -207
- package/dist/server.js +18 -8
- package/dist/tool-categories.js +93 -0
- package/package.json +1 -1
package/dist/graph-tools.js
CHANGED
|
@@ -4,11 +4,194 @@ import { z } from "zod";
|
|
|
4
4
|
import { readFileSync } from "fs";
|
|
5
5
|
import path from "path";
|
|
6
6
|
import { fileURLToPath } from "url";
|
|
7
|
+
import { TOOL_CATEGORIES } from "./tool-categories.js";
|
|
7
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
9
|
const __dirname = path.dirname(__filename);
|
|
9
10
|
const endpointsData = JSON.parse(
|
|
10
11
|
readFileSync(path.join(__dirname, "endpoints.json"), "utf8")
|
|
11
12
|
);
|
|
13
|
+
async function executeGraphTool(tool, config, graphClient, params) {
|
|
14
|
+
logger.info(`Tool ${tool.alias} called with params: ${JSON.stringify(params)}`);
|
|
15
|
+
try {
|
|
16
|
+
const parameterDefinitions = tool.parameters || [];
|
|
17
|
+
let path2 = tool.path;
|
|
18
|
+
const queryParams = {};
|
|
19
|
+
const headers = {};
|
|
20
|
+
let body = null;
|
|
21
|
+
for (const [paramName, paramValue] of Object.entries(params)) {
|
|
22
|
+
if (["fetchAllPages", "includeHeaders", "excludeResponse", "timezone"].includes(paramName)) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const odataParams = [
|
|
26
|
+
"filter",
|
|
27
|
+
"select",
|
|
28
|
+
"expand",
|
|
29
|
+
"orderby",
|
|
30
|
+
"skip",
|
|
31
|
+
"top",
|
|
32
|
+
"count",
|
|
33
|
+
"search",
|
|
34
|
+
"format"
|
|
35
|
+
];
|
|
36
|
+
const normalizedParamName = paramName.startsWith("$") ? paramName.slice(1) : paramName;
|
|
37
|
+
const isOdataParam = odataParams.includes(normalizedParamName.toLowerCase());
|
|
38
|
+
const fixedParamName = isOdataParam ? `$${normalizedParamName.toLowerCase()}` : paramName;
|
|
39
|
+
const paramDef = parameterDefinitions.find(
|
|
40
|
+
(p) => p.name === paramName || isOdataParam && p.name === normalizedParamName
|
|
41
|
+
);
|
|
42
|
+
if (paramDef) {
|
|
43
|
+
switch (paramDef.type) {
|
|
44
|
+
case "Path":
|
|
45
|
+
path2 = path2.replace(`{${paramName}}`, encodeURIComponent(paramValue)).replace(`:${paramName}`, encodeURIComponent(paramValue));
|
|
46
|
+
break;
|
|
47
|
+
case "Query":
|
|
48
|
+
queryParams[fixedParamName] = `${paramValue}`;
|
|
49
|
+
break;
|
|
50
|
+
case "Body":
|
|
51
|
+
if (paramDef.schema) {
|
|
52
|
+
const parseResult = paramDef.schema.safeParse(paramValue);
|
|
53
|
+
if (!parseResult.success) {
|
|
54
|
+
const wrapped = { [paramName]: paramValue };
|
|
55
|
+
const wrappedResult = paramDef.schema.safeParse(wrapped);
|
|
56
|
+
if (wrappedResult.success) {
|
|
57
|
+
logger.info(
|
|
58
|
+
`Auto-corrected parameter '${paramName}': AI passed nested field directly, wrapped it as {${paramName}: ...}`
|
|
59
|
+
);
|
|
60
|
+
body = wrapped;
|
|
61
|
+
} else {
|
|
62
|
+
body = paramValue;
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
body = paramValue;
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
body = paramValue;
|
|
69
|
+
}
|
|
70
|
+
break;
|
|
71
|
+
case "Header":
|
|
72
|
+
headers[fixedParamName] = `${paramValue}`;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
} else if (paramName === "body") {
|
|
76
|
+
body = paramValue;
|
|
77
|
+
logger.info(`Set body param: ${JSON.stringify(body)}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (config?.supportsTimezone && params.timezone) {
|
|
81
|
+
headers["Prefer"] = `outlook.timezone="${params.timezone}"`;
|
|
82
|
+
logger.info(`Setting timezone header: Prefer: outlook.timezone="${params.timezone}"`);
|
|
83
|
+
}
|
|
84
|
+
if (Object.keys(queryParams).length > 0) {
|
|
85
|
+
const queryString = Object.entries(queryParams).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
|
|
86
|
+
path2 = `${path2}${path2.includes("?") ? "&" : "?"}${queryString}`;
|
|
87
|
+
}
|
|
88
|
+
const options = {
|
|
89
|
+
method: tool.method.toUpperCase(),
|
|
90
|
+
headers
|
|
91
|
+
};
|
|
92
|
+
if (options.method !== "GET" && body) {
|
|
93
|
+
options.body = typeof body === "string" ? body : JSON.stringify(body);
|
|
94
|
+
}
|
|
95
|
+
const isProbablyMediaContent = tool.errors?.some((error) => error.description === "Retrieved media content") || path2.endsWith("/content");
|
|
96
|
+
if (config?.returnDownloadUrl && path2.endsWith("/content")) {
|
|
97
|
+
path2 = path2.replace(/\/content$/, "");
|
|
98
|
+
logger.info(
|
|
99
|
+
`Auto-returning download URL for ${tool.alias} (returnDownloadUrl=true in endpoints.json)`
|
|
100
|
+
);
|
|
101
|
+
} else if (isProbablyMediaContent) {
|
|
102
|
+
options.rawResponse = true;
|
|
103
|
+
}
|
|
104
|
+
if (params.includeHeaders === true) {
|
|
105
|
+
options.includeHeaders = true;
|
|
106
|
+
}
|
|
107
|
+
if (params.excludeResponse === true) {
|
|
108
|
+
options.excludeResponse = true;
|
|
109
|
+
}
|
|
110
|
+
logger.info(`Making graph request to ${path2} with options: ${JSON.stringify(options)}`);
|
|
111
|
+
let response = await graphClient.graphRequest(path2, options);
|
|
112
|
+
const fetchAllPages = params.fetchAllPages === true;
|
|
113
|
+
if (fetchAllPages && response?.content?.[0]?.text) {
|
|
114
|
+
try {
|
|
115
|
+
let combinedResponse = JSON.parse(response.content[0].text);
|
|
116
|
+
let allItems = combinedResponse.value || [];
|
|
117
|
+
let nextLink = combinedResponse["@odata.nextLink"];
|
|
118
|
+
let pageCount = 1;
|
|
119
|
+
while (nextLink && pageCount < 100) {
|
|
120
|
+
logger.info(`Fetching page ${pageCount + 1} from: ${nextLink}`);
|
|
121
|
+
const url = new URL(nextLink);
|
|
122
|
+
const nextPath = url.pathname.replace("/v1.0", "");
|
|
123
|
+
const nextOptions = { ...options };
|
|
124
|
+
const nextQueryParams = {};
|
|
125
|
+
for (const [key, value] of url.searchParams.entries()) {
|
|
126
|
+
nextQueryParams[key] = value;
|
|
127
|
+
}
|
|
128
|
+
nextOptions.queryParams = nextQueryParams;
|
|
129
|
+
const nextResponse = await graphClient.graphRequest(nextPath, nextOptions);
|
|
130
|
+
if (nextResponse?.content?.[0]?.text) {
|
|
131
|
+
const nextJsonResponse = JSON.parse(nextResponse.content[0].text);
|
|
132
|
+
if (nextJsonResponse.value && Array.isArray(nextJsonResponse.value)) {
|
|
133
|
+
allItems = allItems.concat(nextJsonResponse.value);
|
|
134
|
+
}
|
|
135
|
+
nextLink = nextJsonResponse["@odata.nextLink"];
|
|
136
|
+
pageCount++;
|
|
137
|
+
} else {
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (pageCount >= 100) {
|
|
142
|
+
logger.warn(`Reached maximum page limit (100) for pagination`);
|
|
143
|
+
}
|
|
144
|
+
combinedResponse.value = allItems;
|
|
145
|
+
if (combinedResponse["@odata.count"]) {
|
|
146
|
+
combinedResponse["@odata.count"] = allItems.length;
|
|
147
|
+
}
|
|
148
|
+
delete combinedResponse["@odata.nextLink"];
|
|
149
|
+
response.content[0].text = JSON.stringify(combinedResponse);
|
|
150
|
+
logger.info(
|
|
151
|
+
`Pagination complete: collected ${allItems.length} items across ${pageCount} pages`
|
|
152
|
+
);
|
|
153
|
+
} catch (e) {
|
|
154
|
+
logger.error(`Error during pagination: ${e}`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (response?.content?.[0]?.text) {
|
|
158
|
+
const responseText = response.content[0].text;
|
|
159
|
+
logger.info(`Response size: ${responseText.length} characters`);
|
|
160
|
+
try {
|
|
161
|
+
const jsonResponse = JSON.parse(responseText);
|
|
162
|
+
if (jsonResponse.value && Array.isArray(jsonResponse.value)) {
|
|
163
|
+
logger.info(`Response contains ${jsonResponse.value.length} items`);
|
|
164
|
+
}
|
|
165
|
+
if (jsonResponse["@odata.nextLink"]) {
|
|
166
|
+
logger.info(`Response has pagination nextLink: ${jsonResponse["@odata.nextLink"]}`);
|
|
167
|
+
}
|
|
168
|
+
} catch {
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const content = response.content.map((item) => ({
|
|
172
|
+
type: "text",
|
|
173
|
+
text: item.text
|
|
174
|
+
}));
|
|
175
|
+
return {
|
|
176
|
+
content,
|
|
177
|
+
_meta: response._meta,
|
|
178
|
+
isError: response.isError
|
|
179
|
+
};
|
|
180
|
+
} catch (error) {
|
|
181
|
+
logger.error(`Error in tool ${tool.alias}: ${error.message}`);
|
|
182
|
+
return {
|
|
183
|
+
content: [
|
|
184
|
+
{
|
|
185
|
+
type: "text",
|
|
186
|
+
text: JSON.stringify({
|
|
187
|
+
error: `Error in tool ${tool.alias}: ${error.message}`
|
|
188
|
+
})
|
|
189
|
+
}
|
|
190
|
+
],
|
|
191
|
+
isError: true
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
12
195
|
function registerGraphTools(server, graphClient, readOnly = false, enabledToolsPattern, orgMode = false) {
|
|
13
196
|
let enabledToolsRegex;
|
|
14
197
|
if (enabledToolsPattern) {
|
|
@@ -19,18 +202,24 @@ function registerGraphTools(server, graphClient, readOnly = false, enabledToolsP
|
|
|
19
202
|
logger.error(`Invalid tool filter regex pattern: ${enabledToolsPattern}. Ignoring filter.`);
|
|
20
203
|
}
|
|
21
204
|
}
|
|
205
|
+
let registeredCount = 0;
|
|
206
|
+
let skippedCount = 0;
|
|
207
|
+
let failedCount = 0;
|
|
22
208
|
for (const tool of api.endpoints) {
|
|
23
209
|
const endpointConfig = endpointsData.find((e) => e.toolName === tool.alias);
|
|
24
210
|
if (!orgMode && endpointConfig && !endpointConfig.scopes && endpointConfig.workScopes) {
|
|
25
211
|
logger.info(`Skipping work account tool ${tool.alias} - not in org mode`);
|
|
212
|
+
skippedCount++;
|
|
26
213
|
continue;
|
|
27
214
|
}
|
|
28
215
|
if (readOnly && tool.method.toUpperCase() !== "GET") {
|
|
29
216
|
logger.info(`Skipping write operation ${tool.alias} in read-only mode`);
|
|
217
|
+
skippedCount++;
|
|
30
218
|
continue;
|
|
31
219
|
}
|
|
32
220
|
if (enabledToolsRegex && !enabledToolsRegex.test(tool.alias)) {
|
|
33
221
|
logger.info(`Skipping tool ${tool.alias} - doesn't match filter pattern`);
|
|
222
|
+
skippedCount++;
|
|
34
223
|
continue;
|
|
35
224
|
}
|
|
36
225
|
const paramSchema = {};
|
|
@@ -55,218 +244,133 @@ function registerGraphTools(server, graphClient, readOnly = false, enabledToolsP
|
|
|
55
244
|
|
|
56
245
|
\u{1F4A1} TIP: ${endpointConfig.llmTip}`;
|
|
57
246
|
}
|
|
58
|
-
|
|
59
|
-
tool
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
body = paramValue;
|
|
125
|
-
}
|
|
126
|
-
break;
|
|
127
|
-
case "Header":
|
|
128
|
-
headers[fixedParamName] = `${paramValue}`;
|
|
129
|
-
break;
|
|
130
|
-
}
|
|
131
|
-
} else if (paramName === "body") {
|
|
132
|
-
body = paramValue;
|
|
133
|
-
logger.info(`Set body param: ${JSON.stringify(body)}`);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
if (endpointConfig?.supportsTimezone && params.timezone) {
|
|
137
|
-
headers["Prefer"] = `outlook.timezone="${params.timezone}"`;
|
|
138
|
-
logger.info(`Setting timezone header: Prefer: outlook.timezone="${params.timezone}"`);
|
|
139
|
-
}
|
|
140
|
-
if (Object.keys(queryParams).length > 0) {
|
|
141
|
-
const queryString = Object.entries(queryParams).map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`).join("&");
|
|
142
|
-
path2 = `${path2}${path2.includes("?") ? "&" : "?"}${queryString}`;
|
|
143
|
-
}
|
|
144
|
-
const options = {
|
|
145
|
-
method: tool.method.toUpperCase(),
|
|
146
|
-
headers
|
|
147
|
-
};
|
|
148
|
-
if (options.method !== "GET" && body) {
|
|
149
|
-
options.body = typeof body === "string" ? body : JSON.stringify(body);
|
|
150
|
-
}
|
|
151
|
-
const isProbablyMediaContent = tool.errors?.some((error) => error.description === "Retrieved media content") || path2.endsWith("/content");
|
|
152
|
-
if (endpointConfig?.returnDownloadUrl && path2.endsWith("/content")) {
|
|
153
|
-
path2 = path2.replace(/\/content$/, "");
|
|
154
|
-
logger.info(
|
|
155
|
-
`Auto-returning download URL for ${tool.alias} (returnDownloadUrl=true in endpoints.json)`
|
|
156
|
-
);
|
|
157
|
-
} else if (isProbablyMediaContent) {
|
|
158
|
-
options.rawResponse = true;
|
|
159
|
-
}
|
|
160
|
-
if (params.includeHeaders === true) {
|
|
161
|
-
options.includeHeaders = true;
|
|
162
|
-
}
|
|
163
|
-
if (params.excludeResponse === true) {
|
|
164
|
-
options.excludeResponse = true;
|
|
165
|
-
}
|
|
166
|
-
logger.info(`Making graph request to ${path2} with options: ${JSON.stringify(options)}`);
|
|
167
|
-
let response = await graphClient.graphRequest(path2, options);
|
|
168
|
-
const fetchAllPages = params.fetchAllPages === true;
|
|
169
|
-
if (fetchAllPages && response && response.content && response.content.length > 0) {
|
|
170
|
-
try {
|
|
171
|
-
let combinedResponse = JSON.parse(response.content[0].text);
|
|
172
|
-
let allItems = combinedResponse.value || [];
|
|
173
|
-
let nextLink = combinedResponse["@odata.nextLink"];
|
|
174
|
-
let pageCount = 1;
|
|
175
|
-
while (nextLink) {
|
|
176
|
-
logger.info(`Fetching page ${pageCount + 1} from: ${nextLink}`);
|
|
177
|
-
const url = new URL(nextLink);
|
|
178
|
-
const nextPath = url.pathname.replace("/v1.0", "");
|
|
179
|
-
const nextOptions = { ...options };
|
|
180
|
-
const nextQueryParams = {};
|
|
181
|
-
for (const [key, value] of url.searchParams.entries()) {
|
|
182
|
-
nextQueryParams[key] = value;
|
|
183
|
-
}
|
|
184
|
-
nextOptions.queryParams = nextQueryParams;
|
|
185
|
-
const nextResponse = await graphClient.graphRequest(nextPath, nextOptions);
|
|
186
|
-
if (nextResponse && nextResponse.content && nextResponse.content.length > 0) {
|
|
187
|
-
const nextJsonResponse = JSON.parse(nextResponse.content[0].text);
|
|
188
|
-
if (nextJsonResponse.value && Array.isArray(nextJsonResponse.value)) {
|
|
189
|
-
allItems = allItems.concat(nextJsonResponse.value);
|
|
190
|
-
}
|
|
191
|
-
nextLink = nextJsonResponse["@odata.nextLink"];
|
|
192
|
-
pageCount++;
|
|
193
|
-
if (pageCount > 100) {
|
|
194
|
-
logger.warn(`Reached maximum page limit (100) for pagination`);
|
|
195
|
-
break;
|
|
196
|
-
}
|
|
197
|
-
} else {
|
|
198
|
-
break;
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
combinedResponse.value = allItems;
|
|
202
|
-
if (combinedResponse["@odata.count"]) {
|
|
203
|
-
combinedResponse["@odata.count"] = allItems.length;
|
|
204
|
-
}
|
|
205
|
-
delete combinedResponse["@odata.nextLink"];
|
|
206
|
-
response.content[0].text = JSON.stringify(combinedResponse);
|
|
207
|
-
logger.info(
|
|
208
|
-
`Pagination complete: collected ${allItems.length} items across ${pageCount} pages`
|
|
209
|
-
);
|
|
210
|
-
} catch (e) {
|
|
211
|
-
logger.error(`Error during pagination: ${e}`);
|
|
212
|
-
}
|
|
247
|
+
try {
|
|
248
|
+
server.tool(
|
|
249
|
+
tool.alias,
|
|
250
|
+
toolDescription,
|
|
251
|
+
paramSchema,
|
|
252
|
+
{
|
|
253
|
+
title: tool.alias,
|
|
254
|
+
readOnlyHint: tool.method.toUpperCase() === "GET"
|
|
255
|
+
},
|
|
256
|
+
async (params) => executeGraphTool(tool, endpointConfig, graphClient, params)
|
|
257
|
+
);
|
|
258
|
+
registeredCount++;
|
|
259
|
+
} catch (error) {
|
|
260
|
+
logger.error(`Failed to register tool ${tool.alias}: ${error.message}`);
|
|
261
|
+
failedCount++;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
logger.info(
|
|
265
|
+
`Tool registration complete: ${registeredCount} registered, ${skippedCount} skipped, ${failedCount} failed`
|
|
266
|
+
);
|
|
267
|
+
return registeredCount;
|
|
268
|
+
}
|
|
269
|
+
function buildToolsRegistry(readOnly, orgMode) {
|
|
270
|
+
const toolsMap = /* @__PURE__ */ new Map();
|
|
271
|
+
for (const tool of api.endpoints) {
|
|
272
|
+
const endpointConfig = endpointsData.find((e) => e.toolName === tool.alias);
|
|
273
|
+
if (!orgMode && endpointConfig && !endpointConfig.scopes && endpointConfig.workScopes) {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (readOnly && tool.method.toUpperCase() !== "GET") {
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
toolsMap.set(tool.alias, { tool, config: endpointConfig });
|
|
280
|
+
}
|
|
281
|
+
return toolsMap;
|
|
282
|
+
}
|
|
283
|
+
function registerDiscoveryTools(server, graphClient, readOnly = false, orgMode = false) {
|
|
284
|
+
const toolsRegistry = buildToolsRegistry(readOnly, orgMode);
|
|
285
|
+
logger.info(`Discovery mode: ${toolsRegistry.size} tools available in registry`);
|
|
286
|
+
server.tool(
|
|
287
|
+
"search-tools",
|
|
288
|
+
`Search through ${toolsRegistry.size} available Microsoft Graph API tools. Use this to find tools by name, path, or description before executing them.`,
|
|
289
|
+
{
|
|
290
|
+
query: z.string().describe("Search query to filter tools (searches name, path, and description)").optional(),
|
|
291
|
+
category: z.string().describe(
|
|
292
|
+
"Filter by category: mail, calendar, files, contacts, tasks, onenote, search, users, excel"
|
|
293
|
+
).optional(),
|
|
294
|
+
limit: z.number().describe("Maximum results to return (default: 20, max: 50)").optional()
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
title: "search-tools",
|
|
298
|
+
readOnlyHint: true
|
|
299
|
+
},
|
|
300
|
+
async ({ query, category, limit = 20 }) => {
|
|
301
|
+
const maxLimit = Math.min(limit, 50);
|
|
302
|
+
const results = [];
|
|
303
|
+
const queryLower = query?.toLowerCase();
|
|
304
|
+
const categoryDef = category ? TOOL_CATEGORIES[category] : void 0;
|
|
305
|
+
for (const [name, { tool, config }] of toolsRegistry) {
|
|
306
|
+
if (categoryDef && !categoryDef.pattern.test(name)) {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
if (queryLower) {
|
|
310
|
+
const searchText = `${name} ${tool.path} ${tool.description || ""} ${config?.llmTip || ""}`.toLowerCase();
|
|
311
|
+
if (!searchText.includes(queryLower)) {
|
|
312
|
+
continue;
|
|
213
313
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
);
|
|
238
|
-
}
|
|
314
|
+
}
|
|
315
|
+
results.push({
|
|
316
|
+
name,
|
|
317
|
+
method: tool.method.toUpperCase(),
|
|
318
|
+
path: tool.path,
|
|
319
|
+
description: tool.description || `${tool.method.toUpperCase()} ${tool.path}`
|
|
320
|
+
});
|
|
321
|
+
if (results.length >= maxLimit) break;
|
|
322
|
+
}
|
|
323
|
+
return {
|
|
324
|
+
content: [
|
|
325
|
+
{
|
|
326
|
+
type: "text",
|
|
327
|
+
text: JSON.stringify(
|
|
328
|
+
{
|
|
329
|
+
found: results.length,
|
|
330
|
+
total: toolsRegistry.size,
|
|
331
|
+
tools: results,
|
|
332
|
+
tip: "Use execute-tool with the tool name and required parameters to call any of these tools."
|
|
333
|
+
},
|
|
334
|
+
null,
|
|
335
|
+
2
|
|
336
|
+
)
|
|
239
337
|
}
|
|
240
|
-
|
|
241
|
-
|
|
338
|
+
]
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
);
|
|
342
|
+
server.tool(
|
|
343
|
+
"execute-tool",
|
|
344
|
+
"Execute a Microsoft Graph API tool by name. Use search-tools first to find available tools and their parameters.",
|
|
345
|
+
{
|
|
346
|
+
tool_name: z.string().describe('Name of the tool to execute (e.g., "list-mail-messages")'),
|
|
347
|
+
parameters: z.record(z.any()).describe("Parameters to pass to the tool as key-value pairs").optional()
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
title: "execute-tool",
|
|
351
|
+
readOnlyHint: false
|
|
352
|
+
},
|
|
353
|
+
async ({ tool_name, parameters = {} }) => {
|
|
354
|
+
const toolData = toolsRegistry.get(tool_name);
|
|
355
|
+
if (!toolData) {
|
|
356
|
+
return {
|
|
357
|
+
content: [
|
|
358
|
+
{
|
|
242
359
|
type: "text",
|
|
243
|
-
text:
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
};
|
|
252
|
-
return result;
|
|
253
|
-
} catch (error) {
|
|
254
|
-
logger.error(`Error in tool ${tool.alias}: ${error.message}`);
|
|
255
|
-
const errorContent = {
|
|
256
|
-
type: "text",
|
|
257
|
-
text: JSON.stringify({
|
|
258
|
-
error: `Error in tool ${tool.alias}: ${error.message}`
|
|
259
|
-
})
|
|
260
|
-
};
|
|
261
|
-
return {
|
|
262
|
-
content: [errorContent],
|
|
263
|
-
isError: true
|
|
264
|
-
};
|
|
265
|
-
}
|
|
360
|
+
text: JSON.stringify({
|
|
361
|
+
error: `Tool not found: ${tool_name}`,
|
|
362
|
+
tip: "Use search-tools to find available tools."
|
|
363
|
+
})
|
|
364
|
+
}
|
|
365
|
+
],
|
|
366
|
+
isError: true
|
|
367
|
+
};
|
|
266
368
|
}
|
|
267
|
-
|
|
268
|
-
|
|
369
|
+
return executeGraphTool(toolData.tool, toolData.config, graphClient, parameters);
|
|
370
|
+
}
|
|
371
|
+
);
|
|
269
372
|
}
|
|
270
373
|
export {
|
|
374
|
+
registerDiscoveryTools,
|
|
271
375
|
registerGraphTools
|
|
272
376
|
};
|
package/dist/server.js
CHANGED
|
@@ -6,7 +6,7 @@ import express from "express";
|
|
|
6
6
|
import crypto from "crypto";
|
|
7
7
|
import logger, { enableConsoleLogging } from "./logger.js";
|
|
8
8
|
import { registerAuthTools } from "./auth-tools.js";
|
|
9
|
-
import { registerGraphTools } from "./graph-tools.js";
|
|
9
|
+
import { registerGraphTools, registerDiscoveryTools } from "./graph-tools.js";
|
|
10
10
|
import GraphClient from "./graph-client.js";
|
|
11
11
|
import { buildScopesFromEndpoints } from "./auth.js";
|
|
12
12
|
import { MicrosoftOAuthProvider } from "./oauth-provider.js";
|
|
@@ -33,13 +33,23 @@ class MicrosoftGraphServer {
|
|
|
33
33
|
if (shouldRegisterAuthTools) {
|
|
34
34
|
registerAuthTools(this.server, this.authManager);
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
if (this.options.discovery) {
|
|
37
|
+
logger.info("Discovery mode enabled (experimental) - registering discovery tool only");
|
|
38
|
+
registerDiscoveryTools(
|
|
39
|
+
this.server,
|
|
40
|
+
this.graphClient,
|
|
41
|
+
this.options.readOnly,
|
|
42
|
+
this.options.orgMode
|
|
43
|
+
);
|
|
44
|
+
} else {
|
|
45
|
+
registerGraphTools(
|
|
46
|
+
this.server,
|
|
47
|
+
this.graphClient,
|
|
48
|
+
this.options.readOnly,
|
|
49
|
+
this.options.enabledTools,
|
|
50
|
+
this.options.orgMode
|
|
51
|
+
);
|
|
52
|
+
}
|
|
43
53
|
}
|
|
44
54
|
async start() {
|
|
45
55
|
if (this.options.v) {
|