# I created a Fabric MCP endpoint with Fabric REST API, Graph API, Azure Functions and Microsoft Agent Framework

**Disclaimer** : If you think that I used a coding assistant to develop this MCP endpoint, then sorry I have to disappoint you :) because the core of this approach is ingrained into the application logic that I developed in Jan-2025. Check out the date of that [article](https://www.azureguru.net/retrieve-and-export-user-access-details-for-various-objects-in-microsoft-fabric) .

That implementation was limited in a sense that the entire operation was done through a console application. In this article I tried to take that logic a step ahead by integrating it with MAF and Azure Functions and expose it through MCP.

### Introduction

Recently, [**Fabric Core MCP Server**](https://learn.microsoft.com/en-us/rest/api/fabric/articles/mcp-servers/core-remote/overview-core-mcp-server) was released and is currently under preview. Its integration with other applications is quite seamless and it exposes a lot of useful tools that can significantly simplify and enhance external tool interactions with Microsoft Fabric.

The screenshot below lists the complete set of tools available through the Fabric MCP server.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/307cb3b9-932c-45c8-87e4-2aa026d6180b.png align="center")

I published a detailed article a couple of days ago explaining on how to integrate your external client application with Fabric Core MCP Server. You can go through that article [here](https://www.azureguru.net/integrate-microsoft-agent-framework-with-microsoft-fabric-core-mcp-server) .

Before we get started, following is a list of key tools and technologies used in the implementation of this MCP tool.

*   [**Fabric REST API's**](https://learn.microsoft.com/en-us/rest/api/fabric/articles/)
    
*   [**Microsoft Graph API**](https://learn.microsoft.com/en-us/graph/use-the-api)
    
*   [**Microsoft Graph Service Client**](https://learn.microsoft.com/en-us/entra/msidweb/call-downstream-apis/graph-service-client)
    
*   [**MCP for Azure Functions**](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-mcp?pivots=programming-language-csharp)
    
*   [**MCP C# SDK**](https://csharp.sdk.modelcontextprotocol.io/v2/)
    
*   [**Microsoft Agent Framework**](https://learn.microsoft.com/en-us/agent-framework/overview/)
    

It would be helpful if you have some understanding of the above tools and technologies. Of the above list, Microsoft Agent Framework (MAF) is the least used as its implementation is limited for setting up the instructions, tool bindings and passing the user prompts to the MCP Server (Azure Function).

### What's the need for this MCP tool ?

Imagine an employee leaving an organization and you wanna know which Fabric items does the user has access to. Wouldn't it be great if you could just send a prompt like

```csharp
Give me list of items user ABC has access to.
Or
Give me list of items user ABC has access to, which are of type Reports
```

I couldn't find a tool function in the [**Fabric Core MCP Server**](https://learn.microsoft.com/en-us/rest/api/fabric/articles/mcp-servers/core-remote/overview-core-mcp-server) that would return item access of an user.

This is exactly why this MCP is designed for. As mentioned at the very start, I created a similar utility about an year and half ago and I blogged it [here](https://www.azureguru.net/retrieve-and-export-user-access-details-for-various-objects-in-microsoft-fabric).

In that implementation I extensively used the [**Users - List Access Entities**](https://learn.microsoft.com/en-us/rest/api/fabric/admin/users/list-access-entities?tabs=HTTP) Fabric REST API's in a combination with [**Microsoft Graph API's**](https://learn.microsoft.com/en-us/graph/use-the-api)**.**

If you want to know more about Microsoft Graph API's, I have a detailed [article](https://www.azureguru.net/microsoft-graph-api) on that topic as well.

### Delegated Permissions and Scopes

We need to assign the following Delegated permissions to the service principal we will be using.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/9c018fb3-9ba1-4237-84fc-799cd258e1f0.png align="center")

We will require two scopes. One for Graph API and second for Fabric REST API's

*   **https://graph.microsoft.com/.default**
    
*   **https://api.fabric.microsoft.com/.default**
    

So the authentication header will required two access tokens. One for Microsoft Graph and second for Microsoft Fabric.

Thank fully MCP SDK supports passing multiple tokens through a type Dictionary.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/11698060-7419-404f-8f2a-2c2593b7ca50.png align="center")

### TokenCredentials

**GraphServiceClient** authentication requires [TokenCredentials](https://learn.microsoft.com/en-us/dotnet/api/azure.core.tokencredential?view=azure-dotnet) for authentication, so just using the access token will not be useful. For that we will have to convert bearer token to TokenCredentials.

I have an detailed [article](https://www.azureguru.net/customize-clientsecretcredential-class-for-onelake-authentication-in-microsoft-fabric) on how that can be implemented.

The example in the above article is specific for **OneLake authentication** but can be used for other implementations as well. OneLake authentication also requires the bearer access token to be converted into a TokenCredential before it can be used with the Azure SDK.

Along with that, we will use [PublicApplicationBuilder](https://learn.microsoft.com/en-us/dotnet/api/microsoft.identity.client.publicclientapplicationbuilder?view=msal-dotnet-latest) from the [MSAL](https://learn.microsoft.com/en-us/entra/identity-platform/msal-overview)(Microsoft Authentication Library) to create the bearer access token so that we don't have maintain client secret of the underlying service principal.

### Set Up

We will create a Azure Function for MCP endpoints and a console application as the client. The client will send prompts to the MCP Azure functions through an **AIAgent** . The agent tools are exposed through the MCP C# SDK.

**Lets first set up the Azure Function**

The Azure function type is MCP Tool trigger as our Azure Function execution should be tool driven instead of prompt driven.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/03cc1016-ab7c-45e7-a2fb-e5cba20d893d.png align="center")

Add the following references to the Azure Function project.

```csharp
dotnet add package Microsoft.Azure.Functions.Worker;
dotnet add package Microsoft.Azure.Functions.Worker.Extensions.Mcp
dotnet add package Microsoft.Extensions.Logging;
dotnet add package Microsoft.Graph;
dotnet add package Newtonsoft.Json.Linq;
```

The Fabric REST API output is in JSON format and is very dynamic.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/8a3eb4bd-75e9-45a8-8104-7649719b5442.png align="center")

So we will use [Json.NET](https://www.newtonsoft.com/json) to traverse the response object.

To get started, declare a bunch of variables and define the constructor of class in the Azure function that is assigned to the **\_logger** variable.

The fabric endpoint used is **https://api.fabric.microsoft.com/v1**

```csharp
 private static Dictionary<string, string> itemlist = new();
 private static string endpoint = "https://api.fabric.microsoft.com/v1";
 private static readonly HttpClient client = new HttpClient();
 private static GraphServiceClient graph_Service_Client;
 private ILogger<Function1> _logger;

 public Function1(ILogger<Function1> logger)
 {
     _logger = logger;
 }
```

**GetAsync >>**

This function returns HTTP response received from the endpoint using an **HttpClient**.

```csharp
  public async static Task<string> GetAsync(string url, string token)
  {
      client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
      HttpResponseMessage response = await client.GetAsync(url);
      try
      {
          response.EnsureSuccessStatusCode();
          return await response.Content.ReadAsStringAsync();
      }
      catch (HttpRequestException httpRequestException)
      {
          return null;
      }

  }
```

**GetUserDetails >>**

This function is used to parse the JSON response and extract the relevant data

```csharp
 [Function(nameof(GetUserDetails))]
 public async Task <IDictionary<string,string>> GetUserDetails
(
   [McpToolTrigger(nameof(GetUserDetails), "Gets the user access details")] ToolInvocationContext context,
   [McpToolProperty(nameof(username), "The name of the user for whom access details are sought")] string ? username) {
   List <string> tokens = new();

   if (context.TryGetHttpTransport(out var authHeaders)) 
   {     tokens.Add(authHeaders.Headers["Authorization_graph"].Replace("Bearer ", ""));
     tokens.Add(authHeaders.Headers["Authorization_fabric"].Replace("Bearer ", ""));
   }

   AccessTokenCredential tokenCredential = new AccessTokenCredential(tokens[0]);
   graph_Service_Client = new GraphServiceClient(tokenCredential);

   Microsoft.Graph.Models.UserCollectionResponse result = await graph_Service_Client.Users.GetAsync((requestConfiguration) => requestConfiguration.QueryParameters.Top = 999);

   IDictionary < string, object > userdetails = new Dictionary < string, object > ();

   int i = 0;

   if (username == "All") 
{
     foreach(var str in result.Value) {
       userdetails.Add(result.Value[i].DisplayName.ToString(), result.Value[i]);
       i++;
     }
   } 
else 
{
     foreach(var str in result.Value.Select(i => i.DisplayName == username)) {
       userdetails.Add(result.Value[i].DisplayName.ToString(), result.Value[i]);
       i++;
     }
   }

   i = 0;
   itemlist.Clear();

   foreach(var user in userdetails) 
 {
     string userId = ((Microsoft.Graph.Models.Entity) userdetails.Where(a => a.Key == user.Key).ToList()[0].Value).Id.ToString();
     string response = await GetAsync(endpoint + "/admin/Users/" + userId + "/access", tokens[1]);

     if (response == null) 
     {
       continue;
     }
     JObject j_response = JObject.Parse(response);
     JArray j_array = (JArray) j_response["accessEntities"];
     foreach(JObject path in j_array) {

       JToken itemid = path["id"];
       JToken item = path["itemAccessDetails"]["type"];
       JToken name = path["displayName"];

       JToken permissions = path["itemAccessDetails"]["permissions"];
       JArray permissions_arr = (JArray) permissions;
       string permission = "";
       foreach(JToken p in permissions_arr) 
       {
         permission = permission + ("/" + p.ToString());
         p.ToString();
       }

       JToken additionalPermissions = path["itemAccessDetails"]["additionalPermissions"];
       if (additionalPermissions != null) 
       {
         JArray permissions_ad_arr = (JArray) additionalPermissions;
         string permission_a = "";
         foreach(JToken p in permissions_ad_arr) {
           permission_a = permission_a + ("/" + p.ToString());
           p.ToString();
         }
         itemlist.Add(i + "`" + item, "Item Type" + "~" + item + "~" + "Item Name" + "~" + name + "Permissions" + "~" + permissions + "~" + "Additional Permissions" + "~" + permission_a);
       } 
     else 
       {
         itemlist.Add(i + "`" + item, "Item Type" + "~" + item + "~" + "Item Name" + "~" + name + "Permissions" + "~" + permissions + "~" + "Additional Permissions" + "~" + "N/A");

       }
       i++;
     }
     return itemlist;
   }
   return null;
 }
```

Lets look at the important aspects of the above function.

We have **McpToolTrigger** of type **GetUserDetails** that accepts username as **McpToolProperty.** It is required for MCP tool to process a prompt, something like the below

```plaintext
Give me list of items user ABC has access to.
```

Then we have the [ToolInvocationContext](https://github.com/Azure/azure-functions-mcp-extension/blob/main/src/Microsoft.Azure.Functions.Worker.Extensions.Mcp/Abstractions/ToolInvocationContext.cs) that exposes the underlying tools, HTTP headers and arguments of the tool call.

> With ToolInvocationContext, it becomes quite easy to read the HTTP headers invoked by the client. Recall that earlier I mentioned that we will require two bearer tokens, one for Fabric REST API and second for Graph API.

```csharp
List <string> tokens = new();

if (context.TryGetHttpTransport(out var authHeaders)) 
{
tokens.Add(authHeaders.Headers["Authorization_graph"].Replace("Bearer ", ""));
  tokens.Add(authHeaders.Headers["Authorization_fabric"].Replace("Bearer ", ""));
}
```

Also earlier I mentioned that Graph API requires bearer token to be converted to **TokenCredentials**. The following line of code does that.

```csharp
AccessTokenCredential tokenCredential = new AccessTokenCredential(tokens[0]);
graph_Service_Client = new GraphServiceClient(tokenCredential);
```

**tokens\[0\]** above is the token for Graph API and **AccessTokenCredential** is the custom class that does the conversion. I will post the code for that class later in the article.

**GraphServiceClient** gets a list of users and add it to **UserCollectionResponse** object of the Graph Model object.

```csharp
Microsoft.Graph.Models.UserCollectionResponse result = await graph_Service_Client.Users.GetAsync((requestConfiguration) => requestConfiguration.QueryParameters.Top = 999);
```

We then add the usernames to a dictionary object called **userdetails** **.**

```csharp
IDictionary<string,object> userdetails = new Dictionary<string, object>();
int i = 0;

if (username == "All")
{
    foreach (var str in result.Value)
    {
        userdetails.Add(result.Value[i].DisplayName.ToString(), result.Value[i]);
        i++;
    }
}
else
{
    foreach (var str in result.Value.Select(i => i.DisplayName == username))
    {
        userdetails.Add(result.Value[i].DisplayName.ToString(), result.Value[i]);
        i++;
    }
}
```

and then filter the dictionary based on filters passed through the prompt.

If the prompt contains the keyword **"All"** we save all the usernames to userdetails dictionary and if not, then just store the filtered username to the userdetails dictionary.

The code below calls the [**Users - List Access Entities**](https://learn.microsoft.com/en-us/rest/api/fabric/admin/users/list-access-entities?tabs=HTTP) API for each user object in the userdetails dictionary and the response is traversed with [JSON.Net](http://Json.Net) library.

```csharp
foreach(var user in userdetails) 
{
    string userId = ((Microsoft.Graph.Models.Entity) userdetails.Where(a => a.Key == user.Key).ToList()[0].Value).Id.ToString();
    string response = await GetAsync(endpoint + "/admin/Users/" + userId + "/access", tokens[1]);
    if (response == null) 
    {
      continue;
    }
    JObject j_response = JObject.Parse(response);
    JArray j_array = (JArray) j_response["accessEntities"];
    foreach(JObject path in j_array) 
   {

      JToken itemid = path["id"];
      JToken item = path["itemAccessDetails"]["type"];
      JToken name = path["displayName"];

      JToken permissions = path["itemAccessDetails"]["permissions"];
      JArray permissions_arr = (JArray) permissions;
      string permission = "";
      foreach(JToken p in permissions_arr) 
      {
        permission = permission + ("/" + p.ToString());
        p.ToString();
      }

      JToken additionalPermissions = path["itemAccessDetails"]["additionalPermissions"];
      if (additionalPermissions != null) {
        JArray permissions_ad_arr = (JArray) additionalPermissions;
        string permission_a = "";
        foreach(JToken p in permissions_ad_arr) 
        {
          permission_a = permission_a + ("/" + p.ToString());
          p.ToString();
        }
        itemlist.Add(i + "`" + item, "Item Type" + "~" + item + "~" + "Item Name" + "~" + name + "Permissions" + "~" + permissions + "~" + "Additional Permissions" + "~" + permission_a);
      } 
     else 
     {
        itemlist.Add(i + "`" + item, "Item Type" + "~" + item + "~" + "Item Name" + "~" + name + "Permissions" + "~" + permissions + "~" + "Additional Permissions" + "~" + "N/A");
      }
      i++;
    }
return itemlist;
```

API call is : **https://api.fabric.microsoft.com/v1/admin/users/{userId}/access** that fetches the userId from the Graph API.

**API Response >>**

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/af0b64b3-e2bb-4156-bbf8-e82f4f83a745.png align="center")

The access details are finally stored in a dictionary called **itemlist** with "~" used as the separator.

**itemlist Dictionary >>**

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/f8a18bd4-77f3-4408-8911-81f20bd4a6bd.png align="center")

the user access details are stored in the dictionary object in the following format which is sent back the agent for the display.

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/0b36283f-758a-49d2-a748-53a96a6a98f5.png align="center")

**GetUserDetailsForItemType** **\>>**

This function is similar to the **GetUserDetails** function with the only difference being that it is used to get access details for a specific item type.

```csharp
[Function(nameof(GetUserDetailsForItemType))]
public async Task <IDictionary <string,string>> GetUserDetailsForItemType
(
  [McpToolTrigger(nameof(GetUserDetailsForItemType), "Gets the user access detail for a given item type")] ToolInvocationContext context,
  [McpToolProperty(nameof(username), "The name of the user for whom access details are sought")] string ? username,
  [McpToolProperty(nameof(itemtype), "The name of the item type for whom access details are sought")] string ? itemtype) {

  List <string> tokens = new();

  if (context.TryGetHttpTransport(out var authHeaders)) 
{
    tokens.Add(authHeaders.Headers["Authorization_graph"].Replace("Bearer ", ""));
    tokens.Add(authHeaders.Headers["Authorization_fabric"].Replace("Bearer ", ""));

  }
  AccessTokenCredential tokenCredential = new AccessTokenCredential(tokens[0]);
  graph_Service_Client = new GraphServiceClient(tokenCredential);

  Microsoft.Graph.Models.UserCollectionResponse result =
  await graph_Service_Client.Users.GetAsync((requestConfiguration) => requestConfiguration.QueryParameters.Top = 999);

  IDictionary <string,object> userdetails = new Dictionary< string,object> ();
  int i = 0;
  if (username == "All") 
   {
    foreach(var str in result.Value) 
   {
      userdetails.Add(result.Value[i].DisplayName.ToString(), result.Value[i]);
      i++;
    }
  } 
  else 
   {
    foreach(var str in result.Value.Select(i => i.DisplayName == username)) 
    {
      userdetails.Add(result.Value[i].DisplayName.ToString(), result.Value[i]);
      i++;
    }
  }
  itemlist.Clear();

  foreach(var user in userdetails) 
  {
    string userId = ((Microsoft.Graph.Models.Entity) userdetails.Where(a => a.Key == user.Key).ToList()[0].Value).Id.ToString();
    string response = await GetAsync(endpoint + "/admin/Users/" + userId + "/access?type=" + itemtype, tokens[1]);

    if (response == null) 
    {
      continue;
    }

    JObject j_response = JObject.Parse(response);

    JArray j_array = (JArray) j_response["accessEntities"];
    foreach(JObject path in j_array) {

      JToken itemid = path["id"];
      JToken item = path["itemAccessDetails"]["type"];
      JToken name = path["displayName"];

      JToken permissions = path["itemAccessDetails"]["permissions"];
      JArray permissions_arr = (JArray) permissions;
      string permission = "";
      foreach(JToken p in permissions_arr) 
      {
        permission = permission + ("/" + p.ToString());
        p.ToString();
      }

      JToken additionalPermissions = path["itemAccessDetails"]["additionalPermissions"];

      if (additionalPermissions.HasValues == true) 
      {
        JArray permissions_ad_arr = (JArray) additionalPermissions;
        string permission_a = "";
        foreach(JToken p in permissions_ad_arr) 
        {
          permission_a = permission_a + ("/" + p.ToString());
          p.ToString();
        }
        itemlist.Add(i + "`" + item, "Item Type" + "~" + item + "~" + "Item Name" + "~" + name + "Permissions" + "~" + permissions + "~" + "Additional Permissions" + "~" + permission_a);
      } 
     else 
      {
        itemlist.Add(i + "`" + item, "Item Type" + "~" + item + "~" + "Item Name" + "~" + name + "Permissions" + "~" + permissions + "~" + "Additional Permissions" + "~" + "N/A");
      }

      i++;

    }
    return itemlist;

  }
  return null;
}
```

Unlike **GetUserDetails** that accepts only username as **McpToolProperty,** the function **GetUserDetailsForItemType** has two **McpToolProperty:** username and itemtype.

This is required for MCP tool to process a prompt something like the below

```plaintext
Get access details for user ABC for item type Notebooks
```

The API call is now made to the following endpoint :

**https://api.fabric.microsoft.com/v1/admin/users/{userId}/access/access?type={itemtype}**

**Client Console Application >>**

This console application is used to call the MCP endpoint. You can use any other client tool like VSCode Copilot or through a Minimal API incase you want to expose the output through API's

```csharp
using Azure;
using Azure.AI.OpenAI;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Identity.Client;
using ModelContextProtocol.Client;

public static class Program 
{
  private static string RedirectURI = "http://localhost";
  private static string clientId = "Service Principal Client Id";
  private static string tenantId = "Tenant Id";
  private static readonly HttpClient client = new HttpClient();
  private static string[] scopes_g = new string[] {
    "https://graph.microsoft.com/.default"};
  private static string[] scopes_f = new string[] {
    "https://api.fabric.microsoft.com/.default"};
  private static string Authority = $"https://login.microsoftonline.com/{tenantId}";
  public static HttpClient Client => client;

  private static async Task Main() 
{

    ServiceCollection servicecollection = new();

    var configuration = new ConfigurationBuilder()
      .SetBasePath(Directory.GetCurrentDirectory())
      .AddJsonFile("appsettings.json", optional: false)
      .Build();

    var credential = new AzureKeyCredential(configuration["AppSettings:ApiKey"]);

    servicecollection.AddKeyedChatClient("ChatClient", 
   (sp => new AzureOpenAIClient(new Uri(configuration["AppSettings:EndPoint"]), credential)      .GetChatClient(configuration["AppSettings:Chat_DeploymentName"])
      .AsIChatClient()));

    servicecollection.AddSingleton <ChatClientAgent> (sp => 
   {
      return new ChatClientAgent(sp.GetKeyedService <IChatClient > ("ChatClient"));
    });

    ServiceProvider serviceprovider = servicecollection.BuildServiceProvider();

    var chatlient = serviceprovider.GetServices < ChatClientAgent > ();
    List < ChatClientAgent > lstchatclient = new(chatlient);

    var options = new ChatClientAgentOptions() {

      ChatOptions = new ChatOptions() {
          Instructions = "You are a Fabric Agent and you execute the appropriate tools. " +
            "You will display the results in table format with columns Item Type, Item Name, Access Details. Please singularize the itemtype if use prompt contains them." +
            "For example, if the prompt contains reports then singularize to report and if Notebooks then to Notebook and so and so forth",
            ToolMode = AutoChatToolMode.Auto,
            Tools = [..await ConnectMCP()]
        },
        Name = "Fabric Agent"
    };

    var agent = lstchatclient[0].ChatClient.AsAIAgent(options);

    AgentResponse agentresponse = await agent.RunAsync("Get access details for user ABC that he has access to");

    Console.Write(agentresponse.Text);

  }

  public async static Task <AuthenticationResult> ReturnAuthenticationResult(string[] scopes) {
    string AccessToken;
    PublicClientApplicationBuilder PublicClientAppBuilder =
      PublicClientApplicationBuilder.Create(clientId)
      .WithAuthority(Authority)
      .WithCacheOptions(CacheOptions.EnableSharedCacheOptions)
      .WithRedirectUri(RedirectURI);

    IPublicClientApplication PublicClientApplication = PublicClientAppBuilder.Build();
    var accounts = await PublicClientApplication.GetAccountsAsync();
    AuthenticationResult result;
    try 
    {
      result = await PublicClientApplication.AcquireTokenSilent(scopes, accounts.First())
        .ExecuteAsync()
        .ConfigureAwait(false);
    } 
  catch 
   {
      result = await PublicClientApplication.AcquireTokenInteractive(scopes)
        .ExecuteAsync()
        .ConfigureAwait(false);
    }
    return result;
  }

  public static async Task <List<McpClientTool>> ConnectMCP() 
{
    AuthenticationResult result_graph = await ReturnAuthenticationResult(scopes_g);
    AuthenticationResult result_fabric = await ReturnAuthenticationResult(scopes_f);

    var httpClient = new HttpClient 
    {
      Timeout = Timeout.InfiniteTimeSpan
    };
    var transport = new ModelContextProtocol.Client.HttpClientTransport(
      new HttpClientTransportOptions {
        Endpoint = new Uri("http://{Azure Function URL }/runtime/webhooks/mcp"),
          Name = "Custom Fabric MCP",
          TransportMode = HttpTransportMode.AutoDetect,
          EnableStandaloneGetStream = false,
          AdditionalHeaders = new Dictionary < string, string > {
            {
              "Authorization_graph",
              $ "Bearer {result_graph.AccessToken}"
            },
            {
              "Authorization_fabric",
              $ "Bearer {result_fabric.AccessToken}"
            }
          }
      },
      httpClient
    );

    var mcpClient = await McpClient.CreateAsync(transport);
    var tools = await mcpClient.ListToolsAsync();
    return tools.ToList();
  }
}
```

The above code is similar to the one used in my earlier [article](https://www.azureguru.net/integrate-microsoft-agent-framework-with-microsoft-fabric-core-mcp-server#code) on Fabric MCP. Only difference being that I am passing two tokens through the header

```csharp
AdditionalHeaders = new Dictionary <string,string> 
         {
            {
              "Authorization_graph",
              $ "Bearer {result_graph.AccessToken}"
            },
            {
              "Authorization_fabric",
              $ "Bearer {result_fabric.AccessToken}"
            }
          }
```

which is used to call [**GraphServiceClient**](https://learn.microsoft.com/en-us/entra/msidweb/call-downstream-apis/graph-service-client) & [**Users - List Access Entities**](https://learn.microsoft.com/en-us/rest/api/fabric/admin/users/list-access-entities?tabs=HTTP) API .

Also notice the agent instructions

```csharp
You are a Fabric Agent and you execute the appropriate tools.
You will display the results in table format with columns Item Type, Item Name, Access Details. Please singularize the itemtype if use prompt contains them. For example, if the prompt contains reports then singularize to report and if Notebooks then to Notebook and so and so forth.
```

**AccessTokenCredential.cs >>**

This class converts bearer token generated by **ReturnAuthenticationResult** in the client code above to TokenCredentials for reasons explained earlier

```csharp
using Azure.Core;
using System.IdentityModel.Tokens.Jwt;

namespace AccesTokenCredentials
{
    public class AccessTokenCredential : Azure.Identity.ClientSecretCredential
    {
        public AccessTokenCredential(string accessToken)
        {
            AccessToken = accessToken;
        }

        private string AccessToken;

        public AccessToken FetchAccessToken()
        {
            JwtSecurityToken token = new JwtSecurityToken(AccessToken);
            return new AccessToken(AccessToken, token.ValidTo);
        }

        public override ValueTask<AccessToken> GetTokenAsync(
            TokenRequestContext requestContext,
            CancellationToken cancellationToken
        )
        {
            return new ValueTask<AccessToken>(FetchAccessToken());
        }

        public override AccessToken GetToken(
            TokenRequestContext requestContext,
            CancellationToken cancellationToken
        )
        {
            JwtSecurityToken token = new JwtSecurityToken(AccessToken);
            return new AccessToken(AccessToken, token.ValidTo);
        }
    }
}
```

**Limitations >>**

The output does not lists the workspace under which a particular item exists and also it does not lists access details of a service principal. This can be a major shortcoming but I have a solution for that as well :) which I will post in my upcoming blog.

### Execution

![](https://cdn.hashnode.com/uploads/covers/6693c62c166ee9c594cffda0/46a15883-2758-4689-b851-8345ee229238.gif align="center")

### Conclusion

The intention of this article is not to make a production level agent but to provide a starting point incase you do decide to create a custom agent that interacts with the Fabric services.

The possibilities and quite endless and when coupled with Microsoft Agent Framework(MAF) you can leverage the fantastic features and properties of MAF that could interact with Fabric services and ecosystem.

Thanks for reading !!!
