Chapter 08 · Systems

MCP Server & Client

The Model Context Protocol is an open standard for how AI agents discover and invoke tools. This harness speaks MCP in both directions — it can expose its own tools to other agents (server) and discover tools hosted elsewhere (client). Same protocol, two roles.

What MCP is, in one minute

Before MCP, every AI integration was bespoke — your agent had to know about Slack's API, then Jira's, then GitHub's. MCP standardizes the conversation: servers describe their tools, prompts, and resources in a uniform JSON schema; clients discover and invoke them over a transport (HTTP or stdio). Once an integration speaks MCP, any MCP-aware agent can use it.

The three MCP primitives

Tools — callable functions (with JSON schema for parameters). Prompts — parameterized prompt templates the client can fill in. Resources — addressable read-only content (files, query results, URIs). The harness exposes all three via its server and consumes Tools from external servers.

"Transport" — HTTP vs stdio

MCP supports two ways of physically connecting a client to a server:

HTTP transport — the server is a long-running web service at a URL. The client makes HTTP requests to it. Use this for remote / shared servers.

Stdio transport — the server is a local program (often an npm package or a Python script) that the client launches as a subprocess and talks to via standard input / standard output. Use this for local-machine tools that aren't worth running as a separate service.

Both look identical from the agent's perspective once tools are discovered. The transport choice is just about where the server lives.

Two roles, two projects

RoleProjectWhat it does
Server — expose this app's tools Infrastructure.AI.MCPServer ASP.NET Core WebAPI host with MCP endpoints, JWT auth, rate limiting.
Client — consume external MCP servers Infrastructure.AI.MCP Discovers tools at startup, wraps them as internal ITool, exposes to agents.

The server side

Infrastructure.AI.MCPServer is hostable as a Presentation entry point — it's the WebAPI process that lets external agents call into this harness. Setup is one line in Program.cs:

C# · MCPServer Program.cs
builder.Services.AddCustomMCPServer(appConfig);

// ...

var app = builder.Build();
app.MapMcp()
   .RequireAuthorization()
   .RequireRateLimiting("RATE_LIMITER_AI_MCPSERVER_POLICY");

The AddCustomMCPServer extension chains:

  • .WithHttpTransport() — selects HTTP (vs stdio) for transport.
  • .LoadTools() — enumerates registered ITool implementations and converts their schemas via IToolSchemaService.
  • .LoadPrompts() — registers prompt templates (defined as Razor or string-builder definitions).
  • .LoadResources() — exposes resources, including meta-harness execution traces when EnableMcpTraceResources is on.

Authentication

MCP server endpoints sit behind JWT Bearer authentication wired to your Entra ID tenant (configurable via AppConfig.Azure.AzureADB2C). Validation parameters require ValidateLifetime, ClockSkew = TimeSpan.Zero, and explicit issuer and audience matching. Tokens that don't pass return 401 before any tool code runs.

Rate limiting

Every endpoint is rate-limited via the policy RATE_LIMITER_AI_MCPSERVER_POLICY. The default is set in Infrastructure.APIAccess — tune via your DI registration if your deployment requires different limits.

The client side

When this app boots, Infrastructure.AI.MCP reads AppConfig.AI.McpServers.Servers and connects to each entry. For each server, it:

  1. Opens a connection over the configured transport (HTTP or stdio).
  2. Calls the MCP tools/list method to enumerate exposed tools.
  3. For each tool, McpToolProvider casts the SDK's McpClientTool straight to a Microsoft.Extensions.AI AITool (McpClientTool already derives from AITool). There is no internal McpTool : ITool wrapper and no per-tool keyed DI registration.
  4. Returns the resulting AITool list — per server, and merged across servers — so the agent can offer MCP tools alongside its built-in tools in the same turn.
json · McpServers config
"McpServers": {
  "Servers": {
    "bifrost": {
      "Type": "Http",
      "Url": "http://your-mcp-gateway:8090/mcp",
      "Auth": { "Type": "Bearer", "BearerToken": "${BIFROST_TOKEN}" }
    },
    "local-fs": {
      "Type": "Stdio",
      "Command": "npx",
      "Args": ["@modelcontextprotocol/server-filesystem", "/var/data"],
      "StartupTimeoutSeconds": 30
    }
  }
}

Outbound authentication

Each server's Auth.Type controls how this app authenticates to that MCP server. The supported values are None, ApiKey, Bearer (the BearerToken shown above), and Entra. For Entra, the harness mints its own short-lived, auto-rotating token via EntraTokenAuthHandler (wired by McpConnectionManager) rather than forwarding a caller's credential:

json · Entra (managed identity) MCP auth
"secure-remote": {
  "Type": "Http",
  "Url": "https://secure-mcp.example.com/mcp",
  "Auth": {
    "Type": "Entra",
    "Scopes": ["api://<target-app-id>/.default"]
  }
}
Managed identity is Scopes-only

The secure-by-default Entra shape supplies only Scopes (at least one is required) and optionally ClientId for a user-assigned identity — no standing secret is stored. A client secret or certificate is supported as an explicit fallback, but then both TenantId and ClientId are required. A lone TenantId with no secret or certificate is rejected as half-configured, so a forgotten credential fails loudly instead of silently minting a token from an ambient identity.

Why this matters

Because MCP tools arrive as first-class AITool instances, the rest of the harness treats them like any other tool handed to the agent:

  • They appear in the same tool list the model sees each turn.
  • They can be referenced from a skill's allowed-tools declaration.
  • They emit the same OTel spans on invocation.
  • They pass through the same tool-execution governance gates.

The agent has no idea whether a tool lives in this process or two networks away. That's the point.

Security scanning of MCP tools

Tools coming from external servers are not trusted by default. When AppConfig.AI.Governance.EnableMcpSecurity is on, every tool's name, description and parameter schema is scanned at discovery time — before the model ever sees it — for tool poisoning, hidden instructions, description injection and homoglyph typosquatting. That text is the attack surface: the harness copies it into the model's context so the model knows the tool exists, which means a poisoned description works whether or not the tool is ever called.

A finding at or above AppConfig.AI.Governance.McpToolBlockThreshold (default High) causes the tool to be withheld — it is never published to the model. Findings below that threshold are logged and counted, and the tool is still published. Full detail, including how the detection rules were calibrated against live MCP servers, is in the MCP tool-definition scanning security guide.

!
Untrusted MCP servers are an attack surface

A malicious MCP server could expose a tool described as "summarize text" but actually exfiltrate prompts to an attacker. Only connect to MCP servers you trust, and keep EnableMcpSecurity on. The deeper response-sanitization pipe (ResponseSanitizationBehavior) also redacts known PII patterns from tool responses before they reach the LLM.

The flip side: A2A

MCP is for tools. A2A (Agent-to-Agent) is the sister protocol for delegating work between agents. Each agent publishes an Agent Card at /.well-known/agent.json describing its capabilities and endpoint; an orchestrator agent reads cards, picks the right delegate, and sends a task over HTTP. See Infrastructure.AI/A2A/ for the host implementation. MCP and A2A are complementary — MCP gives the agent more hands; A2A gives it more colleagues.

Try the MCP demo

The console app has an MCP tools discovery example — option [3] from the menu:

bash
dotnet run --project src/Content/Presentation/Presentation.ConsoleUI -- --example mcp-tools

It connects to your configured MCP servers, lists every tool discovered, and prints their schemas. Run this when you change McpServers to verify wiring before letting an agent use them.


Where to go from here