@north7/entraaware 0.0.1 → 0.0.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 +1 -1
- package/build/index.js +345 -71
- package/package.json +2 -2
package/README.md
CHANGED
package/build/index.js
CHANGED
@@ -5,8 +5,9 @@ import { z } from "zod";
|
|
5
5
|
import { Client } from "@microsoft/microsoft-graph-client";
|
6
6
|
import { ClientSecretCredential } from "@azure/identity";
|
7
7
|
import { TokenCredentialAuthenticationProvider } from "@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials/index.js";
|
8
|
-
// Initialize
|
8
|
+
// Initialize clients
|
9
9
|
let graphClient = null;
|
10
|
+
let azureCredential = null;
|
10
11
|
// Create server instance
|
11
12
|
const server = new McpServer({
|
12
13
|
name: "EntraAware",
|
@@ -16,92 +17,365 @@ const server = new McpServer({
|
|
16
17
|
tools: {},
|
17
18
|
},
|
18
19
|
});
|
19
|
-
//
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
// SHARED UTILITIES
|
21
|
+
function getCredentials() {
|
22
|
+
const tenantId = process.env.TENANT_ID;
|
23
|
+
const clientId = process.env.CLIENT_ID;
|
24
|
+
const clientSecret = process.env.CLIENT_SECRET;
|
25
|
+
if (!tenantId || !clientId || !clientSecret) {
|
26
|
+
throw new Error("Missing required environment variables: TENANT_ID, CLIENT_ID, or CLIENT_SECRET");
|
27
|
+
}
|
28
|
+
return { tenantId, clientId, clientSecret };
|
29
|
+
}
|
30
|
+
function getAzureCredential() {
|
31
|
+
if (!azureCredential) {
|
32
|
+
const { tenantId, clientId, clientSecret } = getCredentials();
|
33
|
+
azureCredential = new ClientSecretCredential(tenantId, clientId, clientSecret);
|
34
|
+
}
|
35
|
+
return azureCredential;
|
36
|
+
}
|
37
|
+
function formatApiResponse(apiType, method, path, result) {
|
38
|
+
return {
|
39
|
+
content: [
|
40
|
+
{
|
41
|
+
type: "text",
|
42
|
+
text: `${apiType} API Result (${method.toUpperCase()} ${path}):\n\n${JSON.stringify(result, null, 2)}`,
|
43
|
+
},
|
44
|
+
],
|
45
|
+
};
|
46
|
+
}
|
47
|
+
function formatErrorResponse(err, apiType) {
|
48
|
+
const errorDetail = err instanceof Error
|
49
|
+
? {
|
50
|
+
message: err.message,
|
51
|
+
name: err.name,
|
52
|
+
detail: apiType === 'Entra'
|
53
|
+
? err.body ? JSON.stringify(err.body) : undefined
|
54
|
+
: err.response?.body || undefined,
|
55
|
+
status: err.statusCode || err.status
|
56
|
+
}
|
57
|
+
: String(err);
|
58
|
+
return {
|
59
|
+
content: [
|
60
|
+
{
|
61
|
+
type: "text",
|
62
|
+
text: `Error querying ${apiType} API: ${JSON.stringify(errorDetail, null, 2)}`,
|
63
|
+
},
|
64
|
+
],
|
65
|
+
};
|
66
|
+
}
|
67
|
+
// MICROSOFT GRAPH API TOOL
|
68
|
+
server.tool("askEntra", "Direct access to Microsoft Graph API for accurate Entra (Azure AD) data", {
|
69
|
+
path: z.string().describe("The Graph API URL path (e.g. '/users/{id}/memberOf', '/directoryRoles')"),
|
70
|
+
method: z.enum(["get", "post", "put", "patch", "delete"]).default("get").describe("HTTP method to use"),
|
71
|
+
queryParams: z.record(z.string()).optional().describe("Query parameters for the request"),
|
72
|
+
body: z.record(z.string(), z.any()).optional().describe("Request body for POST/PUT/PATCH requests"),
|
73
|
+
apiVersion: z.enum(["v1.0", "beta"]).default("v1.0").describe("Microsoft Graph API version"),
|
74
|
+
fetchAllPages: z.boolean().optional().default(false).describe("Automatically fetch all pages of results"),
|
75
|
+
consistencyLevel: z.string().optional().describe("ConsistencyLevel header value (use 'eventual' for queries with $filter, $search, etc.)"),
|
76
|
+
// Shorthand params
|
77
|
+
select: z.string().optional().describe("Shorthand for $select query parameter"),
|
78
|
+
filter: z.string().optional().describe("Shorthand for $filter query parameter"),
|
79
|
+
expand: z.string().optional().describe("Shorthand for $expand query parameter"),
|
80
|
+
orderBy: z.string().optional().describe("Shorthand for $orderBy query parameter"),
|
81
|
+
top: z.number().optional().describe("Shorthand for $top query parameter"),
|
82
|
+
count: z.boolean().optional().describe("Shorthand for $count=true to include count of items"),
|
83
|
+
}, async ({ path, method, queryParams = {}, body, apiVersion, fetchAllPages, consistencyLevel, select, filter, expand, orderBy, top, count }) => {
|
23
84
|
try {
|
24
|
-
//
|
85
|
+
// Process shorthand query parameters
|
86
|
+
const processedParams = { ...queryParams };
|
87
|
+
if (select)
|
88
|
+
processedParams['$select'] = select;
|
89
|
+
if (filter)
|
90
|
+
processedParams['$filter'] = filter;
|
91
|
+
if (expand)
|
92
|
+
processedParams['$expand'] = expand;
|
93
|
+
if (orderBy)
|
94
|
+
processedParams['$orderby'] = orderBy;
|
95
|
+
if (top !== undefined)
|
96
|
+
processedParams['$top'] = top.toString();
|
97
|
+
if (count)
|
98
|
+
processedParams['$count'] = 'true';
|
99
|
+
// Initialize or get Graph client
|
25
100
|
if (!graphClient) {
|
26
|
-
|
101
|
+
const credential = getAzureCredential();
|
102
|
+
const authProvider = new TokenCredentialAuthenticationProvider(credential, {
|
103
|
+
scopes: ["https://graph.microsoft.com/.default"],
|
104
|
+
});
|
105
|
+
graphClient = Client.initWithMiddleware({ authProvider });
|
106
|
+
}
|
107
|
+
// Build request with API path and version
|
108
|
+
let request = graphClient.api(path).version(apiVersion);
|
109
|
+
// Add query parameters
|
110
|
+
if (Object.keys(processedParams).length > 0) {
|
111
|
+
request = request.query(processedParams);
|
27
112
|
}
|
28
|
-
//
|
29
|
-
|
30
|
-
|
31
|
-
const emailPattern = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/;
|
32
|
-
const guidPattern = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/i;
|
33
|
-
const emailMatch = question.match(emailPattern);
|
34
|
-
const guidMatch = question.match(guidPattern);
|
35
|
-
// Check for specific keywords in the question to determine the right path
|
36
|
-
const lowerQuestion = question.toLowerCase();
|
37
|
-
if (lowerQuestion.includes("conditional access") || lowerQuestion.includes("policies")) {
|
38
|
-
path = "/identity/conditionalAccess/policies";
|
113
|
+
// Add consistency level header if provided
|
114
|
+
if (consistencyLevel) {
|
115
|
+
request = request.header('ConsistencyLevel', consistencyLevel);
|
39
116
|
}
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
117
|
+
// Handle pagination for GET requests
|
118
|
+
let result;
|
119
|
+
if (method === 'get' && fetchAllPages) {
|
120
|
+
const firstPage = await request.get();
|
121
|
+
// If no pagination needed, return first page
|
122
|
+
if (!firstPage["@odata.nextLink"]) {
|
123
|
+
result = firstPage;
|
124
|
+
}
|
125
|
+
else {
|
126
|
+
// Collect all items from all pages
|
127
|
+
const allItems = [...(firstPage.value || [])];
|
128
|
+
let nextLink = firstPage["@odata.nextLink"];
|
129
|
+
while (nextLink) {
|
130
|
+
const nextPage = await graphClient.api(nextLink).get();
|
131
|
+
if (nextPage.value)
|
132
|
+
allItems.push(...nextPage.value);
|
133
|
+
nextLink = nextPage["@odata.nextLink"] || null;
|
134
|
+
}
|
135
|
+
result = {
|
136
|
+
"@odata.context": firstPage["@odata.context"],
|
137
|
+
value: allItems,
|
138
|
+
"@odata.count": firstPage["@odata.count"],
|
139
|
+
totalItemsFetched: allItems.length
|
140
|
+
};
|
44
141
|
}
|
45
142
|
}
|
46
|
-
else
|
47
|
-
|
143
|
+
else {
|
144
|
+
// Execute appropriate method
|
145
|
+
switch (method) {
|
146
|
+
case 'get':
|
147
|
+
result = await request.get();
|
148
|
+
break;
|
149
|
+
case 'post':
|
150
|
+
result = await request.post(body || {});
|
151
|
+
break;
|
152
|
+
case 'put':
|
153
|
+
result = await request.put(body || {});
|
154
|
+
break;
|
155
|
+
case 'patch':
|
156
|
+
result = await request.patch(body || {});
|
157
|
+
break;
|
158
|
+
case 'delete':
|
159
|
+
await request.delete();
|
160
|
+
result = { status: "Successfully deleted" };
|
161
|
+
break;
|
162
|
+
}
|
48
163
|
}
|
49
|
-
|
50
|
-
|
164
|
+
return formatApiResponse('Entra', method, path, result);
|
165
|
+
}
|
166
|
+
catch (err) {
|
167
|
+
return formatErrorResponse(err, 'Entra');
|
168
|
+
}
|
169
|
+
});
|
170
|
+
// AZURE RESOURCE MANAGEMENT API TOOL
|
171
|
+
server.tool("askAzure", "Direct access to Azure Resource Management API for managing Azure resources", {
|
172
|
+
path: z.string().describe("The Azure API path (e.g. '/subscriptions', '/resourceGroups/{name}')"),
|
173
|
+
method: z.enum(["get", "post", "put", "patch", "delete"]).default("get").describe("HTTP method to use"),
|
174
|
+
apiVersion: z.string().optional().describe("Azure API version - required for each Azure Resource Provider"),
|
175
|
+
subscriptionId: z.string().optional().describe("Azure Subscription ID (if not included in path)"),
|
176
|
+
body: z.record(z.string(), z.any()).optional().describe("Request body for POST/PUT/PATCH requests"),
|
177
|
+
queryParams: z.record(z.string()).optional().describe("Additional query parameters"),
|
178
|
+
fetchAllPages: z.boolean().optional().default(false).describe("Automatically fetch all pages of results"),
|
179
|
+
// Predefined operations
|
180
|
+
operation: z.enum([
|
181
|
+
"listResources", "listResourceProviders", "getResourceProvider",
|
182
|
+
"registerResourceProvider", "getResourceTypes", "getApiVersions",
|
183
|
+
"getLocations", "createResource", "deployTemplate", "deleteResource", "custom"
|
184
|
+
]).optional().default("custom").describe("Predefined Azure operations"),
|
185
|
+
// Parameters for predefined operations
|
186
|
+
providerNamespace: z.string().optional().describe("Resource provider namespace (e.g. 'Microsoft.Compute')"),
|
187
|
+
resourceType: z.string().optional().describe("Resource type for specific operations"),
|
188
|
+
resourceGroupName: z.string().optional().describe("Resource group name for resource operations"),
|
189
|
+
resourceName: z.string().optional().describe("Resource name for resource operations"),
|
190
|
+
}, async ({ path, method, apiVersion, subscriptionId, body, queryParams = {}, fetchAllPages, operation = "custom", providerNamespace, resourceType, resourceGroupName, resourceName }) => {
|
191
|
+
try {
|
192
|
+
// Default API versions for common resource types
|
193
|
+
const defaultApiVersions = {
|
194
|
+
'resources': '2021-04-01',
|
195
|
+
'resourceGroups': '2021-04-01',
|
196
|
+
'subscriptions': '2021-01-01',
|
197
|
+
'providers': '2021-04-01',
|
198
|
+
'deployments': '2021-04-01',
|
199
|
+
'Microsoft.Compute/virtualMachines': '2023-03-01',
|
200
|
+
'Microsoft.Storage/storageAccounts': '2023-01-01',
|
201
|
+
'Microsoft.Network/virtualNetworks': '2023-04-01',
|
202
|
+
'Microsoft.KeyVault/vaults': '2023-02-01'
|
203
|
+
};
|
204
|
+
// Handle predefined operations
|
205
|
+
if (operation !== "custom") {
|
206
|
+
const requiredSubscriptionId = !['listResourceProviders', 'getResourceProvider', 'registerResourceProvider'].includes(operation);
|
207
|
+
if (requiredSubscriptionId && !subscriptionId) {
|
208
|
+
throw new Error(`Operation '${operation}' requires a subscriptionId`);
|
209
|
+
}
|
210
|
+
switch (operation) {
|
211
|
+
case 'listResources':
|
212
|
+
path = resourceGroupName
|
213
|
+
? `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/resources`
|
214
|
+
: `/subscriptions/${subscriptionId}/resources`;
|
215
|
+
apiVersion = apiVersion || defaultApiVersions['resources'];
|
216
|
+
break;
|
217
|
+
case 'listResourceProviders':
|
218
|
+
path = `/subscriptions/${subscriptionId}/providers`;
|
219
|
+
apiVersion = apiVersion || defaultApiVersions['providers'];
|
220
|
+
break;
|
221
|
+
case 'getResourceProvider':
|
222
|
+
if (!providerNamespace)
|
223
|
+
throw new Error("Operation 'getResourceProvider' requires a providerNamespace");
|
224
|
+
path = `/subscriptions/${subscriptionId}/providers/${providerNamespace}`;
|
225
|
+
apiVersion = apiVersion || defaultApiVersions['providers'];
|
226
|
+
break;
|
227
|
+
case 'registerResourceProvider':
|
228
|
+
if (!providerNamespace)
|
229
|
+
throw new Error("Operation 'registerResourceProvider' requires a providerNamespace");
|
230
|
+
path = `/subscriptions/${subscriptionId}/providers/${providerNamespace}/register`;
|
231
|
+
method = 'post';
|
232
|
+
apiVersion = apiVersion || defaultApiVersions['providers'];
|
233
|
+
break;
|
234
|
+
case 'getResourceTypes':
|
235
|
+
case 'getApiVersions':
|
236
|
+
case 'getLocations':
|
237
|
+
if (!providerNamespace)
|
238
|
+
throw new Error(`Operation '${operation}' requires providerNamespace`);
|
239
|
+
path = `/subscriptions/${subscriptionId}/providers/${providerNamespace}`;
|
240
|
+
apiVersion = apiVersion || defaultApiVersions['providers'];
|
241
|
+
break;
|
242
|
+
case 'createResource':
|
243
|
+
if (!resourceGroupName || !providerNamespace || !resourceType || !resourceName) {
|
244
|
+
throw new Error("Operation 'createResource' requires resourceGroupName, providerNamespace, resourceType, and resourceName");
|
245
|
+
}
|
246
|
+
if (!body)
|
247
|
+
throw new Error("Operation 'createResource' requires a request body");
|
248
|
+
path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/${providerNamespace}/${resourceType}/${resourceName}`;
|
249
|
+
method = 'put';
|
250
|
+
const providerResourceKey = `${providerNamespace}/${resourceType}`;
|
251
|
+
apiVersion = apiVersion || defaultApiVersions[providerResourceKey] || '2021-04-01';
|
252
|
+
break;
|
253
|
+
case 'deployTemplate':
|
254
|
+
if (!resourceGroupName)
|
255
|
+
throw new Error("Operation 'deployTemplate' requires resourceGroupName");
|
256
|
+
if (!body?.properties?.template)
|
257
|
+
throw new Error("Operation 'deployTemplate' requires a template in the body");
|
258
|
+
const deploymentName = body.deploymentName || `deployment-${new Date().getTime()}`;
|
259
|
+
delete body.deploymentName;
|
260
|
+
path = `/subscriptions/${subscriptionId}/resourcegroups/${resourceGroupName}/providers/Microsoft.Resources/deployments/${deploymentName}`;
|
261
|
+
method = 'put';
|
262
|
+
apiVersion = apiVersion || defaultApiVersions['deployments'];
|
263
|
+
break;
|
264
|
+
case 'deleteResource':
|
265
|
+
if (!resourceGroupName || !providerNamespace || !resourceType || !resourceName) {
|
266
|
+
throw new Error("Operation 'deleteResource' requires resourceGroupName, providerNamespace, resourceType, and resourceName");
|
267
|
+
}
|
268
|
+
path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/${providerNamespace}/${resourceType}/${resourceName}`;
|
269
|
+
method = 'delete';
|
270
|
+
const deleteResourceKey = `${providerNamespace}/${resourceType}`;
|
271
|
+
apiVersion = apiVersion || defaultApiVersions[deleteResourceKey] || '2021-04-01';
|
272
|
+
break;
|
273
|
+
}
|
51
274
|
}
|
52
|
-
|
53
|
-
|
275
|
+
// Ensure API version is provided
|
276
|
+
if (!apiVersion && !queryParams['api-version']) {
|
277
|
+
throw new Error("Azure Resource Management API requires an 'apiVersion' parameter");
|
54
278
|
}
|
55
|
-
//
|
56
|
-
|
57
|
-
|
279
|
+
// Get Azure credential
|
280
|
+
const credential = getAzureCredential();
|
281
|
+
// Construct the base URL and path
|
282
|
+
const baseUrl = "https://management.azure.com";
|
283
|
+
let fullPath = path;
|
284
|
+
if (subscriptionId && !path.includes('/subscriptions/')) {
|
285
|
+
fullPath = `/subscriptions/${subscriptionId}${path.startsWith('/') ? path : `/${path}`}`;
|
58
286
|
}
|
59
|
-
//
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
//
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
287
|
+
// Add api-version and other query parameters
|
288
|
+
const params = new URLSearchParams(queryParams);
|
289
|
+
if (apiVersion)
|
290
|
+
params.set('api-version', apiVersion);
|
291
|
+
// Get access token
|
292
|
+
const tokenResponse = await credential.getToken("https://management.azure.com/.default");
|
293
|
+
if (!tokenResponse?.token)
|
294
|
+
throw new Error("Failed to acquire Azure access token");
|
295
|
+
// Prepare request options
|
296
|
+
const headers = {
|
297
|
+
'Authorization': `Bearer ${tokenResponse.token}`,
|
298
|
+
'Content-Type': 'application/json'
|
299
|
+
};
|
300
|
+
const options = {
|
301
|
+
method: method.toUpperCase(),
|
302
|
+
headers
|
71
303
|
};
|
304
|
+
if (['POST', 'PUT', 'PATCH'].includes(method.toUpperCase()) && body) {
|
305
|
+
options.body = JSON.stringify(body);
|
306
|
+
}
|
307
|
+
// Construct URL
|
308
|
+
const url = `${baseUrl}${fullPath}?${params.toString()}`;
|
309
|
+
// Execute request with pagination if needed
|
310
|
+
let result;
|
311
|
+
if (method === 'get' && fetchAllPages) {
|
312
|
+
// Fetch first page
|
313
|
+
const response = await fetch(url, options);
|
314
|
+
if (!response.ok) {
|
315
|
+
const errorText = await response.text();
|
316
|
+
throw new Error(`Azure API error: ${response.status} - ${errorText}`);
|
317
|
+
}
|
318
|
+
const firstPage = await response.json();
|
319
|
+
// If no pagination needed, return first page
|
320
|
+
if (!firstPage.nextLink) {
|
321
|
+
result = firstPage;
|
322
|
+
}
|
323
|
+
else {
|
324
|
+
// Collect all items from all pages
|
325
|
+
const allItems = [...(firstPage.value || [])];
|
326
|
+
let nextLink = firstPage.nextLink;
|
327
|
+
while (nextLink) {
|
328
|
+
const pageResponse = await fetch(nextLink, options);
|
329
|
+
if (!pageResponse.ok)
|
330
|
+
throw new Error(`Azure API pagination error: ${pageResponse.status}`);
|
331
|
+
const nextPage = await pageResponse.json();
|
332
|
+
if (nextPage.value)
|
333
|
+
allItems.push(...nextPage.value);
|
334
|
+
nextLink = nextPage.nextLink || null;
|
335
|
+
}
|
336
|
+
result = {
|
337
|
+
value: allItems,
|
338
|
+
count: allItems.length
|
339
|
+
};
|
340
|
+
}
|
341
|
+
}
|
342
|
+
else {
|
343
|
+
// Single page request
|
344
|
+
const response = await fetch(url, options);
|
345
|
+
if (!response.ok) {
|
346
|
+
const errorText = await response.text();
|
347
|
+
let errorDetail;
|
348
|
+
try {
|
349
|
+
errorDetail = JSON.parse(errorText);
|
350
|
+
}
|
351
|
+
catch {
|
352
|
+
errorDetail = errorText;
|
353
|
+
}
|
354
|
+
const error = new Error(`Azure API error: ${response.status}`);
|
355
|
+
error.status = response.status;
|
356
|
+
error.response = { body: errorDetail };
|
357
|
+
throw error;
|
358
|
+
}
|
359
|
+
const text = await response.text();
|
360
|
+
result = text ? JSON.parse(text) : { status: "Success" };
|
361
|
+
}
|
362
|
+
return formatApiResponse('Azure', method, path, result);
|
72
363
|
}
|
73
364
|
catch (err) {
|
74
|
-
return
|
75
|
-
content: [
|
76
|
-
{
|
77
|
-
type: "text",
|
78
|
-
text: `Error querying Entra API: ${err instanceof Error ? err.message : String(err)}`,
|
79
|
-
},
|
80
|
-
],
|
81
|
-
};
|
365
|
+
return formatErrorResponse(err, 'Azure');
|
82
366
|
}
|
83
367
|
});
|
84
|
-
//
|
85
|
-
function initGraphClient() {
|
86
|
-
const tenantId = process.env.TENANT_ID;
|
87
|
-
const clientId = process.env.CLIENT_ID;
|
88
|
-
const clientSecret = process.env.CLIENT_SECRET;
|
89
|
-
if (!tenantId || !clientId || !clientSecret) {
|
90
|
-
throw new Error("Missing required Entra environment variables: TENANT_ID, CLIENT_ID, or CLIENT_SECRET");
|
91
|
-
}
|
92
|
-
const credential = new ClientSecretCredential(tenantId, clientId, clientSecret);
|
93
|
-
const authProvider = new TokenCredentialAuthenticationProvider(credential, {
|
94
|
-
scopes: ["https://graph.microsoft.com/.default"],
|
95
|
-
});
|
96
|
-
return Client.initWithMiddleware({
|
97
|
-
authProvider: authProvider,
|
98
|
-
});
|
99
|
-
}
|
100
|
-
// Start the server with stdio transport
|
368
|
+
// SERVER STARTUP
|
101
369
|
async function main() {
|
102
|
-
|
103
|
-
|
104
|
-
|
370
|
+
try {
|
371
|
+
const transport = new StdioServerTransport();
|
372
|
+
await server.connect(transport);
|
373
|
+
console.error("EntraAware MCP Server running on stdio");
|
374
|
+
}
|
375
|
+
catch (error) {
|
376
|
+
console.error("Error starting server:", error);
|
377
|
+
process.exit(1);
|
378
|
+
}
|
105
379
|
}
|
106
380
|
main().catch((error) => {
|
107
381
|
console.error("Fatal error in main():", error);
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@north7/entraaware",
|
3
|
-
"version": "0.0.
|
3
|
+
"version": "0.0.2",
|
4
4
|
"type": "module",
|
5
5
|
"main": "build/index.js",
|
6
6
|
"bin": {
|
@@ -16,7 +16,7 @@
|
|
16
16
|
"keywords": ["mcp-server", "entra", "azure-ad", "mcp", "microsoft-graph"],
|
17
17
|
"author": "",
|
18
18
|
"license": "ISC",
|
19
|
-
"description": "MCP server for
|
19
|
+
"description": "MCP server for querying Entra and Azure",
|
20
20
|
"files": [
|
21
21
|
"build"
|
22
22
|
],
|