September 2, 2026

Build .NET MCP Tools with Azure Functions and Dataverse

Build discoverable .NET MCP tools on Azure Functions, connect Microsoft Dataverse, test Streamable HTTP, and secure production access with Microsoft Entra ID.

Posted on September 2, 2026  •  10 minutes  • 1977 words

This article is part of a series.

  1. Part 1
    Introduction to SitecoreAI and Microsoft Dataverse Integration Read more →
  2. Part 2
    Connecting SitecoreAI with Microsoft Dataverse: Authentication & Access Configuration Read more →
  3. Part 3
    Azure Functions for Dataverse CRUD in .NET Read more →
  4. Part 4
    Deploy Azure Functions and Connect SitecoreAI Forms to Dataverse Read more →
  5. Part 5
    Build .NET MCP Tools with Azure Functions and Dataverse
Table of contents

📊 Introduction

In the previous article, Deploy Azure Functions and Connect SitecoreAI Forms to Microsoft Dataverse , we used HTTP-triggered .NET isolated Azure Functions as a controlled backend for customer-enquiry data.

This next step keeps the same IDataverseService layer but introduces another entry point: Model Context Protocol (MCP) tools. An MCP client can discover operations such as create_enquiry, inspect their input schemas, and invoke them through a standard protocol instead of learning bespoke REST routes.

That progression also matches the architecture established in the earlier series: business-focused Functions remain in front of Dataverse, while MCP becomes an additional controlled caller rather than replacing the service layer.

🏗️ Architectural Overview: Azure Functions as a Remote MCP Server

Instead of hosting a continuous container application, Azure Functions runs as an event-driven, scale-to-zero remote MCP server:

Azure Functions MCP Server

The MCP classes should remain thin adapters. They own tool names, descriptions, protocol-facing arguments, validation, and result shaping. IDataverseService continues to own Dataverse queries and writes.

This separation allows existing HTTP Functions and new MCP tools to reuse the same tested business logic. Microsoft describes ServiceClient as the primary Dataverse API implementation; it supports asynchronous create, retrieve, update, delete, and query operations and uses MSAL for authentication.

🧩 Azure Functions MCP extension versus the C# MCP SDK

DimensionAzure Functions MCP extensionOfficial C# MCP SDK
HostingAzure Functions programming modelASP.NET Core, hosted services, containers, App Service, ACA, AKS, or another .NET host
Main HTTP packageMicrosoft.Azure .Functions.Worker.Extensions.McpModelContextProtocol.AspNetCore
Tool declaration[McpToolTrigger], [McpToolProperty], POCO binding, or builder configuration[McpServerToolType], [McpServerTool], descriptions, or programmatic registration
Protocol setupExtension exposes the Functions MCP endpointConfigure AddMcpServer(), WithHttpTransport(), and MapMcp()
Scaling and costDepend on the selected Azure Functions planDepend on the selected ASP.NET Core hosting service
AuthenticationFunctions system key by default; built-in MCP authorization is available in previewFull ASP.NET Core authentication and authorization pipeline
Operational controlFunctions conventions and bindingsGreater control over routing, middleware, transport, sessions, and hosting
Best fitExisting Function Apps and bindings-oriented workloadsDedicated MCP services requiring deeper ASP.NET Core control

The official C# SDK separates packages by scenario: ModelContextProtocol for most clients and stdio servers, and ModelContextProtocol.AspNetCore for HTTP servers. Its current HTTP setup uses WithHttpTransport() and MapMcp(), while tools can be discovered from classes marked with [McpServerToolType] and methods marked with [McpServerTool].

Azure Functions MCP extension

Use the Azure Functions extension when your business logic already lives in Functions and the trigger/binding model is a natural fit.

C# MCP SDK

Choose the standalone SDK when the MCP server is a first-class service requiring custom ASP.NET Core middleware or more transport control.

📋 Prerequisites

