Skip to main content

Expose your stored procedures as AI agent tools with DAB 2.0

Data API builder 2.0 (currently in public preview) is a major release focused on MCP and AI integration. Among its headline features is the ability to expose stored procedures as custom MCP tools, making them discoverable and callable by AI agents. No glue code, no middleware, no extra plumbing.

In this post I'll walk through how the feature works, and show a practical example: wiring up a full-text search stored procedure as its own dedicated tool that any MCP client can discover and call by name.

The idea: a dedicated search tool

By default, DAB's SQL MCP Server exposes tables and views through generic DML tools — things like list_books, get_book, and so on. These are useful for straightforward CRUD, but they're not designed for complex operations like full-text search.

With custom-tool: true, you can go further. Set that flag on a stored-procedure entity and DAB dynamically registers the procedure as a named, purpose-built tool in tools/list. The AI agent discovers it by name, reads its description, and calls it directly — no SQL knowledge required.

Naming note: tool names are derived from the entity name, converted to snake_case. An entity called SearchProducts appears in the tool list as search_products. Use the snake_case name when calling the tool.

Step by step: adding a search command

Here's how to add a custom SearchProducts tool backed by a stored procedure that does full-text search across product names and descriptions.

1. Install the 2.0 preview CLI

dotnet tool install microsoft.dataapibuilder  --prerelease

2. Enable MCP in your configuration

dotnet dab configure --runtime.mcp.enabled true

3. Add the stored procedure as a custom tool

dotnet dab add SearchProducts \
  --source dbo.search_products \
  --source.type "stored-procedure" \
  --permissions "anonymous:execute" \
  --mcp.custom-tool true \
  --description "Full-text search across product names and descriptions"

This produces the following in your dab-config.json:

4. Start DAB and verify the tool is registered

dotnet dab start

When an MCP client calls tools/list, it sees your tool alongside the built-in DML tools:

tools/list response

{
  "tools": [
    {
      "name": "search_products",
      "description": "Full-text search across product names and descriptions",
      "inputSchema": {
        "type": "object",
        "properties": {}
      }
    }
  ]
}

Why descriptions matter

The --description flag might look optional, but it's arguably the most important part of the setup. Without a description, an agent sees only the technical name. With a good description, it understands what the tool does, when to use it, and what kind of input it expects.

The inputSchema currently returns empty properties. Agents rely on the tool description and describe_entities to determine the correct parameters, so write descriptions that are specific and actionable.

A few things worth knowing

Only works on stored procedures

The custom-tool flag is only valid on entities with source.type: stored-procedure. Setting it on a table or view entity causes a configuration error at startup.

RBAC is respected

Custom tools honour the same role-based access control as every other DAB entity. If you restrict the entity to authenticated only, the tool won't appear in tools/list for anonymous agents, and any direct call returns a permission error.

You can register multiple tools

There's no limit. Run dab add for each procedure you want to expose. They all show up in the MCP tool list alongside each other.

You can disable without deleting

Set --mcp.custom-tool false via dab update to hide the tool from agents without removing the entity from your config. Re-enable it anytime by flipping the flag back to true.

More information

What's new for version 2.0 - Preview - Data API builder | Microsoft Learn

Data Manipulation Language Tools (DML) - SQL MCP Server | Microsoft Learn

Popular posts from this blog

Podman– Command execution failed with exit code 125

After updating WSL on one of the developer machines, Podman failed to work. When we took a look through Podman Desktop, we noticed that Podman had stopped running and returned the following error message: Error: Command execution failed with exit code 125 Here are the steps we tried to fix the issue: We started by running podman info to get some extra details on what could be wrong: >podman info OS: windows/amd64 provider: wsl version: 5.3.1 Cannot connect to Podman. Please verify your connection to the Linux system using `podman system connection list`, or try `podman machine init` and `podman machine start` to manage a new Linux VM Error: unable to connect to Podman socket: failed to connect: dial tcp 127.0.0.1:2655: connectex: No connection could be made because the target machine actively refused it. That makes sense as the podman VM was not running. Let’s check the VM: >podman machine list NAME         ...

Azure DevOps/ GitHub emoji

I’m really bad at remembering emoji’s. So here is cheat sheet with all emoji’s that can be used in tools that support the github emoji markdown markup: All credits go to rcaviers who created this list.

Cleaner switch expressions with pattern matching in C#

Ever find yourself mapping multiple string values to the same result? Being a C# developer for a long time, I sometimes forget that the C# has evolved so I still dare to chain case labels or reach for a dictionary. Of course with pattern matching this is no longer necessary. With pattern matching, you can express things inline, declaratively, and with zero repetition. A small example I was working on a small script that should invoke different actions depending on the environment. As our developers were using different variations for the same environment e.g.  "tst" alongside "test" , "prd" alongside "prod" .  We asked to streamline this a long time ago, but as these things happen, we still see variations in the wild. This brought me to the following code that is a perfect example for pattern matching: The or keyword here is a logical pattern combinator , not a boolean operator. It matches if either of the specified pattern...