Skip to content

Added missing stored procedure parameters in describe_entities response.#3425

Open
anushakolan wants to merge 1 commit intomainfrom
dev/anushakolan/describe_entities_missing_sp_params
Open

Added missing stored procedure parameters in describe_entities response.#3425
anushakolan wants to merge 1 commit intomainfrom
dev/anushakolan/describe_entities_missing_sp_params

Conversation

@anushakolan
Copy link
Copy Markdown
Contributor

Why make this change?

Closes #3400.

describe_entities could return stored procedure entities with empty or partial parameters metadata when parameters were not fully listed in config. This caused MCP clients/agents to miss required inputs and fail execute_entity calls.

Additional discussion: #3400 issue thread.

What is this change?

  • Updated DescribeEntitiesTool to include stored procedure parameters from runtime DB metadata.
  • Merged DB metadata with config metadata so config values override matching fields (required, default, description).
  • Preserved fallback behavior: if metadata resolution fails, config-defined parameters are still returned.
  • Added focused MCP tests for:
    • metadata-only parameter discovery
    • config-overrides-DB merge behavior

How was this tested?

  • Integration Tests
  • Unit Tests

Ran:

  • dotnet test src/Service.Tests/Azure.DataApiBuilder.Service.Tests.csproj --filter "FullyQualifiedName~DescribeEntitiesStoredProcedureParametersTests"

Sample Request(s)

MCP request example:

  • describe_entities with full metadata:

    • {"name":"describe_entities","arguments":{}}
  • execute_entity using discovered parameters:

    • {"name":"execute_entity","arguments":{"entity":"GetBook","parameters":{"id":1}}}

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates MCP describe_entities to reliably include complete stored procedure parameter metadata by augmenting/merging config-defined parameters with runtime database metadata, and adds targeted tests to validate the behavior.

Changes:

  • Resolve stored procedure parameter metadata from runtime DB metadata when config parameters are missing/partial.
  • Merge config + DB parameter metadata, preferring config values for overlapping fields.
  • Add MCP unit tests covering metadata-only discovery and config-overrides-DB merging.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
src/Azure.DataApiBuilder.Mcp/BuiltInTools/DescribeEntitiesTool.cs Adds metadata resolution for stored procedure entities and merges DB/config parameter metadata in describe_entities output.
src/Service.Tests/Mcp/DescribeEntitiesStoredProcedureParametersTests.cs Adds unit tests validating parameter discovery from DB metadata and config-overrides behavior.

Comment on lines +521 to +553
foreach ((string parameterName, ParameterDefinition parameterDefinition) in storedProcedure.StoredProcedureDefinition.Parameters)
{
configParameters.TryGetValue(parameterName, out ParameterMetadata? configParameter);

Dictionary<string, object?> paramInfo = new()
{
["name"] = configParameter?.Name ?? parameterName,
["required"] = configParameter?.Required ?? parameterDefinition.Required ?? false,
["default"] = configParameter?.Default ?? parameterDefinition.Default,
["description"] = configParameter?.Description ?? parameterDefinition.Description ?? string.Empty
};

result.Add(paramInfo);
}

// Preserve config-only parameters if metadata is not available for a configured name.
foreach (ParameterMetadata configParameter in configParameters.Values)
{
if (!storedProcedure.StoredProcedureDefinition.Parameters.ContainsKey(configParameter.Name))
{
Dictionary<string, object?> paramInfo = new()
{
["name"] = configParameter.Name,
["required"] = configParameter.Required,
["default"] = configParameter.Default,
["description"] = configParameter.Description ?? string.Empty
};

result.Add(paramInfo);
}
}

return result;
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BuildParameterMetadataInfo assumes storedProcedure.StoredProcedureDefinition (and its Parameters) is non-null. DatabaseStoredProcedure.StoredProcedureDefinition is declared nullable-forgiving (null!) and there are code paths/tests that construct DatabaseStoredProcedure without setting it, which would throw here and cause the entity to be omitted from the response. Guard for null (or empty) StoredProcedureDefinition/Parameters and fall back to config parameters when it isn't available.

