Microsoft Agent Framework Agents Created: 10 Jun 2026 Updated: 10 Jun 2026

Governing MCP Tool Calls in .NET with the Agent Governance Toolkit

The Model Context Protocol (MCP) makes it easy to expose tools and resources to AI agents. But once those tools are reachable by an agent, you also need a reliable way to control what gets registered, what gets executed, and what comes back from each tool call.

The Public Preview package Microsoft.AgentGovernance.Extensions.ModelContextProtocol adds that control to the official MCP C# SDK. It exposes a single extension method, WithGovernance(...), on the IMcpServerBuilder pipeline you already use, so the secure path is also the simple path.

Why MCP servers need governance

The flexibility of MCP creates a fresh set of security questions that a plain tool registration cannot answer on its own:

  1. Should every registered tool be callable by every agent?
  2. What if a tool description hides prompt-injection style instructions?
  3. How do you fail closed when a tool definition changes in a risky way?
  4. How do you stop unsafe tool output from flowing straight back into the model?

The package folds these concerns into one call instead of scattering custom filters and ad-hoc validation across your codebase.

What WithGovernance(...) adds

That single call registers startup and runtime controls in one place:

  1. Startup scanning — tool definitions are scanned before they are exposed; unsafe tools fail startup (tool poisoning, typosquatting, hidden instructions, schema abuse, and more).
  2. Policy enforcement — each tool call is evaluated against YAML-backed rules that allow, deny, or rate-limit it.
  3. Identity awareness — an authenticated agent identity is used when present; otherwise a configurable default DID such as did:mcp:anonymous applies.
  4. Response sanitization — text output is scrubbed of injection tags, override phrasing, credential leaks, and exfiltration URLs before it reaches the model.
  5. Audit & metrics — instrumentation is enabled by default.

Safe by default

McpGovernanceOptions turns protections on out of the box: ScanToolsOnStartup, FailOnUnsafeTools, SanitizeResponses, GovernFallbackHandlers, EnableAudit, and EnableMetrics are all true. You get a strong baseline before your first deployment.

The example: a governed veterinary clinic agent

Our demo runs a small Paws & Claws veterinary clinic agent. It exposes three tools — looking up a pet's vaccination status, booking a grooming slot, and exporting an owner's billing details. Instead of hosting an MCP server, the demo uses the GovernanceKernel directly: it loads the YAML policy and, before any tool runs, decides whether the call is allowed. The agent then calls the allowed tools and is blocked from the sensitive one.

Install the package

dotnet add package Microsoft.AgentGovernance.Extensions.ModelContextProtocol

Full Example (vet-clinic.yaml)

The policy denies everything by default, then explicitly allows the two safe tools. The sensitive billing export is denied, so a governed error result is returned instead of running it.

apiVersion: governance.toolkit/v1
version: "1.0"
name: vet-clinic-mcp-policy
default_action: deny
rules:
- name: allow-vaccination-lookup
condition: "tool_name == 'lookup_vaccination_status'"
action: allow
priority: 10

- name: allow-grooming-booking
condition: "tool_name == 'book_grooming_slot'"
action: allow
priority: 10

- name: deny-billing-export
condition: "tool_name == 'export_owner_billing'"
action: deny
priority: 20

Full Example (GovernedVetClinicMcpDemo.cs)

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using AgentGovernance;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI;

namespace MicrosoftAgentFrameworkLesson.ConsoleApp.Agents;

