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(); } [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 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("client", () => new McpClientPrompt(null!, promptDefinition)); } [Fact] public async Task Constructor_WithNullPrompt_ThrowsArgumentNullException() { await using McpClient client = await CreateMcpClientForServer(); Assert.Throws("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 { ["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 { ["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(message.Content); var receivedMetadata = JsonNode.Parse(textContent.Text)?.AsObject(); Assert.NotNull(receivedMetadata); Assert.Equal("test-trace-123", receivedMetadata["traceId"]?.GetValue()); Assert.Equal("customValue", receivedMetadata["customKey"]?.GetValue()); } }