Clean up (use of) McpException and McpTransportException (#321)
* Clean up (use of) McpException * Replace McpTransportException with InvalidOperationException * Add cancellation test
This commit is contained in:
parent
d65ddce268
commit
7d77be68cf
9 changed files with 231 additions and 219 deletions
|
|
@ -1,32 +0,0 @@
|
|||
namespace ModelContextProtocol.Protocol.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Standard JSON-RPC error codes as defined in the MCP specification.
|
||||
/// </summary>
|
||||
internal static class ErrorCodes
|
||||
{
|
||||
/// <summary>
|
||||
/// Invalid JSON was received by the server.
|
||||
/// </summary>
|
||||
public const int ParseError = -32700;
|
||||
|
||||
/// <summary>
|
||||
/// The JSON sent is not a valid Request object.
|
||||
/// </summary>
|
||||
public const int InvalidRequest = -32600;
|
||||
|
||||
/// <summary>
|
||||
/// The method does not exist / is not available.
|
||||
/// </summary>
|
||||
public const int MethodNotFound = -32601;
|
||||
|
||||
/// <summary>
|
||||
/// Invalid method parameter(s).
|
||||
/// </summary>
|
||||
public const int InvalidParams = -32602;
|
||||
|
||||
/// <summary>
|
||||
/// Internal JSON-RPC error.
|
||||
/// </summary>
|
||||
public const int InternalError = -32603;
|
||||
}
|
||||
|
|
@ -42,6 +42,6 @@ public interface IClientTransport
|
|||
/// This method is used by <see cref="McpClientFactory"/> to initialize the connection.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <exception cref="McpTransportException">The transport connection could not be established.</exception>
|
||||
/// <exception cref="InvalidOperationException">The transport connection could not be established.</exception>
|
||||
Task<ITransport> ConnectAsync(CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
namespace ModelContextProtocol.Protocol.Transport;
|
||||
|
||||
/// <summary>
|
||||
/// Represents errors that occur in MCP transport operations.
|
||||
/// </summary>
|
||||
public class McpTransportException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="McpTransportException"/> class.
|
||||
/// </summary>
|
||||
public McpTransportException()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="McpTransportException"/> class with a specified error message.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
public McpTransportException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="McpTransportException"/> class with a specified error message
|
||||
/// and a reference to the inner exception that is the cause of this exception.
|
||||
/// </summary>
|
||||
/// <param name="message">The message that describes the error.</param>
|
||||
/// <param name="innerException">The exception that is the cause of the current exception.</param>
|
||||
public McpTransportException(string message, Exception? innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -58,11 +58,11 @@ internal sealed partial class SseClientSessionTransport : TransportBase
|
|||
|
||||
await _connectionEstablished.Task.WaitAsync(_options.ConnectionTimeout, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex) when (ex is not McpTransportException) // propagate transport exceptions
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogTransportConnectFailed(Name, ex);
|
||||
await CloseAsync().ConfigureAwait(false);
|
||||
throw new McpTransportException("Failed to connect transport", ex);
|
||||
throw new InvalidOperationException("Failed to connect transport", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -110,7 +110,7 @@ internal sealed partial class SseClientSessionTransport : TransportBase
|
|||
else
|
||||
{
|
||||
JsonRpcResponse initializeResponse = JsonSerializer.Deserialize(responseContent, McpJsonUtilities.JsonContext.Default.JsonRpcResponse) ??
|
||||
throw new McpTransportException("Failed to initialize client");
|
||||
throw new InvalidOperationException("Failed to initialize client");
|
||||
|
||||
LogTransportReceivedMessage(Name, messageId);
|
||||
await WriteMessageAsync(initializeResponse, cancellationToken).ConfigureAwait(false);
|
||||
|
|
@ -136,7 +136,7 @@ internal sealed partial class SseClientSessionTransport : TransportBase
|
|||
LogRejectedPost(Name, messageId);
|
||||
}
|
||||
|
||||
throw new McpTransportException("Failed to send message");
|
||||
throw new InvalidOperationException("Failed to send message");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -273,34 +273,18 @@ internal sealed partial class SseClientSessionTransport : TransportBase
|
|||
|
||||
private void HandleEndpointEvent(string data)
|
||||
{
|
||||
try
|
||||
if (string.IsNullOrEmpty(data))
|
||||
{
|
||||
if (string.IsNullOrEmpty(data))
|
||||
{
|
||||
LogTransportEndpointEventInvalid(Name);
|
||||
return;
|
||||
}
|
||||
|
||||
// If data is an absolute URL, the Uri will be constructed entirely from it and not the _sseEndpoint.
|
||||
_messageEndpoint = new Uri(_sseEndpoint, data);
|
||||
|
||||
// Set connected state
|
||||
SetConnected(true);
|
||||
_connectionEstablished.TrySetResult(true);
|
||||
LogTransportEndpointEventInvalid(Name);
|
||||
return;
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
if (_logger.IsEnabled(LogLevel.Trace))
|
||||
{
|
||||
LogTransportEndpointEventParseFailedSensitive(Name, data, ex);
|
||||
}
|
||||
else
|
||||
{
|
||||
LogTransportEndpointEventParseFailed(Name, ex);
|
||||
}
|
||||
|
||||
throw new McpTransportException("Failed to parse endpoint event", ex);
|
||||
}
|
||||
// If data is an absolute URL, the Uri will be constructed entirely from it and not the _sseEndpoint.
|
||||
_messageEndpoint = new Uri(_sseEndpoint, data);
|
||||
|
||||
// Set connected state
|
||||
SetConnected(true);
|
||||
_connectionEstablished.TrySetResult(true);
|
||||
}
|
||||
|
||||
private void CopyAdditionalHeaders(HttpRequestHeaders headers)
|
||||
|
|
|
|||
|
|
@ -23,14 +23,14 @@ internal sealed class StdioClientSessionTransport : StreamClientSessionTransport
|
|||
/// <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.
|
||||
/// be accessed, a <see cref="InvalidOperationException"/> 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">
|
||||
/// <exception cref="InvalidOperationException">
|
||||
/// Thrown when the underlying process has exited or cannot be accessed.
|
||||
/// </exception>
|
||||
public override async Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default)
|
||||
|
|
@ -49,7 +49,7 @@ internal sealed class StdioClientSessionTransport : StreamClientSessionTransport
|
|||
|
||||
if (hasExited)
|
||||
{
|
||||
throw new McpTransportException("Transport is not connected", processException);
|
||||
throw new InvalidOperationException("Transport is not connected", processException);
|
||||
}
|
||||
|
||||
await base.SendMessageAsync(message, cancellationToken).ConfigureAwait(false);
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ public sealed partial class StdioClientTransport : IClientTransport
|
|||
if (!processStarted)
|
||||
{
|
||||
LogTransportProcessStartFailed(logger, endpointName);
|
||||
throw new McpTransportException("Failed to start MCP server process");
|
||||
throw new InvalidOperationException("Failed to start MCP server process");
|
||||
}
|
||||
|
||||
LogTransportProcessStarted(logger, endpointName, process.Id);
|
||||
|
|
@ -176,7 +176,7 @@ public sealed partial class StdioClientTransport : IClientTransport
|
|||
LogTransportShutdownFailed(logger, endpointName, ex2);
|
||||
}
|
||||
|
||||
throw new McpTransportException("Failed to connect transport", ex);
|
||||
throw new InvalidOperationException("Failed to connect transport", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,28 +57,11 @@ internal class StreamClientSessionTransport : TransportBase
|
|||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// For stream-based transports, this implementation serializes the JSON-RPC message to the
|
||||
/// underlying output stream. The specific serialization format includes:
|
||||
/// <list type="bullet">
|
||||
/// <item>A Content-Length header that specifies the byte length of the JSON message</item>
|
||||
/// <item>A blank line separator</item>
|
||||
/// <item>The UTF-8 encoded JSON representation of the message</item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This implementation first checks if the transport is connected and throws a <see cref="McpTransportException"/>
|
||||
/// if it's not. It then extracts the message ID (if present) for logging purposes, serializes the message,
|
||||
/// and writes it to the output stream.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <exception cref="McpTransportException">Thrown when the transport is not connected.</exception>
|
||||
public override async Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
throw new McpTransportException("Transport is not connected");
|
||||
throw new InvalidOperationException("Transport is not connected");
|
||||
}
|
||||
|
||||
string id = "(no id)";
|
||||
|
|
@ -99,7 +82,7 @@ internal class StreamClientSessionTransport : TransportBase
|
|||
catch (Exception ex)
|
||||
{
|
||||
LogTransportSendFailed(Name, id, ex);
|
||||
throw new McpTransportException("Failed to send message", ex);
|
||||
throw new InvalidOperationException("Failed to send message", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ public class StreamServerTransport : TransportBase
|
|||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
throw new McpTransportException("Transport is not connected");
|
||||
throw new InvalidOperationException("Transport is not connected");
|
||||
}
|
||||
|
||||
using var _ = await _sendLock.LockAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
|
@ -80,7 +80,7 @@ public class StreamServerTransport : TransportBase
|
|||
catch (Exception ex)
|
||||
{
|
||||
LogTransportSendFailed(Name, id, ex);
|
||||
throw new McpTransportException("Failed to send message", ex);
|
||||
throw new InvalidOperationException("Failed to send message", ex);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@ public abstract partial class TransportBase : ITransport
|
|||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
throw new McpTransportException("Transport is not connected");
|
||||
throw new InvalidOperationException("Transport is not connected");
|
||||
}
|
||||
|
||||
await _messageChannel.Writer.WriteAsync(message, cancellationToken).ConfigureAwait(false);
|
||||
|
|
|
|||
Loading…
Reference in a new issue