// ---------------------------------------------------------------------------
// A veterinary clinic agent whose tools are guarded by the Agent Governance
// Toolkit. There is no MCP host here: the GovernanceKernel loads the YAML
// policy and decides, per call, whether each tool may run.
//
// Allowed: lookup_vaccination_status, book_grooming_slot
// Denied: export_owner_billing (sensitive -> blocked before it executes)
// ---------------------------------------------------------------------------
public class GovernedVetClinicMcpDemo
{
// The identity used when the policy is evaluated.
private const string AgentId = "did:mcp:anonymous";

public static async Task RunAsync()
{
Console.WriteLine("--- Governed Vet Clinic Agent Demo ---");

// 1) Spin up the governance kernel and load the clinic policy.
using var governance = new GovernanceKernel(new GovernanceOptions
{
PolicyPaths = { "policies/vet-clinic.yaml" },
EnableAudit = true
});

// 2) A small helper that asks governance for permission before a tool runs.
string Guard(string toolName, Dictionary<string, object> args, Func<string> run)
{
var decision = governance.EvaluateToolCall(AgentId, toolName, args);
if (!decision.Allowed)
{
Console.WriteLine($"[governance] DENIED {toolName}: {decision.Reason}");
return $"Request blocked by governance policy: {decision.Reason}";
}

Console.WriteLine($"[governance] ALLOWED {toolName}");
return run();
}

// 3) Expose the clinic tools as governed AI functions for the agent.
var lookupVaccinationStatus = AIFunctionFactory.Create(
([Description("The pet's clinic id, e.g. PAW-1042.")] string petId) =>
Guard("lookup_vaccination_status", new() { ["petId"] = petId },
() => $"Pet {petId}: Rabies up to date, Distemper due in 14 days."),
"lookup_vaccination_status",
"Returns the vaccination record for a pet by its clinic id.");

var bookGroomingSlot = AIFunctionFactory.Create(
([Description("The pet's clinic id.")] string petId,
[Description("Requested date in yyyy-MM-dd format.")] string date) =>
Guard("book_grooming_slot", new() { ["petId"] = petId, ["date"] = date },
() => $"Grooming slot confirmed for {petId} on {date}."),
"book_grooming_slot",
"Reserves a grooming appointment for a pet on a given date.");

var exportOwnerBilling = AIFunctionFactory.Create(
([Description("The owner's account id.")] string ownerId) =>
Guard("export_owner_billing", new() { ["ownerId"] = ownerId },
() => $"Billing export for {ownerId} (card, address, balance)."),
"export_owner_billing",
"Exports an owner's full billing and payment details.");

// 4) Build the agent and hand it the governed tools.
var openAiKey = Environment.GetEnvironmentVariable("OPEN_AI_KEY") ?? throw new InvalidOperationException("OPEN_AI_KEY is missing");

var agent = new OpenAIClient(openAiKey)
.GetChatClient("gpt-4o-mini")
.AsIChatClient()
.AsAIAgent(new ChatClientAgentOptions
{
Name = "VetClinicAgent",
ChatOptions = new()
{
Instructions = "You are the assistant for the Paws & Claws veterinary clinic. " +
"Use the available tools to help. If a tool is blocked by policy, explain that to the user.",
Tools = [lookupVaccinationStatus, bookGroomingSlot, exportOwnerBilling]
}
});

Console.WriteLine("Agent configured with governed tools successfully.\n");

// 5) One allowed request and one denied request.
foreach (var question in new[]
{
"What is the vaccination status for pet PAW-1042?",
"Please export the full billing details for owner ACC-7781."
})
{
Console.WriteLine($"User: {question}");
var response = await agent.RunAsync(question);
Console.WriteLine($"Agent: {response.Text}\n");
}
}
}

How the governed flow works

Each tool is wrapped by the Guard helper. Before the tool body runs, GovernanceKernel.EvaluateToolCall(...) checks the request against vet-clinic.yaml: lookup_vaccination_status and book_grooming_slot are allowed, while export_owner_billing is denied. When the agent decides to call the billing tool, the guard returns a governed denial message instead of running the sensitive code, so the secret never leaves the boundary. Every decision is also written to the audit log.

Compliance note: the Agent Governance Toolkit provides technical controls that help support your security and privacy programs, but it does not by itself guarantee legal or regulatory compliance. You remain responsible for validating your end-to-end implementation.

Reference: Announcing Agent Governance Toolkit MCP Extensions for .NET


Share this lesson: