Clean up types (#6)
This commit is contained in:
parent
c9ace236a0
commit
211cd2175b
24 changed files with 154 additions and 307 deletions
|
|
@ -118,7 +118,7 @@ public class IntegrationTests
|
|||
// assert
|
||||
Assert.NotNull(result);
|
||||
Assert.False(result.IsError);
|
||||
var textContent = Assert.Single(result.Content.Where(c => c.Type == "text"));
|
||||
var textContent = Assert.Single(result.Content, c => c.Type == "text");
|
||||
Assert.Equal("Echo: Hello MCP!", textContent.Text);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -107,6 +107,8 @@ public interface IMcpClient : IAsyncDisposable
|
|||
|
||||
/// <summary>
|
||||
/// Sends a generic JSON-RPC request to the server.
|
||||
/// NB! This is a temporary method that is available to send not yet implemented feature messages.
|
||||
/// Once all MCP features are implemented this will be made private, as it is purely a convenience for those who wish to implement features ahead of the library.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The expected response type.</typeparam>
|
||||
/// <param name="request">The JSON-RPC request to send.</param>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ using McpDotNet.Protocol.Messages;
|
|||
using System.Diagnostics;
|
||||
using System.Text.Json;
|
||||
using McpDotNet.Utils.Json;
|
||||
using System.Xml.Linq;
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal class McpClient : IMcpClient
|
||||
|
|
@ -251,7 +250,7 @@ internal class McpClient : IMcpClient
|
|||
).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private static Dictionary<string, object> CreateParametersDictionary(string nameParameter, Dictionary<string, object>? optionalArguments = null)
|
||||
private static Dictionary<string, object?> CreateParametersDictionary(string nameParameter, Dictionary<string, object>? optionalArguments = null)
|
||||
{
|
||||
var parameters = new Dictionary<string, object?>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
/// <summary>
|
||||
/// Standard JSON-RPC error codes as defined in the MCP specification.
|
||||
/// </summary>
|
||||
public static class ErrorCodes
|
||||
internal static class ErrorCodes
|
||||
{
|
||||
/// <summary>
|
||||
/// Invalid JSON was received by the server.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using System.Text.Json.Serialization;
|
|||
/// <summary>
|
||||
/// An error response message in the JSON-RPC protocol.
|
||||
/// </summary>
|
||||
public record JsonRpcError : IJsonRpcMessageWithId
|
||||
internal record JsonRpcError : IJsonRpcMessageWithId
|
||||
{
|
||||
/// <summary>
|
||||
/// JSON-RPC protocol version. Always "2.0".
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using System.Text.Json.Serialization;
|
|||
/// <summary>
|
||||
/// Detailed error information for JSON-RPC error responses.
|
||||
/// </summary>
|
||||
public record JsonRpcErrorDetail
|
||||
internal record JsonRpcErrorDetail
|
||||
{
|
||||
/// <summary>
|
||||
/// Integer error code.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using System.Text.Json.Serialization;
|
|||
/// <summary>
|
||||
/// A successful response message in the JSON-RPC protocol.
|
||||
/// </summary>
|
||||
public record JsonRpcResponse : IJsonRpcMessageWithId
|
||||
internal record JsonRpcResponse : IJsonRpcMessageWithId
|
||||
{
|
||||
/// <summary>
|
||||
/// JSON-RPC protocol version. Always "2.0".
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
namespace McpDotNet.Protocol.Messages;
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -16,49 +15,65 @@ public readonly struct RequestId : IEquatable<RequestId>
|
|||
_value = 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>
|
||||
/// 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>
|
||||
/// 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>
|
||||
/// 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() ?? "";
|
||||
|
||||
/// <summary>
|
||||
/// Compares this RequestId to another RequestId.
|
||||
/// </summary>
|
||||
public bool Equals(RequestId other) => _value.Equals(other._value);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool Equals(object? obj) => obj is RequestId other && Equals(other);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetHashCode() => _value.GetHashCode();
|
||||
|
||||
/// <summary>
|
||||
/// Compares two RequestIds for equality.
|
||||
/// </summary>
|
||||
public static bool operator ==(RequestId left, RequestId right) => left.Equals(right);
|
||||
|
||||
/// <summary>
|
||||
/// 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>
|
||||
public class RequestIdConverter : JsonConverter<RequestId>
|
||||
{
|
||||
public override RequestId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
{
|
||||
switch (reader.TokenType)
|
||||
{
|
||||
case JsonTokenType.String:
|
||||
return RequestId.FromString(reader.GetString()!);
|
||||
case JsonTokenType.Number:
|
||||
return RequestId.FromNumber(reader.GetInt64());
|
||||
default:
|
||||
throw new JsonException("RequestId must be either a string or a number");
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, RequestId value, JsonSerializerOptions options)
|
||||
{
|
||||
if (value.IsString)
|
||||
writer.WriteStringValue(value.AsString);
|
||||
else
|
||||
writer.WriteNumberValue(value.AsNumber);
|
||||
}
|
||||
}
|
||||
33
src/mcpdotnet/Protocol/Messages/RequestIdConverter.cs
Normal file
33
src/mcpdotnet/Protocol/Messages/RequestIdConverter.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
namespace McpDotNet.Protocol.Messages;
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <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)
|
||||
{
|
||||
switch (reader.TokenType)
|
||||
{
|
||||
case JsonTokenType.String:
|
||||
return RequestId.FromString(reader.GetString()!);
|
||||
case JsonTokenType.Number:
|
||||
return RequestId.FromNumber(reader.GetInt64());
|
||||
default:
|
||||
throw new JsonException("RequestId must be either a string or a number");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(Utf8JsonWriter writer, RequestId value, JsonSerializerOptions options)
|
||||
{
|
||||
if (value.IsString)
|
||||
writer.WriteStringValue(value.AsString);
|
||||
else
|
||||
writer.WriteNumberValue(value.AsNumber);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@
|
|||
namespace McpDotNet.Protocol.Transport;
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using McpDotNet.Protocol.Messages;
|
||||
|
|
@ -13,14 +12,29 @@ using McpDotNet.Utils.Json;
|
|||
/// </summary>
|
||||
public record StdioTransportOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// The command to execute to start the server process.
|
||||
/// </summary>
|
||||
public required string Command { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Arguments to pass to the server process.
|
||||
/// </summary>
|
||||
public string[]? Arguments { get; set; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// The working directory for the server process.
|
||||
/// </summary>
|
||||
public string? WorkingDirectory { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Environment variables to set for the server process.
|
||||
/// </summary>
|
||||
public Dictionary<string, string>? EnvironmentVariables { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The timeout to wait for the server to shut down gracefully.
|
||||
/// </summary>
|
||||
public TimeSpan ShutdownTimeout { get; init; } = TimeSpan.FromSeconds(5);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,8 @@
|
|||
// Protocol/Transport/IMcpTransport.cs
|
||||
using System.Threading.Channels;
|
||||
using McpDotNet.Protocol.Messages;
|
||||
|
||||
namespace McpDotNet.Protocol.Transport;
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Threading.Channels;
|
||||
using global::McpDotNet.Protocol.Messages;
|
||||
using McpDotNet.Utils.Json;
|
||||
|
||||
// Protocol/Transport/TransportBase.cs
|
||||
|
||||
/// <summary>
|
||||
/// Base class for implementing MCP transports with common functionality.
|
||||
/// </summary>
|
||||
|
|
@ -15,8 +10,10 @@ public abstract class TransportBase : IMcpTransport
|
|||
{
|
||||
private readonly Channel<IJsonRpcMessage> _messageChannel;
|
||||
private bool _isConnected;
|
||||
private readonly JsonSerializerOptions _jsonOptions = new JsonSerializerOptions().ConfigureForMcp();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TransportBase"/> class.
|
||||
/// </summary>
|
||||
protected TransportBase()
|
||||
{
|
||||
// Unbounded channel to prevent blocking on writes
|
||||
|
|
|
|||
21
src/mcpdotnet/Protocol/Types/Annotations.cs
Normal file
21
src/mcpdotnet/Protocol/Types/Annotations.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
namespace McpDotNet.Protocol.Types;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Represents annotations that can be attached to content.
|
||||
/// </summary>
|
||||
public record Annotations
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes who the intended customer of this object or data is.
|
||||
/// </summary>
|
||||
[JsonPropertyName("audience")]
|
||||
public Role[]? Audience { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Describes how important this data is for operating the server (0 to 1).
|
||||
/// </summary>
|
||||
[JsonPropertyName("priority")]
|
||||
public float? Priority { get; init; }
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
namespace McpDotNet.Protocol.Types;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Represents binary resource contents.
|
||||
/// </summary>
|
||||
public record BlobResourceContents : IResourceContents
|
||||
{
|
||||
[JsonPropertyName("uri")]
|
||||
public required string Uri { get; init; }
|
||||
|
||||
[JsonPropertyName("mimeType")]
|
||||
public string? MimeType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The base64-encoded binary content of the resource.
|
||||
/// </summary>
|
||||
[JsonPropertyName("blob")]
|
||||
public required string Blob { get; init; }
|
||||
}
|
||||
|
||||
|
|
@ -28,9 +28,9 @@ public class Content
|
|||
public string? MimeType { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// One of BlobResourceContents or TextResourceContents.
|
||||
/// The resource content of the message (if embedded).
|
||||
/// </summary>
|
||||
public object? Resource { get; set; }
|
||||
public ResourceContents? Resource { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Validates the content object.
|
||||
|
|
@ -48,7 +48,7 @@ public class Content
|
|||
}
|
||||
else if (Type == "resource")
|
||||
{
|
||||
return Resource is BlobResourceContents || Resource is TextResourceContents;
|
||||
return Resource != null;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,97 +0,0 @@
|
|||
// Protocol/Types/Content.cs
|
||||
namespace McpDotNet.Protocol.Types;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the type of role in the conversation.
|
||||
/// </summary>
|
||||
public enum Role
|
||||
{
|
||||
[JsonPropertyName("user")]
|
||||
User,
|
||||
|
||||
[JsonPropertyName("assistant")]
|
||||
Assistant
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base interface for content types in messages.
|
||||
/// </summary>
|
||||
public interface IContent
|
||||
{
|
||||
string Type { get; }
|
||||
Annotations? Annotations { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents text content in messages.
|
||||
/// </summary>
|
||||
public record TextContent : IContent
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of content. Always "text".
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public string Type => "text";
|
||||
|
||||
/// <summary>
|
||||
/// The text content of the message.
|
||||
/// </summary>
|
||||
[JsonPropertyName("text")]
|
||||
public required string Text { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional annotations for the content.
|
||||
/// </summary>
|
||||
[JsonPropertyName("annotations")]
|
||||
public Annotations? Annotations { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents image content in messages.
|
||||
/// </summary>
|
||||
public record ImageContent : IContent
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of content. Always "image".
|
||||
/// </summary>
|
||||
[JsonPropertyName("type")]
|
||||
public string Type => "image";
|
||||
|
||||
/// <summary>
|
||||
/// The base64-encoded image data.
|
||||
/// </summary>
|
||||
[JsonPropertyName("data")]
|
||||
public required string Data { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The MIME type of the image.
|
||||
/// </summary>
|
||||
[JsonPropertyName("mimeType")]
|
||||
public required string MimeType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional annotations for the content.
|
||||
/// </summary>
|
||||
[JsonPropertyName("annotations")]
|
||||
public Annotations? Annotations { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents annotations that can be attached to content.
|
||||
/// </summary>
|
||||
public record Annotations
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes who the intended customer of this object or data is.
|
||||
/// </summary>
|
||||
[JsonPropertyName("audience")]
|
||||
public Role[]? Audience { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Describes how important this data is for operating the server (0 to 1).
|
||||
/// </summary>
|
||||
[JsonPropertyName("priority")]
|
||||
public float? Priority { get; init; }
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
namespace McpDotNet.Protocol.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for resource contents.
|
||||
/// </summary>
|
||||
public interface IResourceContents
|
||||
{
|
||||
string Uri { get; }
|
||||
string? MimeType { get; }
|
||||
}
|
||||
|
|
@ -18,4 +18,4 @@ public class PromptMessage
|
|||
/// The role of the message ("user" or "assistant").
|
||||
/// </summary>
|
||||
public Role Role { get; set; } = new();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ public class ResourceContents
|
|||
/// <summary>
|
||||
/// The URI of the resource.
|
||||
/// </summary>
|
||||
public string Uri { get; set; }
|
||||
public string Uri { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The type of content.
|
||||
|
|
|
|||
21
src/mcpdotnet/Protocol/Types/Role.cs
Normal file
21
src/mcpdotnet/Protocol/Types/Role.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace McpDotNet.Protocol.Types;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the type of role in the conversation.
|
||||
/// </summary>
|
||||
public enum Role
|
||||
{
|
||||
/// <summary>
|
||||
/// Corresponds to the user in the conversation.
|
||||
/// </summary>
|
||||
[JsonPropertyName("user")]
|
||||
User,
|
||||
|
||||
/// <summary>
|
||||
/// Corresponds to the AI in the conversation.
|
||||
/// </summary>
|
||||
[JsonPropertyName("assistant")]
|
||||
Assistant
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
namespace McpDotNet.Protocol.Types;
|
||||
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
/// <summary>
|
||||
/// Represents text-based resource contents.
|
||||
/// </summary>
|
||||
public record TextResourceContents : IResourceContents
|
||||
{
|
||||
[JsonPropertyName("uri")]
|
||||
public required string Uri { get; init; }
|
||||
|
||||
[JsonPropertyName("mimeType")]
|
||||
public string? MimeType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The text content of the resource.
|
||||
/// </summary>
|
||||
[JsonPropertyName("text")]
|
||||
public required string Text { get; init; }
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue