using Microsoft.Extensions.Logging;
using ModelContextProtocol.Protocol.Messages;
using System;
using System.Diagnostics;
namespace ModelContextProtocol.Protocol.Transport;
/// Provides the client side of a stdio-based session transport.
internal sealed class StdioClientSessionTransport : StreamClientSessionTransport
{
private readonly StdioClientTransportOptions _options;
private readonly Process _process;
public StdioClientSessionTransport(StdioClientTransportOptions options, Process process, string endpointName, ILoggerFactory? loggerFactory)
: base(process.StandardInput, process.StandardOutput, endpointName, loggerFactory)
{
_process = process;
_options = options;
}
///
///
///
/// For stdio-based transports, this implementation first verifies that the underlying process
/// is still running before attempting to send the message. If the process has exited or cannot
/// be accessed, a is thrown with details about the failure.
///
///
/// After verifying the process state, this method delegates to the base class implementation
/// to handle the actual message serialization and transmission to the process's standard input stream.
///
///
///
/// Thrown when the underlying process has exited or cannot be accessed.
///
public override async Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default)
{
Exception? processException = null;
bool hasExited = false;
try
{
hasExited = _process.HasExited;
}
catch (Exception e)
{
processException = e;
hasExited = true;
}
if (hasExited)
{
throw new McpTransportException("Transport is not connected", processException);
}
await base.SendMessageAsync(message, cancellationToken).ConfigureAwait(false);
}
///
protected override ValueTask CleanupAsync(CancellationToken cancellationToken)
{
try
{
StdioClientTransport.DisposeProcess(_process, processRunning: true, _options.ShutdownTimeout, Name);
}
catch (Exception ex)
{
LogTransportShutdownFailed(Name, ex);
}
return base.CleanupAsync(cancellationToken);
}
}