Skip to content

MCP Integration — Expose Granit Modules to AI Agents

Granit wraps the official MCP C# SDK to let any Granit-based application expose its capabilities to AI agents via the Model Context Protocol (MCP). An agent connects to your app, discovers available tools, and invokes them — with full Granit authorization, multi-tenancy, and GDPR sanitization applied automatically.

CapabilityPackageWhat it does
Expose toolsGranit.McpAuto-discover [McpServerTool] methods from module assemblies
HTTP transportGranit.Mcp.ServerStreamable HTTP endpoint with [Authorize] + RBAC permissions
GDPR sanitizationGranit.McpOmit PII from tool responses, truncate large payloads, strip stack traces
Multi-tenancyGranit.Mcp.ServerPer-tenant tool visibility, module scoping, opt-in discovery
Consume external serversGranit.Mcp.ClientNamed factory for HTTP + stdio connections with credential forwarding
AI workspace bridgeGranit.AI.McpInject MCP tools into IChatClient pipelines, cost-controlled sampling
  • DirectoryGranit.Mcp/ Core abstractions, auto-discovery, sanitization pipeline, diagnostics
    • DirectoryGranit.Mcp.Server/ ASP.NET Core Streamable HTTP transport, auth, permissions, visibility filters
    • DirectoryGranit.Mcp.Client/ Named factory for external MCP server connections (HTTP + stdio)
    • DirectoryGranit.AI.Mcp/ Bridge MCP tools into Granit.AI workspaces, sampling guard
flowchart LR
    subgraph "Your Granit App"
        direction TB
        M1["[McpServerTool]<br/>BlobStorage tools"] --> MCP["Granit.Mcp<br/>Auto-discovery + Filters"]
        M2["[McpServerTool]<br/>Workflow tools"] --> MCP
        MCP --> SRV["Granit.Mcp.Server<br/>HTTP transport + Auth"]
    end

    AI["AI Agent<br/>(Claude, Copilot, Cursor)"] -- "Streamable HTTP<br/>/mcp" --> SRV

    subgraph "External"
        ERP["ERP MCP Server"]
    end

    CLI["Granit.Mcp.Client"] -- "HTTP / stdio" --> ERP
    CLI --> AIM["Granit.AI.Mcp<br/>→ IChatClient tools"]

Granit delegates to the MCP C# SDK wherever possible. Cross-cutting concerns (sanitization, visibility, audit) are wired into the SDK’s onion-model filter pipeline (AddCallToolFilter, AddListToolsFilter) — not a parallel abstraction.

No custom auth attributes. Tool authorization uses [Authorize(Policy = "...")] via the SDK’s .AddAuthorizationFilters(). Granit’s DynamicPermissionPolicyProvider maps policy names to RBAC permissions automatically.

In production (McpToolDiscoveryMode.Explicit), tool classes need [McpExposed] alongside [McpServerToolType]. An accidental [McpServerTool] on an internal service stays hidden until explicitly opted in.

Tool responses pass through a sanitization pipeline before reaching the AI agent. PII is omitted (not masked — masked values waste LLM tokens and trigger hallucinations), large payloads are truncated, and error messages are stripped of stack traces.

Three lines to expose your first MCP tool:

// 1. Register
builder.AddGranitMcpServer();
// 2. Map
app.MapGranitMcpServer();
// 3. Create a tool (in any module assembly)
[McpServerToolType, McpExposed]
public sealed class InvoiceMcpTools(IInvoiceReader reader)
{
[McpServerTool, Description("Search invoices by status and date range.")]
[Authorize(Policy = "Invoicing.Invoices.Read")]
[McpToolOptions(ReadOnly = true)]
public async Task<string> SearchInvoicesAsync(
[Description("Invoice status filter")] string status,
[Description("Start date (ISO 8601)")] DateOnly from,
ICurrentTenant tenant,
CancellationToken ct)
{
var invoices = await reader.SearchAsync(status, from, ct);
return JsonSerializer.Serialize(invoices);
}
}

Connect Claude Desktop, Cursor, or any MCP client to https://your-app/mcp — your tools appear automatically.

Meter: Granit.Mcp — all metrics follow Granit conventions.

MetricTypeTags
granit.mcp.tools.invokedCountertenant_id, tool_name, status
granit.mcp.resources.readCountertenant_id, resource_uri
granit.mcp.sessions.activeUpDownCountertenant_id, transport
granit.mcp.request.durationHistogram (s)tenant_id, method

Activity source: Granit.Mcp — registered via GranitActivitySourceRegistry.