Suggested change
foreach ((string parameterName, ParameterDefinition parameterDefinition) in storedProcedure.StoredProcedureDefinition.Parameters)
{
configParameters.TryGetValue(parameterName, out ParameterMetadata? configParameter);
Dictionary<string, object?> paramInfo = new()
{
["name"] = configParameter?.Name ?? parameterName,
["required"] = configParameter?.Required ?? parameterDefinition.Required ?? false,
["default"] = configParameter?.Default ?? parameterDefinition.Default,
["description"] = configParameter?.Description ?? parameterDefinition.Description ?? string.Empty
};
result.Add(paramInfo);
}
// Preserve config-only parameters if metadata is not available for a configured name.
foreach (ParameterMetadata configParameter in configParameters.Values)
{
if (!storedProcedure.StoredProcedureDefinition.Parameters.ContainsKey(configParameter.Name))
{
Dictionary<string, object?> paramInfo = new()
{
["name"] = configParameter.Name,
["required"] = configParameter.Required,
["default"] = configParameter.Default,
["description"] = configParameter.Description ?? string.Empty
};
result.Add(paramInfo);
}
}
return result;
IReadOnlyDictionary<string, ParameterDefinition>? storedProcedureParameters =
storedProcedure.StoredProcedureDefinition?.Parameters;
if (storedProcedureParameters is not null && storedProcedureParameters.Count > 0)
{
foreach ((string parameterName, ParameterDefinition parameterDefinition) in storedProcedureParameters)
{
configParameters.TryGetValue(parameterName, out ParameterMetadata? configParameter);
Dictionary<string, object?> paramInfo = new()
{
["name"] = configParameter?.Name ?? parameterName,
["required"] = configParameter?.Required ?? parameterDefinition.Required ?? false,
["default"] = configParameter?.Default ?? parameterDefinition.Default,
["description"] = configParameter?.Description ?? parameterDefinition.Description ?? string.Empty
};
result.Add(paramInfo);
}
// Preserve config-only parameters if metadata is not available for a configured name.
foreach (ParameterMetadata configParameter in configParameters.Values)
{
if (!storedProcedureParameters.ContainsKey(configParameter.Name))
{
Dictionary<string, object?> paramInfo = new()
{
["name"] = configParameter.Name,
["required"] = configParameter.Required,
["default"] = configParameter.Default,
["description"] = configParameter.Description ?? string.Empty
};
result.Add(paramInfo);
}
}
return result;
}

Copilot uses AI. Check for mistakes.
Comment on lines +536 to +541
// Preserve config-only parameters if metadata is not available for a configured name.
foreach (ParameterMetadata configParameter in configParameters.Values)
{
if (!storedProcedure.StoredProcedureDefinition.Parameters.ContainsKey(configParameter.Name))
{
Dictionary<string, object?> paramInfo = new()
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The config-only parameter preservation check uses Parameters.ContainsKey(configParameter.Name), but StoredProcedureDefinition.Parameters is a default Dictionary<string, ParameterDefinition> (case-sensitive). Since configParameters matching earlier is case-insensitive, a casing difference (e.g., "Id" vs "id") can cause duplicates to be added. Use a case-insensitive key comparer for Parameters or perform the contains check with OrdinalIgnoreCase.

Copilot uses AI. Check for mistakes.
Dictionary<string, object?> paramInfo = new()
{
["name"] = configParameter?.Name ?? parameterName,
["required"] = configParameter?.Required ?? parameterDefinition.Required ?? false,
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merging Required prefers configParameter.Required whenever a parameter is present in config, but ParameterMetadata.Required is a non-nullable bool so an omitted "required" in config deserializes to false and will override DB metadata (potentially marking required DB parameters as optional). If the intended behavior is "config overrides only when explicitly set", Required likely needs to become nullable (bool?) or track a user-provided flag and only override when provided.

Suggested change
["required"] = configParameter?.Required ?? parameterDefinition.Required ?? false,
["required"] = parameterDefinition.Required ?? configParameter?.Required ?? false,

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Collaborator

@Aniruddh25 Aniruddh25 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Want to know reasoning behind why the bug happened.

return null;
}

IMetadataProviderFactory? metadataProviderFactory = serviceProvider.GetService<IMetadataProviderFactory>();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where is the metadataProviderFactory used?


if (databaseObject is DatabaseStoredProcedure storedProcedure)
{
foreach ((string parameterName, ParameterDefinition parameterDefinition) in storedProcedure.StoredProcedureDefinition.Parameters)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What was the reason again to have two different data structures? ParameterDefinitionand ParameterMetadata? why couldnt we have used ParameterDefinition for the config parameters?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: describe_entities missing stored procedure parameters

4 participants