Microsoft currently documents these minimums for C#:

  • Azure Functions Core Tools 4.0.7030 or later.
  • Microsoft.Azure.Functions.Worker 2.1.0 or later.
  • Microsoft.Azure.Functions.Worker.Sdk 2.0.2 or later.
  • The .NET isolated worker model

Add dependencies without copying old preview wildcards:

dotnet add package Microsoft.Azure.Functions.Worker.Extensions.Mcp
dotnet add package Microsoft.Azure.Functions.Worker.ApplicationInsights
dotnet add package Microsoft.ApplicationInsights.WorkerService
dotnet add package Microsoft.PowerPlatform.Dataverse.Client

Use central package management policy.

Program.cs

using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using SitecoreAI.Dataverse.Functions.Services;

var builder = FunctionsApplication.CreateBuilder(args);

builder.ConfigureFunctionsWebApplication();

builder.Services
    .AddApplicationInsightsTelemetryWorkerService()
    .ConfigureFunctionsApplicationInsights();

builder.Services.AddSingleton<IDataverseService, DataverseService>();

builder.Build().Run();

host.json

Configure the server identification and instructions surfaced to the AI client during tool discovery:

{
  "version": "2.0",
  "extensions": {
    "mcp": {
      "serverName": "DataverseEnquiryMcpServer",
      "serverVersion": "1.0.0",
      "instructions": "Use these tools only for approved customer-enquiry operations.",
      "system": {
        "webhookAuthorizationLevel": "System"
      }
    }
  }
}

MCP Security

System is the default authorization level. When deployed, callers must supply the mcp_extension system key through x-functions-key or the code query parameter. Do not switch to Anonymous merely to resolve a client configuration issue; use it when a documented identity layer is intentionally protecting the endpoint.

🔄 Convert HTTP Functions into MCP tools

Microsoft’s current C# API supports [McpToolTrigger], ToolInvocationContext, and parameter-level [McpToolProperty]. Tool properties are optional by default, so required arguments must be marked explicitly.

Tool 1: create_enquiry

[Function(nameof(CreateEnquiryTool))]
    public async Task<string> CreateEnquiryTool(
        [McpToolTrigger(
            "create_enquiry",
            "Creates a new customer enquiry record in Microsoft Dataverse.")]
        ToolInvocationContext context,
        [McpToolProperty("fullName", "The full name of the customer submitting the enquiry.", isRequired: true)]
        string fullName,
        [McpToolProperty("email", "Customer valid email address.", isRequired: true)]
        string email,
        [McpToolProperty("message", "The enquiry message or requirements body.", isRequired: true)]
        string message,
        [McpToolProperty("source", "The origin of the lead (e.g., 'SitecoreAI Form', 'AI Agent').", isRequired: false)]
        string? source = "MCP Agent",
        CancellationToken cancellationToken = default)
    {
        _logger.LogInformation("MCP Tool 'create_enquiry' invoked for customer: {Email}", email);

        var request = new CreateEnquiryRequest
        {
            Name = fullName,
            Email = email,
            Message = message,
            Source = source ?? "MCP Agent"
        };

        var recordId = await _dataverseService.CreateEnquiryAsync(request, cancellationToken);

        return System.Text.Json.JsonSerializer.Serialize(new
        {
            success = true,
            message = "Enquiry created successfully in Dataverse.",
            recordId = recordId.ToString()
        });
    }

Tool 2: get_recent_enquiries

    [Function(nameof(GetRecentEnquiriesTool))]
        public async Task<string> GetRecentEnquiriesTool(
            [McpToolTrigger(
            "get_recent_enquiries",
            "Retrieves a list of recent customer enquiry records from Dataverse.")]
        ToolInvocationContext context,
            [McpToolProperty("pageSize", "Number of enquiry records to return (1-100). Default is 10.", isRequired: false)]
        int pageSize = 10,
            CancellationToken cancellationToken = default)
        {
            var clampedSize = Math.Clamp(pageSize, 1, 100);
            _logger.LogInformation("MCP Tool 'get_recent_enquiries' invoked with pageSize: {Size}", clampedSize);

            var enquiries = await _dataverseService.GetEnquiriesAsync(clampedSize, cancellationToken);
            return System.Text.Json.JsonSerializer.Serialize(enquiries);
        }

