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
27 changed files with 231 additions and 219 deletions
|
|
@ -176,7 +176,7 @@ builder.Services
|
||||||
{
|
{
|
||||||
if (ctx.Params?.Level is null)
|
if (ctx.Params?.Level is null)
|
||||||
{
|
{
|
||||||
throw new McpException("Missing required argument 'level'");
|
throw new McpException("Missing required argument 'level'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
_minimumLoggingLevel = ctx.Params.Level;
|
_minimumLoggingLevel = ctx.Params.Level;
|
||||||
|
|
|
||||||
|
|
@ -155,10 +155,10 @@ internal sealed partial class McpClient : McpEndpoint, IMcpClient
|
||||||
new JsonRpcNotification { Method = NotificationMethods.InitializedNotification },
|
new JsonRpcNotification { Method = NotificationMethods.InitializedNotification },
|
||||||
initializationCts.Token).ConfigureAwait(false);
|
initializationCts.Token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException oce) when (initializationCts.IsCancellationRequested)
|
catch (OperationCanceledException oce) when (initializationCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
LogClientInitializationTimeout(EndpointName);
|
LogClientInitializationTimeout(EndpointName);
|
||||||
throw new McpException("Initialization timed out", oce);
|
throw new TimeoutException("Initialization timed out", oce);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|
|
||||||
49
src/ModelContextProtocol/McpErrorCode.cs
Normal file
49
src/ModelContextProtocol/McpErrorCode.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
namespace ModelContextProtocol;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents standard JSON-RPC error codes as defined in the MCP specification.
|
||||||
|
/// </summary>
|
||||||
|
public enum McpErrorCode
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the JSON received could not be parsed.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This error occurs when the input contains malformed JSON or incorrect syntax.
|
||||||
|
/// </remarks>
|
||||||
|
ParseError = -32700,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the JSON payload does not conform to the expected Request object structure.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The request is considered invalid if it lacks required fields or fails to follow the JSON-RPC protocol.
|
||||||
|
/// </remarks>
|
||||||
|
InvalidRequest = -32600,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that the requested method does not exist or is not available on the server.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This error is returned when the method name specified in the request cannot be found.
|
||||||
|
/// </remarks>
|
||||||
|
MethodNotFound = -32601,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that one or more parameters provided in the request are invalid.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This error is returned when the parameters do not match the expected method signature or constraints.
|
||||||
|
/// This includes cases where required parameters are missing or not understood, such as when a name for
|
||||||
|
/// a tool or prompt is not recognized.
|
||||||
|
/// </remarks>
|
||||||
|
InvalidParams = -32602,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that an internal error occurred while processing the request.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This error is used when the endpoint encounters an unexpected condition that prevents it from fulfilling the request.
|
||||||
|
/// </remarks>
|
||||||
|
InternalError = -32603,
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,15 @@
|
||||||
namespace ModelContextProtocol;
|
namespace ModelContextProtocol;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Represents an exception that is thrown when a Model Context Protocol (MCP) error occurs.
|
/// Represents an exception that is thrown when an Model Context Protocol (MCP) error occurs.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This exception is used to represent failures to do with protocol-level concerns, such as invalid JSON-RPC requests,
|
||||||
|
/// invalid parameters, or internal errors. It is not intended to be used for application-level errors.
|
||||||
|
/// <see cref="Exception.Message"/> or <see cref="ErrorCode"/> from a <see cref="McpException"/> may be
|
||||||
|
/// propagated to the remote endpoint; sensitive information should not be included. If sensitive details need
|
||||||
|
/// to be included, a different exception type should be used.
|
||||||
|
/// </remarks>
|
||||||
public class McpException : Exception
|
public class McpException : Exception
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -33,8 +40,8 @@ public class McpException : Exception
|
||||||
/// Initializes a new instance of the <see cref="McpException"/> class with a specified error message and JSON-RPC error code.
|
/// Initializes a new instance of the <see cref="McpException"/> class with a specified error message and JSON-RPC error code.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="message">The message that describes the error.</param>
|
/// <param name="message">The message that describes the error.</param>
|
||||||
/// <param name="errorCode">A JSON-RPC error code from <see cref="Protocol.Messages.ErrorCodes"/> class.</param>
|
/// <param name="errorCode">A <see cref="McpErrorCode"/>.</param>
|
||||||
public McpException(string message, int? errorCode) : this(message, null, errorCode)
|
public McpException(string message, McpErrorCode errorCode) : this(message, null, errorCode)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,18 +50,15 @@ public class McpException : Exception
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="message">The message that describes the error.</param>
|
/// <param name="message">The message that describes the error.</param>
|
||||||
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
|
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference if no inner exception is specified.</param>
|
||||||
/// <param name="errorCode">A JSON-RPC error code from <see cref="Protocol.Messages.ErrorCodes"/> class.</param>
|
/// <param name="errorCode">A <see cref="McpErrorCode"/>.</param>
|
||||||
public McpException(string message, Exception? innerException, int? errorCode) : base(message, innerException)
|
public McpException(string message, Exception? innerException, McpErrorCode errorCode) : base(message, innerException)
|
||||||
{
|
{
|
||||||
ErrorCode = errorCode;
|
ErrorCode = errorCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the JSON-RPC error code associated with this exception.
|
/// Gets the error code associated with this exception.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <value>
|
|
||||||
/// A standard JSON-RPC error code, or <see langword="null"/> if the exception wasn't caused by a JSON-RPC error.
|
|
||||||
/// </value>
|
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This property contains a standard JSON-RPC error code as defined in the MCP specification. Common error codes include:
|
/// This property contains a standard JSON-RPC error code as defined in the MCP specification. Common error codes include:
|
||||||
/// <list type="bullet">
|
/// <list type="bullet">
|
||||||
|
|
@ -65,5 +69,5 @@ public class McpException : Exception
|
||||||
/// <item><description>-32603: Internal error - Internal JSON-RPC error</description></item>
|
/// <item><description>-32603: Internal error - Internal JSON-RPC error</description></item>
|
||||||
/// </list>
|
/// </list>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public int? ErrorCode { get; }
|
public McpErrorCode ErrorCode { get; } = McpErrorCode.InternalError;
|
||||||
}
|
}
|
||||||
|
|
@ -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.
|
/// This method is used by <see cref="McpClientFactory"/> to initialize the connection.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </remarks>
|
/// </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);
|
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);
|
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);
|
LogTransportConnectFailed(Name, ex);
|
||||||
await CloseAsync().ConfigureAwait(false);
|
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
|
else
|
||||||
{
|
{
|
||||||
JsonRpcResponse initializeResponse = JsonSerializer.Deserialize(responseContent, McpJsonUtilities.JsonContext.Default.JsonRpcResponse) ??
|
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);
|
LogTransportReceivedMessage(Name, messageId);
|
||||||
await WriteMessageAsync(initializeResponse, cancellationToken).ConfigureAwait(false);
|
await WriteMessageAsync(initializeResponse, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
@ -136,7 +136,7 @@ internal sealed partial class SseClientSessionTransport : TransportBase
|
||||||
LogRejectedPost(Name, messageId);
|
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)
|
private void HandleEndpointEvent(string data)
|
||||||
{
|
{
|
||||||
try
|
if (string.IsNullOrEmpty(data))
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(data))
|
LogTransportEndpointEventInvalid(Name);
|
||||||
{
|
return;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
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)
|
private void CopyAdditionalHeaders(HttpRequestHeaders headers)
|
||||||
|
|
|
||||||
|
|
@ -23,14 +23,14 @@ internal sealed class StdioClientSessionTransport : StreamClientSessionTransport
|
||||||
/// <para>
|
/// <para>
|
||||||
/// For stdio-based transports, this implementation first verifies that the underlying process
|
/// 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
|
/// 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>
|
||||||
/// <para>
|
/// <para>
|
||||||
/// After verifying the process state, this method delegates to the base class implementation
|
/// 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.
|
/// to handle the actual message serialization and transmission to the process's standard input stream.
|
||||||
/// </para>
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <exception cref="McpTransportException">
|
/// <exception cref="InvalidOperationException">
|
||||||
/// Thrown when the underlying process has exited or cannot be accessed.
|
/// Thrown when the underlying process has exited or cannot be accessed.
|
||||||
/// </exception>
|
/// </exception>
|
||||||
public override async Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default)
|
public override async Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default)
|
||||||
|
|
@ -49,7 +49,7 @@ internal sealed class StdioClientSessionTransport : StreamClientSessionTransport
|
||||||
|
|
||||||
if (hasExited)
|
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);
|
await base.SendMessageAsync(message, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,7 @@ public sealed partial class StdioClientTransport : IClientTransport
|
||||||
if (!processStarted)
|
if (!processStarted)
|
||||||
{
|
{
|
||||||
LogTransportProcessStartFailed(logger, endpointName);
|
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);
|
LogTransportProcessStarted(logger, endpointName, process.Id);
|
||||||
|
|
@ -176,7 +176,7 @@ public sealed partial class StdioClientTransport : IClientTransport
|
||||||
LogTransportShutdownFailed(logger, endpointName, ex2);
|
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/>
|
/// <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)
|
public override async Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
if (!IsConnected)
|
if (!IsConnected)
|
||||||
{
|
{
|
||||||
throw new McpTransportException("Transport is not connected");
|
throw new InvalidOperationException("Transport is not connected");
|
||||||
}
|
}
|
||||||
|
|
||||||
string id = "(no id)";
|
string id = "(no id)";
|
||||||
|
|
@ -99,7 +82,7 @@ internal class StreamClientSessionTransport : TransportBase
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LogTransportSendFailed(Name, id, 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)
|
if (!IsConnected)
|
||||||
{
|
{
|
||||||
throw new McpTransportException("Transport is not connected");
|
throw new InvalidOperationException("Transport is not connected");
|
||||||
}
|
}
|
||||||
|
|
||||||
using var _ = await _sendLock.LockAsync(cancellationToken).ConfigureAwait(false);
|
using var _ = await _sendLock.LockAsync(cancellationToken).ConfigureAwait(false);
|
||||||
|
|
@ -80,7 +80,7 @@ public class StreamServerTransport : TransportBase
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
LogTransportSendFailed(Name, id, 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)
|
if (!IsConnected)
|
||||||
{
|
{
|
||||||
throw new McpTransportException("Transport is not connected");
|
throw new InvalidOperationException("Transport is not connected");
|
||||||
}
|
}
|
||||||
|
|
||||||
await _messageChannel.Writer.WriteAsync(message, cancellationToken).ConfigureAwait(false);
|
await _messageChannel.Writer.WriteAsync(message, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
|
||||||
BindParameter = (pi, args) =>
|
BindParameter = (pi, args) =>
|
||||||
GetRequestContext(args)?.Services?.GetService(pi.ParameterType) ??
|
GetRequestContext(args)?.Services?.GetService(pi.ParameterType) ??
|
||||||
(pi.HasDefaultValue ? null :
|
(pi.HasDefaultValue ? null :
|
||||||
throw new ArgumentException("No service of the requested type was found.")),
|
throw new InvalidOperationException("No service of the requested type was found.")),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -113,7 +113,7 @@ internal sealed class AIFunctionMcpServerPrompt : McpServerPrompt
|
||||||
BindParameter = (pi, args) =>
|
BindParameter = (pi, args) =>
|
||||||
(GetRequestContext(args)?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
|
(GetRequestContext(args)?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
|
||||||
(pi.HasDefaultValue ? null :
|
(pi.HasDefaultValue ? null :
|
||||||
throw new ArgumentException("No service of the requested type was found.")),
|
throw new InvalidOperationException("No service of the requested type was found.")),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -122,7 +122,7 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool
|
||||||
BindParameter = (pi, args) =>
|
BindParameter = (pi, args) =>
|
||||||
GetRequestContext(args)?.Services?.GetService(pi.ParameterType) ??
|
GetRequestContext(args)?.Services?.GetService(pi.ParameterType) ??
|
||||||
(pi.HasDefaultValue ? null :
|
(pi.HasDefaultValue ? null :
|
||||||
throw new ArgumentException("No service of the requested type was found.")),
|
throw new InvalidOperationException("No service of the requested type was found.")),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,7 +134,7 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool
|
||||||
BindParameter = (pi, args) =>
|
BindParameter = (pi, args) =>
|
||||||
(GetRequestContext(args)?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
|
(GetRequestContext(args)?.Services as IKeyedServiceProvider)?.GetKeyedService(pi.ParameterType, keyedAttr.Key) ??
|
||||||
(pi.HasDefaultValue ? null :
|
(pi.HasDefaultValue ? null :
|
||||||
throw new ArgumentException("No service of the requested type was found.")),
|
throw new InvalidOperationException("No service of the requested type was found.")),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -265,10 +265,14 @@ internal sealed class AIFunctionMcpServerTool : McpServerTool
|
||||||
}
|
}
|
||||||
catch (Exception e) when (e is not OperationCanceledException)
|
catch (Exception e) when (e is not OperationCanceledException)
|
||||||
{
|
{
|
||||||
return new CallToolResponse()
|
string errorMessage = e is McpException ?
|
||||||
|
$"An error occurred invoking '{request.Params?.Name}': {e.Message}" :
|
||||||
|
$"An error occurred invoking '{request.Params?.Name}'.";
|
||||||
|
|
||||||
|
return new()
|
||||||
{
|
{
|
||||||
IsError = true,
|
IsError = true,
|
||||||
Content = [new() { Text = $"An error occurred invoking '{request.Params?.Name}'.", Type = "text" }],
|
Content = [new() { Text = errorMessage, Type = "text" }],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,9 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
||||||
}
|
}
|
||||||
|
|
||||||
var completeHandler = completionsCapability.CompleteHandler ??
|
var completeHandler = completionsCapability.CompleteHandler ??
|
||||||
throw new McpException("Completions capability was enabled, but Complete handler was not specified.");
|
throw new InvalidOperationException(
|
||||||
|
$"{nameof(ServerCapabilities)}.{nameof(ServerCapabilities.Completions)} was enabled, " +
|
||||||
|
$"but {nameof(CompletionsCapability.CompleteHandler)} was not specified.");
|
||||||
|
|
||||||
// This capability is not optional, so return an empty result if there is no handler.
|
// This capability is not optional, so return an empty result if there is no handler.
|
||||||
RequestHandlers.Set(
|
RequestHandlers.Set(
|
||||||
|
|
@ -219,7 +221,9 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
||||||
if ((listResourcesHandler is not { } && listResourceTemplatesHandler is not { }) ||
|
if ((listResourcesHandler is not { } && listResourceTemplatesHandler is not { }) ||
|
||||||
resourcesCapability.ReadResourceHandler is not { } readResourceHandler)
|
resourcesCapability.ReadResourceHandler is not { } readResourceHandler)
|
||||||
{
|
{
|
||||||
throw new McpException("Resources capability was enabled, but ListResources and/or ReadResource handlers were not specified.");
|
throw new InvalidOperationException(
|
||||||
|
$"{nameof(ServerCapabilities)}.{nameof(ServerCapabilities.Resources)} was enabled, " +
|
||||||
|
$"but {nameof(ResourcesCapability.ListResourcesHandler)} or {nameof(ResourcesCapability.ReadResourceHandler)} was not specified.");
|
||||||
}
|
}
|
||||||
|
|
||||||
listResourcesHandler ??= static async (_, _) => new ListResourcesResult();
|
listResourcesHandler ??= static async (_, _) => new ListResourcesResult();
|
||||||
|
|
@ -252,7 +256,9 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
||||||
var unsubscribeHandler = resourcesCapability.UnsubscribeFromResourcesHandler;
|
var unsubscribeHandler = resourcesCapability.UnsubscribeFromResourcesHandler;
|
||||||
if (subscribeHandler is null || unsubscribeHandler is null)
|
if (subscribeHandler is null || unsubscribeHandler is null)
|
||||||
{
|
{
|
||||||
throw new McpException("Resources capability was enabled with subscribe support, but SubscribeToResources and/or UnsubscribeFromResources handlers were not specified.");
|
throw new InvalidOperationException(
|
||||||
|
$"{nameof(ServerCapabilities)}.{nameof(ServerCapabilities.Resources)}.{nameof(ResourcesCapability.Subscribe)} is set, " +
|
||||||
|
$"but {nameof(ResourcesCapability.SubscribeToResourcesHandler)} or {nameof(ResourcesCapability.UnsubscribeFromResourcesHandler)} was not specified.");
|
||||||
}
|
}
|
||||||
|
|
||||||
RequestHandlers.Set(
|
RequestHandlers.Set(
|
||||||
|
|
@ -277,7 +283,10 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
||||||
|
|
||||||
if (listPromptsHandler is null != getPromptHandler is null)
|
if (listPromptsHandler is null != getPromptHandler is null)
|
||||||
{
|
{
|
||||||
throw new McpException("ListPrompts and GetPrompt handlers should be specified together.");
|
throw new InvalidOperationException(
|
||||||
|
$"{nameof(PromptsCapability)}.{nameof(promptsCapability.ListPromptsHandler)} or " +
|
||||||
|
$"{nameof(PromptsCapability)}.{nameof(promptsCapability.GetPromptHandler)} was specified without the other. " +
|
||||||
|
$"Both or neither must be provided.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle prompts provided via DI.
|
// Handle prompts provided via DI.
|
||||||
|
|
@ -310,7 +319,7 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
||||||
return originalGetPromptHandler(request, cancellationToken);
|
return originalGetPromptHandler(request, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new McpException($"Unknown prompt '{request.Params?.Name}'");
|
throw new McpException($"Unknown prompt: '{request.Params?.Name}'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
return prompt.GetAsync(request, cancellationToken);
|
return prompt.GetAsync(request, cancellationToken);
|
||||||
|
|
@ -344,7 +353,9 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
||||||
// Make sure the handlers are provided if the capability is enabled.
|
// Make sure the handlers are provided if the capability is enabled.
|
||||||
if (listPromptsHandler is null || getPromptHandler is null)
|
if (listPromptsHandler is null || getPromptHandler is null)
|
||||||
{
|
{
|
||||||
throw new McpException("ListPrompts and/or GetPrompt handlers were not specified but the Prompts capability was enabled.");
|
throw new InvalidOperationException(
|
||||||
|
$"{nameof(ServerCapabilities)}.{nameof(ServerCapabilities.Prompts)} was enabled, " +
|
||||||
|
$"but {nameof(PromptsCapability.ListPromptsHandler)} or {nameof(PromptsCapability.GetPromptHandler)} was not specified.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -370,7 +381,10 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
||||||
|
|
||||||
if (listToolsHandler is null != callToolHandler is null)
|
if (listToolsHandler is null != callToolHandler is null)
|
||||||
{
|
{
|
||||||
throw new McpException("ListTools and CallTool handlers should be specified together.");
|
throw new InvalidOperationException(
|
||||||
|
$"{nameof(ToolsCapability)}.{nameof(ToolsCapability.ListToolsHandler)} or " +
|
||||||
|
$"{nameof(ToolsCapability)}.{nameof(ToolsCapability.CallToolHandler)} was specified without the other. " +
|
||||||
|
$"Both or neither must be provided.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle tools provided via DI.
|
// Handle tools provided via DI.
|
||||||
|
|
@ -403,7 +417,7 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
||||||
return originalCallToolHandler(request, cancellationToken);
|
return originalCallToolHandler(request, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new McpException($"Unknown tool '{request.Params?.Name}'");
|
throw new McpException($"Unknown tool: '{request.Params?.Name}'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
return tool.InvokeAsync(request, cancellationToken);
|
return tool.InvokeAsync(request, cancellationToken);
|
||||||
|
|
@ -437,7 +451,9 @@ internal sealed class McpServer : McpEndpoint, IMcpServer
|
||||||
// Make sure the handlers are provided if the capability is enabled.
|
// Make sure the handlers are provided if the capability is enabled.
|
||||||
if (listToolsHandler is null || callToolHandler is null)
|
if (listToolsHandler is null || callToolHandler is null)
|
||||||
{
|
{
|
||||||
throw new McpException("ListTools and/or CallTool handlers were not specified but the Tools capability was enabled.");
|
throw new InvalidOperationException(
|
||||||
|
$"{nameof(ServerCapabilities)}.{nameof(ServerCapabilities.Tools)} was enabled, " +
|
||||||
|
$"but {nameof(ToolsCapability.ListToolsHandler)} or {nameof(ToolsCapability.CallToolHandler)} was not specified.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ public static class McpServerExtensions
|
||||||
|
|
||||||
if (server.ClientCapabilities?.Sampling is null)
|
if (server.ClientCapabilities?.Sampling is null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Client connected to the server does not support sampling.", nameof(server));
|
throw new InvalidOperationException("Client does not support sampling.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return server.SendRequestAsync(
|
return server.SendRequestAsync(
|
||||||
|
|
@ -166,7 +166,7 @@ public static class McpServerExtensions
|
||||||
|
|
||||||
if (server.ClientCapabilities?.Sampling is null)
|
if (server.ClientCapabilities?.Sampling is null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Client connected to the server does not support sampling.", nameof(server));
|
throw new InvalidOperationException("Client does not support sampling.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SamplingChatClient(server);
|
return new SamplingChatClient(server);
|
||||||
|
|
@ -204,7 +204,7 @@ public static class McpServerExtensions
|
||||||
|
|
||||||
if (server.ClientCapabilities?.Roots is null)
|
if (server.ClientCapabilities?.Roots is null)
|
||||||
{
|
{
|
||||||
throw new ArgumentException("Client connected to the server does not support roots.", nameof(server));
|
throw new InvalidOperationException("Client does not support roots.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return server.SendRequestAsync(
|
return server.SendRequestAsync(
|
||||||
|
|
|
||||||
|
|
@ -142,15 +142,24 @@ internal sealed partial class McpSession : IDisposable
|
||||||
if (!isUserCancellation && message is JsonRpcRequest request)
|
if (!isUserCancellation && message is JsonRpcRequest request)
|
||||||
{
|
{
|
||||||
LogRequestHandlerException(EndpointName, request.Method, ex);
|
LogRequestHandlerException(EndpointName, request.Method, ex);
|
||||||
|
|
||||||
|
JsonRpcErrorDetail detail = ex is McpException mcpe ?
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Code = (int)mcpe.ErrorCode,
|
||||||
|
Message = mcpe.Message,
|
||||||
|
} :
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Code = (int)McpErrorCode.InternalError,
|
||||||
|
Message = "An error occurred.",
|
||||||
|
};
|
||||||
|
|
||||||
await _transport.SendMessageAsync(new JsonRpcError
|
await _transport.SendMessageAsync(new JsonRpcError
|
||||||
{
|
{
|
||||||
Id = request.Id,
|
Id = request.Id,
|
||||||
JsonRpc = "2.0",
|
JsonRpc = "2.0",
|
||||||
Error = new JsonRpcErrorDetail
|
Error = detail,
|
||||||
{
|
|
||||||
Code = (ex as McpException)?.ErrorCode ?? ErrorCodes.InternalError,
|
|
||||||
Message = ex.Message
|
|
||||||
}
|
|
||||||
}, cancellationToken).ConfigureAwait(false);
|
}, cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
else if (ex is not OperationCanceledException)
|
else if (ex is not OperationCanceledException)
|
||||||
|
|
@ -287,7 +296,7 @@ internal sealed partial class McpSession : IDisposable
|
||||||
if (!_requestHandlers.TryGetValue(request.Method, out var handler))
|
if (!_requestHandlers.TryGetValue(request.Method, out var handler))
|
||||||
{
|
{
|
||||||
LogNoHandlerFoundForRequest(EndpointName, request.Method);
|
LogNoHandlerFoundForRequest(EndpointName, request.Method);
|
||||||
throw new McpException("The method does not exist or is not available.", ErrorCodes.MethodNotFound);
|
throw new McpException($"Method '{request.Method}' is not available.", McpErrorCode.MethodNotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
LogRequestHandlerCalled(EndpointName, request.Method);
|
LogRequestHandlerCalled(EndpointName, request.Method);
|
||||||
|
|
@ -342,7 +351,7 @@ internal sealed partial class McpSession : IDisposable
|
||||||
{
|
{
|
||||||
if (!_transport.IsConnected)
|
if (!_transport.IsConnected)
|
||||||
{
|
{
|
||||||
throw new McpException("Transport is not connected");
|
throw new InvalidOperationException("Transport is not connected");
|
||||||
}
|
}
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
@ -399,7 +408,7 @@ internal sealed partial class McpSession : IDisposable
|
||||||
if (response is JsonRpcError error)
|
if (response is JsonRpcError error)
|
||||||
{
|
{
|
||||||
LogSendingRequestFailed(EndpointName, request.Method, error.Error.Message, error.Error.Code);
|
LogSendingRequestFailed(EndpointName, request.Method, error.Error.Message, error.Error.Code);
|
||||||
throw new McpException($"Request failed (remote): {error.Error.Message}", error.Error.Code);
|
throw new McpException($"Request failed (remote): {error.Error.Message}", (McpErrorCode)error.Error.Code);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response is JsonRpcResponse success)
|
if (response is JsonRpcResponse success)
|
||||||
|
|
@ -443,7 +452,7 @@ internal sealed partial class McpSession : IDisposable
|
||||||
|
|
||||||
if (!_transport.IsConnected)
|
if (!_transport.IsConnected)
|
||||||
{
|
{
|
||||||
throw new McpException("Transport is not connected");
|
throw new InvalidOperationException("Transport is not connected");
|
||||||
}
|
}
|
||||||
|
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
@ -591,13 +600,16 @@ internal sealed partial class McpSession : IDisposable
|
||||||
e = ae.InnerException;
|
e = ae.InnerException;
|
||||||
}
|
}
|
||||||
|
|
||||||
int? intErrorCode = (e as McpException)?.ErrorCode is int errorCode ? errorCode :
|
int? intErrorCode =
|
||||||
e is JsonException ? ErrorCodes.ParseError : null;
|
(int?)((e as McpException)?.ErrorCode) is int errorCode ? errorCode :
|
||||||
|
e is JsonException ? (int)McpErrorCode.ParseError :
|
||||||
|
null;
|
||||||
|
|
||||||
tags.Add("error.type", intErrorCode == null ? e.GetType().FullName : intErrorCode.ToString());
|
string? errorType = intErrorCode?.ToString() ?? e.GetType().FullName;
|
||||||
|
tags.Add("error.type", errorType);
|
||||||
if (intErrorCode is not null)
|
if (intErrorCode is not null)
|
||||||
{
|
{
|
||||||
tags.Add("rpc.jsonrpc.error_code", intErrorCode.ToString());
|
tags.Add("rpc.jsonrpc.error_code", errorType);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activity is { IsAllDataRequested: true })
|
if (activity is { IsAllDataRequested: true })
|
||||||
|
|
|
||||||
|
|
@ -166,7 +166,7 @@ internal static class Program
|
||||||
{
|
{
|
||||||
if (request.Params?.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message))
|
if (request.Params?.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message))
|
||||||
{
|
{
|
||||||
throw new McpException("Missing required argument 'message'");
|
throw new McpException("Missing required argument 'message'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
return new CallToolResponse()
|
return new CallToolResponse()
|
||||||
{
|
{
|
||||||
|
|
@ -179,7 +179,7 @@ internal static class Program
|
||||||
!request.Params.Arguments.TryGetValue("prompt", out var prompt) ||
|
!request.Params.Arguments.TryGetValue("prompt", out var prompt) ||
|
||||||
!request.Params.Arguments.TryGetValue("maxTokens", out var maxTokens))
|
!request.Params.Arguments.TryGetValue("maxTokens", out var maxTokens))
|
||||||
{
|
{
|
||||||
throw new McpException("Missing required arguments 'prompt' and 'maxTokens'");
|
throw new McpException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
var sampleResult = await request.Server.RequestSamplingAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.GetRawText())),
|
var sampleResult = await request.Server.RequestSamplingAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.GetRawText())),
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
@ -191,7 +191,7 @@ internal static class Program
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new McpException($"Unknown tool: {request.Params?.Name}");
|
throw new McpException($"Unknown tool: {request.Params?.Name}", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -285,7 +285,7 @@ internal static class Program
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new McpException($"Unknown prompt: {request.Params?.Name}");
|
throw new McpException($"Unknown prompt: {request.Params?.Name}", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new GetPromptResult()
|
return new GetPromptResult()
|
||||||
|
|
@ -306,7 +306,7 @@ internal static class Program
|
||||||
{
|
{
|
||||||
if (request.Params?.Level is null)
|
if (request.Params?.Level is null)
|
||||||
{
|
{
|
||||||
throw new McpException("Missing required argument 'level'");
|
throw new McpException("Missing required argument 'level'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
_minimumLoggingLevel = request.Params.Level;
|
_minimumLoggingLevel = request.Params.Level;
|
||||||
|
|
@ -388,7 +388,7 @@ internal static class Program
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
throw new McpException("Invalid cursor.", e);
|
throw new McpException($"Invalid cursor: '{request.Params.Cursor}'", e, McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -410,7 +410,7 @@ internal static class Program
|
||||||
{
|
{
|
||||||
if (request.Params?.Uri is null)
|
if (request.Params?.Uri is null)
|
||||||
{
|
{
|
||||||
throw new McpException("Missing required argument 'uri'");
|
throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.Params.Uri.StartsWith("test://dynamic/resource/"))
|
if (request.Params.Uri.StartsWith("test://dynamic/resource/"))
|
||||||
|
|
@ -418,7 +418,7 @@ internal static class Program
|
||||||
var id = request.Params.Uri.Split('/').LastOrDefault();
|
var id = request.Params.Uri.Split('/').LastOrDefault();
|
||||||
if (string.IsNullOrEmpty(id))
|
if (string.IsNullOrEmpty(id))
|
||||||
{
|
{
|
||||||
throw new McpException("Invalid resource URI");
|
throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ReadResourceResult()
|
return new ReadResourceResult()
|
||||||
|
|
@ -435,7 +435,7 @@ internal static class Program
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceContents contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri)
|
ResourceContents contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri)
|
||||||
?? throw new McpException("Resource not found");
|
?? throw new McpException($"Resource not found: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||||
|
|
||||||
return new ReadResourceResult()
|
return new ReadResourceResult()
|
||||||
{
|
{
|
||||||
|
|
@ -447,12 +447,12 @@ internal static class Program
|
||||||
{
|
{
|
||||||
if (request?.Params?.Uri is null)
|
if (request?.Params?.Uri is null)
|
||||||
{
|
{
|
||||||
throw new McpException("Missing required argument 'uri'");
|
throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
if (!request.Params.Uri.StartsWith("test://static/resource/")
|
if (!request.Params.Uri.StartsWith("test://static/resource/")
|
||||||
&& !request.Params.Uri.StartsWith("test://dynamic/resource/"))
|
&& !request.Params.Uri.StartsWith("test://dynamic/resource/"))
|
||||||
{
|
{
|
||||||
throw new McpException("Invalid resource URI");
|
throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
_subscribedResources.TryAdd(request.Params.Uri, true);
|
_subscribedResources.TryAdd(request.Params.Uri, true);
|
||||||
|
|
@ -464,12 +464,12 @@ internal static class Program
|
||||||
{
|
{
|
||||||
if (request?.Params?.Uri is null)
|
if (request?.Params?.Uri is null)
|
||||||
{
|
{
|
||||||
throw new McpException("Missing required argument 'uri'");
|
throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
if (!request.Params.Uri.StartsWith("test://static/resource/")
|
if (!request.Params.Uri.StartsWith("test://static/resource/")
|
||||||
&& !request.Params.Uri.StartsWith("test://dynamic/resource/"))
|
&& !request.Params.Uri.StartsWith("test://dynamic/resource/"))
|
||||||
{
|
{
|
||||||
throw new McpException("Invalid resource URI");
|
throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
_subscribedResources.TryRemove(request.Params.Uri, out _);
|
_subscribedResources.TryRemove(request.Params.Uri, out _);
|
||||||
|
|
@ -514,7 +514,7 @@ internal static class Program
|
||||||
return new CompleteResult() { Completion = new() { Values = values, HasMore = false, Total = values.Length } };
|
return new CompleteResult() { Completion = new() { Values = values, HasMore = false, Total = values.Length } };
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new McpException($"Unknown reference type: {request.Params?.Ref.Type}");
|
throw new McpException($"Unknown reference type: '{request.Params?.Ref.Type}'", McpErrorCode.InvalidParams);
|
||||||
};
|
};
|
||||||
|
|
||||||
return new() { CompleteHandler = handler };
|
return new() { CompleteHandler = handler };
|
||||||
|
|
|
||||||
|
|
@ -157,13 +157,13 @@ public class Program
|
||||||
{
|
{
|
||||||
if (request.Params is null)
|
if (request.Params is null)
|
||||||
{
|
{
|
||||||
throw new McpException("Missing required parameter 'name'");
|
throw new McpException("Missing required parameter 'name'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
if (request.Params.Name == "echo")
|
if (request.Params.Name == "echo")
|
||||||
{
|
{
|
||||||
if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message))
|
if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message))
|
||||||
{
|
{
|
||||||
throw new McpException("Missing required argument 'message'");
|
throw new McpException("Missing required argument 'message'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
return new CallToolResponse()
|
return new CallToolResponse()
|
||||||
{
|
{
|
||||||
|
|
@ -176,7 +176,7 @@ public class Program
|
||||||
!request.Params.Arguments.TryGetValue("prompt", out var prompt) ||
|
!request.Params.Arguments.TryGetValue("prompt", out var prompt) ||
|
||||||
!request.Params.Arguments.TryGetValue("maxTokens", out var maxTokens))
|
!request.Params.Arguments.TryGetValue("maxTokens", out var maxTokens))
|
||||||
{
|
{
|
||||||
throw new McpException("Missing required arguments 'prompt' and 'maxTokens'");
|
throw new McpException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
var sampleResult = await request.Server.RequestSamplingAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.ToString())),
|
var sampleResult = await request.Server.RequestSamplingAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.ToString())),
|
||||||
cancellationToken);
|
cancellationToken);
|
||||||
|
|
@ -188,7 +188,7 @@ public class Program
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new McpException($"Unknown tool: {request.Params.Name}");
|
throw new McpException($"Unknown tool: '{request.Params.Name}'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -220,9 +220,9 @@ public class Program
|
||||||
var startIndexAsString = Encoding.UTF8.GetString(Convert.FromBase64String(requestParams.Cursor));
|
var startIndexAsString = Encoding.UTF8.GetString(Convert.FromBase64String(requestParams.Cursor));
|
||||||
startIndex = Convert.ToInt32(startIndexAsString);
|
startIndex = Convert.ToInt32(startIndexAsString);
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
throw new McpException("Invalid cursor");
|
throw new McpException($"Invalid cursor: '{requestParams.Cursor}'", e, McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -244,7 +244,7 @@ public class Program
|
||||||
{
|
{
|
||||||
if (request.Params?.Uri is null)
|
if (request.Params?.Uri is null)
|
||||||
{
|
{
|
||||||
throw new McpException("Missing required argument 'uri'");
|
throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (request.Params.Uri.StartsWith("test://dynamic/resource/"))
|
if (request.Params.Uri.StartsWith("test://dynamic/resource/"))
|
||||||
|
|
@ -252,7 +252,7 @@ public class Program
|
||||||
var id = request.Params.Uri.Split('/').LastOrDefault();
|
var id = request.Params.Uri.Split('/').LastOrDefault();
|
||||||
if (string.IsNullOrEmpty(id))
|
if (string.IsNullOrEmpty(id))
|
||||||
{
|
{
|
||||||
throw new McpException("Invalid resource URI");
|
throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ReadResourceResult()
|
return new ReadResourceResult()
|
||||||
|
|
@ -269,7 +269,7 @@ public class Program
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceContents? contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri) ??
|
ResourceContents? contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri) ??
|
||||||
throw new McpException("Resource not found");
|
throw new McpException($"Resource not found: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||||
|
|
||||||
return new ReadResourceResult()
|
return new ReadResourceResult()
|
||||||
{
|
{
|
||||||
|
|
@ -316,7 +316,7 @@ public class Program
|
||||||
{
|
{
|
||||||
if (request.Params is null)
|
if (request.Params is null)
|
||||||
{
|
{
|
||||||
throw new McpException("Missing required parameter 'name'");
|
throw new McpException("Missing required parameter 'name'", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
List<PromptMessage> messages = new();
|
List<PromptMessage> messages = new();
|
||||||
if (request.Params.Name == "simple_prompt")
|
if (request.Params.Name == "simple_prompt")
|
||||||
|
|
@ -366,7 +366,7 @@ public class Program
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new McpException($"Unknown prompt: {request.Params.Name}");
|
throw new McpException($"Unknown prompt: {request.Params.Name}", McpErrorCode.InvalidParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new GetPromptResult()
|
return new GetPromptResult()
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue