DEV Community

Cover image for Building a Foundry Agent That Queries Fabric Data, On Behalf of the User
Jubin Soni
Jubin Soni Subscriber

Posted on

Building a Foundry Agent That Queries Fabric Data, On Behalf of the User

A regional sales manager and a sales rep both ask the same Foundry agent the exact same question: "show me revenue by region." The manager sees every region. The rep sees only their own. Same agent, same prompt, same tool call under the hood, two completely different answers, and nobody wrote a single if user.role == "manager" line to make that happen.

That's the pitch for wiring a Microsoft Fabric data agent into Microsoft Foundry as a tool. The integration isn't about giving your agent a new data source so much as it's about giving your agent a data source that already knows who's asking. Fabric has spent years building row-level security (RLS) and column-level security (CLS) into Power BI semantic models. Foundry has spent the last year building agents that can call tools mid-conversation. The thing connecting them is On-Behalf-Of (OBO) identity passthrough, and once you've set it up correctly, you stop worrying about data leakage through your agent because the enforcement never left Fabric in the first place.

This is a hands-on build guide. We'll publish a Fabric data agent, connect it to a Foundry project, attach it as a tool using the fabric_dataagent_preview tool type, and run it as two different signed-in users to prove the RLS boundary actually holds.

The mental model

Before touching code, it helps to separate two things people conflate: the Fabric data agent and the Foundry tool that calls it.

The Fabric data agent is a Fabric-native object. You build it inside a Fabric workspace, you point it at one or more data sources (a warehouse, a lakehouse, a KQL database, or a Power BI semantic model), and you publish it so it has a stable endpoint. It already knows how to turn natural language into queries against those sources, and it already respects whatever RLS or CLS rules are defined on the underlying semantic model. This part exists independently of Foundry. You could use it from the Fabric chat UI tomorrow and never touch an agent framework.

The Foundry tool is a thin, typed wrapper that lets a Foundry agent call that published Fabric data agent mid-conversation. Foundry doesn't re-implement any of the security logic. It authenticates as the person using the agent, hands that identity to Fabric, and lets Fabric do what it already does: answer the question using only the rows and columns that identity is allowed to see.

That handoff is the entire value proposition. If you've ever built a RAG pipeline where the retrieval step quietly ignored document-level permissions, you already know why this matters.

Prerequisites

A few things need to be true before any of the code below will work:

  • A Fabric workspace on paid F2 or higher capacity, or Power BI Premium P1 or higher with Fabric enabled
  • A published Fabric data agent, with at least one data source it has read access to
  • The Fabric data agent and your Foundry project in the same tenant, signed in with the same account
  • Fabric data agent and underlying data sources on capacities in the same region
  • At minimum the Foundry User (or AI Developer) RBAC role on the Foundry project for both you and anyone who will use the agent
  • User identity authentication only. Service principal authentication is explicitly not supported for the Fabric data agent tool. If your agent normally runs headless under a managed identity, this is the one tool call where that pattern breaks, and you need a real signed-in user in the loop.

That last point trips people up the most, so it's worth repeating before you build anything: this tool is designed for interactive, user-facing agents. It is not a background job tool.

Step 1: Publish the Fabric data agent

In your Fabric workspace, create a new Data agent item, point it at your semantic model or warehouse, give it instructions on what it's good for, and test it in the Fabric chat pane until it answers correctly. Then publish it.

Once published, open its endpoint URL. It looks like this:

https://fabric.microsoft.com/groups/<workspace_id>/aiskills/<artifact_id>
Enter fullscreen mode Exit fullscreen mode

Copy both the workspace_id and the artifact_id out of that URL. You'll need both in the next step, and this is the only place to find them without digging through the REST API.

Step 2: Create the connection in Foundry

Foundry needs a connection object that stores those two IDs so the Fabric tool knows which data agent to call. You can do this in the portal or with the REST API. The portal path is simpler for a first run:

  1. In your Foundry project, go to Build and customize > Agents
  2. Open an existing agent or start a new one
  3. Add a knowledge source, choose Microsoft Fabric
  4. Create a new connection, paste in workspace_id and artifact_id as custom keys, and check Is secret
  5. Name the connection and choose whether it's scoped to this project or shared across the account

If you'd rather script it, the same connection can be created directly against the management plane:

PUT https://management.azure.com/subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.CognitiveServices/accounts/<foundry-account>/projects/<project>/connections/<connection-name>?api-version=2025-04-01-preview
Authorization: Bearer <token>
Content-Type: application/json

