Restructure IMcpEndpoint, IMcpClient, and IMcpServer (#723)

* Remove `McpEndpoint` & `IMcpClient`

* Revise naming, add abstract classes

* Fix missing renames

* Move extension methods to instance methods

* Add back old API, but obsoleted

* Add back obsoleted factory classes

* Move deprecation attributes

* `SseClientTransport` -> `HttpClientTransport`

* Update src/ModelContextProtocol.Core/Server/AugmentedServiceProvider.cs

Co-authored-by: Stephen Halter <halter73@gmail.com>

* PR feedback

* PR feedback

* Add back dispose lock

* Fix docs projects

* PR feedback

* Undo `docs/*.md` changes

---------

Co-authored-by: Stephen Halter <halter73@gmail.com>
This commit is contained in:
Mackinnon Buck 2025-09-16 13:22:10 -04:00 • committed by GitHub
commit 38b4a26998
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
115 changed files with 5169 additions and 3786 deletions

View file

@ -37,8 +37,8 @@ dotnet add package ModelContextProtocol --prerelease
## Getting Started (Client)
To get started writing a client, the `McpClientFactory.CreateAsync` method is used to instantiate and connect an `IMcpClient`
to a server. Once you have an `IMcpClient`, you can interact with it, such as to enumerate all available tools and invoke tools.
To get started writing a client, the `McpClient.CreateAsync` method is used to instantiate and connect an `McpClient`
to a server. Once you have an `McpClient`, you can interact with it, such as to enumerate all available tools and invoke tools.
```csharp
var clientTransport = new StdioClientTransport(new StdioClientTransportOptions
@ -48,7 +48,7 @@ var clientTransport = new StdioClientTransport(new StdioClientTransportOptions
Arguments = ["-y", "@modelcontextprotocol/server-everything"],
});
var client = await McpClientFactory.CreateAsync(clientTransport);
var client = await McpClient.CreateAsync(clientTransport);
// Print the list of tools available from the server.
foreach (var tool in await client.ListToolsAsync())
@ -122,14 +122,14 @@ public static class EchoTool
}
```
Tools can have the `IMcpServer` representing the server injected via a parameter to the method, and can use that for interaction with
Tools can have the `McpServer` representing the server injected via a parameter to the method, and can use that for interaction with
the connected client. Similarly, arguments may be injected via dependency injection. For example, this tool will use the supplied
`IMcpServer` to make sampling requests back to the client in order to summarize content it downloads from the specified url via
`McpServer` to make sampling requests back to the client in order to summarize content it downloads from the specified url via
an `HttpClient` injected via dependency injection.
```csharp
[McpServerTool(Name = "SummarizeContentFromUrl"), Description("Summarizes content downloaded from a specific URI")]
public static async Task<string> SummarizeDownloadedContent(
IMcpServer thisServer,
McpServer thisServer,
HttpClient httpClient,
[Description("The url from which to download the content to summarize")] string url,
CancellationToken cancellationToken)
@ -224,7 +224,7 @@ McpServerOptions options = new()
},
};
await using IMcpServer server = McpServerFactory.Create(new StdioServerTransport("MyServer"), options);
await using McpServer server = McpServer.Create(new StdioServerTransport("MyServer"), options);
await server.RunAsync();
```

View file

@ -4,7 +4,7 @@ using ModelContextProtocol.Protocol;
var endpoint = Environment.GetEnvironmentVariable("ENDPOINT") ?? "http://localhost:3001";
var clientTransport = new SseClientTransport(new()
var clientTransport = new HttpClientTransport(new()
{
Endpoint = new Uri(endpoint),
TransportMode = HttpTransportMode.StreamableHttp,
@ -27,7 +27,7 @@ McpClientOptions options = new()
}
};
await using var mcpClient = await McpClientFactory.CreateAsync(clientTransport, options);
await using var mcpClient = await McpClient.CreateAsync(clientTransport, options);
// </snippet_McpInitialize>
var tools = await mcpClient.ListToolsAsync();

View file

@ -13,7 +13,7 @@ public sealed class InteractiveTools
// <snippet_GuessTheNumber>
[McpServerTool, Description("A simple game where the user has to guess a number between 1 and 10.")]
public async Task<string> GuessTheNumber(
IMcpServer server, // Get the McpServer from DI container
McpServer server, // Get the McpServer from DI container
CancellationToken token
)
{

View file

@ -4,13 +4,13 @@ using System.Text.Json;
var endpoint = Environment.GetEnvironmentVariable("ENDPOINT") ?? "http://localhost:3001";
var clientTransport = new SseClientTransport(new()
var clientTransport = new HttpClientTransport(new()
{
Endpoint = new Uri(endpoint),
TransportMode = HttpTransportMode.StreamableHttp,
});
await using var mcpClient = await McpClientFactory.CreateAsync(clientTransport);
await using var mcpClient = await McpClient.CreateAsync(clientTransport);
// <snippet_LoggingCapabilities>
// Verify that the server supports logging

View file

@ -5,7 +5,7 @@ using ModelContextProtocol.Protocol;
var endpoint = Environment.GetEnvironmentVariable("ENDPOINT") ?? "http://localhost:3001";
var clientTransport = new SseClientTransport(new()
var clientTransport = new HttpClientTransport(new()
{
Endpoint = new Uri(endpoint),
TransportMode = HttpTransportMode.StreamableHttp,
@ -20,7 +20,7 @@ McpClientOptions options = new()
}
};
await using var mcpClient = await McpClientFactory.CreateAsync(clientTransport, options);
await using var mcpClient = await McpClient.CreateAsync(clientTransport, options);
var tools = await mcpClient.ListToolsAsync();
foreach (var tool in tools)

View file

@ -10,7 +10,7 @@ public class LongRunningTools
{
[McpServerTool, Description("Demonstrates a long running tool with progress updates")]
public static async Task<string> LongRunningTool(
IMcpServer server,
McpServer server,
RequestContext<CallToolRequestParams> context,
int duration = 10,
int steps = 5)

View file

@ -12,7 +12,7 @@ public sealed class SampleLlmTool
{
[McpServerTool(Name = "sampleLLM"), Description("Samples from an LLM using MCP's sampling feature")]
public static async Task<string> SampleLLM(
IMcpServer thisServer,
McpServer thisServer,
[Description("The prompt to send to the LLM")] string prompt,
[Description("Maximum number of tokens to generate")] int maxTokens,
CancellationToken cancellationToken)

View file

@ -32,7 +32,7 @@ using IChatClient samplingClient = openAIClient.AsIChatClient()
.UseOpenTelemetry(loggerFactory: loggerFactory, configure: o => o.EnableSensitiveData = true)
.Build();
var mcpClient = await McpClientFactory.CreateAsync(
var mcpClient = await McpClient.CreateAsync(
new StdioClientTransport(new()
{
Command = "npx",

View file

@ -5,7 +5,7 @@ using ModelContextProtocol.Server;
namespace EverythingServer;
public class LoggingUpdateMessageSender(IMcpServer server, Func<LoggingLevel> getMinLevel) : BackgroundService
public class LoggingUpdateMessageSender(McpServer server, Func<LoggingLevel> getMinLevel) : BackgroundService
{
readonly Dictionary<LoggingLevel, string> _loggingLevelMap = new()
{

View file

@ -2,7 +2,7 @@
using ModelContextProtocol;
using ModelContextProtocol.Server;
internal class SubscriptionMessageSender(IMcpServer server, HashSet<string> subscriptions) : BackgroundService
internal class SubscriptionMessageSender(McpServer server, HashSet<string> subscriptions) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{

View file

@ -10,7 +10,7 @@ public class LongRunningTool
{
[McpServerTool(Name = "longRunningOperation"), Description("Demonstrates a long running operation with progress updates")]
public static async Task<string> LongRunningOperation(
IMcpServer server,
McpServer server,
RequestContext<CallToolRequestParams> context,
int duration = 10,
int steps = 5)

View file

@ -9,7 +9,7 @@ public class SampleLlmTool
{
[McpServerTool(Name = "sampleLLM"), Description("Samples from an LLM using MCP's sampling feature")]
public static async Task<string> SampleLLM(
IMcpServer server,
McpServer server,
[Description("The prompt to send to the LLM")] string prompt,
[Description("Maximum number of tokens to generate")] int maxTokens,
CancellationToken cancellationToken)

View file

@ -6,7 +6,7 @@ using System.IO.Pipelines;
Pipe clientToServerPipe = new(), serverToClientPipe = new();
// Create a server using a stream-based transport over an in-memory pipe.
await using IMcpServer server = McpServerFactory.Create(
await using McpServer server = McpServer.Create(
new StreamServerTransport(clientToServerPipe.Reader.AsStream(), serverToClientPipe.Writer.AsStream()),
new McpServerOptions()
{
@ -21,7 +21,7 @@ await using IMcpServer server = McpServerFactory.Create(
_ = server.RunAsync();
// Connect a client using a stream-based transport over the same in-memory pipe.
await using IMcpClient client = await McpClientFactory.CreateAsync(
await using McpClient client = await McpClient.CreateAsync(
new StreamClientTransport(clientToServerPipe.Writer.AsStream(), serverToClientPipe.Reader.AsStream()));
// List all tools.

View file

@ -25,7 +25,7 @@ var consoleLoggerFactory = LoggerFactory.Create(builder =>
builder.AddConsole();
});
var transport = new SseClientTransport(new()
var transport = new HttpClientTransport(new()
{
Endpoint = new Uri(serverUrl),
Name = "Secure Weather Client",
@ -40,7 +40,7 @@ var transport = new SseClientTransport(new()
}
}, httpClient, consoleLoggerFactory);
var client = await McpClientFactory.CreateAsync(transport, loggerFactory: consoleLoggerFactory);
var client = await McpClient.CreateAsync(transport, loggerFactory: consoleLoggerFactory);
var tools = await client.ListToolsAsync();
if (tools.Count == 0)

View file

@ -19,7 +19,7 @@ var (command, arguments) = GetCommandAndArguments(args);
if (command == "http")
{
// make sure AspNetCoreMcpServer is running
clientTransport = new SseClientTransport(new()
clientTransport = new HttpClientTransport(new()
{
Endpoint = new Uri("http://localhost:3001")
});
@ -33,7 +33,7 @@ else
Arguments = arguments,
});
}
await using var mcpClient = await McpClientFactory.CreateAsync(clientTransport!);
await using var mcpClient = await McpClient.CreateAsync(clientTransport!);
var tools = await mcpClient.ListToolsAsync();
foreach (var tool in tools)
@ -62,7 +62,7 @@ var messages = new List<ChatMessage>();
var sb = new StringBuilder();
PromptForInput();
while(Console.ReadLine() is string query && !"exit".Equals(query, StringComparison.OrdinalIgnoreCase))
while (Console.ReadLine() is string query && !"exit".Equals(query, StringComparison.OrdinalIgnoreCase))
{
if (string.IsNullOrWhiteSpace(query))
{

View file

@ -12,7 +12,7 @@ public sealed class SampleLlmTool
{
[McpServerTool(Name = "sampleLLM"), Description("Samples from an LLM using MCP's sampling feature")]
public static async Task<string> SampleLLM(
IMcpServer thisServer,
McpServer thisServer,
[Description("The prompt to send to the LLM")] string prompt,
[Description("Maximum number of tokens to generate")] int maxTokens,
CancellationToken cancellationToken)

View file

@ -292,7 +292,7 @@ internal sealed class AuthorizationFilterSetup(IAuthorizationPolicyProvider? pol
if (requestServices is null)
{
// The IAuthorizationPolicyProvider service must be non-null to get to this line, so it's very unexpected for RequestContext.Services to not be set.
throw new InvalidOperationException("RequestContext.Services is not set! The IMcpServer must be initialized with a non-null IServiceProvider.");
throw new InvalidOperationException("RequestContext.Services is not set! The McpServer must be initialized with a non-null IServiceProvider.");
}
// ASP.NET Core's AuthorizationMiddleware resolves the IAuthorizationService from scoped request services, so we do the same.

View file

@ -20,7 +20,7 @@ public class HttpServerTransportOptions
/// Gets or sets an optional asynchronous callback for running new MCP sessions manually.
/// This is useful for running logic before a sessions starts and after it completes.
/// </summary>
public Func<HttpContext, IMcpServer, CancellationToken, Task>? RunSessionHandler { get; set; }
public Func<HttpContext, McpServer, CancellationToken, Task>? RunSessionHandler { get; set; }
/// <summary>
/// Gets or sets whether the server should run in a stateless mode that does not require all requests for a given session

View file

@ -53,7 +53,7 @@ internal sealed class SseHandler(
try
{
await using var mcpServer = McpServerFactory.Create(transport, mcpServerOptions, loggerFactory, context.RequestServices);
await using var mcpServer = McpServer.Create(transport, mcpServerOptions, loggerFactory, context.RequestServices);
context.Features.Set(mcpServer);
var runSessionAsync = httpMcpServerOptions.Value.RunSessionHandler ?? StreamableHttpHandler.RunSessionAsync;

View file

@ -232,7 +232,7 @@ internal sealed class StreamableHttpHandler(
}
}
var server = McpServerFactory.Create(transport, mcpServerOptions, loggerFactory, mcpServerServices);
var server = McpServer.Create(transport, mcpServerOptions, loggerFactory, mcpServerServices);
context.Features.Set(server);
var userIdClaim = statelessId?.UserIdClaim ?? GetUserIdClaim(context.User);
@ -307,7 +307,7 @@ internal sealed class StreamableHttpHandler(
};
}
internal static Task RunSessionAsync(HttpContext httpContext, IMcpServer session, CancellationToken requestAborted)
internal static Task RunSessionAsync(HttpContext httpContext, McpServer session, CancellationToken requestAborted)
=> session.RunAsync(requestAborted);
// SignalR only checks for ClaimTypes.NameIdentifier in HttpConnectionDispatcher, but AspNetCore.Antiforgery checks that plus the sub and UPN claims.

Some files were not shown because too many files have changed in this diff Show more