using ModelContextProtocol; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; using System.ComponentModel; using System.Text.Json; namespace ConformanceServer.Tools; [McpServerToolType] public class ConformanceTools { // Sample base64 encoded 1x1 red PNG pixel for testing private const string TestImageBase64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg=="; // Sample base64 encoded minimal WAV file for testing private const string TestAudioBase64 = "UklGRiYAAABXQVZFZm10IBAAAAABAAEAQB8AAAB9AAACABAAZGF0YQIAAAA="; /// /// Simple text tool - returns simple text content for testing /// [McpServerTool(Name = "test_simple_text")] [Description("Tests simple text content response")] public static string SimpleText() { return "This is a simple text response for testing."; } /// /// Image content tool - returns base64-encoded image content /// [McpServerTool(Name = "test_image_content")] [Description("Tests image content response")] public static ImageContentBlock ImageContent() { return new ImageContentBlock { Data = System.Text.Encoding.UTF8.GetBytes(TestImageBase64), MimeType = "image/png" }; } /// /// Audio content tool - returns base64-encoded audio content /// [McpServerTool(Name = "test_audio_content")] [Description("Tests audio content response")] public static AudioContentBlock AudioContent() { return new AudioContentBlock { Data = System.Text.Encoding.UTF8.GetBytes(TestAudioBase64), MimeType = "audio/wav" }; } /// /// Embedded resource tool - returns embedded resource content /// [McpServerTool(Name = "test_embedded_resource")] [Description("Tests embedded resource content response")] public static EmbeddedResourceBlock EmbeddedResource() { return new EmbeddedResourceBlock { Resource = new TextResourceContents { Uri = "test://embedded-resource", MimeType = "text/plain", Text = "This is an embedded resource content." } }; } /// /// Multiple content types tool - returns mixed content types (text, image, resource) /// [McpServerTool(Name = "test_multiple_content_types")] [Description("Tests response with multiple content types (text, image, resource)")] public static ContentBlock[] MultipleContentTypes() { return [ new TextContentBlock { Text = "Multiple content types test:" }, new ImageContentBlock { Data = System.Text.Encoding.UTF8.GetBytes(TestImageBase64), MimeType = "image/png" }, new EmbeddedResourceBlock { Resource = new TextResourceContents { Uri = "test://mixed-content-resource", MimeType = "application/json", Text = "{ \"test\" = \"data\", \"value\" = 123 }" } } ]; } /// /// Tool with logging - emits log messages during execution /// [McpServerTool(Name = "test_tool_with_logging")] [Description("Tests tool that emits log messages during execution")] public static async Task ToolWithLogging( RequestContext context, CancellationToken cancellationToken) { var server = context.Server; // Use ILogger for logging (will be forwarded to client if supported) ILoggerProvider loggerProvider = server.AsClientLoggerProvider(); ILogger logger = loggerProvider.CreateLogger("ConformanceTools"); logger.LogInformation("Tool execution started"); await Task.Delay(50, cancellationToken); logger.LogInformation("Tool processing data"); await Task.Delay(50, cancellationToken); logger.LogInformation("Tool execution completed"); return "Tool with logging executed successfully"; } /// /// Tool with progress - reports progress notifications /// [McpServerTool(Name = "test_tool_with_progress")] [Description("Tests tool that reports progress notifications")] public static async Task ToolWithProgress( McpServer server, RequestContext context, CancellationToken cancellationToken) { var progressToken = context.Params.ProgressToken; if (progressToken is not null) { await server.NotifyProgressAsync(progressToken.Value, new ProgressNotificationValue { Progress = 0, Total = 100, }, cancellationToken: cancellationToken); await Task.Delay(50, cancellationToken); await server.NotifyProgressAsync(progressToken.Value, new ProgressNotificationValue { Progress = 50, Total = 100, }, cancellationToken: cancellationToken); await Task.Delay(50, cancellationToken); await server.NotifyProgressAsync(progressToken.Value, new ProgressNotificationValue { Progress = 100, Total = 100, }, cancellationToken: cancellationToken); } return progressToken?.ToString() ?? "No progress token provided"; } /// /// Error handling tool - intentionally throws an error for testing /// [McpServerTool(Name = "test_error_handling")] [Description("Tests error response handling")] public static string ErrorHandling() { throw new Exception("This tool intentionally returns an error for testing"); } /// /// Sampling tool - requests LLM completion from client /// [McpServerTool(Name = "test_sampling")] [Description("Tests server-initiated sampling (LLM completion request)")] public static async Task Sampling( McpServer server, [Description("The prompt to send to the LLM")] string prompt, CancellationToken cancellationToken) { try { var samplingParams = new CreateMessageRequestParams { Messages = [new SamplingMessage { Role = Role.User, Content = [new TextContentBlock { Text = prompt }], }], MaxTokens = 100, Temperature = 0.7f }; var result = await server.SampleAsync(samplingParams, cancellationToken); return $"Sampling result: {(result.Content.FirstOrDefault() as TextContentBlock)?.Text ?? "No text content"}"; } catch (Exception ex) { return $"Sampling not supported or error: {ex.Message}"; } } /// /// Elicitation tool - requests user input from client /// [McpServerTool(Name = "test_elicitation")] [Description("Tests elicitation (user input request from client)")] public static async Task Elicitation( McpServer server, [Description("Message to show to the user")] string message, CancellationToken cancellationToken) { try { var schema = new ElicitRequestParams.RequestSchema { Properties = { ["response"] = new ElicitRequestParams.StringSchema() { Description = "User's response to the message" } } }; var result = await server.ElicitAsync(new ElicitRequestParams { Message = message, RequestedSchema = schema }, cancellationToken); if (result.Action == "accept" && result.Content != null) { return $"User responded: {result.Content["response"].GetString()}"; } else { return $"Elicitation {result.Action}"; } } catch (Exception ex) { return $"Elicitation not supported or error: {ex.Message}"; } } /// /// SEP-1034: Elicitation with default values for all primitive types /// [McpServerTool(Name = "test_elicitation_sep1034_defaults")] [Description("Tests elicitation with default values per SEP-1034")] public static async Task ElicitationSep1034Defaults( McpServer server, CancellationToken cancellationToken) { try { var schema = new ElicitRequestParams.RequestSchema { Properties = { ["name"] = new ElicitRequestParams.StringSchema() { Description = "Name", Default = "John Doe" }, ["age"] = new ElicitRequestParams.NumberSchema() { Type = "integer", Description = "Age", Default = 30 }, ["score"] = new ElicitRequestParams.NumberSchema() { Description = "Score", Default = 95.5 }, ["status"] = new ElicitRequestParams.UntitledSingleSelectEnumSchema() { Description = "Status", Enum = ["active", "inactive", "pending"], Default = "active" }, ["verified"] = new ElicitRequestParams.BooleanSchema() { Description = "Verified", Default = true } } }; var result = await server.ElicitAsync(new ElicitRequestParams { Message = "Test elicitation with default values for primitive types", RequestedSchema = schema }, cancellationToken); if (result.Action == "accept" && result.Content != null) { return $"Accepted with values: string={result.Content["stringField"].GetString()}, " + $"number={result.Content["numberField"].GetInt32()}, " + $"boolean={result.Content["booleanField"].GetBoolean()}"; } else { return $"Elicitation {result.Action}"; } } catch (Exception ex) { return $"Elicitation not supported or error: {ex.Message}"; } } /// /// SEP-1330: Elicitation with enum schema improvements /// [McpServerTool(Name = "test_elicitation_sep1330_enums")] [Description("Tests elicitation with enum schema improvements per SEP-1330")] public static async Task ElicitationSep1330Enums( McpServer server, CancellationToken cancellationToken) { try { var schema = new ElicitRequestParams.RequestSchema { Properties = { ["untitledSingle"] = new ElicitRequestParams.UntitledSingleSelectEnumSchema() { Description = "Choose an option", Enum = ["option1", "option2", "option3"] }, ["titledSingle"] = new ElicitRequestParams.TitledSingleSelectEnumSchema() { Description = "Choose a titled option", OneOf = [ new() { Const = "value1", Title = "First Option" }, new() { Const = "value2", Title = "Second Option" }, new() { Const = "value3", Title = "Third Option" } ] }, #pragma warning disable MCP9001 ["legacyEnum"] = new ElicitRequestParams.LegacyTitledEnumSchema() { Description = "Choose a legacy option", Enum = ["opt1", "opt2", "opt3"], EnumNames = ["Option One", "Option Two", "Option Three"] }, #pragma warning restore MCP9001 ["untitledMulti"] = new ElicitRequestParams.UntitledMultiSelectEnumSchema() { Description = "Choose multiple options", Items = new ElicitRequestParams.UntitledEnumItemsSchema { Enum = ["option1", "option2", "option3"] } }, ["titledMulti"] = new ElicitRequestParams.TitledMultiSelectEnumSchema() { Description = "Choose multiple titled options", Items = new ElicitRequestParams.TitledEnumItemsSchema { AnyOf = [ new() { Const = "value1", Title = "First Choice" }, new() { Const = "value2", Title = "Second Choice" }, new() { Const = "value3", Title = "Third Choice" } ] } } } }; var result = await server.ElicitAsync(new ElicitRequestParams { Message = "Test elicitation with enum schema", RequestedSchema = schema }, cancellationToken); if (result.Action == "accept" && result.Content != null) { return $"Elicitation completed: action={result.Action}, content={result.Content}"; } else { return $"Elicitation {result.Action}"; } } catch (Exception ex) { return $"Elicitation not supported or error: {ex.Message}"; } } /// Create the json_schema_2020_12_tool with a raw JSON Schema 2020-12 inputSchema. public static McpServerTool CreateJsonSchema202012Tool() { var tool = McpServerTool.Create( () => "JSON Schema 2020-12 tool executed successfully", new() { Name = "json_schema_2020_12_tool", Description = "Tool with JSON Schema 2020-12 features" }); tool.ProtocolTool.InputSchema = JsonElement.Parse(""" { "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "$defs": { "address": { "type": "object", "properties": { "street": { "type": "string" }, "city": { "type": "string" } } } }, "properties": { "name": { "type": "string" }, "address": { "$ref": "#/$defs/address" } }, "additionalProperties": false } """); return tool; } [McpServerTool(Name = "test_reconnection")] [Description("Tests SSE stream reconnection by closing the stream mid-call")] public static string TestReconnection() { // This tool doesn't need to do anything - the call filter will close the stream after this tool runs, // and the client must reconnect to get the result. return "Reconnection test completed successfully"; } [McpServerTool(Name = "test_header_tool")] [Description("A tool with x-mcp-header annotations for conformance testing")] public static string TestHeaderTool( [McpHeader("Region"), Description("The deployment region")] string region, [Description("The query to execute")] string query) { return $"Executed in region {region}: {query}"; } }