Skip to content

Conversation

@souvikghosh04
Copy link
Contributor

@souvikghosh04 souvikghosh04 commented Jan 12, 2026

Why make this change?

What is this change?

This pull request introduces a new system for dynamically generating and registering custom MCP tools based on stored procedure entity configurations in the runtime configuration. The main changes are the implementation of the DynamicCustomTool class, a factory to create these tools from configuration, and the necessary service registration logic to ensure these custom tools are available at runtime.

Dynamic custom MCP tool support:

  • Added the DynamicCustomTool class, which implements IMcpTool and provides logic for generating tool metadata, validating configuration, handling authorization, executing the underlying stored procedure, and formatting the response. This enables each stored procedure entity with custom-tool enabled to be exposed as a dedicated MCP tool.
  • Introduced the CustomMcpToolFactory class, which scans the runtime configuration for stored procedure entities marked with custom-tool enabled and creates corresponding DynamicCustomTool instances.

Dependency injection and service registration:

  • Updated the MCP server startup (AddDabMcpServer) to register custom tools generated from configuration by calling a new RegisterCustomTools method after auto-discovering static tools.
  • Modified the RegisterAllMcpTools method to exclude DynamicCustomTool from auto-discovery (since these are created dynamically per configuration) and added the RegisterCustomTools method to register each generated custom tool as a singleton service.

How was this tested?

  • Unit Tests
  • Manual Tests using Insomnia and VS code GHCP chat

Sample Request(s)

  1. List All Tools (also includes custom tool)
{
  "jsonrpc": "2.0",
  "method": "tools/list",
  "params": {},
  "id": 1
}
  1. Get Books (no parameters)
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "get_books"
  },
  "id": 2
}
  1. Get Book by ID
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "get_book",
    "arguments": {
      "id": 1
    }
  },
  "id": 3
}
  1. Insert Book
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "insert_book",
    "arguments": {
      "title": "Test Book from MCP",
      "publisher_id": "1234"
    }
  },
  "id": 4
}
  1. Count Books (no parameters)
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "count_books"
  },
  "id": 5
}

Error Scenarios
6. Missing Required Parameter

{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "get_book"
  },
  "id": 6
}
  1. Non-Existent Tool
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "non_existent_tool"
  },
  "id": 7
}
  1. Invalid Foreign Key
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "insert_book",
    "arguments": {
      "title": "Test Book",
      "publisher_id": "999999"
    }
  },
  "id": 8
}

Edge Cases
9. SQL Injection Attempt

{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "get_book",
    "arguments": {
      "id": "1; DROP TABLE books; --"
    }
  },
  "id": 9
}
  1. Special Characters
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "insert_book",
    "arguments": {
      "title": "Test Book with 'quotes' and \"double quotes\" and <tags>",
      "publisher_id": "1234"
    }
  },
  "id": 10
}
  1. Empty String
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "insert_book",
    "arguments": {
      "title": "",
      "publisher_id": "1234"
    }
  },
  "id": 11
}
  1. Invalid Type (string for int)
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "get_book",
    "arguments": {
      "id": "not_a_number"
    }
  },
  "id": 12
}
  1. Negative ID
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "get_book",
    "arguments": {
      "id": -1
    }
  },
  "id": 13
}
  1. Maximum Integer
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "get_book",
    "arguments": {
      "id": 2147483647
    }
  },
  "id": 14
}
  1. Case Sensitivity (should fail)
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "GET_BOOKS"
  },
  "id": 15
}

- Add EntityMcpOptions class with custom-tool and dml-tools properties
- Add JSON converter supporting boolean and object formats
- Add CLI support for --mcp.dml-tools and --mcp.custom-tool flags
- Add schema validation restricting custom-tool to stored procedures
- Entity.Mcp property is optional (default null) to avoid test cascade

Only 9 files changed in this minimal implementation.
- Update EntityMcpOptions documentation to clarify custom-tool behavior in boolean mode
- Replace if-else with switch-case in converter for better extensibility
- Remove unnecessary null writes in serializer
- Change CustomToolEnabled and DmlToolEnabled from nullable to non-nullable bool
- Fix boolean shorthand deserialization to not mark custom-tool as user-provided
- Add consistent else block in constructor for symmetry

All 530 tests passing. Functionality verified with manual testing.
@souvikghosh04 souvikghosh04 requested a review from Copilot January 12, 2026 11:16
@souvikghosh04
Copy link
Contributor Author

/azp run

@souvikghosh04 souvikghosh04 self-assigned this Jan 12, 2026
@souvikghosh04 souvikghosh04 added mcp-server mssql an issue thats specific to mssql labels Jan 12, 2026
@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

@souvikghosh04 souvikghosh04 added this to the Jan 2026 milestone Jan 12, 2026
@souvikghosh04 souvikghosh04 linked an issue Jan 12, 2026 that may be closed by this pull request
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 introduces dynamic custom MCP tool support, allowing stored procedures configured with "custom-tool" enabled to be automatically exposed as dedicated MCP tools. The implementation adds a factory pattern for generating tools from configuration and integrates them into the service registration pipeline.

Changes:

  • Added DynamicCustomTool class to dynamically generate MCP tools from stored procedure configurations
  • Implemented CustomMcpToolFactory to scan runtime configuration and create custom tools
  • Updated service registration in McpServiceCollectionExtensions to exclude dynamic tools from auto-discovery and register custom tools from configuration

Reviewed changes

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

File Description
src/Azure.DataApiBuilder.Mcp/Core/McpServiceCollectionExtensions.cs Added RegisterCustomTools method and modified auto-discovery to exclude DynamicCustomTool
src/Azure.DataApiBuilder.Mcp/Core/DynamicCustomTool.cs New class implementing IMcpTool for dynamically generated stored procedure tools
src/Azure.DataApiBuilder.Mcp/Core/CustomMcpToolFactory.cs New factory class for creating custom tools from runtime configuration

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@souvikghosh04 souvikghosh04 changed the title Usr/sogh/customtoolpoc 10dec2025 Implementation of Custom Tool in MCP Jan 12, 2026
@souvikghosh04
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

@souvikghosh04
Copy link
Contributor Author

/azp run

@souvikghosh04 souvikghosh04 moved this from Todo to In Progress in Data API builder Jan 13, 2026
@azure-pipelines
Copy link

Azure Pipelines successfully started running 6 pipeline(s).

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: In Progress

Development

Successfully merging this pull request may close these issues.

Implement functionality for Custom Tool in MCP

2 participants