Skip to content

Commit 75967d8

Browse files
Allow user-defined JsonSerializerOptions for user-defined and inputs and hardcode everything else to the source generator.
1 parent 25bcb44 commit 75967d8

35 files changed

+522
-343
lines changed

src/ModelContextProtocol/Client/McpClient.cs

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,14 @@ public McpClient(IClientTransport clientTransport, McpClientOptions options, Mcp
4040
throw new InvalidOperationException($"Sampling capability was set but it did not provide a handler.");
4141
}
4242

43-
SetRequestHandler<CreateMessageRequestParams, CreateMessageResult>(
43+
SetRequestHandler(
4444
RequestMethods.SamplingCreateMessage,
4545
(request, cancellationToken) => samplingHandler(
4646
request,
4747
request?.Meta?.ProgressToken is { } token ? new TokenProgress(this, token) : NullProgress.Instance,
48-
cancellationToken));
48+
cancellationToken),
49+
McpJsonUtilities.JsonContext.Default.CreateMessageRequestParams,
50+
McpJsonUtilities.JsonContext.Default.CreateMessageResult);
4951
}
5052

5153
if (options.Capabilities?.Roots is { } rootsCapability)
@@ -55,9 +57,11 @@ public McpClient(IClientTransport clientTransport, McpClientOptions options, Mcp
5557
throw new InvalidOperationException($"Roots capability was set but it did not provide a handler.");
5658
}
5759

58-
SetRequestHandler<ListRootsRequestParams, ListRootsResult>(
60+
SetRequestHandler(
5961
RequestMethods.RootsList,
60-
(request, cancellationToken) => rootsHandler(request, cancellationToken));
62+
rootsHandler,
63+
McpJsonUtilities.JsonContext.Default.ListRootsRequestParams,
64+
McpJsonUtilities.JsonContext.Default.ListRootsResult);
6165
}
6266
}
6367

@@ -93,18 +97,17 @@ public async Task ConnectAsync(CancellationToken cancellationToken = default)
9397
try
9498
{
9599
// Send initialize request
96-
var initializeResponse = await SendRequestAsync<InitializeResult>(
97-
new JsonRpcRequest
100+
var initializeResponse = await this.SendRequestAsync(
101+
RequestMethods.Initialize,
102+
new InitializeRequestParams
98103
{
99-
Method = RequestMethods.Initialize,
100-
Params = new InitializeRequestParams()
101-
{
102-
ProtocolVersion = _options.ProtocolVersion,
103-
Capabilities = _options.Capabilities ?? new ClientCapabilities(),
104-
ClientInfo = _options.ClientInfo
105-
}
104+
ProtocolVersion = _options.ProtocolVersion,
105+
Capabilities = _options.Capabilities ?? new ClientCapabilities(),
106+
ClientInfo = _options.ClientInfo
106107
},
107-
initializationCts.Token).ConfigureAwait(false);
108+
McpJsonUtilities.JsonContext.Default.InitializeRequestParams,
109+
McpJsonUtilities.JsonContext.Default.InitializeResult,
110+
cancellationToken: initializationCts.Token).ConfigureAwait(false);
108111

109112
// Store server information
110113
_logger.ServerCapabilitiesReceived(EndpointName,

src/ModelContextProtocol/Client/McpClientExtensions.cs

Lines changed: 118 additions & 112 deletions
Large diffs are not rendered by default.

src/ModelContextProtocol/Client/McpClientPrompt.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using ModelContextProtocol.Protocol.Types;
2+
using System.Text.Json;
23

34
namespace ModelContextProtocol.Client;
45

@@ -20,17 +21,19 @@ internal McpClientPrompt(IMcpClient client, Prompt prompt)
2021
/// Retrieves a specific prompt with optional arguments.
2122
/// </summary>
2223
/// <param name="arguments">Optional arguments for the prompt</param>
24+
/// <param name="serializerOptions">The serialization options governing argument serialization.</param>
2325
/// <param name="cancellationToken">A token to cancel the operation.</param>
2426
/// <returns>A task containing the prompt's content and messages.</returns>
2527
public async ValueTask<GetPromptResult> GetAsync(
2628
IEnumerable<KeyValuePair<string, object?>>? arguments = null,
29+
JsonSerializerOptions? serializerOptions = null,
2730
CancellationToken cancellationToken = default)
2831
{
2932
IReadOnlyDictionary<string, object?>? argDict =
3033
arguments as IReadOnlyDictionary<string, object?> ??
3134
arguments?.ToDictionary();
3235

33-
return await _client.GetPromptAsync(ProtocolPrompt.Name, argDict, cancellationToken).ConfigureAwait(false);
36+
return await _client.GetPromptAsync(ProtocolPrompt.Name, argDict, serializerOptions, cancellationToken: cancellationToken).ConfigureAwait(false);
3437
}
3538

3639
/// <summary>Gets the name of the prompt.</summary>

src/ModelContextProtocol/Client/McpClientTool.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ internal McpClientTool(IMcpClient client, Tool tool)
3939
arguments as IReadOnlyDictionary<string, object?> ??
4040
arguments.ToDictionary();
4141

42-
CallToolResponse result = await _client.CallToolAsync(ProtocolTool.Name, argDict, cancellationToken).ConfigureAwait(false);
42+
CallToolResponse result = await _client.CallToolAsync(ProtocolTool.Name, argDict, cancellationToken: cancellationToken).ConfigureAwait(false);
4343
return JsonSerializer.SerializeToElement(result, McpJsonUtilities.JsonContext.Default.CallToolResponse);
4444
}
4545
}

