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

Cache stampede: when our cache turned against us

While investigating some performance issues, we ran into an ASP.NET Core API that cached a fairly expensive aggregation query for 60 seconds. Under normal load, that was fine: one request rebuilds the cache, everyone else reads from it. Under peak load, dozens of requests would arrive in that same expiry window, all see a cache miss, and all fire the same expensive query in parallel. The database didn't like that. That was the moment when our caching layer stopped helping and started hurting. A burst of requests comes in at the same time, all miss the cache, and all go hammer the database or the downstream API at once. That's a cache stampede . The cache was supposed to protect our backend, and for a few hundred milliseconds it did the opposite. Why this happens IMemoryCache.GetOrCreate (and its async sibling) looks like it protects you, but it doesn't add any locking on its own. Look at the naive version: public async Task<Report> GetReportAsync(string key) ...

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.