Skip to content

Commit 12d5683

Browse files
Copilotstephentoub
andcommitted
Add support for data to McpProtocolException in JSON-RPC errors
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
1 parent c629834 commit 12d5683

File tree

2 files changed

+84
-1
lines changed

2 files changed

+84
-1
lines changed

src/ModelContextProtocol.Core/McpSessionHandler.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,10 +186,10 @@ ex is OperationCanceledException &&
186186
{
187187
Code = (int)mcpProtocolException.ErrorCode,
188188
Message = mcpProtocolException.Message,
189+
Data = ConvertExceptionData(mcpProtocolException.Data),
189190
} : ex is McpException mcpException ?
190191
new()
191192
{
192-
193193
Code = (int)McpErrorCode.InternalError,
194194
Message = mcpException.Message,
195195
} :
@@ -769,6 +769,29 @@ private static TimeSpan GetElapsed(long startingTimestamp) =>
769769
return null;
770770
}
771771

772+
/// <summary>
773+
/// Converts the Exception.Data dictionary to a serializable Dictionary&lt;string, object?&gt;.
774+
/// Returns null if the data dictionary is empty.
775+
/// </summary>
776+
private static Dictionary<string, object?>? ConvertExceptionData(System.Collections.IDictionary data)
777+
{
778+
if (data.Count == 0)
779+
{
780+
return null;
781+
}
782+
783+
var result = new Dictionary<string, object?>(data.Count);
784+
foreach (System.Collections.DictionaryEntry entry in data)
785+
{
786+
if (entry.Key is string key)
787+
{
788+
result[key] = entry.Value;
789+
}
790+
}
791+
792+
return result.Count > 0 ? result : null;
793+
}
794+
772795
[LoggerMessage(Level = LogLevel.Information, Message = "{EndpointName} message processing canceled.")]
773796
private partial void LogEndpointMessageProcessingCanceled(string endpointName);
774797

tests/ModelContextProtocol.Tests/Server/McpServerTests.cs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -671,6 +671,66 @@ await transport.SendMessageAsync(
671671
await runTask;
672672
}
673673

674+
[Fact]
675+
public async Task Can_Handle_Call_Tool_Requests_With_McpProtocolException_And_Data()
676+
{
677+
const string errorMessage = "Resource not found";
678+
const McpErrorCode errorCode = (McpErrorCode)(-32002);
679+
const string resourceUri = "file:///path/to/resource";
680+
681+
await using var transport = new TestServerTransport();
682+
var options = CreateOptions(new ServerCapabilities { Tools = new() });
683+
options.Handlers.CallToolHandler = async (request, ct) =>
684+
{
685+
throw new McpProtocolException(errorMessage, errorCode)
686+
{
687+
Data =
688+
{
689+
{ "uri", resourceUri }
690+
}
691+
};
692+
};
693+
options.Handlers.ListToolsHandler = (request, ct) => throw new NotImplementedException();
694+
695+
await using var server = McpServer.Create(transport, options, LoggerFactory);
696+
697+
var runTask = server.RunAsync(TestContext.Current.CancellationToken);
698+
699+
var receivedMessage = new TaskCompletionSource<JsonRpcError>();
700+
701+
transport.OnMessageSent = (message) =>
702+
{
703+
if (message is JsonRpcError error && error.Id.ToString() == "55")
704+
receivedMessage.SetResult(error);
705+
};
706+
707+
await transport.SendMessageAsync(
708+
new JsonRpcRequest
709+
{
710+
Method = RequestMethods.ToolsCall,
711+
Id = new RequestId(55)
712+
},
713+
TestContext.Current.CancellationToken
714+
);
715+
716+
var error = await receivedMessage.Task.WaitAsync(TimeSpan.FromSeconds(10), TestContext.Current.CancellationToken);
717+
Assert.NotNull(error);
718+
Assert.NotNull(error.Error);
719+
Assert.Equal((int)errorCode, error.Error.Code);
720+
Assert.Equal(errorMessage, error.Error.Message);
721+
Assert.NotNull(error.Error.Data);
722+
723+
// Verify the data contains the uri
724+
var dataJson = JsonSerializer.Serialize(error.Error.Data, McpJsonUtilities.DefaultOptions);
725+
var dataObject = JsonSerializer.Deserialize<JsonObject>(dataJson, McpJsonUtilities.DefaultOptions);
726+
Assert.NotNull(dataObject);
727+
Assert.True(dataObject.ContainsKey("uri"));
728+
Assert.Equal(resourceUri, dataObject["uri"]?.GetValue<string>());
729+
730+
await transport.DisposeAsync();
731+
await runTask;
732+
}
733+
674734
private async Task Can_Handle_Requests(ServerCapabilities? serverCapabilities, string method, Action<McpServerOptions>? configureOptions, Action<McpServer, JsonNode?> assertResult)
675735
{
676736
await using var transport = new TestServerTransport();

0 commit comments

Comments
 (0)