55using Azure . DataApiBuilder . Auth ;
66using Azure . DataApiBuilder . Config . DatabasePrimitives ;
77using Azure . DataApiBuilder . Config . ObjectModel ;
8- using Azure . DataApiBuilder . Core . Authorization ;
98using Azure . DataApiBuilder . Core . Configurations ;
109using Azure . DataApiBuilder . Core . Models ;
1110using Azure . DataApiBuilder . Core . Resolvers ;
1211using Azure . DataApiBuilder . Core . Resolvers . Factories ;
1312using Azure . DataApiBuilder . Core . Services ;
14- using Azure . DataApiBuilder . Core . Services . MetadataProviders ;
1513using Azure . DataApiBuilder . Mcp . Model ;
14+ using Azure . DataApiBuilder . Mcp . Utils ;
1615using Microsoft . AspNetCore . Http ;
1716using Microsoft . AspNetCore . Mvc ;
1817using Microsoft . Extensions . DependencyInjection ;
@@ -60,78 +59,61 @@ public async Task<CallToolResult> ExecuteAsync(
6059 string toolName = GetToolMetadata ( ) . Name ;
6160 if ( arguments == null )
6261 {
63- return Utils . McpResponseBuilder . BuildErrorResult ( toolName , "Invalid Arguments " , "No arguments provided" , logger ) ;
62+ return McpResponseBuilder . BuildErrorResult ( toolName , "InvalidArguments " , "No arguments provided. " , logger ) ;
6463 }
6564
6665 RuntimeConfigProvider runtimeConfigProvider = serviceProvider . GetRequiredService < RuntimeConfigProvider > ( ) ;
6766 if ( ! runtimeConfigProvider . TryGetConfig ( out RuntimeConfig ? runtimeConfig ) )
6867 {
69- return Utils . McpResponseBuilder . BuildErrorResult ( toolName , "Invalid Configuration " , "Runtime configuration not available" , logger ) ;
68+ return McpResponseBuilder . BuildErrorResult ( toolName , "InvalidConfiguration " , "Runtime configuration not available. " , logger ) ;
7069 }
7170
7271 if ( runtimeConfig . McpDmlTools ? . CreateRecord != true )
7372 {
74- return Utils . McpResponseBuilder . BuildErrorResult (
75- toolName ,
76- "ToolDisabled" ,
77- "The create_record tool is disabled in the configuration." ,
78- logger ) ;
73+ return McpErrorHelpers . ToolDisabled ( toolName , logger ) ;
7974 }
8075
8176 try
8277 {
8378 cancellationToken . ThrowIfCancellationRequested ( ) ;
8479 JsonElement root = arguments . RootElement ;
8580
86- if ( ! root . TryGetProperty ( "entity" , out JsonElement entityElement ) ||
87- ! root . TryGetProperty ( "data" , out JsonElement dataElement ) )
81+ if ( ! McpArgumentParser . TryParseEntityAndData ( root , out string entityName , out JsonElement dataElement , out string parseError ) )
8882 {
89- return Utils . McpResponseBuilder . BuildErrorResult ( toolName , "InvalidArguments" , "Missing required arguments 'entity' or 'data'" , logger ) ;
83+ return McpResponseBuilder . BuildErrorResult ( toolName , "InvalidArguments" , parseError , logger ) ;
9084 }
9185
92- string entityName = entityElement . GetString ( ) ?? string . Empty ;
93- if ( string . IsNullOrWhiteSpace ( entityName ) )
86+ if ( ! McpMetadataHelper . TryResolveMetadata (
87+ entityName ,
88+ runtimeConfig ,
89+ serviceProvider ,
90+ out ISqlMetadataProvider sqlMetadataProvider ,
91+ out DatabaseObject dbObject ,
92+ out string dataSourceName ,
93+ out string metadataError ) )
9494 {
95- return Utils . McpResponseBuilder . BuildErrorResult ( toolName , "InvalidArguments" , "Entity name cannot be empty" , logger ) ;
96- }
97-
98- string dataSourceName ;
99- try
100- {
101- dataSourceName = runtimeConfig . GetDataSourceNameFromEntityName ( entityName ) ;
102- }
103- catch ( Exception )
104- {
105- return Utils . McpResponseBuilder . BuildErrorResult ( toolName , "InvalidConfiguration" , $ "Entity '{ entityName } ' not found in configuration", logger ) ;
106- }
107-
108- IMetadataProviderFactory metadataProviderFactory = serviceProvider . GetRequiredService < IMetadataProviderFactory > ( ) ;
109- ISqlMetadataProvider sqlMetadataProvider = metadataProviderFactory . GetMetadataProvider ( dataSourceName ) ;
110-
111- DatabaseObject dbObject ;
112- try
113- {
114- dbObject = sqlMetadataProvider . GetDatabaseObjectByKey ( entityName ) ;
115- }
116- catch ( Exception )
117- {
118- return Utils . McpResponseBuilder . BuildErrorResult ( toolName , "InvalidConfiguration" , $ "Database object for entity '{ entityName } ' not found", logger ) ;
95+ return McpResponseBuilder . BuildErrorResult ( toolName , "EntityNotFound" , metadataError , logger ) ;
11996 }
12097
12198 // Create an HTTP context for authorization
12299 IHttpContextAccessor httpContextAccessor = serviceProvider . GetRequiredService < IHttpContextAccessor > ( ) ;
123100 HttpContext httpContext = httpContextAccessor . HttpContext ?? new DefaultHttpContext ( ) ;
124101 IAuthorizationResolver authorizationResolver = serviceProvider . GetRequiredService < IAuthorizationResolver > ( ) ;
125102
126- if ( httpContext is null || ! authorizationResolver . IsValidRoleContext ( httpContext ) )
103+ if ( ! McpAuthorizationHelper . ValidateRoleContext ( httpContext , authorizationResolver , out string roleCtxError ) )
127104 {
128- return Utils . McpResponseBuilder . BuildErrorResult ( toolName , "PermissionDenied" , "Permission denied: Unable to resolve a valid role context for update operation." , logger ) ;
105+ return McpErrorHelpers . PermissionDenied ( toolName , entityName , "create" , roleCtxError , logger ) ;
129106 }
130107
131- // Validate that we have at least one role authorized for create
132- if ( ! TryResolveAuthorizedRole ( httpContext , authorizationResolver , entityName , out string authError ) )
108+ if ( ! McpAuthorizationHelper . TryResolveAuthorizedRole (
109+ httpContext ,
110+ authorizationResolver ,
111+ entityName ,
112+ EntityActionOperation . Create ,
113+ out string ? effectiveRole ,
114+ out string authError ) )
133115 {
134- return Utils . McpResponseBuilder . BuildErrorResult ( toolName , "PermissionDenied ", authError , logger ) ;
116+ return McpErrorHelpers . PermissionDenied ( toolName , entityName , "create ", authError , logger ) ;
135117 }
136118
137119 JsonElement insertPayloadRoot = dataElement . Clone ( ) ;
@@ -152,12 +134,12 @@ public async Task<CallToolResult> ExecuteAsync(
152134 }
153135 catch ( Exception ex )
154136 {
155- return Utils . McpResponseBuilder . BuildErrorResult ( toolName , "ValidationFailed" , $ "Request validation failed: { ex . Message } ", logger ) ;
137+ return McpResponseBuilder . BuildErrorResult ( toolName , "ValidationFailed" , $ "Request validation failed: { ex . Message } ", logger ) ;
156138 }
157139 }
158140 else
159141 {
160- return Utils . McpResponseBuilder . BuildErrorResult (
142+ return McpResponseBuilder . BuildErrorResult (
161143 toolName ,
162144 "InvalidCreateTarget" ,
163145 "The create_record tool is only available for tables." ,
@@ -172,7 +154,7 @@ public async Task<CallToolResult> ExecuteAsync(
172154
173155 if ( result is CreatedResult createdResult )
174156 {
175- return Utils . McpResponseBuilder . BuildSuccessResult (
157+ return McpResponseBuilder . BuildSuccessResult (
176158 new Dictionary < string , object ? >
177159 {
178160 [ "entity" ] = entityName ,
@@ -187,15 +169,15 @@ public async Task<CallToolResult> ExecuteAsync(
187169 bool isError = objectResult . StatusCode . HasValue && objectResult . StatusCode . Value >= 400 && objectResult . StatusCode . Value != 403 ;
188170 if ( isError )
189171 {
190- return Utils . McpResponseBuilder . BuildErrorResult (
172+ return McpResponseBuilder . BuildErrorResult (
191173 toolName ,
192174 "CreateFailed" ,
193175 $ "Failed to create record in entity '{ entityName } '. Error: { JsonSerializer . Serialize ( objectResult . Value ) } ",
194176 logger ) ;
195177 }
196178 else
197179 {
198- return Utils . McpResponseBuilder . BuildSuccessResult (
180+ return McpResponseBuilder . BuildSuccessResult (
199181 new Dictionary < string , object ? >
200182 {
201183 [ "entity" ] = entityName ,
@@ -210,15 +192,15 @@ public async Task<CallToolResult> ExecuteAsync(
210192 {
211193 if ( result is null )
212194 {
213- return Utils . McpResponseBuilder . BuildErrorResult (
195+ return McpResponseBuilder . BuildErrorResult (
214196 toolName ,
215197 "UnexpectedError" ,
216198 $ "Mutation engine returned null result for entity '{ entityName } '",
217199 logger ) ;
218200 }
219201 else
220202 {
221- return Utils . McpResponseBuilder . BuildSuccessResult (
203+ return McpResponseBuilder . BuildSuccessResult (
222204 new Dictionary < string , object ? >
223205 {
224206 [ "entity" ] = entityName ,
@@ -231,50 +213,8 @@ public async Task<CallToolResult> ExecuteAsync(
231213 }
232214 catch ( Exception ex )
233215 {
234- return Utils . McpResponseBuilder . BuildErrorResult ( toolName , "Error" , $ "Error: { ex . Message } ", logger ) ;
216+ return McpResponseBuilder . BuildErrorResult ( toolName , "Error" , $ "Error: { ex . Message } ", logger ) ;
235217 }
236218 }
237-
238- private static bool TryResolveAuthorizedRole (
239- HttpContext httpContext ,
240- IAuthorizationResolver authorizationResolver ,
241- string entityName ,
242- out string error )
243- {
244- error = string . Empty ;
245-
246- string roleHeader = httpContext . Request . Headers [ AuthorizationResolver . CLIENT_ROLE_HEADER ] . ToString ( ) ;
247-
248- if ( string . IsNullOrWhiteSpace ( roleHeader ) )
249- {
250- error = "Client role header is missing or empty." ;
251- return false ;
252- }
253-
254- string [ ] roles = roleHeader
255- . Split ( ',' , StringSplitOptions . RemoveEmptyEntries | StringSplitOptions . TrimEntries )
256- . Distinct ( StringComparer . OrdinalIgnoreCase )
257- . ToArray ( ) ;
258-
259- if ( roles . Length == 0 )
260- {
261- error = "Client role header is missing or empty." ;
262- return false ;
263- }
264-
265- foreach ( string role in roles )
266- {
267- bool allowed = authorizationResolver . AreRoleAndOperationDefinedForEntity (
268- entityName , role , EntityActionOperation . Create ) ;
269-
270- if ( allowed )
271- {
272- return true ;
273- }
274- }
275-
276- error = "You do not have permission to create records for this entity." ;
277- return false ;
278- }
279219 }
280220}
0 commit comments