Improve progress reporting (#145)
* Improve progress reporting - Enable injecting an `IProgress<>` into a tool call to report progress when the client has supplied a progress token - Add a custom type for ProgressToken and ProgressNotification to map to the schema - Simplify RequestId to match ProgressToken's shape - Serialize StdioClientTransport's SendMessageAsync implementation - Add strongly-typed names for all request methods - Change all request IDs created by McpJsonRpcEndpoint to include a guid for that endpoint * Address PR feedback
This commit is contained in:
parent
0294c574a6
commit
2c75acded8
11 changed files with 627 additions and 228 deletions
|
|
@ -34,4 +34,27 @@ public static class NotificationMethods
|
|||
/// Sent by the server when a log message is generated.
|
||||
/// </summary>
|
||||
public const string LoggingMessageNotification = "notifications/message";
|
||||
|
||||
/// <summary>
|
||||
/// Sent from the client to the server after initialization has finished.
|
||||
/// </summary>
|
||||
public const string InitializedNotification = "notifications/initialized";
|
||||
|
||||
/// <summary>
|
||||
/// Sent to inform the receiver of a progress update for a long-running request.
|
||||
/// </summary>
|
||||
public const string ProgressNotification = "notifications/progress";
|
||||
|
||||
/// <summary>
|
||||
/// Sent by either side to indicate that it is cancelling a previously-issued request.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification
|
||||
/// MAY arrive after the request has already finished.
|
||||
///
|
||||
/// This notification indicates that the result will be unused, so any associated processing SHOULD cease.
|
||||
///
|
||||
/// A client MUST NOT attempt to cancel its `initialize` request.".
|
||||
/// </remarks>
|
||||
public const string CancelledNotification = "notifications/cancelled";
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
namespace ModelContextProtocol.Protocol.Messages;
|
||||
|
||||
/// <summary>Provides names of standard operations for use with registering handlers.</summary>
|
||||
/// <remarks>
|
||||
/// These values should not be inspected or relied on for their exact values.
|
||||
/// They serve only as opaque keys. They will be stable for the lifetime of a process
|
||||
/// but may change between versions of this library.
|
||||
/// </remarks>
|
||||
public static class OperationNames
|
||||
{
|
||||
/// <summary>Gets the name of the sampling operation.</summary>
|
||||
public static string Sampling { get; } = "operation/sampling";
|
||||
|
||||
/// <summary>Gets the name of the roots operation.</summary>
|
||||
public static string Roots { get; } = "operation/roots";
|
||||
|
||||
/// <summary>Gets the name of the list tools operation.</summary>
|
||||
public static string ListTools { get; } = "operation/listTools";
|
||||
|
||||
/// <summary>Gets the name of the call tool operation.</summary>
|
||||
public static string CallTool { get; } = "operation/callTool";
|
||||
|
||||
/// <summary>Gets the name of the list prompts operation.</summary>
|
||||
public static string ListPrompts { get; } = "operation/listPrompts";
|
||||
|
||||
/// <summary>Gets the name of the get prompt operation.</summary>
|
||||
public static string GetPrompt { get; } = "operation/getPrompt";
|
||||
|
||||
/// <summary>Gets the name of the list resources operation.</summary>
|
||||
public static string ListResources { get; } = "operation/listResources";
|
||||
|
||||
/// <summary>Gets the name of the read resource operation.</summary>
|
||||
public static string ReadResource { get; } = "operation/readResource";
|
||||
|
||||
/// <summary>Gets the name of the get completion operation.</summary>
|
||||
public static string GetCompletion { get; } = "operation/getCompletion";
|
||||
|
||||
/// <summary>Gets the name of the subscribe to resources operation.</summary>
|
||||
public static string SubscribeToResources { get; } = "operation/subscribeToResources";
|
||||
|
||||
/// <summary>Gets the name of the subscribe to resources operation.</summary>
|
||||
public static string UnsubscribeFromResources { get; } = "operation/unsubscribeFromResources";
|
||||
}
|
||||
|
|
@ -0,0 +1,108 @@
|
|||
using System.ComponentModel;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ModelContextProtocol.Protocol.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// An out-of-band notification used to inform the receiver of a progress update for a long-running request.
|
||||
/// <see href="https://github.com/modelcontextprotocol/specification/blob/main/schema/">See the schema for details</see>
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(Converter))]
|
||||
public class ProgressNotification
|
||||
{
|
||||
/// <summary>
|
||||
/// The progress token which was given in the initial request, used to associate this notification with the request that is proceeding.
|
||||
/// </summary>
|
||||
public required ProgressToken ProgressToken { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The progress thus far. This should increase every time progress is made, even if the total is unknown.
|
||||
/// </summary>
|
||||
public required ProgressNotificationValue Progress { get; init; }
|
||||
|
||||
/// <summary>Provides a <see cref="JsonConverter"/> for <see cref="ProgressNotification"/>.</summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class Converter : JsonConverter<ProgressNotification>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override ProgressNotification? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
ProgressToken? progressToken = null;
|
||||
float? progress = null;
|
||||
float? total = null;
|
||||
string? message = null;
|
||||
|
||||
while (reader.Read() && reader.TokenType != JsonTokenType.EndObject)
|
||||
{
|
||||
if (reader.TokenType == JsonTokenType.PropertyName)
|
||||
{
|
||||
var propertyName = reader.GetString();
|
||||
reader.Read();
|
||||
switch (propertyName)
|
||||
{
|
||||
case "progressToken":
|
||||
progressToken = (ProgressToken)JsonSerializer.Deserialize(ref reader, options.GetTypeInfo(typeof(ProgressToken)))!;
|
||||
break;
|
||||
|
||||
case "progress":
|
||||
progress = reader.GetSingle();
|
||||
break;
|
||||
|
||||
case "total":
|
||||
total = reader.GetSingle();
|
||||
break;
|
||||
|
||||
case "message":
|
||||
message = reader.GetString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (progress is null)
|
||||
{
|
||||
throw new JsonException("Missing required property 'progress'.");
|
||||
}
|
||||
|
||||
if (progressToken is null)
|
||||
{
|
||||
throw new JsonException("Missing required property 'progressToken'.");
|
||||
}
|
||||
|
||||
return new ProgressNotification
|
||||
{
|
||||
ProgressToken = progressToken.GetValueOrDefault(),
|
||||
Progress = new ProgressNotificationValue()
|
||||
{
|
||||
Progress = progress.GetValueOrDefault(),
|
||||
Total = total,
|
||||
Message = message
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, ProgressNotification value, JsonSerializerOptions options)
|
||||
{
|
||||
writer.WriteStartObject();
|
||||
|
||||
writer.WritePropertyName("progressToken");
|
||||
JsonSerializer.Serialize(writer, value.ProgressToken, options.GetTypeInfo(typeof(ProgressToken)));
|
||||
|
||||
writer.WriteNumber("progress", value.Progress.Progress);
|
||||
|
||||
if (value.Progress.Total is { } total)
|
||||
{
|
||||
writer.WriteNumber("total", total);
|
||||
}
|
||||
|
||||
if (value.Progress.Message is { } message)
|
||||
{
|
||||
writer.WriteString("message", message);
|
||||
}
|
||||
|
||||
writer.WriteEndObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
102
src/ModelContextProtocol/Protocol/Messages/ProgressToken.cs
Normal file
102
src/ModelContextProtocol/Protocol/Messages/ProgressToken.cs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
using ModelContextProtocol.Utils;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ModelContextProtocol.Protocol.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a progress token, which can be either a string or an integer.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(Converter))]
|
||||
public readonly struct ProgressToken : IEquatable<ProgressToken>
|
||||
{
|
||||
/// <summary>The id, either a string or a boxed long or null.</summary>
|
||||
private readonly object? _id;
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="ProgressToken"/> with a specified value.</summary>
|
||||
/// <param name="value">The required ID value.</param>
|
||||
public ProgressToken(string value)
|
||||
{
|
||||
Throw.IfNull(value);
|
||||
_id = value;
|
||||
}
|
||||
|
||||
/// <summary>Initializes a new instance of the <see cref="ProgressToken"/> with a specified value.</summary>
|
||||
/// <param name="value">The required ID value.</param>
|
||||
public ProgressToken(long value)
|
||||
{
|
||||
// Box the long. Progress tokens are almost always strings in practice, so this should be rare.
|
||||
_id = value;
|
||||
}
|
||||
|
||||
/// <summary>Gets whether the identifier is uninitialized.</summary>
|
||||
public bool IsDefault => _id is null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string? ToString() =>
|
||||
_id is string stringValue ? $"\"{stringValue}\"" :
|
||||
_id is long longValue ? longValue.ToString(CultureInfo.InvariantCulture) :
|
||||
null;
|
||||
|
||||
/// <summary>
|
||||
/// Compares this ProgressToken to another ProgressToken.
|
||||
/// </summary>
|
||||
public bool Equals(ProgressToken other) => Equals(_id, other._id);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj) => obj is ProgressToken other && Equals(other);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode() => _id?.GetHashCode() ?? 0;
|
||||
|
||||
/// <summary>
|
||||
/// Compares two ProgressTokens for equality.
|
||||
/// </summary>
|
||||
public static bool operator ==(ProgressToken left, ProgressToken right) => left.Equals(right);
|
||||
|
||||
/// <summary>
|
||||
/// Compares two ProgressTokens for inequality.
|
||||
/// </summary>
|
||||
public static bool operator !=(ProgressToken left, ProgressToken right) => !left.Equals(right);
|
||||
|
||||
/// <summary>
|
||||
/// JSON converter for ProgressToken that handles both string and number values.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class Converter : JsonConverter<ProgressToken>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override ProgressToken Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return reader.TokenType switch
|
||||
{
|
||||
JsonTokenType.String => new(reader.GetString()!),
|
||||
JsonTokenType.Number => new(reader.GetInt64()),
|
||||
_ => throw new JsonException("progressToken must be a string or an integer"),
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, ProgressToken value, JsonSerializerOptions options)
|
||||
{
|
||||
Throw.IfNull(writer);
|
||||
|
||||
switch (value._id)
|
||||
{
|
||||
case string str:
|
||||
writer.WriteStringValue(str);
|
||||
return;
|
||||
|
||||
case long longValue:
|
||||
writer.WriteNumberValue(longValue);
|
||||
return;
|
||||
|
||||
case null:
|
||||
writer.WriteStringValue(string.Empty);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,76 +1,55 @@
|
|||
using System.Text.Json.Serialization;
|
||||
using ModelContextProtocol.Utils;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ModelContextProtocol.Protocol.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a JSON-RPC request identifier which can be either a string or a number.
|
||||
/// Represents a JSON-RPC request identifier, which can be either a string or an integer.
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(RequestIdConverter))]
|
||||
[JsonConverter(typeof(Converter))]
|
||||
public readonly struct RequestId : IEquatable<RequestId>
|
||||
{
|
||||
private readonly object _value;
|
||||
/// <summary>The id, either a string or a boxed long or null.</summary>
|
||||
private readonly object? _id;
|
||||
|
||||
private RequestId(object value)
|
||||
/// <summary>Initializes a new instance of the <see cref="RequestId"/> with a specified value.</summary>
|
||||
/// <param name="value">The required ID value.</param>
|
||||
public RequestId(string value)
|
||||
{
|
||||
_value = value;
|
||||
Throw.IfNull(value);
|
||||
_id = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new RequestId from a string.
|
||||
/// </summary>
|
||||
/// <param name="value">The Id</param>
|
||||
/// <returns>Wrapped Id object</returns>
|
||||
public static RequestId FromString(string value) => new(value);
|
||||
/// <summary>Initializes a new instance of the <see cref="RequestId"/> with a specified value.</summary>
|
||||
/// <param name="value">The required ID value.</param>
|
||||
public RequestId(long value)
|
||||
{
|
||||
// Box the long. Request IDs are almost always strings in practice, so this should be rare.
|
||||
_id = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new RequestId from a number.
|
||||
/// </summary>
|
||||
/// <param name="value">The Id</param>
|
||||
/// <returns>Wrapped Id object</returns>
|
||||
public static RequestId FromNumber(long value) => new(value);
|
||||
/// <summary>Gets whether the identifier is uninitialized.</summary>
|
||||
public bool IsDefault => _id is null;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the RequestId is a string.
|
||||
/// </summary>
|
||||
public bool IsString => _value is string;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the RequestId is a number.
|
||||
/// </summary>
|
||||
public bool IsNumber => _value is long;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the request id is valid (has a value)
|
||||
/// </summary>
|
||||
public bool IsValid => _value != null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RequestId as a string.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Thrown if the RequestId is not a string</exception>"
|
||||
public string AsString => _value as string ?? throw new InvalidOperationException("RequestId is not a string");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the RequestId as a number.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Thrown if the RequestId is not a number</exception>""
|
||||
public long AsNumber => _value is long number ? number : throw new InvalidOperationException("RequestId is not a number");
|
||||
|
||||
/// <summary>
|
||||
/// Returns the string representation of the RequestId. Will box the value if it is a number.
|
||||
/// </summary>
|
||||
public override string ToString() => _value.ToString() ?? "";
|
||||
/// <inheritdoc />
|
||||
public override string ToString() =>
|
||||
_id is string stringValue ? $"\"{stringValue}\"" :
|
||||
_id is long longValue ? longValue.ToString(CultureInfo.InvariantCulture) :
|
||||
string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Compares this RequestId to another RequestId.
|
||||
/// </summary>
|
||||
public bool Equals(RequestId other) => _value.Equals(other._value);
|
||||
public bool Equals(RequestId other) => Equals(_id, other._id);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj) => obj is RequestId other && Equals(other);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode() => _value.GetHashCode();
|
||||
public override int GetHashCode() => _id?.GetHashCode() ?? 0;
|
||||
|
||||
/// <summary>
|
||||
/// Compares two RequestIds for equality.
|
||||
|
|
@ -81,4 +60,43 @@ public readonly struct RequestId : IEquatable<RequestId>
|
|||
/// Compares two RequestIds for inequality.
|
||||
/// </summary>
|
||||
public static bool operator !=(RequestId left, RequestId right) => !left.Equals(right);
|
||||
|
||||
/// <summary>
|
||||
/// JSON converter for RequestId that handles both string and number values.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public sealed class Converter: JsonConverter<RequestId>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override RequestId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return reader.TokenType switch
|
||||
{
|
||||
JsonTokenType.String => new(reader.GetString()!),
|
||||
JsonTokenType.Number => new(reader.GetInt64()),
|
||||
_ => throw new JsonException("requestId must be a string or an integer"),
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, RequestId value, JsonSerializerOptions options)
|
||||
{
|
||||
Throw.IfNull(writer);
|
||||
|
||||
switch (value._id)
|
||||
{
|
||||
case string str:
|
||||
writer.WriteStringValue(str);
|
||||
return;
|
||||
|
||||
case long longValue:
|
||||
writer.WriteNumberValue(longValue);
|
||||
return;
|
||||
|
||||
case null:
|
||||
writer.WriteStringValue(string.Empty);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
using ModelContextProtocol.Utils;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ModelContextProtocol.Protocol.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// JSON converter for RequestId that handles both string and number values.
|
||||
/// </summary>
|
||||
public class RequestIdConverter : JsonConverter<RequestId>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override RequestId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
return reader.TokenType switch
|
||||
{
|
||||
JsonTokenType.String => RequestId.FromString(reader.GetString()!),
|
||||
JsonTokenType.Number => RequestId.FromNumber(reader.GetInt64()),
|
||||
_ => throw new JsonException("RequestId must be either a string or a number"),
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, RequestId value, JsonSerializerOptions options)
|
||||
{
|
||||
Throw.IfNull(writer);
|
||||
|
||||
if (value.IsString)
|
||||
{
|
||||
writer.WriteStringValue(value.AsString);
|
||||
}
|
||||
else
|
||||
{
|
||||
writer.WriteNumberValue(value.AsNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
82
src/ModelContextProtocol/Protocol/Messages/RequestMethods.cs
Normal file
82
src/ModelContextProtocol/Protocol/Messages/RequestMethods.cs
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
namespace ModelContextProtocol.Protocol.Messages;
|
||||
|
||||
/// <summary>
|
||||
/// Provides names for request methods used in the Model Context Protocol (MCP).
|
||||
/// </summary>
|
||||
public static class RequestMethods
|
||||
{
|
||||
/// <summary>
|
||||
/// Sent from the client to request a list of tools the server has.
|
||||
/// </summary>
|
||||
public const string ToolsList = "tools/list";
|
||||
|
||||
/// <summary>
|
||||
/// Used by the client to invoke a tool provided by the server.
|
||||
/// </summary>
|
||||
public const string ToolsCall = "tools/call";
|
||||
|
||||
/// <summary>
|
||||
/// Sent from the client to request a list of prompts and prompt templates the server has.
|
||||
/// </summary>
|
||||
public const string PromptsList = "prompts/list";
|
||||
|
||||
/// <summary>
|
||||
/// Used by the client to get a prompt provided by the server.
|
||||
/// </summary>
|
||||
public const string PromptsGet = "prompts/get";
|
||||
|
||||
/// <summary>
|
||||
/// Sent from the client to request a list of resources the server has.
|
||||
/// </summary>
|
||||
public const string ResourcesList = "resources/list";
|
||||
|
||||
/// <summary>
|
||||
/// Sent from the client to the server, to read a specific resource URI.
|
||||
/// </summary>
|
||||
public const string ResourcesRead = "resources/read";
|
||||
|
||||
/// <summary>
|
||||
/// Sent from the client to request a list of resource templates the server has.
|
||||
/// </summary>
|
||||
public const string ResourcesTemplatesList = "resources/templates/list";
|
||||
|
||||
/// <summary>
|
||||
/// Sent from the client to request resources/updated notifications from the server whenever a particular resource changes.
|
||||
/// </summary>
|
||||
public const string ResourcesSubscribe = "resources/subscribe";
|
||||
|
||||
/// <summary>
|
||||
/// Sent from the client to request cancellation of resources/updated notifications from the server.
|
||||
/// </summary>
|
||||
public const string ResourcesUnsubscribe = "resources/unsubscribe";
|
||||
|
||||
/// <summary>
|
||||
/// Sent from the server to request a list of root URIs from the client.
|
||||
/// </summary>
|
||||
public const string RootsList = "roots/list";
|
||||
|
||||
/// <summary>
|
||||
/// A ping, issued by either the server or the client, to check that the other party is still alive.
|
||||
/// </summary>
|
||||
public const string Ping = "ping";
|
||||
|
||||
/// <summary>
|
||||
/// A request from the client to the server, to enable or adjust logging.
|
||||
/// </summary>
|
||||
public const string LoggingSetLevel = "logging/setLevel";
|
||||
|
||||
/// <summary>
|
||||
/// A request from the client to the server, to ask for completion options.
|
||||
/// </summary>
|
||||
public const string CompletionComplete = "completion/complete";
|
||||
|
||||
/// <summary>
|
||||
/// A request from the server to sample an LLM via the client.
|
||||
/// </summary>
|
||||
public const string SamplingCreateMessage = "sampling/createMessage";
|
||||
|
||||
/// <summary>
|
||||
/// This request is sent from the client to the server when it first connects, asking it to begin initialization.
|
||||
/// </summary>
|
||||
public const string Initialize = "initialize";
|
||||
}
|
||||
|
|
@ -118,7 +118,7 @@ internal sealed class SseClientSessionTransport : TransportBase
|
|||
var responseContent = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Check if the message was an initialize request
|
||||
if (message is JsonRpcRequest request && request.Method == "initialize")
|
||||
if (message is JsonRpcRequest request && request.Method == RequestMethods.Initialize)
|
||||
{
|
||||
// If the response is not a JSON-RPC response, it is an SSE message
|
||||
if (responseContent.Equals("accepted", StringComparison.OrdinalIgnoreCase))
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ internal sealed class StdioClientStreamTransport : TransportBase
|
|||
private readonly ILogger _logger;
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
private readonly DataReceivedEventHandler _logProcessErrors;
|
||||
private readonly SemaphoreSlim _sendLock = new(1, 1);
|
||||
private Process? _process;
|
||||
private Task? _readTask;
|
||||
private CancellationTokenSource? _shutdownCts;
|
||||
|
|
@ -150,6 +151,8 @@ internal sealed class StdioClientStreamTransport : TransportBase
|
|||
/// <inheritdoc/>
|
||||
public override async Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default)
|
||||
{
|
||||
using var _ = await _sendLock.LockAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
if (!IsConnected || _process?.HasExited == true)
|
||||
{
|
||||
_logger.TransportNotConnected(EndpointName);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
namespace ModelContextProtocol.Protocol.Types;
|
||||
using ModelContextProtocol.Protocol.Messages;
|
||||
|
||||
namespace ModelContextProtocol.Protocol.Types;
|
||||
|
||||
/// <summary>
|
||||
/// A request from the server to get a list of root URIs from the client.
|
||||
|
|
@ -10,5 +12,5 @@ public class ListRootsRequestParams
|
|||
/// Optional progress token for out-of-band progress notifications.
|
||||
/// </summary>
|
||||
[System.Text.Json.Serialization.JsonPropertyName("progressToken")]
|
||||
public string? ProgressToken { get; init; }
|
||||
public ProgressToken? ProgressToken { get; init; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using ModelContextProtocol.Protocol.Messages;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ModelContextProtocol.Protocol.Types;
|
||||
|
|
@ -11,5 +12,5 @@ public class RequestParamsMetadata
|
|||
/// If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications.
|
||||
/// </summary>
|
||||
[JsonPropertyName("progressToken")]
|
||||
public object ProgressToken { get; set; } = default!;
|
||||
public ProgressToken? ProgressToken { get; set; } = default!;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue