Skip to main content

MCP Client

Connect to remote MCP servers, discover their capabilities, and make their tools available to the AI orchestrator.

Quick Start

builder.Services
.AddCoreAIServices()
.AddCoreAIOrchestration()
.AddCoreAIMcpClient();

This registers transport providers, OAuth2 support, the core McpService that manages connections to remote MCP servers, and the shared AI-profile completion-context handler that flows selected MCP connection IDs into the completion request.

Problem & Solution

AI applications often need to call tools hosted on external servers — code execution sandboxes, data retrieval APIs, domain-specific utilities. The Model Context Protocol (MCP) standardizes how clients discover and invoke those remote capabilities.

The MCP client framework:

  • Connects to remote MCP servers over SSE (HTTP) or StdIO (local process)
  • Discovers tools, prompts, and resources from each server
  • Makes discovered tools available in the AI orchestrator's tool registry
  • Proxies tool calls transparently to the correct remote server
  • Supports multiple authentication methods for secure connections

Registered Services

AddCoreAIMcpClient() registers these services:

ServiceImplementationLifetimePurpose
McpServiceScopedCreates MCP clients for configured connections
IOAuth2TokenServiceDefaultOAuth2TokenServiceScopedOAuth2 token acquisition and caching
IMcpClientTransportProviderSseClientTransportProviderScopedServer-Sent Events transport
IMcpClientTransportProviderStdioClientTransportProviderScopedStandard I/O transport
IAICompletionContextBuilderHandlerMcpAICompletionContextBuilderHandlerScopedCopies selected MCP connection IDs from AI profile metadata into the completion context

Two transport types are automatically registered in McpClientAIOptions:

Type KeyDisplay NameDescription
sseServer-Sent EventsUses a remote MCP server over HTTP
stdIoStandard Input/OutputUses a local MCP process over standard input/output

Transport Types

SSE (Server-Sent Events)

Use SSE to connect to remote MCP servers over HTTP. The SseMcpConnectionMetadata model configures the connection:

public sealed class SseMcpConnectionMetadata
{
public Uri Endpoint { get; set; }
public McpClientAuthenticationType AuthenticationType { get; set; }

// API Key
public string ApiKeyHeaderName { get; set; }
public string ApiKeyPrefix { get; set; }
public string ApiKey { get; set; }

// Basic Auth
public string BasicUsername { get; set; }
public string BasicPassword { get; set; }

// OAuth2 Client Credentials
public string OAuth2TokenEndpoint { get; set; }
public string OAuth2ClientId { get; set; }
public string OAuth2ClientSecret { get; set; }
public string OAuth2Scopes { get; set; }

// OAuth2 Private Key JWT
public string OAuth2PrivateKey { get; set; }
public string OAuth2KeyId { get; set; }

// OAuth2 Mutual TLS
public string OAuth2ClientCertificate { get; set; }
public string OAuth2ClientCertificatePassword { get; set; }

// Custom Headers
public Dictionary<string, string> AdditionalHeaders { get; set; }
}

The transport provider reads this metadata from the McpConnection and builds the appropriate HTTP headers before opening the SSE stream. Credentials stored via Data Protection are decrypted at connection time.

StdIO (Standard I/O)

Use StdIO to communicate with locally installed MCP server processes via stdin/stdout. The StdioMcpConnectionMetadata model configures the process:

public sealed class StdioMcpConnectionMetadata
{
public string Command { get; set; }
public string[] Arguments { get; set; }
public string WorkingDirectory { get; set; }
public Dictionary<string, string> EnvironmentVariables { get; set; }
}

Example — connect to a local Python MCP server:

// The StdIO transport spawns the process and communicates over stdin/stdout.
var metadata = new StdioMcpConnectionMetadata
{
Command = "python",
Arguments = ["-m", "my_mcp_server"],
WorkingDirectory = "/opt/tools",
EnvironmentVariables = new Dictionary<string, string>
{
["MY_API_KEY"] = "sk-..."
}
};