Tool 3: get_enquiry_by_id

 [Function(nameof(GetEnquiryByIdTool))]
    public async Task<string> GetEnquiryByIdTool(
        [McpToolTrigger(
            "get_enquiry_by_id",
            "Fetches a single enquiry record by its unique Dataverse GUID.")]
        ToolInvocationContext context,
        [McpToolProperty("enquiryId", "The GUID of the Dataverse enquiry record.", isRequired: true)]
        string enquiryId,
        CancellationToken cancellationToken = default)
    {
        if (!Guid.TryParse(enquiryId, out var idGuid))
        {
            return System.Text.Json.JsonSerializer.Serialize(new { error = "Invalid GUID format." });
        }

        var enquiry = await _dataverseService.GetEnquiryByIdAsync(idGuid, cancellationToken);
        if (enquiry is null)
        {
            return System.Text.Json.JsonSerializer.Serialize(new { error = $"Record with ID {enquiryId} not found." });
        }

        return System.Text.Json.JsonSerializer.Serialize(enquiry);
    }

PII Data

Do not log customer names, email addresses, enquiry messages, access keys, or tokens.

Azure Functions Custom .NET MCP Tool

The repository includes .NET isolated Azure Functions, McpToolTrigger, Dataverse and Sitecore Edge GraphQL integrations, dependency injection, validation, and telemetry, explore the full application repository on GitHub:

👉 AmitKumar-AK/azure-functions-custom-dotnet-mcp-tool

🌟 Support the Community: Feel free to star the repo, raise issues, submit PRs, or share your feedback to help improve this resource for the Sitecore developer community!


🔍 MCP discovery and execution flow


MCP Tool Discovery & Execution Flow

Copilot first discovers available MCP tools, selects the appropriate tool, and invokes the Azure Function. The function queries Microsoft Dataverse, returns structured data, and Copilot uses it to generate the final response.

Discover available toolsSelect a toolInvoke the Azure FunctionQuery DataverseReturn structured dataGenerate the final response.

📺 Video Walkthrough: End-to-End Changes Details

🧪 Local Testing & Validation

Start the Azure Functions runtime locally

dotnet clean
dotnet build
func start

When started, it will provision the MCP server endpoint at:

MCP server endpoint: http://localhost:<PORT NUMBER>/runtime/webhooks/mcp

Test tool execution using curl

curl.exe -X POST "http://localhost:7232/runtime/webhooks/mcp" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 104,
    "method": "tools/call",
    "params": {
      "name": "create_enquiry",
      "arguments": {
        "fullName": "Priya Sharma",
        "email": "priya.sharma@cloudarchitects.com",
        "message": "Planning a migration to .NET Isolated Azure Functions with Key Vault secret references for Dataverse CRM integration.",
        "source": "Webinar"
      }
    }
  }'

Integrate with Visual Studio Code

In my pervious article, Sitecore Marketer MCP & VS Code Integration , I demonstrated how to integrate the MCP tools with Visual Studio Code to discover and invoke MCP tools. The same approach applies here.

{
  "servers": {
    "local-azure-functions-mcp": {
      "type": "sse",
      "url": "http://localhost:<PORT NUMBER>/runtime/webhooks/mcp"
    }
  }
}

The type: sse setting tells the MCP client to connect using Server-Sent Events. The client uses the configured local Azure Functions URL to discover available tools and exchange MCP messages with the Function App.

☁️ Deploy and connect

Publish your Function App using the Azure CLI or Visual Studio 2022:

# Publish Function App
func azure functionapp publish func-sitecoreai-dataverse-mcp-prod

Configure System Keys for Authentication