{
  "properties": {
    "category": "CustomKeys",
    "authType": "CustomKeys",
    "credentials": {
      "keys": {
        "workspace_id": "<fabric-workspace-id>",
        "artifact_id": "<fabric-data-agent-id>"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Nothing in this connection object carries a user identity. It's purely routing information, pointing Foundry at the right Fabric data agent. The identity gets layered on later, automatically, at call time.

Step 3: Wire the Fabric tool into a Foundry agent

With the connection in place, attaching it to an agent is a few lines in the SDK. The flow looks like every other Foundry tool: resolve the connection ID, pass it into a typed tool definition, and hand that tool to the agent definition.

import os
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient
from azure.ai.projects.models import (
    PromptAgentDefinition,
    MicrosoftFabricPreviewTool,
    FabricDataAgentToolParameters,
    ToolProjectConnection,
)

project = AIProjectClient(
    endpoint=os.environ["PROJECT_ENDPOINT"],
    credential=DefaultAzureCredential(),
)

fabric_connection = project.connections.get(os.environ["FABRIC_CONNECTION_NAME"])

agent = project.agents.create_version(
    agent_name="sales-insights-agent",
    definition=PromptAgentDefinition(
        model="gpt-4.1-mini",
        instructions=(
            "You are a sales insights assistant. Use the Fabric data agent tool "
            "to answer questions about revenue, pipeline, and regional performance. "
            "Only answer from data the tool returns, never guess numbers."
        ),
        tools=[
            MicrosoftFabricPreviewTool(
                fabric_dataagent_preview=FabricDataAgentToolParameters(
                    project_connections=[
                        ToolProjectConnection(project_connection_id=fabric_connection.id)
                    ]
                )
            )
        ],
        tool_choice="required",
    ),
)
print(f"Created agent version: {agent.name}/{agent.version}")
Enter fullscreen mode Exit fullscreen mode

Setting tool_choice="required" is a small thing but worth doing deliberately. Without it, a sufficiently confident model will sometimes answer a numeric question from its own training data instead of calling the tool, which defeats the entire point of grounding the answer in live, permission-scoped Fabric data.

Step 4: Run it, and run it as two different users

This is the step that actually proves the OBO flow works, and it's also the step people skip because it's more fun to write code than to go find a second test account.

Calling the agent uses the Responses API, same as any other Foundry agent:

client = project.get_openai_client()

response = client.responses.create(
    extra_body={"agent": {"name": agent.name, "type": "agent_reference"}},
    input="What was our revenue by region last quarter?",
)
print(response.output_text)
Enter fullscreen mode Exit fullscreen mode

Run that once, authenticated as a user who has full access to every region in the underlying semantic model. You'll get a complete breakdown. Now run the exact same script, same agent, same question, authenticated as a user who's scoped by RLS to a single region. The tool call looks identical on the Foundry side. The answer that comes back only has that user's region in it.

Nothing in the agent's instructions, the tool definition, or the prompt mentions roles or regions. The restriction isn't implemented in your code at all. It's enforced by Fabric, using whatever RLS or CLS rules already exist on the semantic model, the same rules that would apply if that user opened the report directly in Power BI.

Here's the shape of that single call, end to end:

A question, answered with the asking user's own Fabric permissions

Where this fits in the bigger picture

Zoom out past a single request and the interesting part becomes what stays constant. The Foundry agent is the same agent, with the same tool definition, regardless of who's using it. What changes per request is a token exchange that happens automatically, between Foundry and the Entra app registered on the connection, before the call ever reaches Fabric.

Two users, same agent, two different answers

That's the architectural payoff. You build the agent once. You don't build a separate agent per role, and you don't maintain a permissions table inside your application that has to stay in sync with whatever's configured in Power BI. The permissions model lives in exactly one place, which is also the place your data team already manages it.

Production considerations

A handful of things are worth knowing before this goes past a demo.

One Fabric data agent per Foundry agent, for now. The current tool only supports attaching a single Fabric data agent as a knowledge source. If you need to ground against multiple Fabric data agents, you're looking at multiple Foundry agents, or an orchestration layer in front of them, not one agent with several Fabric tools attached.

Service principals are a hard no. If any part of your pipeline runs unattended, under a managed identity, with no real user in the loop, it cannot use this tool. Build your unattended paths against the underlying data sources directly, and reserve the Fabric data agent tool for interactive, user-present sessions.

Region and tenant boundaries are enforced, not advisory. The Fabric data agent, its underlying data sources, and the Foundry project all need to line up on tenant and region. If you're running a multi-region deployment, plan your Fabric capacity placement before you plan your agent architecture, not after.

Compliance boundary shift. Microsoft is explicit that once a Fabric data agent is consumed through Foundry, responses may leave Fabric's compliance boundary and get processed under Foundry's own terms. If your data has specific residency or compliance requirements, read that section of the docs closely before you wire this up for production traffic.

Test with real RLS, not just admin accounts. It's tempting to build and demo everything under your own developer account, which usually has broad access. The entire value of this integration is invisible until you test with a properly restricted account. Budget time for that before you call the integration done.

Where this leaves you

The code to attach a Fabric data agent to a Foundry agent is genuinely small, a tool definition, a connection ID, a few lines. The part worth taking seriously is everything that small piece of code is standing on: years of RLS and CLS enforcement inside Fabric, an identity passthrough model that means your agent never has to see or store a permissions table, and a guarantee that whoever's asking the question only ever sees what they were already allowed to see.

If you're building an internal agent on top of data your organization already governs through Power BI or Fabric, this is very likely less work than building and maintaining your own access control layer on top of a generic retrieval tool. Publish the data agent, wire up the connection, and let Fabric keep doing the job it was already doing.

References

Top comments (0)