Authentication

The SSE transport supports these authentication types via the McpClientAuthenticationType enum:

TypeDescription
AnonymousNo authentication
ApiKeyCustom API key sent via a configurable header and prefix
BasicHTTP Basic authentication (username + password)
OAuth2ClientCredentialsOAuth2 client credentials grant
OAuth2PrivateKeyJwtOAuth2 with private key JWT assertion (RS256)
OAuth2MtlsOAuth2 with mutual TLS client certificate
CustomHeadersArbitrary custom headers for legacy or proprietary auth

OAuth2 Token Service

The IOAuth2TokenService handles token acquisition for all OAuth2 flows:

public interface IOAuth2TokenService
{
Task<string> AcquireTokenAsync(
string tokenEndpoint, string clientId,
string clientSecret, string scopes = null,
CancellationToken cancellationToken = default);

Task<string> AcquireTokenWithPrivateKeyJwtAsync(
string tokenEndpoint, string clientId,
string privateKeyPem, string keyId = null,
string scopes = null,
CancellationToken cancellationToken = default);

Task<string> AcquireTokenWithMtlsAsync(
string tokenEndpoint, string clientId,
byte[] clientCertificateBytes,
string certificatePassword = null,
string scopes = null,
CancellationToken cancellationToken = default);
}

The default implementation (DefaultOAuth2TokenService) caches tokens in IMemoryCache with a 60-second expiration buffer to prevent token expiry during active requests.

Connection Management

McpConnection

MCP connections are stored as catalog entries via McpConnection:

public sealed class McpConnection : SourceCatalogEntry, IDisplayTextAwareModel
{
public string DisplayText { get; set; }
public DateTime CreatedUtc { get; set; }
public string Author { get; set; }
public string OwnerId { get; set; }
}

The Source property (inherited from SourceCatalogEntry) indicates the transport type — "sse" or "stdIo". Transport-specific metadata (e.g., SseMcpConnectionMetadata) is stored in the Properties bag and retrieved using connection.As<SseMcpConnectionMetadata>().

McpService

McpService is the central service for creating MCP clients from connections:

public sealed class McpService
{
public async Task<McpClient> GetOrCreateClientAsync(McpConnection connection);
}

It iterates through registered IMcpClientTransportProvider implementations to find one that supports the connection's transport type, then creates an IClientTransport and returns an McpClient from the ModelContextProtocol C# SDK.

IMcpClientTransportProvider

Transport providers implement this interface to support different connection mechanisms:

public interface IMcpClientTransportProvider
{
bool CanHandle(McpConnection connection);
Task<IClientTransport> GetAsync(McpConnection connection);
}

The framework registers SseClientTransportProvider and StdioClientTransportProvider by default. You can add custom transport providers by registering additional IMcpClientTransportProvider implementations.

Server Metadata Caching

IMcpServerMetadataCacheProvider

Before tools can be discovered, the framework queries MCP servers for their capabilities and caches the results:

public interface IMcpServerMetadataCacheProvider
{
Task<McpServerCapabilities> GetCapabilitiesAsync(McpConnection connection);
Task InvalidateAsync(string connectionId);
}

The McpServerCapabilities model holds the complete set of capabilities from a server:

public sealed class McpServerCapabilities
{
public string ConnectionId { get; set; }
public string ConnectionDisplayText { get; set; }
public IReadOnlyList<McpServerCapability> Tools { get; set; }
public IReadOnlyList<McpServerCapability> Prompts { get; set; }
public IReadOnlyList<McpServerCapability> Resources { get; set; }
public IReadOnlyList<McpServerCapability> ResourceTemplates { get; set; }
public DateTime FetchedUtc { get; set; }
public bool IsHealthy { get; set; }
}

Each McpServerCapability captures a tool's name, description, input schema, or a resource's URI and MIME type.

Tool Discovery & Invocation

McpToolRegistryProvider

