@north7/entraaware 0.0.4 → 0.0.6
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/LICENSE +22 -0
- package/README.md +75 -23
- package/build/index.js +150 -50
- package/package.json +11 -5
package/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 North7 (EntraAware)
|
4
|
+
Portions copyright (c) 2025 Merill Fernando
|
5
|
+
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
8
|
+
in the Software without restriction, including without limitation the rights
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
11
|
+
furnished to do so, subject to the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
14
|
+
copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
22
|
+
SOFTWARE.
|
package/README.md
CHANGED
@@ -1,27 +1,29 @@
|
|
1
1
|
# EntraAware MCP Server
|
2
2
|
|
3
|
-
A
|
3
|
+
A lightweight Model Context Protocol (MCP) server for querying Microsoft Entra (Azure AD) and Azure Resource Management data.
|
4
4
|
|
5
5
|
## What is EntraAware?
|
6
6
|
|
7
|
-
EntraAware is an MCP Server allows AI assistants to directly access your Microsoft Entra (Azure AD) tenant data through the Microsoft Graph API. With EntraAware, you can ask natural language questions
|
7
|
+
EntraAware is an MCP Server that allows AI assistants to directly access your Microsoft Entra (Azure AD) tenant data through the Microsoft Graph API and Azure Resource Management API. With EntraAware, you can ask natural language questions or make structured API calls to your Microsoft cloud environments.
|
8
|
+
|
9
|
+
This project is inspired by and builds upon the [Lokka-Microsoft](https://github.com/lokkamcp/microsoft) MCP server (MIT license).
|
8
10
|
|
9
11
|
## Setup
|
10
12
|
|
11
13
|
### Prerequisites
|
12
14
|
|
13
15
|
- Microsoft Entra (Azure AD) tenant
|
14
|
-
- Application registration with appropriate Graph API permissions
|
16
|
+
- Application registration with appropriate Graph API permissions and Azure Resource Management permissions
|
15
17
|
- Node.js 18 or higher
|
16
18
|
|
17
19
|
### Installation
|
18
20
|
|
19
21
|
```bash
|
20
22
|
# Install globally
|
21
|
-
npm install -g @
|
23
|
+
npm install -g @north7/entraaware
|
22
24
|
|
23
25
|
# Or use with npx (no installation needed)
|
24
|
-
npx @
|
26
|
+
npx @north7/entraaware
|
25
27
|
```
|
26
28
|
|
27
29
|
### Configuration
|
@@ -56,37 +58,87 @@ Replace the environment variables with your own:
|
|
56
58
|
|
57
59
|
## Usage
|
58
60
|
|
59
|
-
Once configured, you can use EntraAware through VS Code
|
61
|
+
Once configured, you can use EntraAware through a compatible MCP client (like VS Code with the MCP extension).
|
60
62
|
|
61
|
-
|
62
|
-
ask EntraAware>
|
63
|
-
```
|
63
|
+
### Available Tools
|
64
64
|
|
65
|
-
|
65
|
+
EntraAware provides three MCP tools:
|
66
66
|
|
67
|
-
|
67
|
+
#### 1. askEntra
|
68
|
+
|
69
|
+
Direct access to Microsoft Graph API for accurate Entra (Azure AD) data.
|
70
|
+
|
71
|
+
```javascript
|
72
|
+
// Example usage
|
68
73
|
{
|
69
|
-
"
|
74
|
+
"path": "/users",
|
75
|
+
"method": "get",
|
76
|
+
"select": "displayName,userPrincipalName,id",
|
77
|
+
"top": 10
|
78
|
+
}
|
79
|
+
|
80
|
+
// Advanced filtering
|
81
|
+
{
|
82
|
+
"path": "/users",
|
83
|
+
"method": "get",
|
84
|
+
"filter": "startsWith(displayName,'J')",
|
85
|
+
"consistencyLevel": "eventual"
|
70
86
|
}
|
71
87
|
```
|
72
88
|
|
73
|
-
|
89
|
+
#### 2. askAzure
|
90
|
+
|
91
|
+
Direct access to Azure Resource Management API for managing Azure resources.
|
92
|
+
|
93
|
+
```javascript
|
94
|
+
// List subscriptions
|
95
|
+
{
|
96
|
+
"path": "/subscriptions",
|
97
|
+
"method": "get"
|
98
|
+
}
|
74
99
|
|
100
|
+
// List all resource groups in a subscription
|
101
|
+
{
|
102
|
+
"path": "/resourceGroups",
|
103
|
+
"method": "get",
|
104
|
+
"subscriptionId": "your-subscription-id",
|
105
|
+
"apiVersion": "2021-04-01"
|
106
|
+
}
|
107
|
+
|
108
|
+
// Use predefined operations
|
109
|
+
{
|
110
|
+
"operation": "listResources",
|
111
|
+
"subscriptionId": "your-subscription-id"
|
112
|
+
}
|
75
113
|
```
|
76
|
-
// Get organization details
|
77
|
-
Show me details about my organization
|
78
114
|
|
79
|
-
|
80
|
-
|
115
|
+
#### 3. Lokka-Microsoft (Compatibility Layer)
|
116
|
+
|
117
|
+
A compatibility layer for the Lokka-Microsoft MCP server to ensure backward compatibility.
|
81
118
|
|
82
|
-
|
83
|
-
|
119
|
+
```javascript
|
120
|
+
// Query Graph API
|
121
|
+
{
|
122
|
+
"apiType": "graph",
|
123
|
+
"path": "/users",
|
124
|
+
"method": "get"
|
125
|
+
}
|
84
126
|
|
85
|
-
//
|
86
|
-
|
127
|
+
// Query Azure API
|
128
|
+
{
|
129
|
+
"apiType": "azure",
|
130
|
+
"path": "/subscriptions",
|
131
|
+
"method": "get",
|
132
|
+
"apiVersion": "2022-12-01"
|
133
|
+
}
|
87
134
|
```
|
88
135
|
|
89
|
-
##
|
136
|
+
## License
|
137
|
+
|
138
|
+
MIT License - See LICENSE file for details.
|
139
|
+
|
140
|
+
## Acknowledgements
|
90
141
|
|
142
|
+
- This project is inspired by and builds upon the [Lokka-Microsoft](https://github.com/merill/lokka) MCP server.
|
91
143
|
- [Microsoft Graph API Documentation](https://learn.microsoft.com/en-us/graph/api/overview)
|
92
|
-
- [
|
144
|
+
- [Azure Resource Management API Documentation](https://learn.microsoft.com/en-us/rest/api/azure/)
|
package/build/index.js
CHANGED
@@ -1,4 +1,10 @@
|
|
1
1
|
#!/usr/bin/env node
|
2
|
+
/**
|
3
|
+
* EntraAware MCP Server
|
4
|
+
*
|
5
|
+
* Portions of this code are adapted from the Lokka-Microsoft MCP Server (MIT License)
|
6
|
+
* Original repository: https://github.com/merill/lokka
|
7
|
+
*/
|
2
8
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
3
9
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
4
10
|
import { z } from "zod";
|
@@ -11,13 +17,14 @@ let azureCredential = null;
|
|
11
17
|
// Create server instance
|
12
18
|
const server = new McpServer({
|
13
19
|
name: "EntraAware",
|
14
|
-
version: "0.0.
|
20
|
+
version: "0.0.6",
|
15
21
|
capabilities: {
|
16
22
|
resources: {},
|
17
23
|
tools: {},
|
18
24
|
},
|
19
25
|
});
|
20
26
|
// SHARED UTILITIES
|
27
|
+
// The following credential handling utilities are adapted from Lokka-Microsoft
|
21
28
|
function getCredentials() {
|
22
29
|
const tenantId = process.env.TENANT_ID;
|
23
30
|
const clientId = process.env.CLIENT_ID;
|
@@ -27,6 +34,52 @@ function getCredentials() {
|
|
27
34
|
}
|
28
35
|
return { tenantId, clientId, clientSecret };
|
29
36
|
}
|
37
|
+
// Helper to fetch the latest API version for a given resource provider and resource type
|
38
|
+
async function getLatestApiVersion(providerNamespace, resourceType) {
|
39
|
+
try {
|
40
|
+
// Get Azure credential
|
41
|
+
const credential = getAzureCredential();
|
42
|
+
// Get access token
|
43
|
+
const tokenResponse = await credential.getToken("https://management.azure.com/.default");
|
44
|
+
if (!tokenResponse?.token)
|
45
|
+
throw new Error("Failed to acquire Azure access token");
|
46
|
+
// Prepare request options
|
47
|
+
const headers = {
|
48
|
+
'Authorization': `Bearer ${tokenResponse.token}`,
|
49
|
+
'Content-Type': 'application/json'
|
50
|
+
};
|
51
|
+
// Make request to list provider details
|
52
|
+
const url = `https://management.azure.com/providers/${providerNamespace}?api-version=2021-04-01`;
|
53
|
+
const response = await fetch(url, { method: 'GET', headers });
|
54
|
+
if (!response.ok) {
|
55
|
+
throw new Error(`Failed to fetch API versions: ${response.status}`);
|
56
|
+
}
|
57
|
+
const providerData = await response.json();
|
58
|
+
// If resourceType is specified, find that specific type
|
59
|
+
if (resourceType) {
|
60
|
+
const resourceTypeInfo = providerData.resourceTypes.find((rt) => rt.resourceType.toLowerCase() === resourceType.toLowerCase());
|
61
|
+
if (resourceTypeInfo && resourceTypeInfo.apiVersions && resourceTypeInfo.apiVersions.length > 0) {
|
62
|
+
// Return the first (most recent) API version
|
63
|
+
return resourceTypeInfo.apiVersions[0];
|
64
|
+
}
|
65
|
+
}
|
66
|
+
else {
|
67
|
+
// Otherwise, just return a stable API version for the provider
|
68
|
+
// Providers typically have their most recent versions first
|
69
|
+
if (providerData.apiVersions && providerData.apiVersions.length > 0) {
|
70
|
+
return providerData.apiVersions[0];
|
71
|
+
}
|
72
|
+
}
|
73
|
+
// If we get here, we couldn't find a good API version
|
74
|
+
throw new Error(`Could not find API version for ${providerNamespace}${resourceType ? '/' + resourceType : ''}`);
|
75
|
+
}
|
76
|
+
catch (error) {
|
77
|
+
console.error(`Error fetching API versions: ${error instanceof Error ? error.message : String(error)}`);
|
78
|
+
// Return a default fallback version - you might want to customize this per provider
|
79
|
+
return '2021-04-01';
|
80
|
+
}
|
81
|
+
}
|
82
|
+
// This Azure credential handling approach is similar to Lokka-Microsoft
|
30
83
|
function getAzureCredential() {
|
31
84
|
if (!azureCredential) {
|
32
85
|
try {
|
@@ -50,6 +103,7 @@ function getAzureCredential() {
|
|
50
103
|
}
|
51
104
|
return azureCredential;
|
52
105
|
}
|
106
|
+
// Response formatting utilities inspired by Lokka-Microsoft
|
53
107
|
function formatApiResponse(apiType, method, path, result) {
|
54
108
|
return {
|
55
109
|
content: [
|
@@ -80,7 +134,25 @@ function formatErrorResponse(err, apiType) {
|
|
80
134
|
],
|
81
135
|
};
|
82
136
|
}
|
137
|
+
// Process OData parameters for Graph API
|
138
|
+
function processODataParams({ queryParams = {}, select, filter, expand, orderBy, top, count }) {
|
139
|
+
const processedParams = { ...queryParams };
|
140
|
+
if (select)
|
141
|
+
processedParams['$select'] = select;
|
142
|
+
if (filter)
|
143
|
+
processedParams['$filter'] = filter;
|
144
|
+
if (expand)
|
145
|
+
processedParams['$expand'] = expand;
|
146
|
+
if (orderBy)
|
147
|
+
processedParams['$orderby'] = orderBy;
|
148
|
+
if (top !== undefined)
|
149
|
+
processedParams['$top'] = top.toString();
|
150
|
+
if (count)
|
151
|
+
processedParams['$count'] = 'true';
|
152
|
+
return processedParams;
|
153
|
+
}
|
83
154
|
// MICROSOFT GRAPH API TOOL
|
155
|
+
// This tool implementation is inspired by and adapted from Lokka-Microsoft's Graph API handling
|
84
156
|
server.tool("askEntra", "Direct access to Microsoft Graph API for accurate Entra (Azure AD) data", {
|
85
157
|
path: z.string().describe("The Graph API URL path (e.g. '/users/{id}/memberOf', '/directoryRoles')"),
|
86
158
|
method: z.enum(["get", "post", "put", "patch", "delete"]).default("get").describe("HTTP method to use"),
|
@@ -97,51 +169,38 @@ server.tool("askEntra", "Direct access to Microsoft Graph API for accurate Entra
|
|
97
169
|
top: z.number().optional().describe("Shorthand for $top query parameter"),
|
98
170
|
count: z.boolean().optional().describe("Shorthand for $count=true to include count of items"),
|
99
171
|
}, async ({ path, method, queryParams = {}, body, apiVersion, fetchAllPages, consistencyLevel, select, filter, expand, orderBy, top, count }) => {
|
100
|
-
console.error(`[askEntra] Processing request: ${method} ${path}`);
|
101
172
|
try {
|
102
173
|
// Process shorthand query parameters
|
103
|
-
const processedParams = {
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
processedParams['$top'] = top.toString();
|
114
|
-
if (count)
|
115
|
-
processedParams['$count'] = 'true';
|
116
|
-
console.error(`[askEntra] Getting Azure credential`);
|
117
|
-
// Initialize or get Azure credential
|
118
|
-
const credential = getAzureCredential();
|
119
|
-
console.error(`[askEntra] Initializing Graph client`);
|
120
|
-
// Initialize Graph client if not already done
|
174
|
+
const processedParams = processODataParams({
|
175
|
+
queryParams,
|
176
|
+
select,
|
177
|
+
filter,
|
178
|
+
expand,
|
179
|
+
orderBy,
|
180
|
+
top,
|
181
|
+
count
|
182
|
+
});
|
183
|
+
// Initialize client on demand
|
121
184
|
if (!graphClient) {
|
185
|
+
const credential = getAzureCredential();
|
122
186
|
const authProvider = new TokenCredentialAuthenticationProvider(credential, {
|
123
187
|
scopes: ["https://graph.microsoft.com/.default"],
|
124
188
|
});
|
125
189
|
graphClient = Client.initWithMiddleware({ authProvider });
|
126
|
-
console.error(`[askEntra] Graph client initialized`);
|
127
190
|
}
|
128
191
|
// Build request with API path and version
|
129
|
-
console.error(`[askEntra] Creating request for ${path} with version ${apiVersion}`);
|
130
192
|
let request = graphClient.api(path).version(apiVersion);
|
131
193
|
// Add query parameters
|
132
194
|
if (Object.keys(processedParams).length > 0) {
|
133
|
-
console.error(`[askEntra] Adding query parameters: ${JSON.stringify(processedParams)}`);
|
134
195
|
request = request.query(processedParams);
|
135
196
|
}
|
136
197
|
// Add consistency level header if provided
|
137
198
|
if (consistencyLevel) {
|
138
|
-
console.error(`[askEntra] Adding consistency level: ${consistencyLevel}`);
|
139
199
|
request = request.header('ConsistencyLevel', consistencyLevel);
|
140
200
|
}
|
141
201
|
// Handle pagination for GET requests
|
142
202
|
let result;
|
143
203
|
if (method === 'get' && fetchAllPages) {
|
144
|
-
console.error(`[askEntra] Executing GET with pagination`);
|
145
204
|
const firstPage = await request.get();
|
146
205
|
// If no pagination needed, return first page
|
147
206
|
if (!firstPage["@odata.nextLink"]) {
|
@@ -152,7 +211,6 @@ server.tool("askEntra", "Direct access to Microsoft Graph API for accurate Entra
|
|
152
211
|
const allItems = [...(firstPage.value || [])];
|
153
212
|
let nextLink = firstPage["@odata.nextLink"];
|
154
213
|
while (nextLink) {
|
155
|
-
console.error(`[askEntra] Fetching next page: ${nextLink}`);
|
156
214
|
const nextPage = await graphClient.api(nextLink).get();
|
157
215
|
if (nextPage.value)
|
158
216
|
allItems.push(...nextPage.value);
|
@@ -168,7 +226,6 @@ server.tool("askEntra", "Direct access to Microsoft Graph API for accurate Entra
|
|
168
226
|
}
|
169
227
|
else {
|
170
228
|
// Execute appropriate method
|
171
|
-
console.error(`[askEntra] Executing ${method} request`);
|
172
229
|
switch (method) {
|
173
230
|
case 'get':
|
174
231
|
result = await request.get();
|
@@ -188,18 +245,14 @@ server.tool("askEntra", "Direct access to Microsoft Graph API for accurate Entra
|
|
188
245
|
break;
|
189
246
|
}
|
190
247
|
}
|
191
|
-
console.error(`[askEntra] Successfully executed request`);
|
192
248
|
return formatApiResponse('Entra', method, path, result);
|
193
249
|
}
|
194
250
|
catch (err) {
|
195
|
-
console.error(`[askEntra] ERROR: ${err instanceof Error ? err.message : String(err)}`);
|
196
|
-
if (err instanceof Error && err.stack) {
|
197
|
-
console.error(`[askEntra] Stack trace: ${err.stack}`);
|
198
|
-
}
|
199
251
|
return formatErrorResponse(err, 'Entra');
|
200
252
|
}
|
201
253
|
});
|
202
254
|
// AZURE RESOURCE MANAGEMENT API TOOL
|
255
|
+
// This tool implementation is inspired by and adapted from Lokka-Microsoft's Azure API handling
|
203
256
|
server.tool("askAzure", "Direct access to Azure Resource Management API for managing Azure resources", {
|
204
257
|
path: z.string().describe("The Azure API path (e.g. '/subscriptions', '/resourceGroups/{name}')"),
|
205
258
|
method: z.enum(["get", "post", "put", "patch", "delete"]).default("get").describe("HTTP method to use"),
|
@@ -220,20 +273,35 @@ server.tool("askAzure", "Direct access to Azure Resource Management API for mana
|
|
220
273
|
resourceGroupName: z.string().optional().describe("Resource group name for resource operations"),
|
221
274
|
resourceName: z.string().optional().describe("Resource name for resource operations"),
|
222
275
|
}, async ({ path, method, apiVersion, subscriptionId, body, queryParams = {}, fetchAllPages, operation = "custom", providerNamespace, resourceType, resourceGroupName, resourceName }) => {
|
223
|
-
console.error(`[askAzure] Processing request: ${operation} - ${method} ${path}`);
|
224
276
|
try {
|
225
277
|
// Default API versions for common resource types
|
226
278
|
const defaultApiVersions = {
|
227
279
|
'resources': '2021-04-01',
|
228
280
|
'resourceGroups': '2021-04-01',
|
229
|
-
'subscriptions': '
|
281
|
+
'subscriptions': '2022-12-01',
|
230
282
|
'providers': '2021-04-01',
|
231
283
|
'deployments': '2021-04-01',
|
232
284
|
'Microsoft.Compute/virtualMachines': '2023-03-01',
|
233
285
|
'Microsoft.Storage/storageAccounts': '2023-01-01',
|
234
286
|
'Microsoft.Network/virtualNetworks': '2023-04-01',
|
235
|
-
'Microsoft.KeyVault/vaults': '2023-02-01'
|
287
|
+
'Microsoft.KeyVault/vaults': '2023-02-01',
|
288
|
+
'Microsoft.Billing/billingAccounts': '2024-04-01',
|
289
|
+
'Microsoft.CostManagement/query': '2023-03-01'
|
236
290
|
};
|
291
|
+
// Set default API version for common paths if not provided
|
292
|
+
if (!apiVersion && !queryParams['api-version']) {
|
293
|
+
if (path === '/subscriptions') {
|
294
|
+
apiVersion = defaultApiVersions['subscriptions']; // Default API version for listing subscriptions
|
295
|
+
}
|
296
|
+
}
|
297
|
+
let extractedProviderNamespace = null;
|
298
|
+
// Try to extract provider namespace from the path if not explicitly provided
|
299
|
+
if (!providerNamespace && path.includes('/providers/')) {
|
300
|
+
const match = path.match(/\/providers\/([^\/]+)/);
|
301
|
+
if (match && match[1]) {
|
302
|
+
extractedProviderNamespace = match[1];
|
303
|
+
}
|
304
|
+
}
|
237
305
|
// Handle predefined operations
|
238
306
|
if (operation !== "custom") {
|
239
307
|
const requiredSubscriptionId = !['listResourceProviders', 'getResourceProvider', 'registerResourceProvider'].includes(operation);
|
@@ -281,7 +349,21 @@ server.tool("askAzure", "Direct access to Azure Resource Management API for mana
|
|
281
349
|
path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/${providerNamespace}/${resourceType}/${resourceName}`;
|
282
350
|
method = 'put';
|
283
351
|
const providerResourceKey = `${providerNamespace}/${resourceType}`;
|
284
|
-
|
352
|
+
// Try to get the API version for this resource type
|
353
|
+
if (!apiVersion) {
|
354
|
+
// First check our default versions
|
355
|
+
apiVersion = defaultApiVersions[providerResourceKey];
|
356
|
+
// If not found in defaults, try to fetch it dynamically
|
357
|
+
if (!apiVersion && providerNamespace) {
|
358
|
+
try {
|
359
|
+
apiVersion = await getLatestApiVersion(providerNamespace, resourceType);
|
360
|
+
}
|
361
|
+
catch (error) {
|
362
|
+
console.error(`Error fetching API version: ${error instanceof Error ? error.message : String(error)}`);
|
363
|
+
apiVersion = '2021-04-01'; // Fall back to a safe default
|
364
|
+
}
|
365
|
+
}
|
366
|
+
}
|
285
367
|
break;
|
286
368
|
case 'deployTemplate':
|
287
369
|
if (!resourceGroupName)
|
@@ -301,16 +383,46 @@ server.tool("askAzure", "Direct access to Azure Resource Management API for mana
|
|
301
383
|
path = `/subscriptions/${subscriptionId}/resourceGroups/${resourceGroupName}/providers/${providerNamespace}/${resourceType}/${resourceName}`;
|
302
384
|
method = 'delete';
|
303
385
|
const deleteResourceKey = `${providerNamespace}/${resourceType}`;
|
304
|
-
|
386
|
+
// Try to get the API version for this resource type
|
387
|
+
if (!apiVersion) {
|
388
|
+
// First check our default versions
|
389
|
+
apiVersion = defaultApiVersions[deleteResourceKey];
|
390
|
+
// If not found in defaults, try to fetch it dynamically
|
391
|
+
if (!apiVersion && providerNamespace) {
|
392
|
+
try {
|
393
|
+
apiVersion = await getLatestApiVersion(providerNamespace, resourceType);
|
394
|
+
}
|
395
|
+
catch (error) {
|
396
|
+
console.error(`Error fetching API version: ${error instanceof Error ? error.message : String(error)}`);
|
397
|
+
apiVersion = '2021-04-01'; // Fall back to a safe default
|
398
|
+
}
|
399
|
+
}
|
400
|
+
}
|
305
401
|
break;
|
306
402
|
}
|
307
403
|
}
|
404
|
+
else if (extractedProviderNamespace && !apiVersion && !queryParams['api-version']) {
|
405
|
+
// For custom operations, try to get the API version based on the path
|
406
|
+
try {
|
407
|
+
// Try to extract resource type from path if possible
|
408
|
+
let extractedResourceType;
|
409
|
+
const resourceTypeMatch = path.match(/\/providers\/[^\/]+\/([^\/]+)\/?/);
|
410
|
+
if (resourceTypeMatch && resourceTypeMatch[1]) {
|
411
|
+
extractedResourceType = resourceTypeMatch[1];
|
412
|
+
}
|
413
|
+
// Fetch the latest API version
|
414
|
+
apiVersion = await getLatestApiVersion(extractedProviderNamespace, extractedResourceType);
|
415
|
+
}
|
416
|
+
catch (error) {
|
417
|
+
console.error(`Error fetching API version from path: ${error instanceof Error ? error.message : String(error)}`);
|
418
|
+
// Don't set a default here, let the error handling below catch it
|
419
|
+
}
|
420
|
+
}
|
308
421
|
// Ensure API version is provided
|
309
422
|
if (!apiVersion && !queryParams['api-version']) {
|
310
423
|
throw new Error("Azure Resource Management API requires an 'apiVersion' parameter");
|
311
424
|
}
|
312
425
|
// Get Azure credential
|
313
|
-
console.error(`[askAzure] Getting Azure credential`);
|
314
426
|
const credential = getAzureCredential();
|
315
427
|
// Construct the base URL and path
|
316
428
|
const baseUrl = "https://management.azure.com";
|
@@ -322,12 +434,10 @@ server.tool("askAzure", "Direct access to Azure Resource Management API for mana
|
|
322
434
|
const params = new URLSearchParams(queryParams);
|
323
435
|
if (apiVersion)
|
324
436
|
params.set('api-version', apiVersion);
|
325
|
-
console.error(`[askAzure] Requesting token for Azure Resource Management API`);
|
326
437
|
// Get access token
|
327
438
|
const tokenResponse = await credential.getToken("https://management.azure.com/.default");
|
328
439
|
if (!tokenResponse?.token)
|
329
440
|
throw new Error("Failed to acquire Azure access token");
|
330
|
-
console.error(`[askAzure] Successfully acquired token`);
|
331
441
|
// Prepare request options
|
332
442
|
const headers = {
|
333
443
|
'Authorization': `Bearer ${tokenResponse.token}`,
|
@@ -342,12 +452,10 @@ server.tool("askAzure", "Direct access to Azure Resource Management API for mana
|
|
342
452
|
}
|
343
453
|
// Construct URL
|
344
454
|
const url = `${baseUrl}${fullPath}?${params.toString()}`;
|
345
|
-
console.error(`[askAzure] Making request to ${url}`);
|
346
455
|
// Execute request with pagination if needed
|
347
456
|
let result;
|
348
457
|
if (method === 'get' && fetchAllPages) {
|
349
458
|
// Fetch first page
|
350
|
-
console.error(`[askAzure] Executing GET with pagination`);
|
351
459
|
const response = await fetch(url, options);
|
352
460
|
if (!response.ok) {
|
353
461
|
const errorText = await response.text();
|
@@ -363,7 +471,6 @@ server.tool("askAzure", "Direct access to Azure Resource Management API for mana
|
|
363
471
|
const allItems = [...(firstPage.value || [])];
|
364
472
|
let nextLink = firstPage.nextLink;
|
365
473
|
while (nextLink) {
|
366
|
-
console.error(`[askAzure] Fetching next page: ${nextLink}`);
|
367
474
|
const pageResponse = await fetch(nextLink, options);
|
368
475
|
if (!pageResponse.ok)
|
369
476
|
throw new Error(`Azure API pagination error: ${pageResponse.status}`);
|
@@ -380,11 +487,9 @@ server.tool("askAzure", "Direct access to Azure Resource Management API for mana
|
|
380
487
|
}
|
381
488
|
else {
|
382
489
|
// Single page request
|
383
|
-
console.error(`[askAzure] Executing ${method} request`);
|
384
490
|
const response = await fetch(url, options);
|
385
491
|
if (!response.ok) {
|
386
492
|
const errorText = await response.text();
|
387
|
-
console.error(`[askAzure] Request failed with status ${response.status}: ${errorText}`);
|
388
493
|
let errorDetail;
|
389
494
|
try {
|
390
495
|
errorDetail = JSON.parse(errorText);
|
@@ -400,14 +505,9 @@ server.tool("askAzure", "Direct access to Azure Resource Management API for mana
|
|
400
505
|
const text = await response.text();
|
401
506
|
result = text ? JSON.parse(text) : { status: "Success" };
|
402
507
|
}
|
403
|
-
console.error(`[askAzure] Successfully executed request`);
|
404
508
|
return formatApiResponse('Azure', method, path, result);
|
405
509
|
}
|
406
510
|
catch (err) {
|
407
|
-
console.error(`[askAzure] ERROR: ${err instanceof Error ? err.message : String(err)}`);
|
408
|
-
if (err instanceof Error && err.stack) {
|
409
|
-
console.error(`[askAzure] Stack trace: ${err.stack}`);
|
410
|
-
}
|
411
511
|
return formatErrorResponse(err, 'Azure');
|
412
512
|
}
|
413
513
|
});
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@north7/entraaware",
|
3
|
-
"version": "0.0.
|
3
|
+
"version": "0.0.6",
|
4
4
|
"type": "module",
|
5
5
|
"main": "build/index.js",
|
6
6
|
"bin": {
|
@@ -13,10 +13,16 @@
|
|
13
13
|
"publishConfig": {
|
14
14
|
"access": "public"
|
15
15
|
},
|
16
|
-
"keywords": ["mcp-server", "entra", "azure-ad", "mcp", "microsoft-graph"],
|
17
|
-
"author": "",
|
18
|
-
"
|
19
|
-
|
16
|
+
"keywords": ["mcp-server", "entra", "azure-ad", "mcp", "microsoft-graph", "azure-api"],
|
17
|
+
"author": "North7",
|
18
|
+
"contributors": [
|
19
|
+
{
|
20
|
+
"name": "Merill Fernando",
|
21
|
+
"url": "https://github.com/merill/lokka"
|
22
|
+
}
|
23
|
+
],
|
24
|
+
"license": "MIT",
|
25
|
+
"description": "MCP server for querying Microsoft Entra (Azure AD) and Azure Resource Management APIs",
|
20
26
|
"files": [
|
21
27
|
"build"
|
22
28
|
],
|