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 ;
@@ -57,79 +56,64 @@ public async Task<CallToolResult> ExecuteAsync(
5756 CancellationToken cancellationToken = default )
5857 {
5958 ILogger < CreateRecordTool > ? logger = serviceProvider . GetService < ILogger < CreateRecordTool > > ( ) ;
59+ string toolName = GetToolMetadata ( ) . Name ;
6060 if ( arguments == null )
6161 {
62- return Utils . McpResponseBuilder . BuildErrorResult ( "Invalid Arguments" , "No arguments provided" , logger ) ;
62+ return McpResponseBuilder . BuildErrorResult ( toolName , "InvalidArguments" , "No arguments provided. " , logger ) ;
6363 }
6464
6565 RuntimeConfigProvider runtimeConfigProvider = serviceProvider . GetRequiredService < RuntimeConfigProvider > ( ) ;
6666 if ( ! runtimeConfigProvider . TryGetConfig ( out RuntimeConfig ? runtimeConfig ) )
6767 {
68- return Utils . McpResponseBuilder . BuildErrorResult ( "Invalid Configuration" , "Runtime configuration not available" , logger ) ;
68+ return McpResponseBuilder . BuildErrorResult ( toolName , "InvalidConfiguration" , "Runtime configuration not available. " , logger ) ;
6969 }
7070
7171 if ( runtimeConfig . McpDmlTools ? . CreateRecord != true )
7272 {
73- return Utils . McpResponseBuilder . BuildErrorResult (
74- "ToolDisabled" ,
75- "The create_record tool is disabled in the configuration." ,
76- logger ) ;
73+ return McpErrorHelpers . ToolDisabled ( toolName , logger ) ;
7774 }
7875
7976 try
8077 {
8178 cancellationToken . ThrowIfCancellationRequested ( ) ;
8279 JsonElement root = arguments . RootElement ;
8380
84- if ( ! root . TryGetProperty ( "entity" , out JsonElement entityElement ) ||
85- ! root . TryGetProperty ( "data" , out JsonElement dataElement ) )
81+ if ( ! McpArgumentParser . TryParseEntityAndData ( root , out string entityName , out JsonElement dataElement , out string parseError ) )
8682 {
87- return Utils . McpResponseBuilder . BuildErrorResult ( "InvalidArguments" , "Missing required arguments 'entity' or 'data'" , logger ) ;
83+ return McpResponseBuilder . BuildErrorResult ( toolName , "InvalidArguments" , parseError , logger ) ;
8884 }
8985
90- string entityName = entityElement . GetString ( ) ?? string . Empty ;
91- 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 ) )
9294 {
93- return Utils . McpResponseBuilder . BuildErrorResult ( "InvalidArguments" , "Entity name cannot be empty" , logger ) ;
94- }
95-
96- string dataSourceName ;
97- try
98- {
99- dataSourceName = runtimeConfig . GetDataSourceNameFromEntityName ( entityName ) ;
100- }
101- catch ( Exception )
102- {
103- return Utils . McpResponseBuilder . BuildErrorResult ( "InvalidConfiguration" , $ "Entity '{ entityName } ' not found in configuration", logger ) ;
104- }
105-
106- IMetadataProviderFactory metadataProviderFactory = serviceProvider . GetRequiredService < IMetadataProviderFactory > ( ) ;
107- ISqlMetadataProvider sqlMetadataProvider = metadataProviderFactory . GetMetadataProvider ( dataSourceName ) ;
108-
109- DatabaseObject dbObject ;
110- try
111- {
112- dbObject = sqlMetadataProvider . GetDatabaseObjectByKey ( entityName ) ;
113- }
114- catch ( Exception )
115- {
116- return Utils . McpResponseBuilder . BuildErrorResult ( "InvalidConfiguration" , $ "Database object for entity '{ entityName } ' not found", logger ) ;
95+ return McpResponseBuilder . BuildErrorResult ( toolName , "EntityNotFound" , metadataError , logger ) ;
11796 }
11897
11998 // Create an HTTP context for authorization
12099 IHttpContextAccessor httpContextAccessor = serviceProvider . GetRequiredService < IHttpContextAccessor > ( ) ;
121100 HttpContext httpContext = httpContextAccessor . HttpContext ?? new DefaultHttpContext ( ) ;
122101 IAuthorizationResolver authorizationResolver = serviceProvider . GetRequiredService < IAuthorizationResolver > ( ) ;
123102
124- if ( httpContext is null || ! authorizationResolver . IsValidRoleContext ( httpContext ) )
103+ if ( ! McpAuthorizationHelper . ValidateRoleContext ( httpContext , authorizationResolver , out string roleCtxError ) )
125104 {
126- return Utils . McpResponseBuilder . BuildErrorResult ( " PermissionDenied" , "Permission denied: Unable to resolve a valid role context for update operation." , logger ) ;
105+ return McpErrorHelpers . PermissionDenied ( toolName , entityName , "create" , roleCtxError , logger ) ;
127106 }
128107
129- // Validate that we have at least one role authorized for create
130- 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 ) )
131115 {
132- return Utils . McpResponseBuilder . BuildErrorResult ( "PermissionDenied ", authError , logger ) ;
116+ return McpErrorHelpers . PermissionDenied ( toolName , entityName , "create ", authError , logger ) ;
133117 }
134118
135119 JsonElement insertPayloadRoot = dataElement . Clone ( ) ;
@@ -150,12 +134,13 @@ public async Task<CallToolResult> ExecuteAsync(
150134 }
151135 catch ( Exception ex )
152136 {
153- return Utils . McpResponseBuilder . BuildErrorResult ( "ValidationFailed" , $ "Request validation failed: { ex . Message } ", logger ) ;
137+ return McpResponseBuilder . BuildErrorResult ( toolName , "ValidationFailed" , $ "Request validation failed: { ex . Message } ", logger ) ;
154138 }
155139 }
156140 else
157141 {
158- return Utils . McpResponseBuilder . BuildErrorResult (
142+ return McpResponseBuilder . BuildErrorResult (
143+ toolName ,
159144 "InvalidCreateTarget" ,
160145 "The create_record tool is only available for tables." ,
161146 logger ) ;
@@ -169,7 +154,7 @@ public async Task<CallToolResult> ExecuteAsync(
169154
170155 if ( result is CreatedResult createdResult )
171156 {
172- return Utils . McpResponseBuilder . BuildSuccessResult (
157+ return McpResponseBuilder . BuildSuccessResult (
173158 new Dictionary < string , object ? >
174159 {
175160 [ "entity" ] = entityName ,
@@ -184,14 +169,15 @@ public async Task<CallToolResult> ExecuteAsync(
184169 bool isError = objectResult . StatusCode . HasValue && objectResult . StatusCode . Value >= 400 && objectResult . StatusCode . Value != 403 ;
185170 if ( isError )
186171 {
187- return Utils . McpResponseBuilder . BuildErrorResult (
172+ return McpResponseBuilder . BuildErrorResult (
173+ toolName ,
188174 "CreateFailed" ,
189175 $ "Failed to create record in entity '{ entityName } '. Error: { JsonSerializer . Serialize ( objectResult . Value ) } ",
190176 logger ) ;
191177 }
192178 else
193179 {
194- return Utils . McpResponseBuilder . BuildSuccessResult (
180+ return McpResponseBuilder . BuildSuccessResult (
195181 new Dictionary < string , object ? >
196182 {
197183 [ "entity" ] = entityName ,
@@ -206,14 +192,15 @@ public async Task<CallToolResult> ExecuteAsync(
206192 {
207193 if ( result is null )
208194 {
209- return Utils . McpResponseBuilder . BuildErrorResult (
195+ return McpResponseBuilder . BuildErrorResult (
196+ toolName ,
210197 "UnexpectedError" ,
211198 $ "Mutation engine returned null result for entity '{ entityName } '",
212199 logger ) ;
213200 }
214201 else
215202 {
216- return Utils . McpResponseBuilder . BuildSuccessResult (
203+ return McpResponseBuilder . BuildSuccessResult (
217204 new Dictionary < string , object ? >
218205 {
219206 [ "entity" ] = entityName ,
@@ -226,50 +213,8 @@ public async Task<CallToolResult> ExecuteAsync(
226213 }
227214 catch ( Exception ex )
228215 {
229- return Utils . McpResponseBuilder . BuildErrorResult ( "Error" , $ "Error: { ex . Message } ", logger ) ;
216+ return McpResponseBuilder . BuildErrorResult ( toolName , "Error" , $ "Error: { ex . Message } ", logger ) ;
230217 }
231218 }
232-
233- private static bool TryResolveAuthorizedRole (
234- HttpContext httpContext ,
235- IAuthorizationResolver authorizationResolver ,
236- string entityName ,
237- out string error )
238- {
239- error = string . Empty ;
240-
241- string roleHeader = httpContext . Request . Headers [ AuthorizationResolver . CLIENT_ROLE_HEADER ] . ToString ( ) ;
242-
243- if ( string . IsNullOrWhiteSpace ( roleHeader ) )
244- {
245- error = "Client role header is missing or empty." ;
246- return false ;
247- }
248-
249- string [ ] roles = roleHeader
250- . Split ( ',' , StringSplitOptions . RemoveEmptyEntries | StringSplitOptions . TrimEntries )
251- . Distinct ( StringComparer . OrdinalIgnoreCase )
252- . ToArray ( ) ;
253-
254- if ( roles . Length == 0 )
255- {
256- error = "Client role header is missing or empty." ;
257- return false ;
258- }
259-
260- foreach ( string role in roles )
261- {
262- bool allowed = authorizationResolver . AreRoleAndOperationDefinedForEntity (
263- entityName , role , EntityActionOperation . Create ) ;
264-
265- if ( allowed )
266- {
267- return true ;
268- }
269- }
270-
271- error = "You do not have permission to create records for this entity." ;
272- return false ;
273- }
274219 }
275220}
0 commit comments