Skip to content

MCP: Validate tool name uniqueness across BuiltIn and Custom tools#3110

Open
Copilot wants to merge 4 commits intomainfrom
copilot/handle-duplicate-tool-names
Open

MCP: Validate tool name uniqueness across BuiltIn and Custom tools#3110
Copilot wants to merge 4 commits intomainfrom
copilot/handle-duplicate-tool-names

Conversation

Copy link
Contributor

Copilot AI commented Feb 6, 2026

Why make this change?

MCP tool names must be unique. Without validation, custom tools (generated from stored procedure entities) could conflict with built-in tools (create_record, read_records, etc.) or other custom tools, causing undefined behavior during tool invocation.

What is this change?

Added duplicate detection in McpToolRegistry.RegisterTool() that throws DataApiBuilderException on conflict:

// Check for duplicate tool names
if (_tools.TryGetValue(metadata.Name, out IMcpTool? existingTool))
{
    string existingToolType = existingTool.ToolType == ToolType.BuiltIn ? "built-in" : "custom";
    string newToolType = tool.ToolType == ToolType.BuiltIn ? "built-in" : "custom";
    
    throw new DataApiBuilderException(
        message: $"Duplicate MCP tool name '{metadata.Name}' detected. " +
                 $"A {existingToolType} tool with this name is already registered. " +
                 $"Cannot register {newToolType} tool with the same name. " +
                 $"Tool names must be unique across all tool types.",
        statusCode: HttpStatusCode.ServiceUnavailable,
        subStatusCode: DataApiBuilderException.SubStatusCodes.ErrorInInitialization);
}

Files Modified:

  • McpToolRegistry.cs: Added validation in RegisterTool()

Files Added:

  • McpToolRegistryTests.cs: 12 test cases covering duplicate detection across tool types, case sensitivity, and error messaging

Validation occurs during McpToolRegistryInitializer.StartAsync() ensuring fail-fast at service startup.

How was this tested?

  • Unit Tests

Sample Request(s)

N/A - Internal validation, no user-facing API changes

Original prompt

This section details on the original issue you should resolve

<issue_title>MCP-CustomTool: Handle duplicate tool names, if any</issue_title>
<issue_description>MCP tool names should be unique. Custom tools when configured and exposed should not be duplicate and conflict with existing tool names.

  • MCP tools should not be duplicate
  • Tools of both types- BuiltInTool and DynamicCustomTool should be unique
  • This should be detected and validated during tools registration
  • Appropriate error message should be provided to user if that happens so that user can fix it
  • Add supporting tests with good code coverage and different scenarios and edge cases</issue_description>

<agent_instructions>- Refer to the description and comments

  • Implement the changes and make sure existing functionality is not broken
  • Create a Pull Request</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@JerryNixon Remember tool names are based on Entity aliases which are already required to be unique.

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 3 commits February 6, 2026 06:36
Co-authored-by: souvikghosh04 <210500244+souvikghosh04@users.noreply.github.com>
Co-authored-by: souvikghosh04 <210500244+souvikghosh04@users.noreply.github.com>
…st comments

Co-authored-by: souvikghosh04 <210500244+souvikghosh04@users.noreply.github.com>
Copilot AI changed the title [WIP] Handle duplicate tool names for MCP tools MCP: Validate tool name uniqueness across BuiltIn and Custom tools Feb 6, 2026
Copilot AI requested a review from souvikghosh04 February 6, 2026 06:44
@souvikghosh04 souvikghosh04 added this to the Feb 2026 milestone Feb 6, 2026
@souvikghosh04 souvikghosh04 added mssql an issue thats specific to mssql mcp-server labels Feb 6, 2026
@souvikghosh04 souvikghosh04 marked this pull request as ready for review February 6, 2026 07:03
Copilot AI review requested due to automatic review settings February 6, 2026 07:03
Copy link
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

This PR adds validation to ensure MCP tool names are unique across both built-in and custom tool types. Without this validation, custom tools (generated from stored procedure entities) could conflict with built-in tools like create_record or read_records, causing undefined behavior during tool invocation.

Changes:

  • Added duplicate tool name detection in McpToolRegistry.RegisterTool() that throws DataApiBuilderException when a conflict is detected
  • Implemented comprehensive test suite with 12 test cases covering various duplicate scenarios, case sensitivity, and error messaging

Reviewed changes

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

File Description
src/Azure.DataApiBuilder.Mcp/Core/McpToolRegistry.cs Added validation logic to detect and reject duplicate tool names across BuiltIn and Custom tool types
src/Service.Tests/Mcp/McpToolRegistryTests.cs New test file with comprehensive coverage of duplicate detection scenarios, case sensitivity, and edge cases

@souvikghosh04 souvikghosh04 moved this from Todo to Review In Progress in Data API builder Feb 6, 2026
}

/// <summary>
/// Test that registering two built-in tools with the same name throws an exception.
Copy link
Contributor

