Update content according to latest spec (#513)
* Update content according to latest spec * Add Title to a bunch of types * Add _meta to lots of types * Address feedback, more cleanup, fix NAOT test failure
This commit is contained in:
parent
28d65470f4
commit
2bcff315f8
27 changed files with 1700 additions and 853 deletions
|
|
@ -12,7 +12,7 @@ public class TestServerTransport : ITransport
|
|||
|
||||
public ChannelReader<JsonRpcMessage> MessageReader => _messageChannel;
|
||||
|
||||
public List<JsonRpcMessage> SentMessages { get; } = [];
|
||||
public IList<JsonRpcMessage> SentMessages { get; } = [];
|
||||
|
||||
public Action<JsonRpcMessage>? OnMessageSent { get; set; }
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ public class TestServerTransport : ITransport
|
|||
await WriteMessageAsync(new JsonRpcResponse
|
||||
{
|
||||
Id = request.Id,
|
||||
Result = JsonSerializer.SerializeToNode(new CreateMessageResult { Content = new(), Model = "model", Role = Role.User }, McpJsonUtilities.DefaultOptions),
|
||||
Result = JsonSerializer.SerializeToNode(new CreateMessageResult { Content = new TextContentBlock { Text = "" }, Model = "model", Role = Role.User }, McpJsonUtilities.DefaultOptions),
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ public abstract class HttpServerIntegrationTests : LoggedTest, IClassFixture<Sse
|
|||
// assert
|
||||
Assert.NotNull(result);
|
||||
Assert.False(result.IsError);
|
||||
var textContent = Assert.Single(result.Content, c => c.Type == "text");
|
||||
var textContent = Assert.Single(result.Content.OfType<TextContentBlock>());
|
||||
Assert.Equal("Echo: Hello MCP!", textContent.Text);
|
||||
}
|
||||
|
||||
|
|
@ -119,9 +119,9 @@ public abstract class HttpServerIntegrationTests : LoggedTest, IClassFixture<Sse
|
|||
Assert.False(result2.IsError);
|
||||
Assert.False(result3.IsError);
|
||||
|
||||
var textContent1 = Assert.Single(result1.Content);
|
||||
var textContent2 = Assert.Single(result2.Content);
|
||||
var textContent3 = Assert.Single(result3.Content);
|
||||
var textContent1 = Assert.Single(result1.Content.OfType<TextContentBlock>());
|
||||
var textContent2 = Assert.Single(result2.Content.OfType<TextContentBlock>());
|
||||
var textContent3 = Assert.Single(result3.Content.OfType<TextContentBlock>());
|
||||
|
||||
Assert.NotNull(textContent1.Text);
|
||||
Assert.Equal(textContent1.Text, textContent2.Text);
|
||||
|
|
@ -259,11 +259,7 @@ public abstract class HttpServerIntegrationTests : LoggedTest, IClassFixture<Sse
|
|||
{
|
||||
Model = "test-model",
|
||||
Role = Role.Assistant,
|
||||
Content = new Content
|
||||
{
|
||||
Type = "text",
|
||||
Text = "Test response"
|
||||
}
|
||||
Content = new TextContentBlock { Text = "Test response" },
|
||||
};
|
||||
};
|
||||
await using var client = await GetClientAsync(options);
|
||||
|
|
@ -279,8 +275,7 @@ public abstract class HttpServerIntegrationTests : LoggedTest, IClassFixture<Sse
|
|||
|
||||
// assert
|
||||
Assert.NotNull(result);
|
||||
var textContent = Assert.Single(result.Content);
|
||||
Assert.Equal("text", textContent.Type);
|
||||
var textContent = Assert.Single(result.Content.OfType<TextContentBlock>());
|
||||
Assert.False(string.IsNullOrEmpty(textContent.Text));
|
||||
}
|
||||
|
||||
|
|
@ -304,7 +299,7 @@ public abstract class HttpServerIntegrationTests : LoggedTest, IClassFixture<Sse
|
|||
|
||||
Assert.NotNull(result);
|
||||
Assert.False(result.IsError);
|
||||
var textContent = Assert.Single(result.Content, c => c.Type == "text");
|
||||
var textContent = Assert.Single(result.Content.OfType<TextContentBlock>());
|
||||
Assert.Equal($"Echo: Hello MCP! {i}", textContent.Text);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ public abstract class MapMcpTests(ITestOutputHelper testOutputHelper) : KestrelI
|
|||
new Dictionary<string, object?>() { ["message"] = "Hello world!" },
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
var content = Assert.Single(response.Content);
|
||||
var content = Assert.Single(response.Content.OfType<TextContentBlock>());
|
||||
Assert.Equal("TestUser: Hello world!", content.Text);
|
||||
}
|
||||
|
||||
|
|
@ -149,19 +149,14 @@ public abstract class MapMcpTests(ITestOutputHelper testOutputHelper) : KestrelI
|
|||
Assert.NotNull(parameters?.Messages);
|
||||
var message = Assert.Single(parameters.Messages);
|
||||
Assert.Equal(Role.User, message.Role);
|
||||
Assert.Equal("text", message.Content.Type);
|
||||
Assert.Equal("Test prompt for sampling", message.Content.Text);
|
||||
Assert.Equal("Test prompt for sampling", Assert.IsType<TextContentBlock>(message.Content).Text);
|
||||
|
||||
sampleCount++;
|
||||
return new CreateMessageResult
|
||||
{
|
||||
Model = "test-model",
|
||||
Role = Role.Assistant,
|
||||
Content = new Content
|
||||
{
|
||||
Type = "text",
|
||||
Text = "Sampling response from client"
|
||||
}
|
||||
Content = new TextContentBlock { Text = "Sampling response from client" },
|
||||
};
|
||||
},
|
||||
},
|
||||
|
|
@ -179,7 +174,7 @@ public abstract class MapMcpTests(ITestOutputHelper testOutputHelper) : KestrelI
|
|||
Assert.False(result.IsError);
|
||||
var textContent = Assert.Single(result.Content);
|
||||
Assert.Equal("text", textContent.Type);
|
||||
Assert.Equal("Sampling completed successfully. Client responded: Sampling response from client", textContent.Text);
|
||||
Assert.Equal("Sampling completed successfully. Client responded: Sampling response from client", Assert.IsType<TextContentBlock>(textContent).Text);
|
||||
|
||||
Assert.Equal(2, sampleCount);
|
||||
|
||||
|
|
@ -228,11 +223,7 @@ public abstract class MapMcpTests(ITestOutputHelper testOutputHelper) : KestrelI
|
|||
new SamplingMessage
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new Content
|
||||
{
|
||||
Type = "text",
|
||||
Text = prompt
|
||||
},
|
||||
Content = new TextContentBlock { Text = prompt },
|
||||
}
|
||||
],
|
||||
};
|
||||
|
|
@ -240,7 +231,7 @@ public abstract class MapMcpTests(ITestOutputHelper testOutputHelper) : KestrelI
|
|||
await server.SampleAsync(samplingRequest, cancellationToken);
|
||||
var samplingResult = await server.SampleAsync(samplingRequest, cancellationToken);
|
||||
|
||||
return $"Sampling completed successfully. Client responded: {samplingResult.Content.Text}";
|
||||
return $"Sampling completed successfully. Client responded: {Assert.IsType<TextContentBlock>(samplingResult.Content).Text}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ public partial class SseIntegrationTests(ITestOutputHelper outputHelper) : Kestr
|
|||
["message"] = "from client!"
|
||||
},
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
var textContent = Assert.Single(echoResponse.Content, c => c.Type == "text");
|
||||
var textContent = Assert.Single(echoResponse.Content.OfType<TextContentBlock>());
|
||||
|
||||
Assert.Equal("hello from client!", textContent.Text);
|
||||
}
|
||||
|
|
@ -199,7 +199,7 @@ public partial class SseIntegrationTests(ITestOutputHelper outputHelper) : Kestr
|
|||
{
|
||||
Endpoint = new Uri("http://localhost/sse"),
|
||||
Name = "In-memory SSE Client",
|
||||
AdditionalHeaders = new()
|
||||
AdditionalHeaders = new Dictionary<string, string>
|
||||
{
|
||||
["Authorize"] = "Bearer testToken"
|
||||
},
|
||||
|
|
@ -226,7 +226,7 @@ public partial class SseIntegrationTests(ITestOutputHelper outputHelper) : Kestr
|
|||
{
|
||||
Endpoint = new Uri("http://localhost/sse"),
|
||||
Name = "In-memory SSE Client",
|
||||
AdditionalHeaders = new()
|
||||
AdditionalHeaders = new Dictionary<string, string>()
|
||||
{
|
||||
[""] = ""
|
||||
},
|
||||
|
|
|
|||
|
|
@ -113,7 +113,7 @@ public class StatelessServerTests(ITestOutputHelper outputHelper) : KestrelInMem
|
|||
|
||||
var toolResponse = await client.CallToolAsync("testSamplingErrors", cancellationToken: TestContext.Current.CancellationToken);
|
||||
var toolContent = Assert.Single(toolResponse.Content);
|
||||
Assert.Equal("Server to client requests are not supported in stateless mode.", toolContent.Text);
|
||||
Assert.Equal("Server to client requests are not supported in stateless mode.", Assert.IsType<TextContentBlock>(toolContent).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -133,7 +133,7 @@ public class StatelessServerTests(ITestOutputHelper outputHelper) : KestrelInMem
|
|||
|
||||
var toolResponse = await client.CallToolAsync("testRootsErrors", cancellationToken: TestContext.Current.CancellationToken);
|
||||
var toolContent = Assert.Single(toolResponse.Content);
|
||||
Assert.Equal("Server to client requests are not supported in stateless mode.", toolContent.Text);
|
||||
Assert.Equal("Server to client requests are not supported in stateless mode.", Assert.IsType<TextContentBlock>(toolContent).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -153,7 +153,7 @@ public class StatelessServerTests(ITestOutputHelper outputHelper) : KestrelInMem
|
|||
|
||||
var toolResponse = await client.CallToolAsync("testElicitationErrors", cancellationToken: TestContext.Current.CancellationToken);
|
||||
var toolContent = Assert.Single(toolResponse.Content);
|
||||
Assert.Equal("Server to client requests are not supported in stateless mode.", toolContent.Text);
|
||||
Assert.Equal("Server to client requests are not supported in stateless mode.", Assert.IsType<TextContentBlock>(toolContent).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -190,7 +190,7 @@ public class StatelessServerTests(ITestOutputHelper outputHelper) : KestrelInMem
|
|||
|
||||
var toolResponse = await client.CallToolAsync("testScope", cancellationToken: TestContext.Current.CancellationToken);
|
||||
var toolContent = Assert.Single(toolResponse.Content);
|
||||
Assert.Equal("From request middleware!", toolContent.Text);
|
||||
Assert.Equal("From request middleware!", Assert.IsType<TextContentBlock>(toolContent).Text);
|
||||
}
|
||||
|
||||
[McpServerTool(Name = "testSamplingErrors")]
|
||||
|
|
|
|||
|
|
@ -77,9 +77,9 @@ public class StreamableHttpClientConformanceTests(ITestOutputHelper outputHelper
|
|||
return Results.Json(new JsonRpcResponse
|
||||
{
|
||||
Id = request.Id,
|
||||
Result = JsonSerializer.SerializeToNode(new CallToolResponse()
|
||||
Result = JsonSerializer.SerializeToNode(new CallToolResult()
|
||||
{
|
||||
Content = [new() { Text = parameters.Arguments["message"].ToString() }],
|
||||
Content = [new TextContentBlock { Text = parameters.Arguments["message"].ToString() }],
|
||||
}, McpJsonUtilities.DefaultOptions),
|
||||
});
|
||||
}
|
||||
|
|
@ -141,8 +141,7 @@ public class StreamableHttpClientConformanceTests(ITestOutputHelper outputHelper
|
|||
var response = await echoTool.CallAsync(new Dictionary<string, object?>() { ["message"] = "Hello world!" }, cancellationToken: TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(response);
|
||||
var content = Assert.Single(response.Content);
|
||||
Assert.Equal("text", content.Type);
|
||||
Assert.Equal("Hello world!", content.Text);
|
||||
Assert.Equal("Hello world!", Assert.IsType<TextContentBlock>(content).Text);
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
|
|
|
|||
|
|
@ -218,9 +218,9 @@ public class StreamableHttpServerConformanceTests(ITestOutputHelper outputHelper
|
|||
var response = await HttpClient.PostAsync("", JsonContent(CallTool("throw")), TestContext.Current.CancellationToken);
|
||||
var rpcError = await AssertSingleSseResponseAsync(response);
|
||||
|
||||
var error = AssertType<CallToolResponse>(rpcError.Result);
|
||||
var error = AssertType<CallToolResult>(rpcError.Result);
|
||||
var content = Assert.Single(error.Content);
|
||||
Assert.Contains("'throw'", content.Text);
|
||||
Assert.Contains("'throw'", Assert.IsType<TextContentBlock>(content).Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -371,16 +371,15 @@ public class StreamableHttpServerConformanceTests(ITestOutputHelper outputHelper
|
|||
if (currentSseItem <= 10)
|
||||
{
|
||||
var notification = JsonSerializer.Deserialize(sseEvent, GetJsonTypeInfo<JsonRpcNotification>());
|
||||
var progressNotification = AssertType<ProgressNotification>(notification?.Params);
|
||||
var progressNotification = AssertType<ProgressNotificationParams>(notification?.Params);
|
||||
Assert.Equal($"Progress {currentSseItem - 1}", progressNotification.Progress.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
var rpcResponse = JsonSerializer.Deserialize(sseEvent, GetJsonTypeInfo<JsonRpcResponse>());
|
||||
var callToolResponse = AssertType<CallToolResponse>(rpcResponse?.Result);
|
||||
var callToolResponse = AssertType<CallToolResult>(rpcResponse?.Result);
|
||||
var callToolContent = Assert.Single(callToolResponse.Content);
|
||||
Assert.Equal("text", callToolContent.Type);
|
||||
Assert.Equal("done", callToolContent.Text);
|
||||
Assert.Equal("done", Assert.IsType<TextContentBlock>(callToolContent).Text);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -413,10 +412,9 @@ public class StreamableHttpServerConformanceTests(ITestOutputHelper outputHelper
|
|||
{
|
||||
var response = await HttpClient.PostAsync("", JsonContent(CallTool("async-local-session")), TestContext.Current.CancellationToken);
|
||||
var rpcResponse = await AssertSingleSseResponseAsync(response);
|
||||
var callToolResponse = AssertType<CallToolResponse>(rpcResponse.Result);
|
||||
var callToolResponse = AssertType<CallToolResult>(rpcResponse.Result);
|
||||
var callToolContent = Assert.Single(callToolResponse.Content);
|
||||
Assert.Equal("text", callToolContent.Type);
|
||||
Assert.Equal($"RunSessionHandler ({expectedSessionIndex})", callToolContent.Text);
|
||||
Assert.Equal($"RunSessionHandler ({expectedSessionIndex})", Assert.IsType<TextContentBlock>(callToolContent).Text);
|
||||
}
|
||||
|
||||
await CallAsyncLocalToolAndValidateAsync(expectedSessionIndex: 0);
|
||||
|
|
@ -527,7 +525,7 @@ public class StreamableHttpServerConformanceTests(ITestOutputHelper outputHelper
|
|||
|
||||
private static T AssertType<T>(JsonNode? jsonNode)
|
||||
{
|
||||
var type = JsonSerializer.Deserialize<T>(jsonNode, GetJsonTypeInfo<T>());
|
||||
var type = JsonSerializer.Deserialize(jsonNode, GetJsonTypeInfo<T>());
|
||||
Assert.NotNull(type);
|
||||
return type;
|
||||
}
|
||||
|
|
@ -603,12 +601,11 @@ public class StreamableHttpServerConformanceTests(ITestOutputHelper outputHelper
|
|||
return initializeResult;
|
||||
}
|
||||
|
||||
private static CallToolResponse AssertEchoResponse(JsonRpcResponse rpcResponse)
|
||||
private static CallToolResult AssertEchoResponse(JsonRpcResponse rpcResponse)
|
||||
{
|
||||
var callToolResponse = AssertType<CallToolResponse>(rpcResponse.Result);
|
||||
var callToolResponse = AssertType<CallToolResult>(rpcResponse.Result);
|
||||
var callToolContent = Assert.Single(callToolResponse.Content);
|
||||
Assert.Equal("text", callToolContent.Type);
|
||||
Assert.Equal($"Hello world! ({rpcResponse.Id})", callToolContent.Text);
|
||||
Assert.Equal($"Hello world! ({rpcResponse.Id})", Assert.IsType<TextContentBlock>(callToolContent).Text);
|
||||
return callToolResponse;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -175,16 +175,16 @@ internal static class Program
|
|||
{
|
||||
throw new McpException("Missing required argument 'message'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
return new CallToolResponse()
|
||||
return new CallToolResult()
|
||||
{
|
||||
Content = [new Content() { Text = "Echo: " + message.ToString(), Type = "text" }]
|
||||
Content = [new TextContentBlock { Text = $"Echo: {message}" }]
|
||||
};
|
||||
}
|
||||
else if (request.Params?.Name == "echoSessionId")
|
||||
{
|
||||
return new CallToolResponse()
|
||||
return new CallToolResult()
|
||||
{
|
||||
Content = [new Content() { Text = request.Server.SessionId, Type = "text" }]
|
||||
Content = [new TextContentBlock { Text = request.Server.SessionId ?? string.Empty }]
|
||||
};
|
||||
}
|
||||
else if (request.Params?.Name == "sampleLLM")
|
||||
|
|
@ -198,9 +198,9 @@ internal static class Program
|
|||
var sampleResult = await request.Server.SampleAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.GetRawText())),
|
||||
cancellationToken);
|
||||
|
||||
return new CallToolResponse()
|
||||
return new CallToolResult()
|
||||
{
|
||||
Content = [new Content() { Text = $"LLM sampling result: {sampleResult.Content.Text}", Type = "text" }]
|
||||
Content = [new TextContentBlock { Text = $"LLM sampling result: {(sampleResult.Content as TextContentBlock)?.Text}" }]
|
||||
};
|
||||
}
|
||||
else
|
||||
|
|
@ -257,11 +257,7 @@ internal static class Program
|
|||
messages.Add(new PromptMessage()
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new Content()
|
||||
{
|
||||
Type = "text",
|
||||
Text = "This is a simple prompt without arguments."
|
||||
}
|
||||
Content = new TextContentBlock { Text = "This is a simple prompt without arguments." },
|
||||
});
|
||||
}
|
||||
else if (request.Params?.Name == "complex_prompt")
|
||||
|
|
@ -271,27 +267,18 @@ internal static class Program
|
|||
messages.Add(new PromptMessage()
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new Content()
|
||||
{
|
||||
Type = "text",
|
||||
Text = $"This is a complex prompt with arguments: temperature={temperature}, style={style}"
|
||||
}
|
||||
Content = new TextContentBlock { Text = $"This is a complex prompt with arguments: temperature={temperature}, style={style}" },
|
||||
});
|
||||
messages.Add(new PromptMessage()
|
||||
{
|
||||
Role = Role.Assistant,
|
||||
Content = new Content()
|
||||
{
|
||||
Type = "text",
|
||||
Text = "I understand. You've provided a complex prompt with temperature and style arguments. How would you like me to proceed?"
|
||||
}
|
||||
Content = new TextContentBlock { Text = "I understand. You've provided a complex prompt with temperature and style arguments. How would you like me to proceed?" },
|
||||
});
|
||||
messages.Add(new PromptMessage()
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new Content()
|
||||
Content = new ImageContentBlock()
|
||||
{
|
||||
Type = "image",
|
||||
Data = MCP_TINY_IMAGE,
|
||||
MimeType = "image/png"
|
||||
}
|
||||
|
|
@ -506,29 +493,29 @@ internal static class Program
|
|||
|
||||
Func<RequestContext<CompleteRequestParams>, CancellationToken, ValueTask<CompleteResult>> handler = async (request, cancellationToken) =>
|
||||
{
|
||||
if (request.Params?.Ref?.Type == "ref/resource")
|
||||
string[]? values;
|
||||
switch (request.Params?.Ref)
|
||||
{
|
||||
var resourceId = request.Params?.Ref?.Uri?.Split('/').LastOrDefault();
|
||||
if (string.IsNullOrEmpty(resourceId))
|
||||
return new CompleteResult() { Completion = new() { Values = [] } };
|
||||
case ResourceTemplateReference rtr:
|
||||
var resourceId = rtr.Uri?.Split('/').LastOrDefault();
|
||||
if (string.IsNullOrEmpty(resourceId))
|
||||
return new CompleteResult { Completion = new() { Values = [] } };
|
||||
|
||||
// Filter resource IDs that start with the input value
|
||||
var values = sampleResourceIds.Where(id => id.StartsWith(request.Params!.Argument.Value)).ToArray();
|
||||
return new CompleteResult() { Completion = new() { Values = values, HasMore = false, Total = values.Length } };
|
||||
// Filter resource IDs that start with the input value
|
||||
values = sampleResourceIds.Where(id => id.StartsWith(request.Params!.Argument.Value)).ToArray();
|
||||
return new CompleteResult { Completion = new() { Values = values, HasMore = false, Total = values.Length } };
|
||||
|
||||
case PromptReference pr:
|
||||
// Handle completion for prompt arguments
|
||||
if (!exampleCompletions.TryGetValue(request.Params.Argument.Name, out var completions))
|
||||
return new CompleteResult { Completion = new() { Values = [] } };
|
||||
|
||||
values = completions.Where(value => value.StartsWith(request.Params.Argument.Value)).ToArray();
|
||||
return new CompleteResult { Completion = new() { Values = values, HasMore = false, Total = values.Length } };
|
||||
|
||||
default:
|
||||
throw new McpException($"Unknown reference type: '{request.Params?.Ref.Type}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
if (request.Params?.Ref?.Type == "ref/prompt")
|
||||
{
|
||||
// Handle completion for prompt arguments
|
||||
if (!exampleCompletions.TryGetValue(request.Params.Argument.Name, out var completions))
|
||||
return new CompleteResult() { Completion = new() { Values = [] } };
|
||||
|
||||
var values = completions.Where(value => value.StartsWith(request.Params.Argument.Value)).ToArray();
|
||||
return new CompleteResult() { Completion = new() { Values = values, HasMore = false, Total = values.Length } };
|
||||
}
|
||||
|
||||
throw new McpException($"Unknown reference type: '{request.Params?.Ref.Type}'", McpErrorCode.InvalidParams);
|
||||
};
|
||||
|
||||
return new() { CompleteHandler = handler };
|
||||
|
|
@ -541,11 +528,7 @@ internal static class Program
|
|||
Messages = [new SamplingMessage()
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new Content()
|
||||
{
|
||||
Type = "text",
|
||||
Text = $"Resource {uri} context: {context}"
|
||||
}
|
||||
Content = new TextContentBlock { Text = $"Resource {uri} context: {context}" },
|
||||
}],
|
||||
SystemPrompt = "You are a helpful test server.",
|
||||
MaxTokens = maxTokens,
|
||||
|
|
|
|||
|
|
@ -46,11 +46,7 @@ public class Program
|
|||
Messages = [new SamplingMessage()
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new Content()
|
||||
{
|
||||
Type = "text",
|
||||
Text = $"Resource {uri} context: {context}"
|
||||
}
|
||||
Content = new TextContentBlock { Text = $"Resource {uri} context: {context}" },
|
||||
}],
|
||||
SystemPrompt = "You are a helpful test server.",
|
||||
MaxTokens = maxTokens,
|
||||
|
|
@ -173,16 +169,16 @@ public class Program
|
|||
{
|
||||
throw new McpException("Missing required argument 'message'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
return new CallToolResponse()
|
||||
return new CallToolResult()
|
||||
{
|
||||
Content = [new Content() { Text = "Echo: " + message.ToString(), Type = "text" }]
|
||||
Content = [new TextContentBlock { Text = $"Echo: {message}" }]
|
||||
};
|
||||
}
|
||||
else if (request.Params.Name == "echoSessionId")
|
||||
{
|
||||
return new CallToolResponse()
|
||||
return new CallToolResult()
|
||||
{
|
||||
Content = [new Content() { Text = request.Server.SessionId, Type = "text" }]
|
||||
Content = [new TextContentBlock { Text = request.Server.SessionId ?? string.Empty }]
|
||||
};
|
||||
}
|
||||
else if (request.Params.Name == "sampleLLM")
|
||||
|
|
@ -196,9 +192,9 @@ public class Program
|
|||
var sampleResult = await request.Server.SampleAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.ToString())),
|
||||
cancellationToken);
|
||||
|
||||
return new CallToolResponse()
|
||||
return new CallToolResult()
|
||||
{
|
||||
Content = [new Content() { Text = $"LLM sampling result: {sampleResult.Content.Text}", Type = "text" }]
|
||||
Content = [new TextContentBlock { Text = $"LLM sampling result: {(sampleResult.Content as TextContentBlock)?.Text}" }]
|
||||
};
|
||||
}
|
||||
else
|
||||
|
|
@ -308,8 +304,8 @@ public class Program
|
|||
{
|
||||
Name = "complex_prompt",
|
||||
Description = "A prompt with arguments",
|
||||
Arguments = new()
|
||||
{
|
||||
Arguments =
|
||||
[
|
||||
new PromptArgument()
|
||||
{
|
||||
Name = "temperature",
|
||||
|
|
@ -322,7 +318,7 @@ public class Program
|
|||
Description = "Output style",
|
||||
Required = false
|
||||
}
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
};
|
||||
|
|
@ -339,11 +335,7 @@ public class Program
|
|||
messages.Add(new PromptMessage()
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new Content()
|
||||
{
|
||||
Type = "text",
|
||||
Text = "This is a simple prompt without arguments."
|
||||
}
|
||||
Content = new TextContentBlock { Text = "This is a simple prompt without arguments." },
|
||||
});
|
||||
}
|
||||
else if (request.Params.Name == "complex_prompt")
|
||||
|
|
@ -353,27 +345,18 @@ public class Program
|
|||
messages.Add(new PromptMessage()
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new Content()
|
||||
{
|
||||
Type = "text",
|
||||
Text = $"This is a complex prompt with arguments: temperature={temperature}, style={style}"
|
||||
}
|
||||
Content = new TextContentBlock { Text = $"This is a complex prompt with arguments: temperature={temperature}, style={style}" },
|
||||
});
|
||||
messages.Add(new PromptMessage()
|
||||
{
|
||||
Role = Role.Assistant,
|
||||
Content = new Content()
|
||||
{
|
||||
Type = "text",
|
||||
Text = "I understand. You've provided a complex prompt with temperature and style arguments. How would you like me to proceed?"
|
||||
}
|
||||
Content = new TextContentBlock { Text = "I understand. You've provided a complex prompt with temperature and style arguments. How would you like me to proceed?" },
|
||||
});
|
||||
messages.Add(new PromptMessage()
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new Content()
|
||||
Content = new ImageContentBlock()
|
||||
{
|
||||
Type = "image",
|
||||
Data = MCP_TINY_IMAGE,
|
||||
MimeType = "image/png"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,15 +44,11 @@ public class McpClientExtensionsTests : ClientServerTestBase
|
|||
new SamplingMessage
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new Content { Type = "text", Text = "Hello" }
|
||||
Content = new TextContentBlock { Text = "Hello" }
|
||||
}
|
||||
],
|
||||
Temperature = temperature,
|
||||
MaxTokens = maxTokens,
|
||||
Meta = new RequestParamsMetadata
|
||||
{
|
||||
ProgressToken = new ProgressToken(),
|
||||
}
|
||||
};
|
||||
|
||||
var cancellationToken = CancellationToken.None;
|
||||
|
|
@ -80,7 +76,7 @@ public class McpClientExtensionsTests : ClientServerTestBase
|
|||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("Hello, World!", result.Content.Text);
|
||||
Assert.Equal("Hello, World!", (result.Content as TextContentBlock)?.Text);
|
||||
Assert.Equal("test-model", result.Model);
|
||||
Assert.Equal(Role.Assistant, result.Role);
|
||||
Assert.Equal("endTurn", result.StopReason);
|
||||
|
|
@ -98,9 +94,8 @@ public class McpClientExtensionsTests : ClientServerTestBase
|
|||
new SamplingMessage
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new Content
|
||||
Content = new ImageContentBlock
|
||||
{
|
||||
Type = "image",
|
||||
MimeType = "image/png",
|
||||
Data = Convert.ToBase64String(new byte[] { 1, 2, 3 })
|
||||
}
|
||||
|
|
@ -135,7 +130,7 @@ public class McpClientExtensionsTests : ClientServerTestBase
|
|||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(expectedData, result.Content.Data);
|
||||
Assert.Equal(expectedData, (result.Content as ImageContentBlock)?.Data);
|
||||
Assert.Equal("test-model", result.Model);
|
||||
Assert.Equal(Role.Assistant, result.Role);
|
||||
Assert.Equal("endTurn", result.StopReason);
|
||||
|
|
@ -162,11 +157,7 @@ public class McpClientExtensionsTests : ClientServerTestBase
|
|||
new SamplingMessage
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new Content
|
||||
{
|
||||
Type = "resource",
|
||||
Resource = resource
|
||||
},
|
||||
Content = new EmbeddedResourceBlock { Resource = resource },
|
||||
}
|
||||
],
|
||||
MaxTokens = 100
|
||||
|
|
@ -284,7 +275,7 @@ public class McpClientExtensionsTests : ClientServerTestBase
|
|||
JsonSerializerOptions emptyOptions = new() { TypeInfoResolver = JsonTypeInfoResolver.Combine() };
|
||||
await using IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
await Assert.ThrowsAsync<NotSupportedException>(async () => await client.SendRequestAsync<CallToolRequestParams, CallToolResponse>("Method4", new() { Name = "tool" }, emptyOptions, cancellationToken: TestContext.Current.CancellationToken));
|
||||
await Assert.ThrowsAsync<NotSupportedException>(async () => await client.SendRequestAsync<CallToolRequestParams, CallToolResult>("Method4", new() { Name = "tool" }, emptyOptions, cancellationToken: TestContext.Current.CancellationToken));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ public class McpClientFactoryTests
|
|||
SamplingHandler = async (c, p, t) =>
|
||||
new CreateMessageResult
|
||||
{
|
||||
Content = new Content { Text = "result" },
|
||||
Content = new TextContentBlock { Text = "result" },
|
||||
Model = "test-model",
|
||||
Role = Role.User,
|
||||
StopReason = "endTurn"
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ public partial class McpClientResourceTemplateTests : ClientServerTestBase
|
|||
mcpServerBuilder.WithReadResourceHandler((request, cancellationToken) =>
|
||||
new ValueTask<ReadResourceResult>(new ReadResourceResult()
|
||||
{
|
||||
Contents = [new TextResourceContents() { Text = request.Params?.Uri ?? string.Empty }]
|
||||
Contents = [new TextResourceContents { Text = request.Params?.Uri ?? string.Empty }]
|
||||
}));
|
||||
}
|
||||
|
||||
|
|
@ -95,10 +95,10 @@ public partial class McpClientResourceTemplateTests : ClientServerTestBase
|
|||
public int Level { get; set; } = 4;
|
||||
|
||||
[JsonPropertyName("variables")]
|
||||
public Dictionary<string, JsonElement> Variables { get; set; } = [];
|
||||
public IDictionary<string, JsonElement> Variables { get; set; } = new Dictionary<string, JsonElement>();
|
||||
|
||||
[JsonPropertyName("testcases")]
|
||||
public List<List<JsonElement>> TestCases { get; set; } = [];
|
||||
public IList<List<JsonElement>> TestCases { get; set; } = [];
|
||||
}
|
||||
|
||||
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.SnakeCaseLower)]
|
||||
|
|
|
|||
|
|
@ -92,12 +92,12 @@ public partial class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIn
|
|||
// assert
|
||||
Assert.NotNull(result);
|
||||
Assert.False(result.IsError);
|
||||
var textContent = Assert.Single(result.Content, c => c.Type == "text");
|
||||
var textContent = Assert.Single(result.Content.OfType<TextContentBlock>());
|
||||
Assert.Equal("Echo: Hello MCP!", textContent.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task CallTool_Stdio_EchoSessionId_ReturnsNull()
|
||||
public async Task CallTool_Stdio_EchoSessionId_ReturnsEmpty()
|
||||
{
|
||||
// arrange
|
||||
|
||||
|
|
@ -108,8 +108,8 @@ public partial class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIn
|
|||
// assert
|
||||
Assert.NotNull(result);
|
||||
Assert.False(result.IsError);
|
||||
var textContent = Assert.Single(result.Content, c => c.Type == "text");
|
||||
Assert.Null(textContent.Text);
|
||||
var textContent = Assert.Single(result.Content.OfType<TextContentBlock>());
|
||||
Assert.Empty(textContent.Text);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
@ -326,17 +326,14 @@ public partial class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIn
|
|||
|
||||
[Theory]
|
||||
[MemberData(nameof(GetClients))]
|
||||
public async Task Complete_Stdio_ResourceReference(string clientId)
|
||||
public async Task Complete_Stdio_ResourceTemplateReference(string clientId)
|
||||
{
|
||||
// arrange
|
||||
|
||||
// act
|
||||
await using var client = await _fixture.CreateClientAsync(clientId);
|
||||
var result = await client.CompleteAsync(new Reference
|
||||
{
|
||||
Type = "ref/resource",
|
||||
Uri = "test://static/resource/1"
|
||||
},
|
||||
var result = await client.CompleteAsync(
|
||||
new ResourceTemplateReference { Uri = "test://static/resource/1" },
|
||||
"argument_name", "1",
|
||||
TestContext.Current.CancellationToken
|
||||
);
|
||||
|
|
@ -354,11 +351,8 @@ public partial class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIn
|
|||
|
||||
// act
|
||||
await using var client = await _fixture.CreateClientAsync(clientId);
|
||||
var result = await client.CompleteAsync(new Reference
|
||||
{
|
||||
Type = "ref/prompt",
|
||||
Name = "irrelevant"
|
||||
},
|
||||
var result = await client.CompleteAsync(
|
||||
new PromptReference { Name = "irrelevant" },
|
||||
argumentName: "style", argumentValue: "fo",
|
||||
TestContext.Current.CancellationToken
|
||||
);
|
||||
|
|
@ -387,11 +381,7 @@ public partial class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIn
|
|||
{
|
||||
Model = "test-model",
|
||||
Role = Role.Assistant,
|
||||
Content = new Content
|
||||
{
|
||||
Type = "text",
|
||||
Text = "Test response"
|
||||
}
|
||||
Content = new TextContentBlock { Text = "Test response" },
|
||||
};
|
||||
},
|
||||
},
|
||||
|
|
@ -410,8 +400,7 @@ public partial class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIn
|
|||
|
||||
// assert
|
||||
Assert.NotNull(result);
|
||||
var textContent = Assert.Single(result.Content);
|
||||
Assert.Equal("text", textContent.Type);
|
||||
var textContent = Assert.Single(result.Content.OfType<TextContentBlock>());
|
||||
Assert.False(string.IsNullOrEmpty(textContent.Text));
|
||||
}
|
||||
|
||||
|
|
@ -453,7 +442,7 @@ public partial class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIn
|
|||
await using var client = await _fixture.CreateClientAsync(clientId);
|
||||
|
||||
// Verify we can send notifications without errors
|
||||
await client.SendNotificationAsync(NotificationMethods.RootsUpdatedNotification, cancellationToken: TestContext.Current.CancellationToken);
|
||||
await client.SendNotificationAsync(NotificationMethods.RootsListChangedNotification, cancellationToken: TestContext.Current.CancellationToken);
|
||||
await client.SendNotificationAsync("test/notification", new TestNotification { Test = true }, cancellationToken: TestContext.Current.CancellationToken, serializerOptions: JsonContext3.Default.Options);
|
||||
|
||||
// assert
|
||||
|
|
@ -556,9 +545,9 @@ public partial class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIn
|
|||
|
||||
Assert.NotNull(result);
|
||||
Assert.NotEmpty(result.Content);
|
||||
Assert.Equal("text", result.Content[0].Type);
|
||||
Assert.Contains("LLM sampling result:", result.Content[0].Text);
|
||||
Assert.Contains("Eiffel", result.Content[0].Text);
|
||||
var content = Assert.IsType<TextContentBlock>(result.Content[0]);
|
||||
Assert.Contains("LLM sampling result:", content.Text);
|
||||
Assert.Contains("Eiffel", content.Text);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ public class McpServerBuilderExtensionsHandlerTests
|
|||
[Fact]
|
||||
public void WithCallToolHandler_Sets_Handler()
|
||||
{
|
||||
Func<RequestContext<CallToolRequestParams>, CancellationToken, ValueTask<CallToolResponse>> handler = async (context, token) => new CallToolResponse();
|
||||
Func<RequestContext<CallToolRequestParams>, CancellationToken, ValueTask<CallToolResult>> handler = async (context, token) => new CallToolResult();
|
||||
|
||||
_builder.Object.WithCallToolHandler(handler);
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
|
|||
case "FinalCustomPrompt":
|
||||
return new GetPromptResult()
|
||||
{
|
||||
Messages = [new() { Role = Role.User, Content = new() { Text = $"hello from {request.Params.Name}", Type = "text" } }],
|
||||
Messages = [new() { Role = Role.User, Content = new TextContentBlock { Text = $"hello from {request.Params.Name}" } }],
|
||||
};
|
||||
|
||||
default:
|
||||
|
|
@ -162,6 +162,20 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
|
|||
Assert.DoesNotContain(prompts, t => t.Name == "NewPrompt");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TitleAttributeProperty_PropagatedToTitle()
|
||||
{
|
||||
await using IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
var prompts = await client.ListPromptsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(prompts);
|
||||
Assert.NotEmpty(prompts);
|
||||
|
||||
McpClientPrompt prompt = prompts.First(t => t.Name == nameof(SimplePrompts.ReturnsString));
|
||||
|
||||
Assert.Equal("This is a title", prompt.Title);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Throws_When_Prompt_Fails()
|
||||
{
|
||||
|
|
@ -263,7 +277,7 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
|
|||
public static ChatMessage[] ThrowsException([Description("The first parameter")] string message) =>
|
||||
throw new FormatException("uh oh");
|
||||
|
||||
[McpServerPrompt, Description("Returns chat messages")]
|
||||
[McpServerPrompt(Title = "This is a title"), Description("Returns chat messages")]
|
||||
public string ReturnsString([Description("The first parameter")] string message) =>
|
||||
$"The prompt is: {message}. The id is {id}.";
|
||||
}
|
||||
|
|
@ -273,10 +287,10 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
|
|||
{
|
||||
[McpServerPrompt]
|
||||
public static PromptMessage AnotherPrompt(ObjectWithId id) =>
|
||||
new PromptMessage
|
||||
new()
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new() { Text = "hello", Type = "text" },
|
||||
Content = new TextContentBlock { Text = "hello" },
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ public partial class McpServerBuilderExtensionsResourcesTests : ClientServerTest
|
|||
case "test://ResourceTemplate2":
|
||||
return new ReadResourceResult()
|
||||
{
|
||||
Contents = [new TextResourceContents() { Text = request.Params?.Uri ?? "(null)" }]
|
||||
Contents = [new TextResourceContents { Text = request.Params?.Uri ?? "(null)" }]
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -196,6 +196,24 @@ public partial class McpServerBuilderExtensionsResourcesTests : ClientServerTest
|
|||
Assert.DoesNotContain(resources, t => t.Name == "NewResource");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TitleAttributeProperty_PropagatedToTitle()
|
||||
{
|
||||
await using IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
var resources = await client.ListResourcesAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(resources);
|
||||
Assert.NotEmpty(resources);
|
||||
McpClientResource resource = resources.First(t => t.Name == nameof(SimpleResources.SomeNeatDirectResource));
|
||||
Assert.Equal("This is a title", resource.Title);
|
||||
|
||||
var resourceTemplates = await client.ListResourceTemplatesAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(resourceTemplates);
|
||||
Assert.NotEmpty(resourceTemplates);
|
||||
McpClientResourceTemplate resourceTemplate = resourceTemplates.First(t => t.Name == nameof(SimpleResources.SomeNeatTemplatedResource));
|
||||
Assert.Equal("This is another title", resourceTemplate.Title);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Throws_When_Resource_Fails()
|
||||
{
|
||||
|
|
@ -273,10 +291,10 @@ public partial class McpServerBuilderExtensionsResourcesTests : ClientServerTest
|
|||
[McpServerResourceType]
|
||||
public sealed class SimpleResources
|
||||
{
|
||||
[McpServerResource, Description("Some neat direct resource")]
|
||||
[McpServerResource(Title = "This is a title"), Description("Some neat direct resource")]
|
||||
public static string SomeNeatDirectResource() => "This is a neat resource";
|
||||
|
||||
[McpServerResource, Description("Some neat resource with parameters")]
|
||||
[McpServerResource(Title = "This is another title"), Description("Some neat resource with parameters")]
|
||||
public static string SomeNeatTemplatedResource(string name) => $"This is a neat resource with parameters: {name}";
|
||||
|
||||
[McpServerResource]
|
||||
|
|
|
|||
|
|
@ -95,9 +95,9 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
case "FirstCustomTool":
|
||||
case "SecondCustomTool":
|
||||
case "FinalCustomTool":
|
||||
return new CallToolResponse()
|
||||
return new CallToolResult()
|
||||
{
|
||||
Content = [new Content() { Text = $"{request.Params.Name}Result", Type = "text" }],
|
||||
Content = [new TextContentBlock { Text = $"{request.Params.Name}Result" }],
|
||||
};
|
||||
|
||||
default:
|
||||
|
|
@ -239,8 +239,9 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
Assert.NotNull(result.Content);
|
||||
Assert.NotEmpty(result.Content);
|
||||
|
||||
Assert.Equal("hello Peter", result.Content[0].Text);
|
||||
Assert.Equal("text", result.Content[0].Type);
|
||||
var tc = Assert.IsType<TextContentBlock>(result.Content[0]);
|
||||
Assert.Equal("hello Peter", tc.Text);
|
||||
Assert.Equal("text", tc.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -255,8 +256,8 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
|
||||
Assert.NotNull(result.Content);
|
||||
Assert.NotEmpty(result.Content);
|
||||
Assert.Equal("hello Peter", result.Content[0].Text);
|
||||
Assert.Equal("hello2 Peter", result.Content[1].Text);
|
||||
Assert.Equal("hello Peter", (result.Content[0] as TextContentBlock)?.Text);
|
||||
Assert.Equal("hello2 Peter", (result.Content[1] as TextContentBlock)?.Text);
|
||||
|
||||
result = await client.CallToolAsync(
|
||||
"SecondCustomTool",
|
||||
|
|
@ -264,7 +265,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
Assert.NotNull(result);
|
||||
Assert.NotNull(result.Content);
|
||||
Assert.NotEmpty(result.Content);
|
||||
Assert.Equal("SecondCustomToolResult", result.Content[0].Text);
|
||||
Assert.Equal("SecondCustomToolResult", (result.Content[0] as TextContentBlock)?.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -294,8 +295,8 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
Assert.NotNull(result.Content);
|
||||
Assert.NotEmpty(result.Content);
|
||||
|
||||
Assert.Equal("""{"SomeProp":false}""", Regex.Replace(result.Content[0].Text ?? string.Empty, "\\s+", ""));
|
||||
Assert.Equal("text", result.Content[0].Type);
|
||||
Assert.Equal("""{"SomeProp":false}""", Regex.Replace((result.Content[0] as TextContentBlock)?.Text ?? string.Empty, "\\s+", ""));
|
||||
Assert.Equal("text", (result.Content[0] as TextContentBlock)?.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -310,8 +311,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
Assert.NotNull(result.Content);
|
||||
Assert.NotEmpty(result.Content);
|
||||
|
||||
Assert.Equal("5", result.Content[0].Text);
|
||||
Assert.Equal("text", result.Content[0].Type);
|
||||
Assert.Equal("5", (result.Content[0] as TextContentBlock)?.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -328,8 +328,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
Assert.NotNull(result.Content);
|
||||
Assert.NotEmpty(result.Content);
|
||||
|
||||
Assert.Equal("Peter", result.Content[0].Text);
|
||||
Assert.Equal("text", result.Content[0].Type);
|
||||
Assert.Equal("Peter", (result.Content[0] as TextContentBlock)?.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -348,7 +347,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
Assert.NotNull(result.Content);
|
||||
Assert.NotEmpty(result.Content);
|
||||
|
||||
parts[i] = result.Content[0].Text?.Split(':') ?? [];
|
||||
parts[i] = (result.Content[0] as TextContentBlock)?.Text?.Split(':') ?? [];
|
||||
Assert.Equal(2, parts[i].Length);
|
||||
}
|
||||
|
||||
|
|
@ -373,7 +372,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
Assert.True(result.IsError);
|
||||
Assert.NotNull(result.Content);
|
||||
Assert.NotEmpty(result.Content);
|
||||
Assert.Contains("An error occurred", result.Content[0].Text);
|
||||
Assert.Contains("An error occurred", (result.Content[0] as TextContentBlock)?.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -560,6 +559,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
var annotations = tool.ProtocolTool.Annotations;
|
||||
Assert.NotNull(annotations);
|
||||
Assert.Equal("Return An Integer", annotations.Title);
|
||||
Assert.Equal("Return An Integer", tool.ProtocolTool.Title);
|
||||
Assert.False(annotations.DestructiveHint);
|
||||
Assert.True(annotations.IdempotentHint);
|
||||
Assert.False(annotations.OpenWorldHint);
|
||||
|
|
@ -582,6 +582,22 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
Assert.Null(annotations.ReadOnlyHint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TitleAttributeProperty_PropagatedToTitle()
|
||||
{
|
||||
await using IMcpClient client = await CreateMcpClientForServer();
|
||||
|
||||
var tools = await client.ListToolsAsync(cancellationToken: TestContext.Current.CancellationToken);
|
||||
Assert.NotNull(tools);
|
||||
Assert.NotEmpty(tools);
|
||||
|
||||
McpClientTool tool = tools.First(t => t.Name == nameof(EchoTool.EchoComplex));
|
||||
|
||||
Assert.Equal("This is a title", tool.Title);
|
||||
Assert.Equal("This is a title", tool.ProtocolTool.Title);
|
||||
Assert.Equal("This is a title", tool.ProtocolTool.Annotations?.Title);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HandlesIProgressParameter()
|
||||
{
|
||||
|
|
@ -596,10 +612,10 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
TaskCompletionSource tcs = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
int remainingNotifications = 10;
|
||||
|
||||
ConcurrentQueue<ProgressNotification> notifications = new();
|
||||
ConcurrentQueue<ProgressNotificationParams> notifications = new();
|
||||
await using (client.RegisterNotificationHandler(NotificationMethods.ProgressNotification, (notification, cancellationToken) =>
|
||||
{
|
||||
if (JsonSerializer.Deserialize<ProgressNotification>(notification.Params, McpJsonUtilities.DefaultOptions) is { } pn &&
|
||||
if (JsonSerializer.Deserialize<ProgressNotificationParams>(notification.Params, McpJsonUtilities.DefaultOptions) is { } pn &&
|
||||
pn.ProgressToken == new ProgressToken("abc123"))
|
||||
{
|
||||
notifications.Enqueue(pn);
|
||||
|
|
@ -612,12 +628,12 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
return default;
|
||||
}))
|
||||
{
|
||||
var result = await client.SendRequestAsync<CallToolRequestParams, CallToolResponse>(
|
||||
var result = await client.SendRequestAsync<CallToolRequestParams, CallToolResult>(
|
||||
RequestMethods.ToolsCall,
|
||||
new CallToolRequestParams
|
||||
{
|
||||
Name = progressTool.ProtocolTool.Name,
|
||||
Meta = new() { ProgressToken = new("abc123") },
|
||||
ProgressToken = new("abc123"),
|
||||
},
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
|
|
@ -625,7 +641,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
Assert.Contains("done", JsonSerializer.Serialize(result, McpJsonUtilities.DefaultOptions));
|
||||
}
|
||||
|
||||
ProgressNotification[] array = notifications.OrderBy(n => n.Progress.Progress).ToArray();
|
||||
ProgressNotificationParams[] array = notifications.OrderBy(n => n.Progress.Progress).ToArray();
|
||||
Assert.Equal(10, array.Length);
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
|
|
@ -647,7 +663,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
McpClientTool cancelableTool = tools.First(t => t.Name == nameof(EchoTool.InfiniteCancelableOperation));
|
||||
|
||||
var requestId = new RequestId(Guid.NewGuid().ToString());
|
||||
var invokeTask = client.SendRequestAsync<CallToolRequestParams, CallToolResponse>(
|
||||
var invokeTask = client.SendRequestAsync<CallToolRequestParams, CallToolResult>(
|
||||
RequestMethods.ToolsCall,
|
||||
new CallToolRequestParams { Name = cancelableTool.ProtocolTool.Name },
|
||||
requestId: requestId,
|
||||
|
|
@ -655,7 +671,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
|
||||
await client.SendNotificationAsync(
|
||||
NotificationMethods.CancelledNotification,
|
||||
parameters: new CancelledNotification()
|
||||
parameters: new CancelledNotificationParams()
|
||||
{
|
||||
RequestId = requestId,
|
||||
},
|
||||
|
|
@ -723,7 +739,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
return cancellationToken.GetHashCode();
|
||||
}
|
||||
|
||||
[McpServerTool]
|
||||
[McpServerTool(Title = "This is a title")]
|
||||
public static string EchoComplex(ComplexObject complex)
|
||||
{
|
||||
return complex.Name!;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
using Microsoft.Extensions.Hosting;
|
||||
using ModelContextProtocol.Protocol;
|
||||
using ModelContextProtocol.Server;
|
||||
using Moq;
|
||||
using System.IO.Pipelines;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Configuration;
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public partial class McpServerScopedTests : ClientServerTestBase
|
|||
protected override void ConfigureServices(ServiceCollection services, IMcpServerBuilder mcpServerBuilder)
|
||||
{
|
||||
mcpServerBuilder.WithTools<EchoTool>(serializerOptions: McpServerScopedTestsJsonContext.Default.Options);
|
||||
services.AddScoped(_ => new ComplexObject() { Name = "Scoped" });
|
||||
services.AddScoped(_ => new ComplexObject { Name = "Scoped" });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -83,11 +83,7 @@ public class DockerEverythingServerTests(ITestOutputHelper testOutputHelper) : L
|
|||
{
|
||||
Model = "test-model",
|
||||
Role = Role.Assistant,
|
||||
Content = new Content
|
||||
{
|
||||
Type = "text",
|
||||
Text = "Test response"
|
||||
}
|
||||
Content = new TextContentBlock { Text = "Test response" },
|
||||
};
|
||||
},
|
||||
},
|
||||
|
|
@ -109,7 +105,7 @@ public class DockerEverythingServerTests(ITestOutputHelper testOutputHelper) : L
|
|||
|
||||
// assert
|
||||
Assert.NotNull(result);
|
||||
var textContent = Assert.Single(result.Content);
|
||||
var textContent = Assert.Single(result.Content.OfType<TextContentBlock>());
|
||||
Assert.Equal("text", textContent.Type);
|
||||
Assert.False(string.IsNullOrEmpty(textContent.Text));
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue