22// Licensed under the MIT License.
33
44using System . Text . Json ;
5+ using Azure . DataApiBuilder . Auth ;
56using Azure . DataApiBuilder . Config . ObjectModel ;
7+ using Azure . DataApiBuilder . Core . Authorization ;
68using Azure . DataApiBuilder . Core . Configurations ;
79using Azure . DataApiBuilder . Mcp . Model ;
810using Azure . DataApiBuilder . Mcp . Utils ;
911using Azure . DataApiBuilder . Service . Exceptions ;
12+ using Microsoft . AspNetCore . Http ;
1013using Microsoft . Extensions . DependencyInjection ;
1114using Microsoft . Extensions . Logging ;
1215using ModelContextProtocol . Protocol ;
@@ -80,8 +83,45 @@ public Task<CallToolResult> ExecuteAsync(
8083 logger ) ) ;
8184 }
8285
86+ // Get authorization services to determine current user's role
87+ IAuthorizationResolver authResolver = serviceProvider . GetRequiredService < IAuthorizationResolver > ( ) ;
88+ IHttpContextAccessor httpContextAccessor = serviceProvider . GetRequiredService < IHttpContextAccessor > ( ) ;
89+ HttpContext ? httpContext = httpContextAccessor . HttpContext ;
90+
91+ // Get current user's role for permission filtering
92+ // For discovery tools like describe_entities, we use the first valid role from the header
93+ // This differs from operation-specific tools that check permissions per entity per operation
94+ string ? currentUserRole = null ;
95+ if ( httpContext != null && authResolver . IsValidRoleContext ( httpContext ) )
96+ {
97+ string roleHeader = httpContext . Request . Headers [ AuthorizationResolver . CLIENT_ROLE_HEADER ] . ToString ( ) ;
98+ if ( ! string . IsNullOrWhiteSpace ( roleHeader ) )
99+ {
100+ string [ ] roles = roleHeader
101+ . Split ( ',' , StringSplitOptions . RemoveEmptyEntries | StringSplitOptions . TrimEntries ) ;
102+
103+ if ( roles . Length > 1 )
104+ {
105+ logger ? . LogWarning ( "Multiple roles detected in request header: [{Roles}]. Using first role '{FirstRole}' for entity discovery. " +
106+ "Consider using a single role for consistent permission reporting." ,
107+ string . Join ( ", " , roles ) , roles [ 0 ] ) ;
108+ }
109+
110+ // For discovery operations, take the first role from comma-separated list
111+ // This provides a consistent view of available entities for the primary role
112+ currentUserRole = roles . FirstOrDefault ( ) ;
113+ }
114+ }
115+
83116 ( bool nameOnly , HashSet < string > ? entityFilter ) = ParseArguments ( arguments , logger ) ;
84117
118+ if ( currentUserRole == null )
119+ {
120+ logger ? . LogWarning ( "Current user role could not be determined from HTTP context or role header. " +
121+ "Entity permissions will be empty (no permissions shown) rather than using anonymous permissions. " +
122+ "Ensure the '{RoleHeader}' header is properly set." , AuthorizationResolver . CLIENT_ROLE_HEADER ) ;
123+ }
124+
85125 List < Dictionary < string , object ? > > entityList = new ( ) ;
86126
87127 if ( runtimeConfig . Entities != null )
@@ -102,7 +142,7 @@ public Task<CallToolResult> ExecuteAsync(
102142 {
103143 Dictionary < string , object ? > entityInfo = nameOnly
104144 ? BuildBasicEntityInfo ( entityName , entity )
105- : BuildFullEntityInfo ( entityName , entity ) ;
145+ : BuildFullEntityInfo ( entityName , entity , currentUserRole ) ;
106146
107147 entityList . Add ( entityInfo ) ;
108148 }
@@ -140,19 +180,14 @@ public Task<CallToolResult> ExecuteAsync(
140180 Dictionary < string , object ? > responseData = new ( )
141181 {
142182 [ "entities" ] = finalEntityList ,
143- [ "count" ] = finalEntityList . Count ,
144- [ "mode" ] = nameOnly ? "basic" : "full"
183+ [ "count" ] = finalEntityList . Count
145184 } ;
146185
147- if ( entityFilter != null && entityFilter . Count > 0 )
148- {
149- responseData [ "filter" ] = entityFilter . ToArray ( ) ;
150- }
151-
152186 logger ? . LogInformation (
153- "DescribeEntitiesTool returned {EntityCount} entities in {Mode} mode ." ,
187+ "DescribeEntitiesTool returned {EntityCount} entities. Response type: {ResponseType} (nameOnly={NameOnly}) ." ,
154188 finalEntityList . Count ,
155- nameOnly ? "basic" : "full" ) ;
189+ nameOnly ? "lightweight summary (names and descriptions only)" : "full metadata with fields, parameters, and permissions" ,
190+ nameOnly ) ;
156191
157192 return Task . FromResult ( McpResponseBuilder . BuildSuccessResult (
158193 responseData ,
@@ -276,25 +311,41 @@ private static bool ShouldIncludeEntity(string entityName, HashSet<string>? enti
276311 /// </summary>
277312 /// <param name="entityName">The name of the entity to include in the dictionary.</param>
278313 /// <param name="entity">The entity object from which to extract additional information.</param>
279- /// <returns>A dictionary with two keys: "name", containing the entity name, and "description", containing the entity's
314+ /// <returns>A dictionary with two keys: "name", containing the entity alias (or name if no alias) , and "description", containing the entity's
280315 /// description or an empty string if the description is null.</returns>
281316 private static Dictionary < string , object ? > BuildBasicEntityInfo ( string entityName , Entity entity )
282317 {
318+ // Use GraphQL singular name as alias if available, otherwise use entity name
319+ string displayName = ! string . IsNullOrWhiteSpace ( entity . GraphQL ? . Singular )
320+ ? entity . GraphQL . Singular
321+ : entityName ;
322+
283323 return new Dictionary < string , object ? >
284324 {
285- [ "name" ] = entityName ,
325+ [ "name" ] = displayName ,
286326 [ "description" ] = entity . Description ?? string . Empty
287327 } ;
288328 }
289329
290330 /// <summary>
291331 /// Builds full entity info: name, description, fields, parameters (for stored procs), permissions.
292332 /// </summary>
293- private static Dictionary < string , object ? > BuildFullEntityInfo ( string entityName , Entity entity )
333+ /// <param name="entityName">The name of the entity to include in the dictionary.</param>
334+ /// <param name="entity">The entity object from which to extract additional information.</param>
335+ /// <param name="currentUserRole">The role of the current user, used to determine permissions.</param>
336+ /// <returns>
337+ /// A dictionary containing the entity's name, description, fields, parameters (if applicable), and permissions.
338+ /// </returns>
339+ private static Dictionary < string , object ? > BuildFullEntityInfo ( string entityName , Entity entity , string ? currentUserRole )
294340 {
341+ // Use GraphQL singular name as alias if available, otherwise use entity name
342+ string displayName = ! string . IsNullOrWhiteSpace ( entity . GraphQL ? . Singular )
343+ ? entity . GraphQL . Singular
344+ : entityName ;
345+
295346 Dictionary < string , object ? > info = new ( )
296347 {
297- [ "name" ] = entityName ,
348+ [ "name" ] = displayName ,
298349 [ "description" ] = entity . Description ?? string . Empty ,
299350 [ "fields" ] = BuildFieldMetadataInfo ( entity . Fields ) ,
300351 } ;
@@ -304,7 +355,7 @@ private static bool ShouldIncludeEntity(string entityName, HashSet<string>? enti
304355 info [ "parameters" ] = BuildParameterMetadataInfo ( entity . Source . Parameters ) ;
305356 }
306357
307- info [ "permissions" ] = BuildPermissionsInfo ( entity ) ;
358+ info [ "permissions" ] = BuildPermissionsInfo ( entity , currentUserRole ) ;
308359
309360 return info ;
310361 }
@@ -325,7 +376,7 @@ private static List<object> BuildFieldMetadataInfo(List<FieldMetadata>? fields)
325376 {
326377 result . Add ( new
327378 {
328- name = field . Name ,
379+ name = field . Alias ?? field . Name ,
329380 description = field . Description ?? string . Empty
330381 } ) ;
331382 }
@@ -338,7 +389,7 @@ private static List<object> BuildFieldMetadataInfo(List<FieldMetadata>? fields)
338389 /// Builds a list of parameter metadata objects containing information about each parameter.
339390 /// </summary>
340391 /// <param name="parameters">A list of <see cref="ParameterMetadata"/> objects representing the parameters to process. Can be null.</param>
341- /// <returns>A list of anonymous objects , each containing the parameter's name, whether it is required, its default
392+ /// <returns>A list of dictionaries , each containing the parameter's name, whether it is required, its default
342393 /// value, and its description. Returns an empty list if <paramref name="parameters"/> is null.</returns>
343394 private static List < object > BuildParameterMetadataInfo ( List < ParameterMetadata > ? parameters )
344395 {
@@ -348,27 +399,29 @@ private static List<object> BuildParameterMetadataInfo(List<ParameterMetadata>?
348399 {
349400 foreach ( ParameterMetadata param in parameters )
350401 {
351- result . Add ( new
402+ Dictionary < string , object ? > paramInfo = new ( )
352403 {
353- name = param . Name ,
354- required = param . Default == null , // required if no default
355- @default = param . Default ,
356- description = param . Description ?? string . Empty
357- } ) ;
404+ [ "name" ] = param . Name ,
405+ [ "required" ] = param . Required ,
406+ [ "default" ] = param . Default ,
407+ [ "description" ] = param . Description ?? string . Empty
408+ } ;
409+ result . Add ( paramInfo ) ;
358410 }
359411 }
360412
361413 return result ;
362414 }
363415
364416 /// <summary>
365- /// Build a list of permission metadata info
417+ /// Build a list of permission metadata info for the current user's role
366418 /// </summary>
367419 /// <param name="entity">The entity object</param>
368- /// <returns>A list of permissions available to the entity</returns>
369- private static string [ ] BuildPermissionsInfo ( Entity entity )
420+ /// <param name="currentUserRole">The current user's role - if null, returns empty permissions</param>
421+ /// <returns>A list of permissions available to the current user's role for this entity</returns>
422+ private static string [ ] BuildPermissionsInfo ( Entity entity , string ? currentUserRole )
370423 {
371- if ( entity . Permissions == null )
424+ if ( entity . Permissions == null || string . IsNullOrWhiteSpace ( currentUserRole ) )
372425 {
373426 return Array . Empty < string > ( ) ;
374427 }
@@ -380,8 +433,15 @@ private static string[] BuildPermissionsInfo(Entity entity)
380433
381434 HashSet < string > permissions = new ( StringComparer . OrdinalIgnoreCase ) ;
382435
436+ // Only include permissions for the current user's role
383437 foreach ( EntityPermission permission in entity . Permissions )
384438 {
439+ // Check if this permission applies to the current user's role
440+ if ( ! string . Equals ( permission . Role , currentUserRole , StringComparison . OrdinalIgnoreCase ) )
441+ {
442+ continue ;
443+ }
444+
385445 foreach ( EntityAction action in permission . Actions )
386446 {
387447 if ( action . Action == EntityActionOperation . All )
0 commit comments