Choose a reason for hiding this comment

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

You have four near-identical “duplicate name throws” tests. Use data rows to compress them while still asserting nuanced messages.

    `[DataTestMethod]
    // Duplicate BuiltIn vs BuiltIn
    [DataRow("duplicate_tool", ToolType.BuiltIn, ToolType.BuiltIn, "built-in tool registered", "register built-in tool with same name")]
    // Duplicate Custom vs Custom
    [DataRow("my_custom_tool", ToolType.Custom, ToolType.Custom, "custom tool registered", "Cannot register custom tool with the same name")]
    // Custom conflicts with BuiltIn
    [DataRow("create_record", ToolType.BuiltIn, ToolType.Custom, "built-in tool registered", "Cannot register custom tool with the same name")]
    // BuiltIn conflicts with Custom
    [DataRow("my_stored_proc", ToolType.Custom, ToolType.BuiltIn, "custom tool registered", "register built-in tool with same name")]`

// Check for duplicate tool names
if (_tools.TryGetValue(metadata.Name, out IMcpTool? existingTool))
{
string existingToolType = existingTool.ToolType == ToolType.BuiltIn ? "built-in" : "custom";
Copy link
Contributor

Choose a reason for hiding this comment

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

How is case sensitivity treated here. Are 2 tools with same names but different cases, treated as same tools (throw error) or different tools (succeed)?

If case sensitivity is allowed, in that case we will have the DML tool create_record and Create_record, which is not right.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Good point, the dictionary _tools should use a case insensitive comparer for string keys.

// Check for duplicate tool names
if (_tools.TryGetValue(metadata.Name, out IMcpTool? existingTool))
{
string existingToolType = existingTool.ToolType == ToolType.BuiltIn ? "built-in" : "custom";
Copy link
Contributor

Choose a reason for hiding this comment

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

Are empty tool names allowed? This could be dangerours, one of the test below says empty tool names are accepted, if that is the case, then this should be discussed first that if we want to accept empty tools names.

throw new NotImplementedException();
}
}

Copy link
Contributor

Choose a reason for hiding this comment

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

Add tests for leading/trailing whitespace and invalid characters
This prevents subtle duplication like "tool", " tool", "tool " passing as unique.

If you want to normalize names (e.g., Trim()), then add a test that "tool" and " tool " collide, and enforce trimming in the registry.

Assert.IsTrue(exception.Message.Contains("Duplicate MCP tool name 'my_custom_tool' detected"));
Assert.IsTrue(exception.Message.Contains("custom tool with this name is already registered"));
Assert.IsTrue(exception.Message.Contains("Cannot register custom tool with the same name"));
Assert.AreEqual(DataApiBuilderException.SubStatusCodes.ErrorInInitialization, exception.SubStatusCode);
Copy link
Contributor

Choose a reason for hiding this comment

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

Right now, you assert only SubStatusCode. If the production exception sets an HTTP status, assert that too.

@anushakolan
Copy link
Contributor

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

@anushakolan anushakolan self-assigned this Feb 6, 2026

/// <summary>
/// Test that tool name comparison is case-sensitive.
/// Tools with different casing should be allowed.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Should they really be allowed?

IMcpTool tool2 = new MockMcpTool("", ToolType.Custom);

// Act - Register first tool with empty name
registry.RegisterTool(tool1);
Copy link
Collaborator

Choose a reason for hiding this comment

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

what is the use of an empty tool name?

/// but this ensures the tool registry properly detects any duplicates that might occur.
/// </summary>
[TestMethod]
public void RegisterTool_WithConflictingSnakeCaseNames_DetectsDuplicates()
Copy link
Collaborator

Choose a reason for hiding this comment

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

how is this test different than the previous ones which detected duplicate?

registry.RegisterTool(tool);

// Verify tool was registered
bool found = registry.TryGetTool("unique_tool", out IMcpTool? retrievedTool);
Copy link
Collaborator

@Aniruddh25 Aniruddh25 Feb 6, 2026

Choose a reason for hiding this comment

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

no need for this single unique tool test, other tests cover this scenario.

{
// Arrange
McpToolRegistry registry = new();
IMcpTool tool1 = new MockMcpTool("tool_one", ToolType.BuiltIn);
Copy link
Collaborator

Choose a reason for hiding this comment

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

The only way built in/ custom tools can get duplicated is when a stored proc entity is named as create-record or read-records since those are our built in tool names. We should just add tests for that scenario. other cases of 2 multiple same built in tools / multiple same custom tools are not possible since entity names have to be unique by definition.

We should avoid exploding our test suite to speed up checkin time.

Copy link
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.

Reducing the test matrix to what is really possible.

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

Labels

mcp-server mssql an issue thats specific to mssql

Projects

Status: Review In Progress

Development

Successfully merging this pull request may close these issues.

MCP-CustomTool: Handle duplicate tool names, if any

4 participants