csharp-sdk/tests/ModelContextProtocol.Tests/Client/McpClientPromptTests.cs

170 lines
6.4 KiB
C#

using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.ComponentModel;
using System.Text.Json.Nodes;
namespace ModelContextProtocol.Tests.Client;
public class McpClientPromptTests : ClientServerTestBase
{
public McpClientPromptTests(ITestOutputHelper outputHelper)
: base(outputHelper)
{
}
protected override void ConfigureServices(ServiceCollection services, IMcpServerBuilder mcpServerBuilder)
{
mcpServerBuilder.WithPrompts<GreetingPrompts>();
}
[McpServerPromptType]
private sealed class GreetingPrompts
{
[McpServerPrompt, Description("Generates a greeting prompt")]
public static ChatMessage Greeting([Description("The name to greet")] string name) =>
new(ChatRole.User, $"Hello, {name}!");
[McpServerPrompt, Description("Echoes back the metadata it receives")]
public static ChatMessage MetadataEcho(RequestContext<GetPromptRequestParams> context) =>
new(ChatRole.User, context.Params.Meta?.ToJsonString() ?? "{}");
}
[Fact]
public async Task Constructor_WithValidParameters_CreatesInstance()
{
await using McpClient client = await CreateMcpClientForServer();
var prompts = await client.ListPromptsAsync(cancellationToken: TestContext.Current.CancellationToken);
var originalPrompt = prompts.First(p => p.Name == "greeting");
var promptDefinition = originalPrompt.ProtocolPrompt;
var newPrompt = new McpClientPrompt(client, promptDefinition);
Assert.NotNull(newPrompt);
Assert.Equal("greeting", newPrompt.Name);
Assert.Equal("Generates a greeting prompt", newPrompt.Description);
Assert.Same(promptDefinition, newPrompt.ProtocolPrompt);
}
[Fact]
public void Constructor_WithNullClient_ThrowsArgumentNullException()
{
var promptDefinition = new Prompt
{
Name = "test",
Description = "Test prompt"
};
Assert.Throws<ArgumentNullException>("client", () => new McpClientPrompt(null!, promptDefinition));
}
[Fact]
public async Task Constructor_WithNullPrompt_ThrowsArgumentNullException()
{
await using McpClient client = await CreateMcpClientForServer();
Assert.Throws<ArgumentNullException>("prompt", () => new McpClientPrompt(client, null!));
}
[Fact]
public async Task ReusePromptDefinition_AcrossDifferentClients_InvokesSuccessfully()
{
Prompt promptDefinition;
{
await using McpClient client1 = await CreateMcpClientForServer();
var prompts = await client1.ListPromptsAsync(cancellationToken: TestContext.Current.CancellationToken);
var greetingPrompt = prompts.First(p => p.Name == "greeting");
promptDefinition = greetingPrompt.ProtocolPrompt;
}
await using McpClient client2 = await CreateMcpClientForServer();
var reusedPrompt = new McpClientPrompt(client2, promptDefinition);
var result = await reusedPrompt.GetAsync(
new Dictionary<string, object?> { ["name"] = "World" },
cancellationToken: TestContext.Current.CancellationToken);
Assert.NotNull(result);
Assert.NotNull(result.Messages);
var message = result.Messages.First();
Assert.NotNull(message.Content);
var textContent = message.Content as TextContentBlock;
Assert.NotNull(textContent);
Assert.Equal("Hello, World!", textContent.Text);
}
[Fact]
public async Task ReusePromptDefinition_PreservesPromptMetadata()
{
await using McpClient client = await CreateMcpClientForServer();
var prompts = await client.ListPromptsAsync(cancellationToken: TestContext.Current.CancellationToken);
var originalPrompt = prompts.First(p => p.Name == "greeting");
var promptDefinition = originalPrompt.ProtocolPrompt;
var reusedPrompt = new McpClientPrompt(client, promptDefinition);
Assert.Equal(originalPrompt.Name, reusedPrompt.Name);
Assert.Equal(originalPrompt.Description, reusedPrompt.Description);
Assert.Equal(originalPrompt.ProtocolPrompt.Name, reusedPrompt.ProtocolPrompt.Name);
Assert.Equal(originalPrompt.ProtocolPrompt.Description, reusedPrompt.ProtocolPrompt.Description);
}
[Fact]
public async Task ManuallyConstructedPrompt_CanBeInvoked()
{
await using McpClient client = await CreateMcpClientForServer();
var manualPrompt = new Prompt
{
Name = "greeting",
Description = "Generates a greeting prompt"
};
var clientPrompt = new McpClientPrompt(client, manualPrompt);
var result = await clientPrompt.GetAsync(
new Dictionary<string, object?> { ["name"] = "Test" },
cancellationToken: TestContext.Current.CancellationToken);
Assert.NotNull(result);
Assert.NotNull(result.Messages);
var message = result.Messages.First();
Assert.NotNull(message.Content);
var textContent = message.Content as TextContentBlock;
Assert.NotNull(textContent);
Assert.Equal("Hello, Test!", textContent.Text);
}
[Fact]
public async Task GetAsync_WithRequestOptions_PassesMetaToServer()
{
await using McpClient client = await CreateMcpClientForServer();
var prompts = await client.ListPromptsAsync(cancellationToken: TestContext.Current.CancellationToken);
var prompt = prompts.Single(p => p.Name == "metadata_echo");
RequestOptions requestOptions = new()
{
Meta = new JsonObject
{
["traceId"] = "test-trace-123",
["customKey"] = "customValue"
}
};
var result = await prompt.GetAsync(options: requestOptions, cancellationToken: TestContext.Current.CancellationToken);
Assert.NotNull(result);
var message = result.Messages.First();
var textContent = Assert.IsType<TextContentBlock>(message.Content);
var receivedMetadata = JsonNode.Parse(textContent.Text)?.AsObject();
Assert.NotNull(receivedMetadata);
Assert.Equal("test-trace-123", receivedMetadata["traceId"]?.GetValue<string>());
Assert.Equal("customValue", receivedMetadata["customKey"]?.GetValue<string>());
}
}