src/ModelContextProtocol/IMcpEndpoint.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ namespace ModelContextProtocol;
55
/// <summary>Represents a client or server MCP endpoint.</summary>
66
public interface IMcpEndpoint : IAsyncDisposable
77
{
8-
/// <summary>Sends a generic JSON-RPC request to the connected endpoint.</summary>
9-
/// <typeparam name="TResult">The expected response type.</typeparam>
8+
/// <summary>Sends a JSON-RPC request to the connected endpoint.</summary>
109
/// <param name="request">The JSON-RPC request to send.</param>
1110
/// <param name="cancellationToken">A token to cancel the operation.</param>
1211
/// <returns>A task containing the client's response.</returns>
13-
Task<TResult> SendRequestAsync<TResult>(JsonRpcRequest request, CancellationToken cancellationToken = default) where TResult : class;
12+
Task<JsonRpcResponse> SendRequestAsync(JsonRpcRequest request, CancellationToken cancellationToken = default);
1413

1514
/// <summary>Sends a message to the connected endpoint.</summary>
1615
/// <param name="message">The message.</param>

src/ModelContextProtocol/Logging/Log.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,6 @@ internal static partial class Log
7777
[LoggerMessage(Level = LogLevel.Information, Message = "Request response received for {endpointName} with method {method}")]
7878
internal static partial void RequestResponseReceived(this ILogger logger, string endpointName, string method);
7979

80-
[LoggerMessage(Level = LogLevel.Error, Message = "Request response type conversion error for {endpointName} with method {method}: expected {expectedType}")]
81-
internal static partial void RequestResponseTypeConversionError(this ILogger logger, string endpointName, string method, Type expectedType);
82-
8380
[LoggerMessage(Level = LogLevel.Error, Message = "Request invalid response type for {endpointName} with method {method}")]
8481
internal static partial void RequestInvalidResponseType(this ILogger logger, string endpointName, string method);
8582

src/ModelContextProtocol/McpEndpointExtensions.cs

Lines changed: 133 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,143 @@
11
using ModelContextProtocol.Protocol.Messages;
22
using ModelContextProtocol.Utils;
3+
using ModelContextProtocol.Utils.Json;
4+
using System.Text.Json;
5+
using System.Text.Json.Nodes;
6+
using System.Text.Json.Serialization.Metadata;
37

48
namespace ModelContextProtocol;
59

610
/// <summary>Provides extension methods for interacting with an <see cref="IMcpEndpoint"/>.</summary>
711
public static class McpEndpointExtensions
812
{
13+
/// <summary>
14+
/// Sends a JSON-RPC request and attempts to deserialize the result to <typeparamref name="TResult"/>.
15+
/// </summary>
16+
/// <typeparam name="TParameters">The type of the request parameters to serialize from.</typeparam>
17+
/// <typeparam name="TResult">The type of the result to deserialize to.</typeparam>
18+
/// <param name="endpoint">The MCP client or server instance.</param>
19+
/// <param name="method">The JSON-RPC method name to invoke.</param>
20+
/// <param name="parameters">Object representing the request parameters.</param>
21+
/// <param name="parametersTypeInfo">The type information for request parameter serialization.</param>
22+
/// <param name="resultTypeInfo">The type information for request parameter deserialization.</param>
23+
/// <param name="requestId">The request id for the request.</param>
24+
/// <param name="cancellationToken">A token to cancel the operation.</param>
25+
/// <returns>A task that represents the asynchronous operation. The task result contains the deserialized result.</returns>
26+
public static async Task<TResult> SendRequestAsync<TParameters, TResult>(
27+
this IMcpEndpoint endpoint,
28+
string method,
29+
TParameters parameters,
30+
JsonTypeInfo<TParameters> parametersTypeInfo,
31+
JsonTypeInfo<TResult> resultTypeInfo,
32+
RequestId? requestId = null,
33+
CancellationToken cancellationToken = default)
34+
where TResult : notnull
35+
{
36+
Throw.IfNull(endpoint);
37+
Throw.IfNullOrWhiteSpace(method);
38+
Throw.IfNull(parametersTypeInfo);
39+
Throw.IfNull(resultTypeInfo);
40+
41+
JsonRpcRequest jsonRpcRequest = new()
42+
{
43+
Method = method,
44+
Params = JsonSerializer.SerializeToNode(parameters, parametersTypeInfo),
45+
};
46+
47+
if (requestId is { } id)
48+
{
49+
jsonRpcRequest.Id = id;
50+
}
51+
52+
JsonRpcResponse response = await endpoint.SendRequestAsync(jsonRpcRequest, cancellationToken).ConfigureAwait(false);
53+
return JsonSerializer.Deserialize(response.Result, resultTypeInfo) ?? throw new JsonException("Unexpected JSON result in response.");
54+
}
55+
56+
/// <summary>
57+
/// Sends a JSON-RPC request and attempts to deserialize the result to <typeparamref name="TResult"/>.
58+
/// </summary>
59+
/// <typeparam name="TParameters">The type of the request parameters to serialize from.</typeparam>
60+
/// <typeparam name="TResult">The type of the result to deserialize to.</typeparam>
61+
/// <param name="endpoint">The MCP client or server instance.</param>
62+
/// <param name="method">The JSON-RPC method name to invoke.</param>
63+
/// <param name="parameters">Object representing the request parameters.</param>
64+
/// <param name="serializerOptions">The options governing request serialization.</param>
65+
/// <param name="requestId">The request id for the request.</param>
66+
/// <param name="cancellationToken">A token to cancel the operation.</param>
67+
/// <returns>A task that represents the asynchronous operation. The task result contains the deserialized result.</returns>
68+
public static Task<TResult> SendRequestAsync<TParameters, TResult>(
69+
this IMcpEndpoint endpoint,
70+
string method,
71+
TParameters parameters,
72+
JsonSerializerOptions? serializerOptions = null,
73+
RequestId? requestId = null,
74+
CancellationToken cancellationToken = default)
75+
where TResult : notnull
76+
{
77+
serializerOptions ??= McpJsonUtilities.DefaultOptions;
78+
JsonTypeInfo<TParameters> paramsTypeInfo = serializerOptions.GetTypeInfo<TParameters>();
79+
JsonTypeInfo<TResult> resultTypeInfo = serializerOptions.GetTypeInfo<TResult>();
80+
return SendRequestAsync(endpoint, method, parameters, paramsTypeInfo, resultTypeInfo, requestId, cancellationToken);
81+
}
82+
83+
/// <summary>
84+
/// Sends a notification to the server with parameters.
85+
/// </summary>
86+
/// <param name="client">The client.</param>
87+
/// <param name="method">The notification method name.</param>
88+
/// <param name="cancellationToken">A token to cancel the operation.</param>
89+
public static Task SendNotificationAsync(this IMcpEndpoint client, string method, CancellationToken cancellationToken = default)
90+
{
91+
Throw.IfNull(client);
92+
Throw.IfNullOrWhiteSpace(method);
93+
return client.SendMessageAsync(new JsonRpcNotification { Method = method }, cancellationToken);
94+
}
95+
96+
/// <summary>
97+
/// Sends a notification to the server with parameters.
98+
/// </summary>
99+
/// <param name="endpoint">The MCP client or server instance.</param>
100+
/// <param name="method">The JSON-RPC method name to invoke.</param>
101+
/// <param name="parameters">Object representing the request parameters.</param>
102+
/// <param name="parametersTypeInfo">The type information for request parameter serialization.</param>
103+
/// <param name="cancellationToken">A token to cancel the operation.</param>
104+
public static Task SendNotificationAsync<TParameters>(
105+
this IMcpEndpoint endpoint,
106+
string method,
107+
TParameters parameters,
108+
JsonTypeInfo<TParameters> parametersTypeInfo,
109+
CancellationToken cancellationToken = default)
110+
{
111+
Throw.IfNull(endpoint);
112+
Throw.IfNullOrWhiteSpace(method);
113+
Throw.IfNull(parametersTypeInfo);
114+
115+
JsonNode? parametersJson = JsonSerializer.SerializeToNode(parameters, parametersTypeInfo);
116+
return endpoint.SendMessageAsync(new JsonRpcNotification { Method = method, Params = parametersJson }, cancellationToken);
117+
}
118+
119+
/// <summary>
120+
/// Sends a notification to the server with parameters.
121+
/// </summary>
122+
/// <param name="endpoint">The MCP client or server instance.</param>
123+
/// <param name="method">The JSON-RPC method name to invoke.</param>
124+
/// <param name="parameters">Object representing the request parameters.</param>
125+
/// <param name="serializerOptions">The options governing request serialization.</param>
126+
/// <param name="cancellationToken">A token to cancel the operation.</param>
127+
public static Task SendNotificationAsync<TParameters>(
128+
this IMcpEndpoint endpoint,
129+
string method,
130+
TParameters parameters,
131+
JsonSerializerOptions? serializerOptions = null,
132+
CancellationToken cancellationToken = default)
133+
{
134+
serializerOptions ??= McpJsonUtilities.DefaultOptions;
135+
JsonTypeInfo<TParameters> parametersTypeInfo = serializerOptions.GetTypeInfo<TParameters>();
136+
return SendNotificationAsync(endpoint, method, parameters, parametersTypeInfo, cancellationToken);
137+
}
138+
9139
/// <summary>Notifies the connected endpoint of progress.</summary>
10-
/// <param name="endpoint">The endpoint issueing the notification.</param>
140+
/// <param name="endpoint">The endpoint issuing the notification.</param>
11141
/// <param name="progressToken">The <see cref="ProgressToken"/> identifying the operation.</param>
12142
/// <param name="progress">The progress update to send.</param>
13143
/// <param name="cancellationToken">A token to cancel the operation.</param>
@@ -24,11 +154,11 @@ public static Task NotifyProgressAsync(
24154
return endpoint.SendMessageAsync(new JsonRpcNotification()
25155
{
26156
Method = NotificationMethods.ProgressNotification,
27-
Params = new ProgressNotification()
157+
Params = JsonSerializer.SerializeToNode(new ProgressNotification
28158
{
29159
ProgressToken = progressToken,
30160
Progress = progress,
31-
},
161+
}, McpJsonUtilities.JsonContext.Default.ProgressNotification),
32162
}, cancellationToken);
33163
}
34164
}

