csharp-sdk/src/ModelContextProtocol/Protocol/Transport/StdioClientSessionTransport.cs
2025-04-14 10:55:45 -04:00

72 lines
2.5 KiB
C#

using Microsoft.Extensions.Logging;
using ModelContextProtocol.Protocol.Messages;
using System;
using System.Diagnostics;
namespace ModelContextProtocol.Protocol.Transport;
/// <summary>Provides the client side of a stdio-based session transport.</summary>
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;
}
/// <inheritdoc/>
/// <remarks>
/// <para>
/// 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 <see cref="McpTransportException"/> is thrown with details about the failure.
/// </para>
/// <para>
/// 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.
/// </para>
/// </remarks>
/// <exception cref="McpTransportException">
/// Thrown when the underlying process has exited or cannot be accessed.
/// </exception>
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);
}
/// <inheritdoc/>
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);
}
}