When hosted in Azure, the MCP extension secures endpoints using the mcp_extension system key.

  1. In the Azure Portal, open your Function App.

  2. Navigate to App keys > System keys.

  3. Copy the value of the mcp_extension key.

After deployment of Azure Function and getting the funciton key, update the mcp.json file

{
  "servers": {
    "local-azure-functions-mcp": {
      "type": "sse",
      "url": "http://localhost:<PORT NUMBER>/runtime/webhooks/mcp"
    },
    "azure-functions-mcp": {
      "type": "sse",
      "url": "https://<YOUR HOST NAME>.azurewebsites.net/runtime/webhooks/mcp",
      "headers": {
        "x-functions-key": "functions-key"
      }
    }    
  },
  "inputs": []
}
i
For clients using this mcp.json format, configure the local or deployed Azure Functions MCP endpoint with type: "sse". For remote connections, also provide the x-functions-key header, and verify that the target client supports SSE and this authentication method before connecting.

🛡️ Best Practices & Security Summary

  • Serverless Cost Efficiency: Azure Functions scale to zero when agents are not querying tools, eliminating idle compute costs.
  • Separation of Keys: Keep standard webhook keys separate from the mcp_extension system key.
  • Validation at Tool Boundary: Always enforce boundary restrictions (such as Math.Clamp on page sizes and Guid.TryParse validation) inside MCP tools before querying Dataverse.
  • Zero Secret Exposure: Use Azure Key Vault references for client credentials and Dataverse application users.

💬 Frequently Asked Questions (FAQs)


The Azure Functions MCP extension lets you expose Azure Functions as Model Context Protocol tools that MCP-compatible clients can discover and invoke.

For .NET isolated Functions, tools can be defined using attributes such as McpToolTrigger and McpToolProperty, while the Functions runtime handles the MCP protocol endpoint.

For Streamable HTTP, the Azure Functions MCP endpoint is: http://localhost:7071/runtime/webhooks/mcp, For a deployed Function App: https:///runtime/webhooks/mcp

Yes, but I prefer to think of it as adding an MCP adapter rather than rewriting the existing Function.

Keep reusable business and data-access logic-such as IDataverseService-separate from the HTTP or MCP entry point.

This allows REST endpoints and MCP tools to reuse the same tested Dataverse integration.

McpToolTrigger is part of the Azure Functions MCP extension. It identifies a Function as an MCP tool and provides the tool name and description that MCP clients can discover.

It depends on the hosting and architecture requirements.

Use the Azure Functions MCP extension when:

- your backend already runs on Azure Functions;
- you want to reuse the Functions programming model;
- your tools fit naturally into event-driven or serverless workloads;
- you want to reuse existing Function services and dependencies.

Consider the C# MCP SDK when:
- MCP is a dedicated application or service;
- you need deeper ASP.NET Core middleware control;
- you need more control over routing, authentication, sessions, or hosting;
- you are deploying to containers, App Service, Azure Container Apps, AKS, or another .NET hosting 
  environment.

Neither approach is universally better-the right choice depends on the operating model.

Start the Functions runtime: func start. Then connect an MCP-compatible client to: http://localhost:/runtime/webhooks/mcp.

Useful testing options include:

- Visual Studio Code
- MCP Inspector
- Postman
- protocol-level curl requests

For normal development, an MCP-aware client is usually easier than manually maintaining the initialization and session lifecycle with curl.

🏁 Conclusion

Turning an HTTP Function into an MCP tool does not mean rewriting your Dataverse code. Instead, you add a small adapter that describes the business operation, accepts only the required inputs, validates them, calls IDataverseService, and returns a clear, consistent result.

Azure Functions works well when your application already uses the isolated worker and bindings model. Choose the standalone C# MCP SDK when you need more control over ASP.NET Core. In both cases, a production-ready solution also requires secure identity, least-privilege access, network protection, input validation, monitoring, and proper lifecycle management.

🧾Credit/References

View All

This article is part of a series:   SitecoreAI and Microsoft Dataverse Integration
comments powered by Disqus
All posts