Implements IToolRegistryProvider to discover tools from configured MCP connections:

  1. Reads McpConnectionIds from the current AICompletionContext
  2. Loads matching McpConnection entries from the catalog
  3. Fetches cached capabilities via IMcpServerMetadataCacheProvider
  4. Creates a ToolRegistryEntry for each tool, with a factory that produces an McpToolProxyFunction

Each tool entry is identified as mcp:{connectionId}:{toolName} and tagged with ToolRegistryEntrySource.McpServer.

McpToolProxyFunction

An AIFunction proxy that transparently routes AI tool calls to the remote MCP server:

internal sealed class McpToolProxyFunction : AIFunction
{
public override string Name => _name;
public override string Description => _description;
public override JsonElement JsonSchema => _jsonSchema;

protected override async ValueTask<object> InvokeCoreAsync(
AIFunctionArguments arguments,
CancellationToken cancellationToken)
{
// 1. Resolve the McpConnection from the catalog
// 2. Create an McpClient via McpService
// 3. Call the remote tool: client.CallToolAsync(name, args)
// 4. Return serialized result (or error JSON on failure)
}
}

McpAICompletionContextBuilderHandler

Wires MCP connection IDs from the AI profile into the completion context:

internal sealed class McpAICompletionContextBuilderHandler
: IAICompletionContextBuilderHandler
{
public Task BuildingAsync(AICompletionContextBuildingContext context)
{
// Reads AIProfileMcpMetadata.ConnectionIds from the profile
// Sets context.Context.McpConnectionIds
}
}

This ensures the orchestrator knows which MCP connections to query for tools when processing a request.

Capability Resolution

For profiles with many MCP connections, the framework can filter capabilities using semantic similarity before invoking tools.

IMcpCapabilityResolver

public interface IMcpCapabilityResolver
{
Task<McpCapabilityResolutionResult> ResolveAsync(
string prompt,
string providerName,
string connectionName,
string[] mcpConnectionIds,
CancellationToken cancellationToken = default);
}

This uses embedding vectors to find capabilities semantically relevant to the user's prompt, returning only the top matches. Configuration is available via McpCapabilityResolverOptions:

PropertyDefaultDescription
SimilarityThreshold0.3Minimum cosine similarity score for embedding-based matching
KeywordMatchThreshold0.2Minimum keyword match score for fallback matching
TopK5Maximum number of top matching capabilities to return
IncludeAllThreshold20Total capability count below which all capabilities are included without filtering

IMcpCapabilityEmbeddingCacheProvider

Caches embedding vectors for capability metadata, recomputing them when the underlying metadata cache is invalidated:

public interface IMcpCapabilityEmbeddingCacheProvider
{
Task<IReadOnlyList<McpCapabilityEmbeddingEntry>> GetOrCreateEmbeddingsAsync(
IReadOnlyList<McpServerCapabilities> capabilities,
IEmbeddingGenerator<string, Embedding<float>> embeddingGenerator,
CancellationToken cancellationToken = default);

void Invalidate(string connectionId);
}

Configuration

McpClientAIOptions

Register additional transport types beyond the built-in SSE and StdIO:

services.Configure<McpClientAIOptions>(options =>
{
options.AddTransportType("grpc", entry =>
{
entry.DisplayName = new LocalizedString("gRPC", "gRPC");
entry.Description = new LocalizedString("gRPC Desc", "Connects via gRPC transport.");
});
});

Custom Transport Provider

public sealed class GrpcClientTransportProvider : IMcpClientTransportProvider
{
public bool CanHandle(McpConnection connection)
=> connection.Source == "grpc";

public Task<IClientTransport> GetAsync(McpConnection connection)
{
// Build and return a gRPC-based IClientTransport
}
}

// Register in Startup
services.AddScoped<IMcpClientTransportProvider, GrpcClientTransportProvider>();
  • Connection CRUD with SSE and StdIO configuration
  • Authentication setup (API key, Basic, OAuth2 flows)
  • Assigning MCP connections to AI profiles
  • Viewing discovered capabilities from connected servers