src/ModelContextProtocol/Protocol/Messages/JsonRpcNotification.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Text.Json.Serialization;
1+
using System.Text.Json.Nodes;
2+
using System.Text.Json.Serialization;
23

34
namespace ModelContextProtocol.Protocol.Messages;
45

@@ -23,5 +24,5 @@ public record JsonRpcNotification : IJsonRpcMessage
2324
/// Optional parameters for the notification.
2425
/// </summary>
2526
[JsonPropertyName("params")]
26-
public object? Params { get; init; }
27+
public JsonNode? Params { get; init; }
2728
}

src/ModelContextProtocol/Protocol/Messages/JsonRpcRequest.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Text.Json.Serialization;
1+
using System.Text.Json.Nodes;
2+
using System.Text.Json.Serialization;
23

34
namespace ModelContextProtocol.Protocol.Messages;
45

@@ -29,5 +30,5 @@ public record JsonRpcRequest : IJsonRpcMessageWithId
2930
/// Optional parameters for the method.
3031
/// </summary>
3132
[JsonPropertyName("params")]
32-
public object? Params { get; init; }
33+
public JsonNode? Params { get; init; }
3334
}

src/ModelContextProtocol/Protocol/Messages/JsonRpcResponse.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-

1+
using System.Text.Json.Nodes;
22
using System.Text.Json.Serialization;
33

44
namespace ModelContextProtocol.Protocol.Messages;
@@ -23,5 +23,5 @@ public record JsonRpcResponse : IJsonRpcMessageWithId
2323
/// The result of the method invocation.
2424
/// </summary>
2525
[JsonPropertyName("result")]
26-
public required object? Result { get; init; }
26+
public required JsonNode? Result { get; init; }
2727
}

0 commit comments

Comments
 (0)