Remove McpServerConfig and have McpClientFactory accept IClientTransport instances directly. (#230)

* Remove McpServerConfig and have McpClientFactory accept IClientTransport instances directly.

* Address feedback and use lists when representing stdio arguments.

* Use "Name" for transport descriptors and "EndpointName" for MCP clients and servers.

* Update code samples.

* Fix source file naming
This commit is contained in:
Eirik Tsarpalis 2025-04-08 17:37:41 +01:00 • committed by GitHub
commit 3e21f3594a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 315 additions and 527 deletions

View file

@ -33,9 +33,8 @@ internal sealed class McpClient : McpEndpoint, IMcpClient
/// </summary>
/// <param name="clientTransport">The transport to use for communication with the server.</param>
/// <param name="options">Options for the client, defining protocol version and capabilities.</param>
/// <param name="serverConfig">The server configuration.</param>
/// <param name="loggerFactory">The logger factory.</param>
public McpClient(IClientTransport clientTransport, McpClientOptions? options, McpServerConfig serverConfig, ILoggerFactory? loggerFactory)
public McpClient(IClientTransport clientTransport, McpClientOptions? options, ILoggerFactory? loggerFactory)
: base(loggerFactory)
{
options ??= new();
@ -43,7 +42,7 @@ internal sealed class McpClient : McpEndpoint, IMcpClient
_clientTransport = clientTransport;
_options = options;
EndpointName = $"Client ({serverConfig.Id}: {serverConfig.Name})";
EndpointName = clientTransport.Name;
if (options.Capabilities is { } capabilities)
{

View file

@ -12,128 +12,39 @@ namespace ModelContextProtocol.Client;
public static class McpClientFactory
{
/// <summary>Creates an <see cref="IMcpClient"/>, connecting it to the specified server.</summary>
/// <param name="serverConfig">Configuration for the target server to which the client should connect.</param>
/// <param name="clientTransport">The transport instance used to communicate with the server.</param>
/// <param name="clientOptions">
/// A client configuration object which specifies client capabilities and protocol version.
/// If <see langword="null"/>, details based on the current process will be employed.
/// </param>
/// <param name="createTransportFunc">An optional factory method which returns transport implementations based on a server configuration.</param>
/// <param name="loggerFactory">A logger factory for creating loggers for clients.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>An <see cref="IMcpClient"/> that's connected to the specified server.</returns>
/// <exception cref="ArgumentNullException"><paramref name="serverConfig"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="clientTransport"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentNullException"><paramref name="clientOptions"/> is <see langword="null"/>.</exception>
/// <exception cref="ArgumentException"><paramref name="serverConfig"/> contains invalid information.</exception>
/// <exception cref="InvalidOperationException"><paramref name="createTransportFunc"/> returns an invalid transport.</exception>
public static async Task<IMcpClient> CreateAsync(
McpServerConfig serverConfig,
IClientTransport clientTransport,
McpClientOptions? clientOptions = null,
Func<McpServerConfig, ILoggerFactory?, IClientTransport>? createTransportFunc = null,
ILoggerFactory? loggerFactory = null,
CancellationToken cancellationToken = default)
{
Throw.IfNull(serverConfig);
createTransportFunc ??= CreateTransport;
string endpointName = $"Client ({serverConfig.Id}: {serverConfig.Name})";
Throw.IfNull(clientTransport);
string endpointName = clientTransport.Name;
var logger = loggerFactory?.CreateLogger(typeof(McpClientFactory)) ?? NullLogger.Instance;
logger.CreatingClient(endpointName);
var transport =
createTransportFunc(serverConfig, loggerFactory) ??
throw new InvalidOperationException($"{nameof(createTransportFunc)} returned a null transport.");
McpClient client = new(clientTransport, clientOptions, loggerFactory);
try
{
McpClient client = new(transport, clientOptions, serverConfig, loggerFactory);
try
{
await client.ConnectAsync(cancellationToken).ConfigureAwait(false);
logger.ClientCreated(endpointName);
return client;
}
catch
{
await client.DisposeAsync().ConfigureAwait(false);
throw;
}
await client.ConnectAsync(cancellationToken).ConfigureAwait(false);
logger.ClientCreated(endpointName);
return client;
}
catch
{
if (transport is IAsyncDisposable asyncDisposableTransport)
{
await asyncDisposableTransport.DisposeAsync().ConfigureAwait(false);
}
else if (transport is IDisposable disposableTransport)
{
disposableTransport.Dispose();
}
await client.DisposeAsync().ConfigureAwait(false);
throw;
}
}
private static IClientTransport CreateTransport(McpServerConfig serverConfig, ILoggerFactory? loggerFactory)
{
if (string.Equals(serverConfig.TransportType, TransportTypes.StdIo, StringComparison.OrdinalIgnoreCase))
{
string? command = serverConfig.TransportOptions?.GetValueOrDefault("command");
if (string.IsNullOrWhiteSpace(command))
{
command = serverConfig.Location;
if (string.IsNullOrWhiteSpace(command))
{
throw new ArgumentException("Command is required for stdio transport.", nameof(serverConfig));
}
}
string? arguments = serverConfig.TransportOptions?.GetValueOrDefault("arguments");
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
serverConfig.TransportType.Equals(TransportTypes.StdIo, StringComparison.OrdinalIgnoreCase) &&
!string.IsNullOrEmpty(command) &&
!string.Equals(Path.GetFileName(command), "cmd.exe", StringComparison.OrdinalIgnoreCase))
{
// On Windows, for stdio, we need to wrap non-shell commands with cmd.exe /c {command} (usually npx or uvicorn).
// The stdio transport will not work correctly if the command is not run in a shell.
arguments = string.IsNullOrWhiteSpace(arguments) ?
$"/c {command}" :
$"/c {command} {arguments}";
command = "cmd.exe";
}
return new StdioClientTransport(new StdioClientTransportOptions
{
Command = command!,
Arguments = arguments,
WorkingDirectory = serverConfig.TransportOptions?.GetValueOrDefault("workingDirectory"),
EnvironmentVariables = serverConfig.TransportOptions?
.Where(kv => kv.Key.StartsWith("env:", StringComparison.Ordinal))
.ToDictionary(kv => kv.Key.Substring("env:".Length), kv => kv.Value),
ShutdownTimeout = TimeSpan.TryParse(serverConfig.TransportOptions?.GetValueOrDefault("shutdownTimeout"), CultureInfo.InvariantCulture, out var timespan) ? timespan : StdioClientTransportOptions.DefaultShutdownTimeout
}, serverConfig, loggerFactory);
}
if (string.Equals(serverConfig.TransportType, TransportTypes.Sse, StringComparison.OrdinalIgnoreCase) ||
string.Equals(serverConfig.TransportType, "http", StringComparison.OrdinalIgnoreCase))
{
return new SseClientTransport(new SseClientTransportOptions
{
ConnectionTimeout = TimeSpan.FromSeconds(ParseInt32OrDefault(serverConfig.TransportOptions, "connectionTimeout", 30)),
MaxReconnectAttempts = ParseInt32OrDefault(serverConfig.TransportOptions, "maxReconnectAttempts", 3),
ReconnectDelay = TimeSpan.FromSeconds(ParseInt32OrDefault(serverConfig.TransportOptions, "reconnectDelay", 5)),
AdditionalHeaders = serverConfig.TransportOptions?
.Where(kv => kv.Key.StartsWith("header.", StringComparison.Ordinal))
.ToDictionary(kv => kv.Key.Substring("header.".Length), kv => kv.Value)
}, serverConfig, loggerFactory);
static int ParseInt32OrDefault(Dictionary<string, string>? options, string key, int defaultValue) =>
options?.TryGetValue(key, out var value) is not true ? defaultValue :
int.TryParse(value, out var result) ? result :
throw new ArgumentException($"Invalid value '{value}' for option '{key}' in transport options.", nameof(serverConfig));
}
throw new ArgumentException($"Unsupported transport type '{serverConfig.TransportType}'.", nameof(serverConfig));
}
}

View file

@ -1,34 +0,0 @@
namespace ModelContextProtocol;
/// <summary>
/// Configuration for an MCP server connection.
/// This is passed to the client factory to create a client for a specific server.
/// </summary>
public record McpServerConfig
{
/// <summary>
/// Unique identifier for this server configuration.
/// </summary>
public required string Id { get; init; }
/// <summary>
/// Display name for the server.
/// </summary>
public required string Name { get; init; }
/// <summary>
/// The type of transport to use.
/// </summary>
public required string TransportType { get; init; }
/// <summary>
/// For stdio transport: path to the executable
/// For HTTP transport: base URL of the server
/// </summary>
public string? Location { get; set; }
/// <summary>
/// Additional transport-specific configuration.
/// </summary>
public Dictionary<string, string>? TransportOptions { get; init; }
}

View file

@ -7,6 +7,7 @@
<PackageId>ModelContextProtocol</PackageId>
<Description>.NET SDK for the Model Context Protocol (MCP)</Description>
<PackageReadmeFile>README.md</PackageReadmeFile>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(TargetFramework)' != 'netstandard2.0'">

View file

@ -5,6 +5,11 @@
/// </summary>
public interface IClientTransport
{
/// <summary>
/// Specifies a transport identifier used for logging purposes.
/// </summary>
string Name { get; }
/// <summary>
/// Asynchronously establishes a transport session with an MCP server and returns an interface for the duplex JSON-RPC message stream.
/// </summary>

View file

@ -16,6 +16,7 @@ namespace ModelContextProtocol.Protocol.Transport;
/// </summary>
internal sealed class SseClientSessionTransport : TransportBase
{
private readonly string _endpointName;
private readonly HttpClient _httpClient;
private readonly SseClientTransportOptions _options;
private readonly Uri _sseEndpoint;
@ -23,33 +24,29 @@ internal sealed class SseClientSessionTransport : TransportBase
private readonly CancellationTokenSource _connectionCts;
private Task? _receiveTask;
private readonly ILogger _logger;
private readonly McpServerConfig _serverConfig;
private readonly TaskCompletionSource<bool> _connectionEstablished;
private string EndpointName => $"Client (SSE) for ({_serverConfig.Id}: {_serverConfig.Name})";
/// <summary>
/// SSE transport for client endpoints. Unlike stdio it does not launch a process, but connects to an existing server.
/// The HTTP server can be local or remote, and must support the SSE protocol.
/// </summary>
/// <param name="transportOptions">Configuration options for the transport.</param>
/// <param name="serverConfig">The configuration object indicating which server to connect to.</param>
/// <param name="httpClient">The HTTP client instance used for requests.</param>
/// <param name="loggerFactory">Logger factory for creating loggers.</param>
public SseClientSessionTransport(SseClientTransportOptions transportOptions, McpServerConfig serverConfig, HttpClient httpClient, ILoggerFactory? loggerFactory)
/// <param name="endpointName">The endpoint name used for logging purposes.</param>
public SseClientSessionTransport(SseClientTransportOptions transportOptions, HttpClient httpClient, ILoggerFactory? loggerFactory, string endpointName)
: base(loggerFactory)
{
Throw.IfNull(transportOptions);
Throw.IfNull(serverConfig);
Throw.IfNull(httpClient);
_options = transportOptions;
_serverConfig = serverConfig;
_sseEndpoint = new Uri(serverConfig.Location!);
_sseEndpoint = transportOptions.Endpoint;
_httpClient = httpClient;
_connectionCts = new CancellationTokenSource();
_logger = (ILogger?)loggerFactory?.CreateLogger<SseClientTransport>() ?? NullLogger.Instance;
_connectionEstablished = new TaskCompletionSource<bool>();
_endpointName = endpointName;
}
/// <inheritdoc/>
@ -59,14 +56,14 @@ internal sealed class SseClientSessionTransport : TransportBase
{
if (IsConnected)
{
_logger.TransportAlreadyConnected(EndpointName);
_logger.TransportAlreadyConnected(_endpointName);
throw new McpTransportException("Transport is already connected");
}
// Start message receiving loop
_receiveTask = ReceiveMessagesAsync(_connectionCts.Token);
_logger.TransportReadingMessages(EndpointName);
_logger.TransportReadingMessages(_endpointName);
await _connectionEstablished.Task.WaitAsync(_options.ConnectionTimeout, cancellationToken).ConfigureAwait(false);
}
@ -77,7 +74,7 @@ internal sealed class SseClientSessionTransport : TransportBase
}
catch (Exception ex)
{
_logger.TransportConnectFailed(EndpointName, ex);
_logger.TransportConnectFailed(_endpointName, ex);
await CloseAsync().ConfigureAwait(false);
throw new McpTransportException("Failed to connect transport", ex);
}
@ -120,7 +117,7 @@ internal sealed class SseClientSessionTransport : TransportBase
// If the response is not a JSON-RPC response, it is an SSE message
if (responseContent.Equals("accepted", StringComparison.OrdinalIgnoreCase))
{
_logger.SSETransportPostAccepted(EndpointName, messageId);
_logger.SSETransportPostAccepted(_endpointName, messageId);
// The response will arrive as an SSE message
}
else
@ -128,9 +125,9 @@ internal sealed class SseClientSessionTransport : TransportBase
JsonRpcResponse initializeResponse = JsonSerializer.Deserialize(responseContent, McpJsonUtilities.JsonContext.Default.JsonRpcResponse) ??
throw new McpTransportException("Failed to initialize client");
_logger.TransportReceivedMessageParsed(EndpointName, messageId);
_logger.TransportReceivedMessageParsed(_endpointName, messageId);
await WriteMessageAsync(initializeResponse, cancellationToken).ConfigureAwait(false);
_logger.TransportMessageWritten(EndpointName, messageId);
_logger.TransportMessageWritten(_endpointName, messageId);
}
return;
}
@ -138,11 +135,11 @@ internal sealed class SseClientSessionTransport : TransportBase
// Otherwise, check if the response was accepted (the response will come as an SSE message)
if (responseContent.Equals("accepted", StringComparison.OrdinalIgnoreCase))
{
_logger.SSETransportPostAccepted(EndpointName, messageId);
_logger.SSETransportPostAccepted(_endpointName, messageId);
}
else
{
_logger.SSETransportPostNotAccepted(EndpointName, messageId, responseContent);
_logger.SSETransportPostNotAccepted(_endpointName, messageId, responseContent);
throw new McpTransportException("Failed to send message");
}
}
@ -220,17 +217,17 @@ internal sealed class SseClientSessionTransport : TransportBase
}
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
{
_logger.TransportReadMessagesCancelled(EndpointName);
_logger.TransportReadMessagesCancelled(_endpointName);
// Normal shutdown
}
catch (IOException) when (cancellationToken.IsCancellationRequested)
{
_logger.TransportReadMessagesCancelled(EndpointName);
_logger.TransportReadMessagesCancelled(_endpointName);
// Normal shutdown
}
catch (Exception ex) when (!cancellationToken.IsCancellationRequested)
{
_logger.TransportConnectionError(EndpointName, ex);
_logger.TransportConnectionError(_endpointName, ex);
reconnectAttempts++;
if (reconnectAttempts >= _options.MaxReconnectAttempts)
@ -249,7 +246,7 @@ internal sealed class SseClientSessionTransport : TransportBase
{
if (!IsConnected)
{
_logger.TransportMessageReceivedBeforeConnected(EndpointName, data);
_logger.TransportMessageReceivedBeforeConnected(_endpointName, data);
return;
}
@ -258,7 +255,7 @@ internal sealed class SseClientSessionTransport : TransportBase
var message = JsonSerializer.Deserialize(data, McpJsonUtilities.JsonContext.Default.IJsonRpcMessage);
if (message == null)
{
_logger.TransportMessageParseUnexpectedType(EndpointName, data);
_logger.TransportMessageParseUnexpectedType(_endpointName, data);
return;
}
@ -268,13 +265,13 @@ internal sealed class SseClientSessionTransport : TransportBase
messageId = messageWithId.Id.ToString();
}
_logger.TransportReceivedMessageParsed(EndpointName, messageId);
_logger.TransportReceivedMessageParsed(_endpointName, messageId);
await WriteMessageAsync(message, cancellationToken).ConfigureAwait(false);
_logger.TransportMessageWritten(EndpointName, messageId);
_logger.TransportMessageWritten(_endpointName, messageId);
}
catch (JsonException ex)
{
_logger.TransportMessageParseFailed(EndpointName, data, ex);
_logger.TransportMessageParseFailed(_endpointName, data, ex);
}
}
@ -284,7 +281,7 @@ internal sealed class SseClientSessionTransport : TransportBase
{
if (string.IsNullOrEmpty(data))
{
_logger.TransportEndpointEventInvalid(EndpointName, data);
_logger.TransportEndpointEventInvalid(_endpointName, data);
return;
}
@ -312,7 +309,7 @@ internal sealed class SseClientSessionTransport : TransportBase
}
catch (JsonException ex)
{
_logger.TransportEndpointEventParseFailed(EndpointName, data, ex);
_logger.TransportEndpointEventParseFailed(_endpointName, data, ex);
throw new McpTransportException("Failed to parse endpoint event", ex);
}
}

View file

@ -9,7 +9,6 @@ namespace ModelContextProtocol.Protocol.Transport;
public sealed class SseClientTransport : IClientTransport, IAsyncDisposable
{
private readonly SseClientTransportOptions _options;
private readonly McpServerConfig _serverConfig;
private readonly HttpClient _httpClient;
private readonly ILoggerFactory? _loggerFactory;
private readonly bool _ownsHttpClient;
@ -19,10 +18,9 @@ public sealed class SseClientTransport : IClientTransport, IAsyncDisposable
/// The HTTP server can be local or remote, and must support the SSE protocol.
/// </summary>
/// <param name="transportOptions">Configuration options for the transport.</param>
/// <param name="serverConfig">The configuration object indicating which server to connect to.</param>
/// <param name="loggerFactory">Logger factory for creating loggers.</param>
public SseClientTransport(SseClientTransportOptions transportOptions, McpServerConfig serverConfig, ILoggerFactory? loggerFactory)
: this(transportOptions, serverConfig, new HttpClient(), loggerFactory, true)
public SseClientTransport(SseClientTransportOptions transportOptions, ILoggerFactory? loggerFactory = null)
: this(transportOptions, new HttpClient(), loggerFactory, true)
{
}
@ -31,27 +29,28 @@ public sealed class SseClientTransport : IClientTransport, IAsyncDisposable
/// The HTTP server can be local or remote, and must support the SSE protocol.
/// </summary>
/// <param name="transportOptions">Configuration options for the transport.</param>
/// <param name="serverConfig">The configuration object indicating which server to connect to.</param>
/// <param name="httpClient">The HTTP client instance used for requests.</param>
/// <param name="loggerFactory">Logger factory for creating loggers.</param>
/// <param name="ownsHttpClient">True to dispose HTTP client on close connection.</param>
public SseClientTransport(SseClientTransportOptions transportOptions, McpServerConfig serverConfig, HttpClient httpClient, ILoggerFactory? loggerFactory, bool ownsHttpClient = false)
public SseClientTransport(SseClientTransportOptions transportOptions, HttpClient httpClient, ILoggerFactory? loggerFactory = null, bool ownsHttpClient = false)
{
Throw.IfNull(transportOptions);
Throw.IfNull(serverConfig);
Throw.IfNull(httpClient);
_options = transportOptions;
_serverConfig = serverConfig;
_httpClient = httpClient;
_loggerFactory = loggerFactory;
_ownsHttpClient = ownsHttpClient;
Name = transportOptions.Name ?? transportOptions.Endpoint.ToString();
}
/// <inheritdoc />
public string Name { get; }
/// <inheritdoc />
public async Task<ITransport> ConnectAsync(CancellationToken cancellationToken = default)
{
var sessionTransport = new SseClientSessionTransport(_options, _serverConfig, _httpClient, _loggerFactory);
var sessionTransport = new SseClientSessionTransport(_options, _httpClient, _loggerFactory, Name);
try
{

View file

@ -5,6 +5,36 @@
/// </summary>
public record SseClientTransportOptions
{
/// <summary>
/// The base address of the server for SSE connections.
/// </summary>
public required Uri Endpoint
{
get;
init
{
if (value is null)
{
throw new ArgumentNullException(nameof(value), "Endpoint cannot be null.");
}
if (!value.IsAbsoluteUri)
{
throw new ArgumentException("Endpoint must be an absolute URI.", nameof(value));
}
if (value.Scheme != Uri.UriSchemeHttp && value.Scheme != Uri.UriSchemeHttps)
{
throw new ArgumentException("Endpoint must use HTTP or HTTPS scheme.", nameof(value));
}
field = value;
}
}
/// <summary>
/// Specifies a transport identifier used for logging purposes.
/// </summary>
public string? Name { get; init; }
/// <summary>
/// Timeout for initial connection and endpoint event.
/// </summary>

View file

@ -3,7 +3,9 @@ using Microsoft.Extensions.Logging.Abstractions;
using ModelContextProtocol.Logging;
using ModelContextProtocol.Utils;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
@ -15,33 +17,44 @@ namespace ModelContextProtocol.Protocol.Transport;
public sealed class StdioClientTransport : IClientTransport
{
private readonly StdioClientTransportOptions _options;
private readonly McpServerConfig _serverConfig;
private readonly ILoggerFactory? _loggerFactory;
/// <summary>
/// Initializes a new instance of the <see cref="StdioClientTransport"/> class.
/// </summary>
/// <param name="options">Configuration options for the transport.</param>
/// <param name="serverConfig">The server configuration for the transport.</param>
/// <param name="loggerFactory">A logger factory for creating loggers.</param>
public StdioClientTransport(StdioClientTransportOptions options, McpServerConfig serverConfig, ILoggerFactory? loggerFactory = null)
public StdioClientTransport(StdioClientTransportOptions options, ILoggerFactory? loggerFactory = null)
{
Throw.IfNull(options);
Throw.IfNull(serverConfig);
_options = options;
_serverConfig = serverConfig;
_loggerFactory = loggerFactory;
Name = options.Name ?? $"stdio-{Regex.Replace(Path.GetFileName(options.Command), @"[\s\.]+", "-")}";
}
/// <inheritdoc />
public string Name { get; }
/// <inheritdoc />
public async Task<ITransport> ConnectAsync(CancellationToken cancellationToken = default)
{
string endpointName = $"Client (stdio) for ({_serverConfig.Id}: {_serverConfig.Name})";
string endpointName = Name;
Process? process = null;
bool processStarted = false;
string command = _options.Command;
IList<string>? arguments = _options.Arguments;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
!string.Equals(Path.GetFileName(command), "cmd.exe", StringComparison.OrdinalIgnoreCase))
{
// On Windows, for stdio, we need to wrap non-shell commands with cmd.exe /c {command} (usually npx or uvicorn).
// The stdio transport will not work correctly if the command is not run in a shell.
arguments = arguments is null or [] ? ["/c", command] : ["/c", command, ..arguments];
command = "cmd.exe";
}
ILogger logger = (ILogger?)_loggerFactory?.CreateLogger<StdioClientTransport>() ?? NullLogger.Instance;
try
{
@ -51,7 +64,7 @@ public sealed class StdioClientTransport : IClientTransport
ProcessStartInfo startInfo = new()
{
FileName = _options.Command,
FileName = command,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
@ -65,9 +78,22 @@ public sealed class StdioClientTransport : IClientTransport
#endif
};
if (!string.IsNullOrWhiteSpace(_options.Arguments))
if (arguments is not null)
{
startInfo.Arguments = _options.Arguments;
#if NET
foreach (string arg in arguments)
{
startInfo.ArgumentList.Add(arg);
}
#else
StringBuilder argsBuilder = new();
foreach (string arg in arguments)
{
PasteArguments.AppendArgument(argsBuilder, arg);
}
startInfo.Arguments = argsBuilder.ToString();
#endif
}
if (_options.EnvironmentVariables != null)

View file

@ -1,5 +1,4 @@
// Protocol/Transport/StdioTransport.cs
namespace ModelContextProtocol.Protocol.Transport;
namespace ModelContextProtocol.Protocol.Transport;
/// <summary>
/// Represents configuration options for the stdio transport.
@ -14,12 +13,29 @@ public record StdioClientTransportOptions
/// <summary>
/// The command to execute to start the server process.
/// </summary>
public required string Command { get; set; }
public required string Command
{
get;
set
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException("Command cannot be null or empty.", nameof(value));
}
field = value;
}
}
/// <summary>
/// Arguments to pass to the server process.
/// </summary>
public string? Arguments { get; set; }
public IList<string>? Arguments { get; set; }
/// <summary>
/// Specifies a transport identifier used for logging purposes.
/// </summary>
public string? Name { get; set; }
/// <summary>
/// The working directory for the server process.

View file

@ -35,6 +35,10 @@ public sealed class StreamClientTransport : IClientTransport
_loggerFactory = loggerFactory;
}
/// <inheritdoc />
public string Name => $"in-memory-stream";
/// <inheritdoc />
public Task<ITransport> ConnectAsync(CancellationToken cancellationToken = default)
{

View file

@ -1,17 +0,0 @@
namespace ModelContextProtocol.Protocol.Transport;
/// <summary>
/// List all transport types
/// </summary>
public static class TransportTypes
{
/// <summary>
/// The name of the Standard IO transport.
/// </summary>
public const string StdIo = "stdio";
/// <summary>
/// The name of the ServerSideEvents transport.
/// </summary>
public const string Sse = "sse";
}

View file

@ -5,7 +5,6 @@ using ModelContextProtocol.Protocol.Messages;
using ModelContextProtocol.Protocol.Transport;
using ModelContextProtocol.Server;
using ModelContextProtocol.Utils;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;