Decouple Handlers and Collections from protocol types (#765)
* Decouple Handlers and Collections (e.g. ToolsHandler and ToolsCollection) from Capabilities by promoting them to the options types. Also, group all handlers in container classes, for server, I decided to move McpServerHandlers down to Mcp.Core and move the NotificationHandlers there too. Also, Fix ListResourceTemplatesHandler issue. * Add remarks for empty protocol capability types * Keep JsonIgnore properties and mark them as Obsolete * Continue supporting Obsolete members * Update Obsolete messages to indicate the member will be removed * Add EditorBrowsable Never to the remaining obsolete members
This commit is contained in:
parent
39b0ed3c18
commit
5f992994c3
18 changed files with 1588 additions and 1161 deletions
|
|
@ -251,9 +251,7 @@ public abstract class HttpServerIntegrationTests : LoggedTest, IClassFixture<Sse
|
|||
int samplingHandlerCalls = 0;
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
McpClientOptions options = new();
|
||||
options.Capabilities = new();
|
||||
options.Capabilities.Sampling ??= new();
|
||||
options.Capabilities.Sampling.SamplingHandler = async (_, _, _) =>
|
||||
options.Handlers.SamplingHandler = async (_, _, _) =>
|
||||
{
|
||||
samplingHandlerCalls++;
|
||||
return new CreateMessageResult
|
||||
|
|
|
|||
|
|
@ -161,29 +161,26 @@ public abstract class MapMcpTests(ITestOutputHelper testOutputHelper) : KestrelI
|
|||
await app.StartAsync(TestContext.Current.CancellationToken);
|
||||
|
||||
var sampleCount = 0;
|
||||
var clientOptions = new McpClientOptions
|
||||
var clientOptions = new McpClientOptions()
|
||||
{
|
||||
Capabilities = new()
|
||||
Handlers = new()
|
||||
{
|
||||
Sampling = new()
|
||||
SamplingHandler = async (parameters, _, _) =>
|
||||
{
|
||||
SamplingHandler = async (parameters, _, _) =>
|
||||
{
|
||||
Assert.NotNull(parameters?.Messages);
|
||||
var message = Assert.Single(parameters.Messages);
|
||||
Assert.Equal(Role.User, message.Role);
|
||||
Assert.Equal("Test prompt for sampling", Assert.IsType<TextContentBlock>(message.Content).Text);
|
||||
Assert.NotNull(parameters?.Messages);
|
||||
var message = Assert.Single(parameters.Messages);
|
||||
Assert.Equal(Role.User, message.Role);
|
||||
Assert.Equal("Test prompt for sampling", Assert.IsType<TextContentBlock>(message.Content).Text);
|
||||
|
||||
sampleCount++;
|
||||
return new CreateMessageResult
|
||||
{
|
||||
Model = "test-model",
|
||||
Role = Role.Assistant,
|
||||
Content = new TextContentBlock { Text = "Sampling response from client" },
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
sampleCount++;
|
||||
return new CreateMessageResult
|
||||
{
|
||||
Model = "test-model",
|
||||
Role = Role.Assistant,
|
||||
Content = new TextContentBlock { Text = "Sampling response from client" },
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await using var mcpClient = await ConnectAsync(clientOptions: clientOptions);
|
||||
|
|
|
|||
|
|
@ -102,9 +102,7 @@ public class StatelessServerTests(ITestOutputHelper outputHelper) : KestrelInMem
|
|||
await StartAsync();
|
||||
|
||||
var mcpClientOptions = new McpClientOptions();
|
||||
mcpClientOptions.Capabilities = new();
|
||||
mcpClientOptions.Capabilities.Sampling ??= new();
|
||||
mcpClientOptions.Capabilities.Sampling.SamplingHandler = (_, _, _) =>
|
||||
mcpClientOptions.Handlers.SamplingHandler = (_, _, _) =>
|
||||
{
|
||||
throw new UnreachableException();
|
||||
};
|
||||
|
|
@ -122,9 +120,7 @@ public class StatelessServerTests(ITestOutputHelper outputHelper) : KestrelInMem
|
|||
await StartAsync();
|
||||
|
||||
var mcpClientOptions = new McpClientOptions();
|
||||
mcpClientOptions.Capabilities = new();
|
||||
mcpClientOptions.Capabilities.Roots ??= new();
|
||||
mcpClientOptions.Capabilities.Roots.RootsHandler = (_, _) =>
|
||||
mcpClientOptions.Handlers.RootsHandler = (_, _) =>
|
||||
{
|
||||
throw new UnreachableException();
|
||||
};
|
||||
|
|
@ -142,9 +138,7 @@ public class StatelessServerTests(ITestOutputHelper outputHelper) : KestrelInMem
|
|||
await StartAsync();
|
||||
|
||||
var mcpClientOptions = new McpClientOptions();
|
||||
mcpClientOptions.Capabilities = new();
|
||||
mcpClientOptions.Capabilities.Elicitation ??= new();
|
||||
mcpClientOptions.Capabilities.Elicitation.ElicitationHandler = (_, _) =>
|
||||
mcpClientOptions.Handlers.ElicitationHandler = (_, _) =>
|
||||
{
|
||||
throw new UnreachableException();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using ModelContextProtocol.Protocol;
|
||||
using ModelContextProtocol.Server;
|
||||
using Serilog;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
|
||||
|
||||
|
|
@ -38,17 +38,16 @@ internal static class Program
|
|||
|
||||
McpServerOptions options = new()
|
||||
{
|
||||
Capabilities = new ServerCapabilities
|
||||
{
|
||||
Tools = ConfigureTools(),
|
||||
Resources = ConfigureResources(),
|
||||
Prompts = ConfigurePrompts(),
|
||||
Logging = ConfigureLogging(),
|
||||
Completions = ConfigureCompletions(),
|
||||
},
|
||||
Capabilities = new ServerCapabilities(),
|
||||
ServerInstructions = "This is a test server with only stub functionality",
|
||||
};
|
||||
|
||||
ConfigureTools(options);
|
||||
ConfigureResources(options);
|
||||
ConfigurePrompts(options);
|
||||
ConfigureLogging(options);
|
||||
ConfigureCompletions(options);
|
||||
|
||||
using var loggerFactory = CreateLoggerFactory();
|
||||
await using var stdioTransport = new StdioServerTransport("TestServer", loggerFactory);
|
||||
await using McpServer server = McpServer.Create(stdioTransport, options, loggerFactory);
|
||||
|
|
@ -105,222 +104,215 @@ internal static class Program
|
|||
}
|
||||
}
|
||||
|
||||
private static ToolsCapability ConfigureTools()
|
||||
private static void ConfigureTools(McpServerOptions options)
|
||||
{
|
||||
return new()
|
||||
options.Handlers.ListToolsHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
ListToolsHandler = async (request, cancellationToken) =>
|
||||
return new ListToolsResult
|
||||
{
|
||||
return new ListToolsResult
|
||||
Tools =
|
||||
[
|
||||
new Tool
|
||||
{
|
||||
Name = "echo",
|
||||
Description = "Echoes the input back to the client.",
|
||||
InputSchema = JsonSerializer.Deserialize<JsonElement>("""
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "The input to echo back."
|
||||
}
|
||||
},
|
||||
"required": ["message"]
|
||||
}
|
||||
"""),
|
||||
},
|
||||
new Tool
|
||||
{
|
||||
Name = "echoSessionId",
|
||||
Description = "Echoes the session id back to the client.",
|
||||
InputSchema = JsonSerializer.Deserialize<JsonElement>("""
|
||||
{
|
||||
"type": "object"
|
||||
}
|
||||
""", McpJsonUtilities.DefaultOptions),
|
||||
},
|
||||
new Tool
|
||||
{
|
||||
Name = "sampleLLM",
|
||||
Description = "Samples from an LLM using MCP's sampling feature.",
|
||||
InputSchema = JsonSerializer.Deserialize<JsonElement>("""
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
"description": "The prompt to send to the LLM"
|
||||
},
|
||||
"maxTokens": {
|
||||
"type": "number",
|
||||
"description": "Maximum number of tokens to generate"
|
||||
}
|
||||
},
|
||||
"required": ["prompt", "maxTokens"]
|
||||
}
|
||||
"""),
|
||||
}
|
||||
]
|
||||
};
|
||||
};
|
||||
options.Handlers.CallToolHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
if (request.Params?.Name == "echo")
|
||||
{
|
||||
if (request.Params?.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message))
|
||||
{
|
||||
Tools =
|
||||
[
|
||||
new Tool
|
||||
{
|
||||
Name = "echo",
|
||||
Description = "Echoes the input back to the client.",
|
||||
InputSchema = JsonSerializer.Deserialize<JsonElement>("""
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "The input to echo back."
|
||||
}
|
||||
},
|
||||
"required": ["message"]
|
||||
}
|
||||
"""),
|
||||
},
|
||||
new Tool
|
||||
{
|
||||
Name = "echoSessionId",
|
||||
Description = "Echoes the session id back to the client.",
|
||||
InputSchema = JsonSerializer.Deserialize<JsonElement>("""
|
||||
{
|
||||
"type": "object"
|
||||
}
|
||||
""", McpJsonUtilities.DefaultOptions),
|
||||
},
|
||||
new Tool
|
||||
{
|
||||
Name = "sampleLLM",
|
||||
Description = "Samples from an LLM using MCP's sampling feature.",
|
||||
InputSchema = JsonSerializer.Deserialize<JsonElement>("""
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
"description": "The prompt to send to the LLM"
|
||||
},
|
||||
"maxTokens": {
|
||||
"type": "number",
|
||||
"description": "Maximum number of tokens to generate"
|
||||
}
|
||||
},
|
||||
"required": ["prompt", "maxTokens"]
|
||||
}
|
||||
"""),
|
||||
}
|
||||
]
|
||||
throw new McpException("Missing required argument 'message'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
return new CallToolResult
|
||||
{
|
||||
Content = [new TextContentBlock { Text = $"Echo: {message}" }]
|
||||
};
|
||||
},
|
||||
|
||||
CallToolHandler = async (request, cancellationToken) =>
|
||||
}
|
||||
else if (request.Params?.Name == "echoSessionId")
|
||||
{
|
||||
if (request.Params?.Name == "echo")
|
||||
return new CallToolResult
|
||||
{
|
||||
if (request.Params?.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message))
|
||||
{
|
||||
throw new McpException("Missing required argument 'message'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
return new CallToolResult
|
||||
{
|
||||
Content = [new TextContentBlock { Text = $"Echo: {message}" }]
|
||||
};
|
||||
Content = [new TextContentBlock { Text = request.Server.SessionId ?? string.Empty }]
|
||||
};
|
||||
}
|
||||
else if (request.Params?.Name == "sampleLLM")
|
||||
{
|
||||
if (request.Params?.Arguments is null ||
|
||||
!request.Params.Arguments.TryGetValue("prompt", out var prompt) ||
|
||||
!request.Params.Arguments.TryGetValue("maxTokens", out var maxTokens))
|
||||
{
|
||||
throw new McpException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
else if (request.Params?.Name == "echoSessionId")
|
||||
{
|
||||
return new CallToolResult
|
||||
{
|
||||
Content = [new TextContentBlock { Text = request.Server.SessionId ?? string.Empty }]
|
||||
};
|
||||
}
|
||||
else if (request.Params?.Name == "sampleLLM")
|
||||
{
|
||||
if (request.Params?.Arguments is null ||
|
||||
!request.Params.Arguments.TryGetValue("prompt", out var prompt) ||
|
||||
!request.Params.Arguments.TryGetValue("maxTokens", out var maxTokens))
|
||||
{
|
||||
throw new McpException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
var sampleResult = await request.Server.SampleAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.GetRawText())),
|
||||
cancellationToken);
|
||||
var sampleResult = await request.Server.SampleAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.GetRawText())),
|
||||
cancellationToken);
|
||||
|
||||
return new CallToolResult
|
||||
{
|
||||
Content = [new TextContentBlock { Text = $"LLM sampling result: {(sampleResult.Content as TextContentBlock)?.Text}" }]
|
||||
};
|
||||
}
|
||||
else
|
||||
return new CallToolResult
|
||||
{
|
||||
throw new McpException($"Unknown tool: {request.Params?.Name}", McpErrorCode.InvalidParams);
|
||||
}
|
||||
Content = [new TextContentBlock { Text = $"LLM sampling result: {(sampleResult.Content as TextContentBlock)?.Text}" }]
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new McpException($"Unknown tool: {request.Params?.Name}", McpErrorCode.InvalidParams);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private static PromptsCapability ConfigurePrompts()
|
||||
private static void ConfigurePrompts(McpServerOptions options)
|
||||
{
|
||||
return new()
|
||||
options.Handlers.ListPromptsHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
ListPromptsHandler = async (request, cancellationToken) =>
|
||||
return new ListPromptsResult
|
||||
{
|
||||
return new ListPromptsResult
|
||||
{
|
||||
Prompts = [
|
||||
new Prompt
|
||||
{
|
||||
Name = "simple_prompt",
|
||||
Description = "A prompt without arguments"
|
||||
},
|
||||
new Prompt
|
||||
{
|
||||
Name = "complex_prompt",
|
||||
Description = "A prompt with arguments",
|
||||
Arguments =
|
||||
[
|
||||
new PromptArgument
|
||||
{
|
||||
Name = "temperature",
|
||||
Description = "Temperature setting",
|
||||
Required = true
|
||||
},
|
||||
new PromptArgument
|
||||
{
|
||||
Name = "style",
|
||||
Description = "Output style",
|
||||
Required = false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
Prompts = [
|
||||
new Prompt
|
||||
{
|
||||
Name = "simple_prompt",
|
||||
Description = "A prompt without arguments"
|
||||
},
|
||||
new Prompt
|
||||
{
|
||||
Name = "complex_prompt",
|
||||
Description = "A prompt with arguments",
|
||||
Arguments =
|
||||
[
|
||||
new PromptArgument
|
||||
{
|
||||
Name = "temperature",
|
||||
Description = "Temperature setting",
|
||||
Required = true
|
||||
},
|
||||
new PromptArgument
|
||||
{
|
||||
Name = "style",
|
||||
Description = "Output style",
|
||||
Required = false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
};
|
||||
|
||||
GetPromptHandler = async (request, cancellationToken) =>
|
||||
options.Handlers.GetPromptHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
List<PromptMessage> messages = [];
|
||||
if (request.Params?.Name == "simple_prompt")
|
||||
{
|
||||
List<PromptMessage> messages = [];
|
||||
if (request.Params?.Name == "simple_prompt")
|
||||
messages.Add(new PromptMessage
|
||||
{
|
||||
messages.Add(new PromptMessage
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new TextContentBlock { Text = "This is a simple prompt without arguments." },
|
||||
});
|
||||
}
|
||||
else if (request.Params?.Name == "complex_prompt")
|
||||
{
|
||||
string temperature = request.Params.Arguments?["temperature"].ToString() ?? "unknown";
|
||||
string style = request.Params.Arguments?["style"].ToString() ?? "unknown";
|
||||
messages.Add(new PromptMessage
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new TextContentBlock { Text = $"This is a complex prompt with arguments: temperature={temperature}, style={style}" },
|
||||
});
|
||||
messages.Add(new PromptMessage
|
||||
{
|
||||
Role = Role.Assistant,
|
||||
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 ImageContentBlock
|
||||
{
|
||||
Data = MCP_TINY_IMAGE,
|
||||
MimeType = "image/png"
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new McpException($"Unknown prompt: {request.Params?.Name}", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
return new GetPromptResult
|
||||
{
|
||||
Messages = messages
|
||||
};
|
||||
Role = Role.User,
|
||||
Content = new TextContentBlock { Text = "This is a simple prompt without arguments." },
|
||||
});
|
||||
}
|
||||
else if (request.Params?.Name == "complex_prompt")
|
||||
{
|
||||
string temperature = request.Params.Arguments?["temperature"].ToString() ?? "unknown";
|
||||
string style = request.Params.Arguments?["style"].ToString() ?? "unknown";
|
||||
messages.Add(new PromptMessage
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new TextContentBlock { Text = $"This is a complex prompt with arguments: temperature={temperature}, style={style}" },
|
||||
});
|
||||
messages.Add(new PromptMessage
|
||||
{
|
||||
Role = Role.Assistant,
|
||||
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 ImageContentBlock
|
||||
{
|
||||
Data = MCP_TINY_IMAGE,
|
||||
MimeType = "image/png"
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new McpException($"Unknown prompt: {request.Params?.Name}", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
return new GetPromptResult
|
||||
{
|
||||
Messages = messages
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
private static LoggingLevel? _minimumLoggingLevel = null;
|
||||
|
||||
private static LoggingCapability ConfigureLogging()
|
||||
private static void ConfigureLogging(McpServerOptions options)
|
||||
{
|
||||
return new()
|
||||
options.Handlers.SetLoggingLevelHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
SetLoggingLevelHandler = async (request, cancellationToken) =>
|
||||
if (request.Params?.Level is null)
|
||||
{
|
||||
if (request.Params?.Level is null)
|
||||
{
|
||||
throw new McpException("Missing required argument 'level'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
_minimumLoggingLevel = request.Params.Level;
|
||||
|
||||
return new EmptyResult();
|
||||
throw new McpException("Missing required argument 'level'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
_minimumLoggingLevel = request.Params.Level;
|
||||
|
||||
return new EmptyResult();
|
||||
};
|
||||
}
|
||||
|
||||
private static readonly ConcurrentDictionary<string, bool> _subscribedResources = new();
|
||||
|
||||
private static ResourcesCapability ConfigureResources()
|
||||
private static void ConfigureResources(McpServerOptions options)
|
||||
{
|
||||
var capabilities = options.Capabilities ??= new();
|
||||
capabilities.Resources = new() { Subscribe = true };
|
||||
|
||||
List<Resource> resources = [];
|
||||
List<ResourceContents> resourceContents = [];
|
||||
for (int i = 0; i < 100; ++i)
|
||||
|
|
@ -361,128 +353,123 @@ internal static class Program
|
|||
|
||||
const int pageSize = 10;
|
||||
|
||||
return new()
|
||||
options.Handlers.ListResourceTemplatesHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
ListResourceTemplatesHandler = async (request, cancellationToken) =>
|
||||
return new ListResourceTemplatesResult
|
||||
{
|
||||
return new ListResourceTemplatesResult
|
||||
{
|
||||
ResourceTemplates = [
|
||||
new ResourceTemplate
|
||||
{
|
||||
UriTemplate = "test://dynamic/resource/{id}",
|
||||
Name = "Dynamic Resource",
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
ResourceTemplates = [
|
||||
new ResourceTemplate
|
||||
{
|
||||
UriTemplate = "test://dynamic/resource/{id}",
|
||||
Name = "Dynamic Resource",
|
||||
}
|
||||
]
|
||||
};
|
||||
};
|
||||
|
||||
ListResourcesHandler = async (request, cancellationToken) =>
|
||||
options.Handlers.ListResourcesHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
int startIndex = 0;
|
||||
if (request.Params?.Cursor is not null)
|
||||
{
|
||||
int startIndex = 0;
|
||||
if (request.Params?.Cursor is not null)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
var startIndexAsString = Encoding.UTF8.GetString(Convert.FromBase64String(request.Params.Cursor));
|
||||
startIndex = Convert.ToInt32(startIndexAsString);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new McpException($"Invalid cursor: '{request.Params.Cursor}'", e, McpErrorCode.InvalidParams);
|
||||
}
|
||||
var startIndexAsString = Encoding.UTF8.GetString(Convert.FromBase64String(request.Params.Cursor));
|
||||
startIndex = Convert.ToInt32(startIndexAsString);
|
||||
}
|
||||
|
||||
int endIndex = Math.Min(startIndex + pageSize, resources.Count);
|
||||
string? nextCursor = null;
|
||||
|
||||
if (endIndex < resources.Count)
|
||||
catch (Exception e)
|
||||
{
|
||||
nextCursor = Convert.ToBase64String(Encoding.UTF8.GetBytes(endIndex.ToString()));
|
||||
throw new McpException($"Invalid cursor: '{request.Params.Cursor}'", e, McpErrorCode.InvalidParams);
|
||||
}
|
||||
return new ListResourcesResult
|
||||
{
|
||||
NextCursor = nextCursor,
|
||||
Resources = resources.GetRange(startIndex, endIndex - startIndex)
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
ReadResourceHandler = async (request, cancellationToken) =>
|
||||
int endIndex = Math.Min(startIndex + pageSize, resources.Count);
|
||||
string? nextCursor = null;
|
||||
|
||||
if (endIndex < resources.Count)
|
||||
{
|
||||
if (request.Params?.Uri is null)
|
||||
nextCursor = Convert.ToBase64String(Encoding.UTF8.GetBytes(endIndex.ToString()));
|
||||
}
|
||||
return new ListResourcesResult
|
||||
{
|
||||
NextCursor = nextCursor,
|
||||
Resources = resources.GetRange(startIndex, endIndex - startIndex)
|
||||
};
|
||||
};
|
||||
|
||||
options.Handlers.ReadResourceHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
if (request.Params?.Uri is null)
|
||||
{
|
||||
throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
if (request.Params.Uri.StartsWith("test://dynamic/resource/"))
|
||||
{
|
||||
var id = request.Params.Uri.Split('/').LastOrDefault();
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||
throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
if (request.Params.Uri.StartsWith("test://dynamic/resource/"))
|
||||
{
|
||||
var id = request.Params.Uri.Split('/').LastOrDefault();
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
return new ReadResourceResult
|
||||
{
|
||||
Contents = [
|
||||
new TextResourceContents
|
||||
{
|
||||
Uri = request.Params.Uri,
|
||||
MimeType = "text/plain",
|
||||
Text = $"Dynamic resource {id}: This is a plaintext resource"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
ResourceContents contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri)
|
||||
?? throw new McpException($"Resource not found: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
|
||||
return new ReadResourceResult
|
||||
{
|
||||
Contents = [contents]
|
||||
Contents = [
|
||||
new TextResourceContents
|
||||
{
|
||||
Uri = request.Params.Uri,
|
||||
MimeType = "text/plain",
|
||||
Text = $"Dynamic resource {id}: This is a plaintext resource"
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
}
|
||||
|
||||
SubscribeToResourcesHandler = async (request, cancellationToken) =>
|
||||
ResourceContents contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri)
|
||||
?? throw new McpException($"Resource not found: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
|
||||
return new ReadResourceResult
|
||||
{
|
||||
if (request?.Params?.Uri is null)
|
||||
{
|
||||
throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
if (!request.Params.Uri.StartsWith("test://static/resource/")
|
||||
&& !request.Params.Uri.StartsWith("test://dynamic/resource/"))
|
||||
{
|
||||
throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
Contents = [contents]
|
||||
};
|
||||
};
|
||||
|
||||
_subscribedResources.TryAdd(request.Params.Uri, true);
|
||||
|
||||
return new EmptyResult();
|
||||
},
|
||||
|
||||
UnsubscribeFromResourcesHandler = async (request, cancellationToken) =>
|
||||
options.Handlers.SubscribeToResourcesHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
if (request?.Params?.Uri is null)
|
||||
{
|
||||
if (request?.Params?.Uri is null)
|
||||
{
|
||||
throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
if (!request.Params.Uri.StartsWith("test://static/resource/")
|
||||
&& !request.Params.Uri.StartsWith("test://dynamic/resource/"))
|
||||
{
|
||||
throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
if (!request.Params.Uri.StartsWith("test://static/resource/")
|
||||
&& !request.Params.Uri.StartsWith("test://dynamic/resource/"))
|
||||
{
|
||||
throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
_subscribedResources.TryRemove(request.Params.Uri, out _);
|
||||
_subscribedResources.TryAdd(request.Params.Uri, true);
|
||||
|
||||
return new EmptyResult();
|
||||
},
|
||||
return new EmptyResult();
|
||||
};
|
||||
|
||||
Subscribe = true
|
||||
options.Handlers.UnsubscribeFromResourcesHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
if (request?.Params?.Uri is null)
|
||||
{
|
||||
throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
if (!request.Params.Uri.StartsWith("test://static/resource/")
|
||||
&& !request.Params.Uri.StartsWith("test://dynamic/resource/"))
|
||||
{
|
||||
throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
_subscribedResources.TryRemove(request.Params.Uri, out _);
|
||||
|
||||
return new EmptyResult();
|
||||
};
|
||||
}
|
||||
|
||||
private static CompletionsCapability ConfigureCompletions()
|
||||
private static void ConfigureCompletions(McpServerOptions options)
|
||||
{
|
||||
List<string> sampleResourceIds = ["1", "2", "3", "4", "5"];
|
||||
Dictionary<string, List<string>> exampleCompletions = new()
|
||||
|
|
@ -491,7 +478,7 @@ internal static class Program
|
|||
{"temperature", ["0", "0.5", "0.7", "1.0"]},
|
||||
};
|
||||
|
||||
McpRequestHandler<CompleteRequestParams, CompleteResult> handler = async (request, cancellationToken) =>
|
||||
options.Handlers.CompleteHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
string[]? values;
|
||||
switch (request.Params?.Ref)
|
||||
|
|
@ -517,8 +504,6 @@ internal static class Program
|
|||
throw new McpException($"Unknown reference type: '{request.Params?.Ref.Type}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
};
|
||||
|
||||
return new() { CompleteHandler = handler };
|
||||
}
|
||||
|
||||
static CreateMessageRequestParams CreateRequestSamplingParams(string context, string uri, int maxTokens = 100)
|
||||
|
|
|
|||
|
|
@ -95,284 +95,273 @@ public class Program
|
|||
}
|
||||
|
||||
const int pageSize = 10;
|
||||
|
||||
options.Capabilities = new()
|
||||
options.Handlers = new()
|
||||
{
|
||||
Tools = new()
|
||||
ListToolsHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
ListToolsHandler = async (request, cancellationToken) =>
|
||||
return new ListToolsResult
|
||||
{
|
||||
return new ListToolsResult
|
||||
Tools =
|
||||
[
|
||||
new Tool
|
||||
{
|
||||
Name = "echo",
|
||||
Description = "Echoes the input back to the client.",
|
||||
InputSchema = JsonSerializer.Deserialize<JsonElement>("""
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "The input to echo back."
|
||||
}
|
||||
},
|
||||
"required": ["message"]
|
||||
}
|
||||
""", McpJsonUtilities.DefaultOptions),
|
||||
},
|
||||
new Tool
|
||||
{
|
||||
Name = "echoSessionId",
|
||||
Description = "Echoes the session id back to the client.",
|
||||
InputSchema = JsonSerializer.Deserialize<JsonElement>("""
|
||||
{
|
||||
"type": "object"
|
||||
}
|
||||
""", McpJsonUtilities.DefaultOptions),
|
||||
},
|
||||
new Tool
|
||||
{
|
||||
Name = "sampleLLM",
|
||||
Description = "Samples from an LLM using MCP's sampling feature.",
|
||||
InputSchema = JsonSerializer.Deserialize<JsonElement>("""
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
"description": "The prompt to send to the LLM"
|
||||
},
|
||||
"maxTokens": {
|
||||
"type": "number",
|
||||
"description": "Maximum number of tokens to generate"
|
||||
}
|
||||
},
|
||||
"required": ["prompt", "maxTokens"]
|
||||
}
|
||||
""", McpJsonUtilities.DefaultOptions),
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
CallToolHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
if (request.Params is null)
|
||||
{
|
||||
throw new McpException("Missing required parameter 'name'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
if (request.Params.Name == "echo")
|
||||
{
|
||||
if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message))
|
||||
{
|
||||
Tools =
|
||||
[
|
||||
new Tool
|
||||
{
|
||||
Name = "echo",
|
||||
Description = "Echoes the input back to the client.",
|
||||
InputSchema = JsonSerializer.Deserialize<JsonElement>("""
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string",
|
||||
"description": "The input to echo back."
|
||||
}
|
||||
},
|
||||
"required": ["message"]
|
||||
}
|
||||
""", McpJsonUtilities.DefaultOptions),
|
||||
},
|
||||
new Tool
|
||||
{
|
||||
Name = "echoSessionId",
|
||||
Description = "Echoes the session id back to the client.",
|
||||
InputSchema = JsonSerializer.Deserialize<JsonElement>("""
|
||||
{
|
||||
"type": "object"
|
||||
}
|
||||
""", McpJsonUtilities.DefaultOptions),
|
||||
},
|
||||
new Tool
|
||||
{
|
||||
Name = "sampleLLM",
|
||||
Description = "Samples from an LLM using MCP's sampling feature.",
|
||||
InputSchema = JsonSerializer.Deserialize<JsonElement>("""
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"prompt": {
|
||||
"type": "string",
|
||||
"description": "The prompt to send to the LLM"
|
||||
},
|
||||
"maxTokens": {
|
||||
"type": "number",
|
||||
"description": "Maximum number of tokens to generate"
|
||||
}
|
||||
},
|
||||
"required": ["prompt", "maxTokens"]
|
||||
}
|
||||
""", McpJsonUtilities.DefaultOptions),
|
||||
}
|
||||
]
|
||||
throw new McpException("Missing required argument 'message'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
return new CallToolResult
|
||||
{
|
||||
Content = [new TextContentBlock { Text = $"Echo: {message}" }]
|
||||
};
|
||||
},
|
||||
CallToolHandler = async (request, cancellationToken) =>
|
||||
}
|
||||
else if (request.Params.Name == "echoSessionId")
|
||||
{
|
||||
if (request.Params is null)
|
||||
return new CallToolResult
|
||||
{
|
||||
throw new McpException("Missing required parameter 'name'", McpErrorCode.InvalidParams);
|
||||
Content = [new TextContentBlock { Text = request.Server.SessionId ?? string.Empty }]
|
||||
};
|
||||
}
|
||||
else if (request.Params.Name == "sampleLLM")
|
||||
{
|
||||
if (request.Params.Arguments is null ||
|
||||
!request.Params.Arguments.TryGetValue("prompt", out var prompt) ||
|
||||
!request.Params.Arguments.TryGetValue("maxTokens", out var maxTokens))
|
||||
{
|
||||
throw new McpException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
if (request.Params.Name == "echo")
|
||||
{
|
||||
if (request.Params.Arguments is null || !request.Params.Arguments.TryGetValue("message", out var message))
|
||||
{
|
||||
throw new McpException("Missing required argument 'message'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
return new CallToolResult
|
||||
{
|
||||
Content = [new TextContentBlock { Text = $"Echo: {message}" }]
|
||||
};
|
||||
}
|
||||
else if (request.Params.Name == "echoSessionId")
|
||||
{
|
||||
return new CallToolResult
|
||||
{
|
||||
Content = [new TextContentBlock { Text = request.Server.SessionId ?? string.Empty }]
|
||||
};
|
||||
}
|
||||
else if (request.Params.Name == "sampleLLM")
|
||||
{
|
||||
if (request.Params.Arguments is null ||
|
||||
!request.Params.Arguments.TryGetValue("prompt", out var prompt) ||
|
||||
!request.Params.Arguments.TryGetValue("maxTokens", out var maxTokens))
|
||||
{
|
||||
throw new McpException("Missing required arguments 'prompt' and 'maxTokens'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
var sampleResult = await request.Server.SampleAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.ToString())),
|
||||
cancellationToken);
|
||||
var sampleResult = await request.Server.SampleAsync(CreateRequestSamplingParams(prompt.ToString(), "sampleLLM", Convert.ToInt32(maxTokens.ToString())),
|
||||
cancellationToken);
|
||||
|
||||
return new CallToolResult
|
||||
{
|
||||
Content = [new TextContentBlock { Text = $"LLM sampling result: {(sampleResult.Content as TextContentBlock)?.Text}" }]
|
||||
};
|
||||
}
|
||||
else
|
||||
return new CallToolResult
|
||||
{
|
||||
throw new McpException($"Unknown tool: '{request.Params.Name}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
Content = [new TextContentBlock { Text = $"LLM sampling result: {(sampleResult.Content as TextContentBlock)?.Text}" }]
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new McpException($"Unknown tool: '{request.Params.Name}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
},
|
||||
Resources = new()
|
||||
ListResourceTemplatesHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
ListResourceTemplatesHandler = async (request, cancellationToken) =>
|
||||
|
||||
return new ListResourceTemplatesResult
|
||||
{
|
||||
|
||||
return new ListResourceTemplatesResult
|
||||
ResourceTemplates = [
|
||||
new ResourceTemplate
|
||||
{
|
||||
ResourceTemplates = [
|
||||
new ResourceTemplate
|
||||
{
|
||||
UriTemplate = "test://dynamic/resource/{id}",
|
||||
Name = "Dynamic Resource",
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
ListResourcesHandler = async (request, cancellationToken) =>
|
||||
UriTemplate = "test://dynamic/resource/{id}",
|
||||
Name = "Dynamic Resource",
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
ListResourcesHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
int startIndex = 0;
|
||||
var requestParams = request.Params ?? new();
|
||||
if (requestParams.Cursor is not null)
|
||||
{
|
||||
int startIndex = 0;
|
||||
var requestParams = request.Params ?? new();
|
||||
if (requestParams.Cursor is not null)
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
var startIndexAsString = Encoding.UTF8.GetString(Convert.FromBase64String(requestParams.Cursor));
|
||||
startIndex = Convert.ToInt32(startIndexAsString);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new McpException($"Invalid cursor: '{requestParams.Cursor}'", e, McpErrorCode.InvalidParams);
|
||||
}
|
||||
var startIndexAsString = Encoding.UTF8.GetString(Convert.FromBase64String(requestParams.Cursor));
|
||||
startIndex = Convert.ToInt32(startIndexAsString);
|
||||
}
|
||||
|
||||
int endIndex = Math.Min(startIndex + pageSize, resources.Count);
|
||||
string? nextCursor = null;
|
||||
|
||||
if (endIndex < resources.Count)
|
||||
catch (Exception e)
|
||||
{
|
||||
nextCursor = Convert.ToBase64String(Encoding.UTF8.GetBytes(endIndex.ToString()));
|
||||
throw new McpException($"Invalid cursor: '{requestParams.Cursor}'", e, McpErrorCode.InvalidParams);
|
||||
}
|
||||
}
|
||||
|
||||
return new ListResourcesResult
|
||||
{
|
||||
NextCursor = nextCursor,
|
||||
Resources = resources.GetRange(startIndex, endIndex - startIndex)
|
||||
};
|
||||
},
|
||||
ReadResourceHandler = async (request, cancellationToken) =>
|
||||
int endIndex = Math.Min(startIndex + pageSize, resources.Count);
|
||||
string? nextCursor = null;
|
||||
|
||||
if (endIndex < resources.Count)
|
||||
{
|
||||
if (request.Params?.Uri is null)
|
||||
nextCursor = Convert.ToBase64String(Encoding.UTF8.GetBytes(endIndex.ToString()));
|
||||
}
|
||||
|
||||
return new ListResourcesResult
|
||||
{
|
||||
NextCursor = nextCursor,
|
||||
Resources = resources.GetRange(startIndex, endIndex - startIndex)
|
||||
};
|
||||
},
|
||||
ReadResourceHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
if (request.Params?.Uri is null)
|
||||
{
|
||||
throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
if (request.Params.Uri.StartsWith("test://dynamic/resource/"))
|
||||
{
|
||||
var id = request.Params.Uri.Split('/').LastOrDefault();
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
throw new McpException("Missing required argument 'uri'", McpErrorCode.InvalidParams);
|
||||
throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
if (request.Params.Uri.StartsWith("test://dynamic/resource/"))
|
||||
{
|
||||
var id = request.Params.Uri.Split('/').LastOrDefault();
|
||||
if (string.IsNullOrEmpty(id))
|
||||
{
|
||||
throw new McpException($"Invalid resource URI: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
return new ReadResourceResult
|
||||
{
|
||||
Contents = [
|
||||
new TextResourceContents
|
||||
{
|
||||
Uri = request.Params.Uri,
|
||||
MimeType = "text/plain",
|
||||
Text = $"Dynamic resource {id}: This is a plaintext resource"
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
ResourceContents? contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri) ??
|
||||
throw new McpException($"Resource not found: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
|
||||
return new ReadResourceResult
|
||||
{
|
||||
Contents = [contents]
|
||||
};
|
||||
}
|
||||
},
|
||||
Prompts = new()
|
||||
{
|
||||
ListPromptsHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
return new ListPromptsResult
|
||||
{
|
||||
Prompts = [
|
||||
new Prompt
|
||||
{
|
||||
Name = "simple_prompt",
|
||||
Description = "A prompt without arguments"
|
||||
},
|
||||
new Prompt
|
||||
{
|
||||
Name = "complex_prompt",
|
||||
Description = "A prompt with arguments",
|
||||
Arguments =
|
||||
[
|
||||
new PromptArgument
|
||||
Contents = [
|
||||
new TextResourceContents
|
||||
{
|
||||
Name = "temperature",
|
||||
Description = "Temperature setting",
|
||||
Required = true
|
||||
},
|
||||
new PromptArgument
|
||||
{
|
||||
Name = "style",
|
||||
Description = "Output style",
|
||||
Required = false
|
||||
Uri = request.Params.Uri,
|
||||
MimeType = "text/plain",
|
||||
Text = $"Dynamic resource {id}: This is a plaintext resource"
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
GetPromptHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
if (request.Params is null)
|
||||
{
|
||||
throw new McpException("Missing required parameter 'name'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
List<PromptMessage> messages = new();
|
||||
if (request.Params.Name == "simple_prompt")
|
||||
{
|
||||
messages.Add(new PromptMessage
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new TextContentBlock { Text = "This is a simple prompt without arguments." },
|
||||
});
|
||||
}
|
||||
else if (request.Params.Name == "complex_prompt")
|
||||
{
|
||||
string temperature = request.Params.Arguments?["temperature"].ToString() ?? "unknown";
|
||||
string style = request.Params.Arguments?["style"].ToString() ?? "unknown";
|
||||
messages.Add(new PromptMessage
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new TextContentBlock { Text = $"This is a complex prompt with arguments: temperature={temperature}, style={style}" },
|
||||
});
|
||||
messages.Add(new PromptMessage
|
||||
{
|
||||
Role = Role.Assistant,
|
||||
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 ImageContentBlock
|
||||
{
|
||||
Data = MCP_TINY_IMAGE,
|
||||
MimeType = "image/png"
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new McpException($"Unknown prompt: {request.Params.Name}", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
return new GetPromptResult
|
||||
{
|
||||
Messages = messages
|
||||
};
|
||||
}
|
||||
|
||||
ResourceContents? contents = resourceContents.FirstOrDefault(r => r.Uri == request.Params.Uri) ??
|
||||
throw new McpException($"Resource not found: '{request.Params.Uri}'", McpErrorCode.InvalidParams);
|
||||
|
||||
return new ReadResourceResult
|
||||
{
|
||||
Contents = [contents]
|
||||
};
|
||||
},
|
||||
ListPromptsHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
return new ListPromptsResult
|
||||
{
|
||||
Prompts = [
|
||||
new Prompt
|
||||
{
|
||||
Name = "simple_prompt",
|
||||
Description = "A prompt without arguments"
|
||||
},
|
||||
new Prompt
|
||||
{
|
||||
Name = "complex_prompt",
|
||||
Description = "A prompt with arguments",
|
||||
Arguments =
|
||||
[
|
||||
new PromptArgument
|
||||
{
|
||||
Name = "temperature",
|
||||
Description = "Temperature setting",
|
||||
Required = true
|
||||
},
|
||||
new PromptArgument
|
||||
{
|
||||
Name = "style",
|
||||
Description = "Output style",
|
||||
Required = false
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
};
|
||||
},
|
||||
GetPromptHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
if (request.Params is null)
|
||||
{
|
||||
throw new McpException("Missing required parameter 'name'", McpErrorCode.InvalidParams);
|
||||
}
|
||||
List<PromptMessage> messages = new();
|
||||
if (request.Params.Name == "simple_prompt")
|
||||
{
|
||||
messages.Add(new PromptMessage
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new TextContentBlock { Text = "This is a simple prompt without arguments." },
|
||||
});
|
||||
}
|
||||
else if (request.Params.Name == "complex_prompt")
|
||||
{
|
||||
string temperature = request.Params.Arguments?["temperature"].ToString() ?? "unknown";
|
||||
string style = request.Params.Arguments?["style"].ToString() ?? "unknown";
|
||||
messages.Add(new PromptMessage
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new TextContentBlock { Text = $"This is a complex prompt with arguments: temperature={temperature}, style={style}" },
|
||||
});
|
||||
messages.Add(new PromptMessage
|
||||
{
|
||||
Role = Role.Assistant,
|
||||
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 ImageContentBlock
|
||||
{
|
||||
Data = MCP_TINY_IMAGE,
|
||||
MimeType = "image/png"
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new McpException($"Unknown prompt: {request.Params.Name}", McpErrorCode.InvalidParams);
|
||||
}
|
||||
|
||||
return new GetPromptResult
|
||||
{
|
||||
Messages = messages
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -63,21 +63,20 @@ public class McpClientCreationTests
|
|||
{
|
||||
Capabilities = new ClientCapabilities
|
||||
{
|
||||
Sampling = new SamplingCapability
|
||||
{
|
||||
SamplingHandler = async (c, p, t) =>
|
||||
new CreateMessageResult
|
||||
{
|
||||
Content = new TextContentBlock { Text = "result" },
|
||||
Model = "test-model",
|
||||
Role = Role.User,
|
||||
StopReason = "endTurn"
|
||||
},
|
||||
},
|
||||
Roots = new RootsCapability
|
||||
{
|
||||
ListChanged = true,
|
||||
RootsHandler = async (t, r) => new ListRootsResult { Roots = [] },
|
||||
}
|
||||
},
|
||||
Handlers = new()
|
||||
{
|
||||
RootsHandler = async (t, r) => new ListRootsResult { Roots = [] },
|
||||
SamplingHandler = async (c, p, t) => new CreateMessageResult
|
||||
{
|
||||
Content = new TextContentBlock { Text = "result" },
|
||||
Model = "test-model",
|
||||
Role = Role.User,
|
||||
StopReason = "endTurn"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -331,7 +331,7 @@ public class McpClientTests : ClientServerTestBase
|
|||
int remainingProgress = TotalNotifications;
|
||||
TaskCompletionSource<bool> allProgressReceived = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
|
||||
Server.ServerOptions.Capabilities?.Tools?.ToolCollection?.Add(McpServerTool.Create(async (IProgress<ProgressNotificationValue> progress) =>
|
||||
Server.ServerOptions.ToolCollection?.Add(McpServerTool.Create(async (IProgress<ProgressNotificationValue> progress) =>
|
||||
{
|
||||
for (int i = 0; i < TotalNotifications; i++)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@ public partial class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIn
|
|||
TaskCompletionSource<bool> tcs = new();
|
||||
await using var client = await _fixture.CreateClientAsync(clientId, new()
|
||||
{
|
||||
Capabilities = new()
|
||||
Handlers = new()
|
||||
{
|
||||
NotificationHandlers =
|
||||
[
|
||||
|
|
@ -306,7 +306,7 @@ public partial class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIn
|
|||
TaskCompletionSource<bool> receivedNotification = new();
|
||||
await using var client = await _fixture.CreateClientAsync(clientId, new()
|
||||
{
|
||||
Capabilities = new()
|
||||
Handlers = new()
|
||||
{
|
||||
NotificationHandlers =
|
||||
[
|
||||
|
|
@ -374,22 +374,19 @@ public partial class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIn
|
|||
int samplingHandlerCalls = 0;
|
||||
await using var client = await _fixture.CreateClientAsync(clientId, new()
|
||||
{
|
||||
Capabilities = new()
|
||||
Handlers = new()
|
||||
{
|
||||
Sampling = new()
|
||||
SamplingHandler = async (_, _, _) =>
|
||||
{
|
||||
SamplingHandler = async (_, _, _) =>
|
||||
samplingHandlerCalls++;
|
||||
return new CreateMessageResult
|
||||
{
|
||||
samplingHandlerCalls++;
|
||||
return new CreateMessageResult
|
||||
{
|
||||
Model = "test-model",
|
||||
Role = Role.Assistant,
|
||||
Content = new TextContentBlock { Text = "Test response" },
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
Model = "test-model",
|
||||
Role = Role.Assistant,
|
||||
Content = new TextContentBlock { Text = "Test response" },
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Call the server's sampleLLM tool which should trigger our sampling handler
|
||||
|
|
@ -477,8 +474,8 @@ public partial class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIn
|
|||
|
||||
await using var client = await McpClient.CreateAsync(
|
||||
new StdioClientTransport(stdioOptions),
|
||||
clientOptions,
|
||||
loggerFactory: LoggerFactory,
|
||||
clientOptions,
|
||||
loggerFactory: LoggerFactory,
|
||||
cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
// act
|
||||
|
|
@ -533,13 +530,10 @@ public partial class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIn
|
|||
.CreateSamplingHandler();
|
||||
await using var client = await McpClient.CreateAsync(new StdioClientTransport(_fixture.EverythingServerTransportOptions), new()
|
||||
{
|
||||
Capabilities = new()
|
||||
Handlers = new()
|
||||
{
|
||||
Sampling = new()
|
||||
{
|
||||
SamplingHandler = samplingHandler,
|
||||
},
|
||||
},
|
||||
SamplingHandler = samplingHandler
|
||||
}
|
||||
}, cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
||||
var result = await client.CallToolAsync("sampleLLM", new Dictionary<string, object?>()
|
||||
|
|
@ -561,7 +555,7 @@ public partial class ClientIntegrationTests : LoggedTest, IClassFixture<ClientIn
|
|||
TaskCompletionSource<bool> receivedNotification = new();
|
||||
await using var client = await _fixture.CreateClientAsync(clientId, new()
|
||||
{
|
||||
Capabilities = new()
|
||||
Handlers = new()
|
||||
{
|
||||
NotificationHandlers =
|
||||
[
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
|
|||
public void Adds_Prompts_To_Server()
|
||||
{
|
||||
var serverOptions = ServiceProvider.GetRequiredService<IOptions<McpServerOptions>>().Value;
|
||||
var prompts = serverOptions?.Capabilities?.Prompts?.PromptCollection;
|
||||
var prompts = serverOptions.PromptCollection;
|
||||
Assert.NotNull(prompts);
|
||||
Assert.NotEmpty(prompts);
|
||||
}
|
||||
|
|
@ -137,7 +137,7 @@ public partial class McpServerBuilderExtensionsPromptsTests : ClientServerTestBa
|
|||
Assert.False(notificationRead.IsCompleted);
|
||||
|
||||
var serverOptions = ServiceProvider.GetRequiredService<IOptions<McpServerOptions>>().Value;
|
||||
var serverPrompts = serverOptions.Capabilities?.Prompts?.PromptCollection;
|
||||
var serverPrompts = serverOptions.PromptCollection;
|
||||
Assert.NotNull(serverPrompts);
|
||||
|
||||
var newPrompt = McpServerPrompt.Create([McpServerPrompt(Name = "NewPrompt")] () => "42");
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ public partial class McpServerBuilderExtensionsResourcesTests : ClientServerTest
|
|||
public void Adds_Resources_To_Server()
|
||||
{
|
||||
var serverOptions = ServiceProvider.GetRequiredService<IOptions<McpServerOptions>>().Value;
|
||||
var resources = serverOptions?.Capabilities?.Resources?.ResourceCollection;
|
||||
var resources = serverOptions.ResourceCollection;
|
||||
Assert.NotNull(resources);
|
||||
Assert.NotEmpty(resources);
|
||||
}
|
||||
|
|
@ -172,7 +172,7 @@ public partial class McpServerBuilderExtensionsResourcesTests : ClientServerTest
|
|||
Assert.False(notificationRead.IsCompleted);
|
||||
|
||||
var serverOptions = ServiceProvider.GetRequiredService<IOptions<McpServerOptions>>().Value;
|
||||
var serverResources = serverOptions.Capabilities?.Resources?.ResourceCollection;
|
||||
var serverResources = serverOptions.ResourceCollection;
|
||||
Assert.NotNull(serverResources);
|
||||
|
||||
var newResource = McpServerResource.Create([McpServerResource(Name = "NewResource")] () => "42");
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
public void Adds_Tools_To_Server()
|
||||
{
|
||||
var serverOptions = ServiceProvider.GetRequiredService<IOptions<McpServerOptions>>().Value;
|
||||
var tools = serverOptions.Capabilities?.Tools?.ToolCollection;
|
||||
var tools = serverOptions.ToolCollection;
|
||||
Assert.NotNull(tools);
|
||||
Assert.NotEmpty(tools);
|
||||
}
|
||||
|
|
@ -201,7 +201,7 @@ public partial class McpServerBuilderExtensionsToolsTests : ClientServerTestBase
|
|||
Assert.False(notificationRead.IsCompleted);
|
||||
|
||||
var serverOptions = ServiceProvider.GetRequiredService<IOptions<McpServerOptions>>().Value;
|
||||
var serverTools = serverOptions.Capabilities?.Tools?.ToolCollection;
|
||||
var serverTools = serverOptions.ToolCollection;
|
||||
Assert.NotNull(serverTools);
|
||||
|
||||
var newTool = McpServerTool.Create([McpServerTool(Name = "NewTool")] () => "42");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,194 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Options;
|
||||
using ModelContextProtocol.Protocol;
|
||||
using ModelContextProtocol.Server;
|
||||
|
||||
namespace ModelContextProtocol.Tests.Configuration;
|
||||
|
||||
public class McpServerOptionsSetupTests
|
||||
{
|
||||
#region Prompt Handler Tests
|
||||
[Fact]
|
||||
public void Configure_WithListPromptsHandler_CreatesPromptsCapability()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddMcpServer()
|
||||
.WithListPromptsHandler(async (request, ct) => new ListPromptsResult());
|
||||
|
||||
var options = services.BuildServiceProvider().GetRequiredService<IOptions<McpServerOptions>>().Value;
|
||||
|
||||
Assert.NotNull(options.Handlers.ListPromptsHandler);
|
||||
Assert.NotNull(options.Capabilities?.Prompts);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Configure_WithGetPromptHandler_CreatesPromptsCapability()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddMcpServer()
|
||||
.WithGetPromptHandler(async (request, ct) => new GetPromptResult());
|
||||
|
||||
var options = services.BuildServiceProvider().GetRequiredService<IOptions<McpServerOptions>>().Value;
|
||||
|
||||
Assert.NotNull(options.Handlers.GetPromptHandler);
|
||||
Assert.NotNull(options.Capabilities?.Prompts);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Resource Handler Tests
|
||||
[Fact]
|
||||
public void Configure_WithListResourceTemplatesHandler_CreatesResourcesCapability()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddMcpServer()
|
||||
.WithListResourceTemplatesHandler(async (request, ct) => new ListResourceTemplatesResult());
|
||||
|
||||
var options = services.BuildServiceProvider().GetRequiredService<IOptions<McpServerOptions>>().Value;
|
||||
|
||||
Assert.NotNull(options.Handlers.ListResourceTemplatesHandler);
|
||||
Assert.NotNull(options.Capabilities?.Resources);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Configure_WithListResourcesHandler_CreatesResourcesCapability()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddMcpServer()
|
||||
.WithListResourcesHandler(async (request, ct) => new ListResourcesResult());
|
||||
|
||||
var options = services.BuildServiceProvider().GetRequiredService<IOptions<McpServerOptions>>().Value;
|
||||
|
||||
Assert.NotNull(options.Handlers.ListResourcesHandler);
|
||||
Assert.NotNull(options.Capabilities?.Resources);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Configure_WithReadResourceHandler_CreatesResourcesCapability()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddMcpServer()
|
||||
.WithReadResourceHandler(async (request, ct) => new ReadResourceResult());
|
||||
|
||||
var options = services.BuildServiceProvider().GetRequiredService<IOptions<McpServerOptions>>().Value;
|
||||
|
||||
Assert.NotNull(options.Handlers.ReadResourceHandler);
|
||||
Assert.NotNull(options.Capabilities?.Resources);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Configure_WithSubscribeToResourcesHandler_And_WithOtherResourcesHandler_EnablesSubscription()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddMcpServer()
|
||||
.WithListResourcesHandler(async (request, ct) => new ListResourcesResult())
|
||||
.WithSubscribeToResourcesHandler(async (request, ct) => new EmptyResult());
|
||||
|
||||
var options = services.BuildServiceProvider().GetRequiredService<IOptions<McpServerOptions>>().Value;
|
||||
|
||||
Assert.NotNull(options.Handlers.ListResourcesHandler);
|
||||
Assert.NotNull(options.Handlers.SubscribeToResourcesHandler);
|
||||
Assert.NotNull(options.Capabilities?.Resources);
|
||||
Assert.True(options.Capabilities.Resources.Subscribe);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Configure_WithUnsubscribeFromResourcesHandler_And_WithOtherResourcesHandler_EnablesSubscription()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddMcpServer()
|
||||
.WithListResourcesHandler(async (request, ct) => new ListResourcesResult())
|
||||
.WithUnsubscribeFromResourcesHandler(async (request, ct) => new EmptyResult());
|
||||
|
||||
var options = services.BuildServiceProvider().GetRequiredService<IOptions<McpServerOptions>>().Value;
|
||||
|
||||
Assert.NotNull(options.Handlers.ListResourcesHandler);
|
||||
Assert.NotNull(options.Handlers.UnsubscribeFromResourcesHandler);
|
||||
Assert.NotNull(options.Capabilities?.Resources);
|
||||
Assert.True(options.Capabilities.Resources.Subscribe);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Configure_WithSubscribeToResourcesHandler_WithoutOtherResourcesHandler_DoesNotCreateResourcesCapability()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddMcpServer()
|
||||
.WithSubscribeToResourcesHandler(async (request, ct) => new EmptyResult());
|
||||
|
||||
var options = services.BuildServiceProvider().GetRequiredService<IOptions<McpServerOptions>>().Value;
|
||||
|
||||
Assert.Null(options.Handlers.SubscribeToResourcesHandler);
|
||||
Assert.Null(options.Capabilities?.Resources);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Configure_WithUnsubscribeFromResourcesHandler_WithoutOtherResourcesHandler_DoesNotCreateResourcesCapability()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddMcpServer()
|
||||
.WithUnsubscribeFromResourcesHandler(async (request, ct) => new EmptyResult());
|
||||
|
||||
var options = services.BuildServiceProvider().GetRequiredService<IOptions<McpServerOptions>>().Value;
|
||||
|
||||
Assert.Null(options.Handlers.UnsubscribeFromResourcesHandler);
|
||||
Assert.Null(options.Capabilities?.Resources);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Tool Handler Tests
|
||||
[Fact]
|
||||
public void Configure_WithListToolsHandler_CreatesToolsCapability()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddMcpServer()
|
||||
.WithListToolsHandler(async (request, ct) => new ListToolsResult());
|
||||
|
||||
var options = services.BuildServiceProvider().GetRequiredService<IOptions<McpServerOptions>>().Value;
|
||||
|
||||
Assert.NotNull(options.Handlers.ListToolsHandler);
|
||||
Assert.NotNull(options.Capabilities?.Tools);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Configure_WithCallToolHandler_CreatesToolsCapability()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddMcpServer()
|
||||
.WithCallToolHandler(async (request, ct) => new CallToolResult());
|
||||
|
||||
var options = services.BuildServiceProvider().GetRequiredService<IOptions<McpServerOptions>>().Value;
|
||||
|
||||
Assert.NotNull(options.Handlers.CallToolHandler);
|
||||
Assert.NotNull(options.Capabilities?.Tools);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Logging Handler Tests
|
||||
[Fact]
|
||||
public void Configure_WithSetLoggingLevelHandler_CreatesLoggingCapability()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddMcpServer()
|
||||
.WithSetLoggingLevelHandler(async (request, ct) => new EmptyResult());
|
||||
|
||||
var options = services.BuildServiceProvider().GetRequiredService<IOptions<McpServerOptions>>().Value;
|
||||
|
||||
Assert.NotNull(options.Handlers.SetLoggingLevelHandler);
|
||||
Assert.NotNull(options.Capabilities?.Logging);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Completion Handler Tests
|
||||
[Fact]
|
||||
public void Configure_WithCompleteHandler_CreatesCompletionsCapability()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddMcpServer()
|
||||
.WithCompleteHandler(async (request, ct) => new CompleteResult());
|
||||
|
||||
var options = services.BuildServiceProvider().GetRequiredService<IOptions<McpServerOptions>>().Value;
|
||||
|
||||
Assert.NotNull(options.Handlers.CompleteHandler);
|
||||
Assert.NotNull(options.Capabilities?.Completions);
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
|
@ -139,16 +139,10 @@ public class DiagnosticTests
|
|||
|
||||
await using (McpServer server = McpServer.Create(serverTransport, new()
|
||||
{
|
||||
Capabilities = new()
|
||||
{
|
||||
Tools = new()
|
||||
{
|
||||
ToolCollection = [
|
||||
McpServerTool.Create((int amount) => amount * 2, new() { Name = "DoubleValue", Description = "Doubles the value." }),
|
||||
McpServerTool.Create(() => { throw new Exception("boom"); }, new() { Name = "Throw", Description = "Throws error." }),
|
||||
],
|
||||
}
|
||||
}
|
||||
ToolCollection = [
|
||||
McpServerTool.Create((int amount) => amount * 2, new() { Name = "DoubleValue", Description = "Doubles the value." }),
|
||||
McpServerTool.Create(() => { throw new Exception("boom"); }, new() { Name = "Throw", Description = "Throws error." }),
|
||||
]
|
||||
}))
|
||||
{
|
||||
serverTask = server.RunAsync(TestContext.Current.CancellationToken);
|
||||
|
|
|
|||
|
|
@ -70,24 +70,21 @@ public class DockerEverythingServerTests(ITestOutputHelper testOutputHelper) : L
|
|||
};
|
||||
|
||||
int samplingHandlerCalls = 0;
|
||||
var defaultOptions = new McpClientOptions
|
||||
var defaultOptions = new McpClientOptions()
|
||||
{
|
||||
Capabilities = new()
|
||||
Handlers = new()
|
||||
{
|
||||
Sampling = new()
|
||||
SamplingHandler = async (_, _, _) =>
|
||||
{
|
||||
SamplingHandler = async (_, _, _) =>
|
||||
samplingHandlerCalls++;
|
||||
return new CreateMessageResult
|
||||
{
|
||||
samplingHandlerCalls++;
|
||||
return new CreateMessageResult
|
||||
{
|
||||
Model = "test-model",
|
||||
Role = Role.Assistant,
|
||||
Content = new TextContentBlock { Text = "Test response" },
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
Model = "test-model",
|
||||
Role = Role.Assistant,
|
||||
Content = new TextContentBlock { Text = "Test response" },
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
await using var client = await McpClient.CreateAsync(
|
||||
|
|
|
|||
|
|
@ -69,75 +69,72 @@ public partial class ElicitationTests : ClientServerTestBase
|
|||
{
|
||||
await using McpClient client = await CreateMcpClientForServer(new McpClientOptions
|
||||
{
|
||||
Capabilities = new()
|
||||
Handlers = new McpClientHandlers()
|
||||
{
|
||||
Elicitation = new()
|
||||
ElicitationHandler = async (request, cancellationtoken) =>
|
||||
{
|
||||
ElicitationHandler = async (request, cancellationtoken) =>
|
||||
Assert.NotNull(request);
|
||||
Assert.Equal("Please provide more information.", request.Message);
|
||||
Assert.Equal(4, request.RequestedSchema.Properties.Count);
|
||||
|
||||
foreach (var entry in request.RequestedSchema.Properties)
|
||||
{
|
||||
Assert.NotNull(request);
|
||||
Assert.Equal("Please provide more information.", request.Message);
|
||||
Assert.Equal(4, request.RequestedSchema.Properties.Count);
|
||||
|
||||
foreach (var entry in request.RequestedSchema.Properties)
|
||||
switch (entry.Key)
|
||||
{
|
||||
switch (entry.Key)
|
||||
{
|
||||
case "prop1":
|
||||
var primitiveString = Assert.IsType<ElicitRequestParams.StringSchema>(entry.Value);
|
||||
Assert.Equal("title1", primitiveString.Title);
|
||||
Assert.Equal(1, primitiveString.MinLength);
|
||||
Assert.Equal(100, primitiveString.MaxLength);
|
||||
break;
|
||||
case "prop1":
|
||||
var primitiveString = Assert.IsType<ElicitRequestParams.StringSchema>(entry.Value);
|
||||
Assert.Equal("title1", primitiveString.Title);
|
||||
Assert.Equal(1, primitiveString.MinLength);
|
||||
Assert.Equal(100, primitiveString.MaxLength);
|
||||
break;
|
||||
|
||||
case "prop2":
|
||||
var primitiveNumber = Assert.IsType<ElicitRequestParams.NumberSchema>(entry.Value);
|
||||
Assert.Equal("description2", primitiveNumber.Description);
|
||||
Assert.Equal(0, primitiveNumber.Minimum);
|
||||
Assert.Equal(1000, primitiveNumber.Maximum);
|
||||
break;
|
||||
case "prop2":
|
||||
var primitiveNumber = Assert.IsType<ElicitRequestParams.NumberSchema>(entry.Value);
|
||||
Assert.Equal("description2", primitiveNumber.Description);
|
||||
Assert.Equal(0, primitiveNumber.Minimum);
|
||||
Assert.Equal(1000, primitiveNumber.Maximum);
|
||||
break;
|
||||
|
||||
case "prop3":
|
||||
var primitiveBool = Assert.IsType<ElicitRequestParams.BooleanSchema>(entry.Value);
|
||||
Assert.Equal("title3", primitiveBool.Title);
|
||||
Assert.Equal("description4", primitiveBool.Description);
|
||||
Assert.True(primitiveBool.Default);
|
||||
break;
|
||||
case "prop3":
|
||||
var primitiveBool = Assert.IsType<ElicitRequestParams.BooleanSchema>(entry.Value);
|
||||
Assert.Equal("title3", primitiveBool.Title);
|
||||
Assert.Equal("description4", primitiveBool.Description);
|
||||
Assert.True(primitiveBool.Default);
|
||||
break;
|
||||
|
||||
case "prop4":
|
||||
var primitiveEnum = Assert.IsType<ElicitRequestParams.EnumSchema>(entry.Value);
|
||||
Assert.Equal(["option1", "option2", "option3"], primitiveEnum.Enum);
|
||||
Assert.Equal(["Name1", "Name2", "Name3"], primitiveEnum.EnumNames);
|
||||
break;
|
||||
case "prop4":
|
||||
var primitiveEnum = Assert.IsType<ElicitRequestParams.EnumSchema>(entry.Value);
|
||||
Assert.Equal(["option1", "option2", "option3"], primitiveEnum.Enum);
|
||||
Assert.Equal(["Name1", "Name2", "Name3"], primitiveEnum.EnumNames);
|
||||
break;
|
||||
|
||||
default:
|
||||
Assert.Fail($"Unknown property: {entry.Key}");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Assert.Fail($"Unknown property: {entry.Key}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new ElicitResult
|
||||
return new ElicitResult
|
||||
{
|
||||
Action = "accept",
|
||||
Content = new Dictionary<string, JsonElement>
|
||||
{
|
||||
Action = "accept",
|
||||
Content = new Dictionary<string, JsonElement>
|
||||
{
|
||||
["prop1"] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
"string result"
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
["prop2"] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
42
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
["prop3"] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
true
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
["prop4"] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
"option2"
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
["prop1"] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
"string result"
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
["prop2"] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
42
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
["prop3"] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
true
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
["prop4"] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
"option2"
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var result = await client.CallToolAsync("TestElicitation", cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
|
|
|||
|
|
@ -103,90 +103,87 @@ public partial class ElicitationTypedTests : ClientServerTestBase
|
|||
{
|
||||
await using McpClient client = await CreateMcpClientForServer(new McpClientOptions
|
||||
{
|
||||
Capabilities = new()
|
||||
Handlers = new()
|
||||
{
|
||||
Elicitation = new()
|
||||
ElicitationHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
ElicitationHandler = async (request, cancellationToken) =>
|
||||
Assert.NotNull(request);
|
||||
Assert.Equal("Please provide more information.", request.Message);
|
||||
|
||||
Assert.Equal(6, request.RequestedSchema.Properties.Count);
|
||||
|
||||
foreach (var entry in request.RequestedSchema.Properties)
|
||||
{
|
||||
Assert.NotNull(request);
|
||||
Assert.Equal("Please provide more information.", request.Message);
|
||||
|
||||
Assert.Equal(6, request.RequestedSchema.Properties.Count);
|
||||
|
||||
foreach (var entry in request.RequestedSchema.Properties)
|
||||
var key = entry.Key;
|
||||
var value = entry.Value;
|
||||
switch (key)
|
||||
{
|
||||
var key = entry.Key;
|
||||
var value = entry.Value;
|
||||
switch (key)
|
||||
{
|
||||
case nameof(SampleForm.Name):
|
||||
var stringSchema = Assert.IsType<ElicitRequestParams.StringSchema>(value);
|
||||
Assert.Equal("string", stringSchema.Type);
|
||||
break;
|
||||
case nameof(SampleForm.Name):
|
||||
var stringSchema = Assert.IsType<ElicitRequestParams.StringSchema>(value);
|
||||
Assert.Equal("string", stringSchema.Type);
|
||||
break;
|
||||
|
||||
case nameof(SampleForm.Age):
|
||||
var intSchema = Assert.IsType<ElicitRequestParams.NumberSchema>(value);
|
||||
Assert.Equal("integer", intSchema.Type);
|
||||
break;
|
||||
case nameof(SampleForm.Age):
|
||||
var intSchema = Assert.IsType<ElicitRequestParams.NumberSchema>(value);
|
||||
Assert.Equal("integer", intSchema.Type);
|
||||
break;
|
||||
|
||||
case nameof(SampleForm.Active):
|
||||
var boolSchema = Assert.IsType<ElicitRequestParams.BooleanSchema>(value);
|
||||
Assert.Equal("boolean", boolSchema.Type);
|
||||
break;
|
||||
case nameof(SampleForm.Active):
|
||||
var boolSchema = Assert.IsType<ElicitRequestParams.BooleanSchema>(value);
|
||||
Assert.Equal("boolean", boolSchema.Type);
|
||||
break;
|
||||
|
||||
case nameof(SampleForm.Role):
|
||||
var enumSchema = Assert.IsType<ElicitRequestParams.EnumSchema>(value);
|
||||
Assert.Equal("string", enumSchema.Type);
|
||||
Assert.Equal([nameof(SampleRole.User), nameof(SampleRole.Admin)], enumSchema.Enum);
|
||||
break;
|
||||
case nameof(SampleForm.Role):
|
||||
var enumSchema = Assert.IsType<ElicitRequestParams.EnumSchema>(value);
|
||||
Assert.Equal("string", enumSchema.Type);
|
||||
Assert.Equal([nameof(SampleRole.User), nameof(SampleRole.Admin)], enumSchema.Enum);
|
||||
break;
|
||||
|
||||
case nameof(SampleForm.Score):
|
||||
var numSchema = Assert.IsType<ElicitRequestParams.NumberSchema>(value);
|
||||
Assert.Equal("number", numSchema.Type);
|
||||
break;
|
||||
case nameof(SampleForm.Score):
|
||||
var numSchema = Assert.IsType<ElicitRequestParams.NumberSchema>(value);
|
||||
Assert.Equal("number", numSchema.Type);
|
||||
break;
|
||||
|
||||
case nameof(SampleForm.Created):
|
||||
var dateTimeSchema = Assert.IsType<ElicitRequestParams.StringSchema>(value);
|
||||
Assert.Equal("string", dateTimeSchema.Type);
|
||||
Assert.Equal("date-time", dateTimeSchema.Format);
|
||||
case nameof(SampleForm.Created):
|
||||
var dateTimeSchema = Assert.IsType<ElicitRequestParams.StringSchema>(value);
|
||||
Assert.Equal("string", dateTimeSchema.Type);
|
||||
Assert.Equal("date-time", dateTimeSchema.Format);
|
||||
|
||||
break;
|
||||
break;
|
||||
|
||||
default:
|
||||
Assert.Fail($"Unexpected property in schema: {key}");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
Assert.Fail($"Unexpected property in schema: {key}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new ElicitResult
|
||||
return new ElicitResult
|
||||
{
|
||||
Action = "accept",
|
||||
Content = new Dictionary<string, JsonElement>
|
||||
{
|
||||
Action = "accept",
|
||||
Content = new Dictionary<string, JsonElement>
|
||||
{
|
||||
[nameof(SampleForm.Name)] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
"Alice"
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
[nameof(SampleForm.Age)] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
30
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
[nameof(SampleForm.Active)] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
true
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
[nameof(SampleForm.Role)] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
"Admin"
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
[nameof(SampleForm.Score)] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
99.5
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
[nameof(SampleForm.Created)] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
"2023-08-27T03:05:00"
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
},
|
||||
};
|
||||
},
|
||||
[nameof(SampleForm.Name)] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
"Alice"
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
[nameof(SampleForm.Age)] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
30
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
[nameof(SampleForm.Active)] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
true
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
[nameof(SampleForm.Role)] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
"Admin"
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
[nameof(SampleForm.Score)] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
99.5
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
[nameof(SampleForm.Created)] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
"2023-08-27T03:05:00"
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
var result = await client.CallToolAsync("TestElicitationTyped", cancellationToken: TestContext.Current.CancellationToken);
|
||||
|
|
@ -199,37 +196,34 @@ public partial class ElicitationTypedTests : ClientServerTestBase
|
|||
{
|
||||
await using McpClient client = await CreateMcpClientForServer(new McpClientOptions
|
||||
{
|
||||
Capabilities = new()
|
||||
Handlers = new()
|
||||
{
|
||||
Elicitation = new()
|
||||
ElicitationHandler = async (request, cancellationToken) =>
|
||||
{
|
||||
ElicitationHandler = async (request, cancellationToken) =>
|
||||
Assert.NotNull(request);
|
||||
Assert.Equal("Please provide more information.", request.Message);
|
||||
|
||||
// Expect camelCase names based on serializer options
|
||||
Assert.Contains("firstName", request.RequestedSchema.Properties.Keys);
|
||||
Assert.Contains("zipCode", request.RequestedSchema.Properties.Keys);
|
||||
Assert.Contains("isAdmin", request.RequestedSchema.Properties.Keys);
|
||||
|
||||
return new ElicitResult
|
||||
{
|
||||
Assert.NotNull(request);
|
||||
Assert.Equal("Please provide more information.", request.Message);
|
||||
|
||||
// Expect camelCase names based on serializer options
|
||||
Assert.Contains("firstName", request.RequestedSchema.Properties.Keys);
|
||||
Assert.Contains("zipCode", request.RequestedSchema.Properties.Keys);
|
||||
Assert.Contains("isAdmin", request.RequestedSchema.Properties.Keys);
|
||||
|
||||
return new ElicitResult
|
||||
Action = "accept",
|
||||
Content = new Dictionary<string, JsonElement>
|
||||
{
|
||||
Action = "accept",
|
||||
Content = new Dictionary<string, JsonElement>
|
||||
{
|
||||
["firstName"] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
"Bob"
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
["zipCode"] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
90210
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
["isAdmin"] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
false
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
},
|
||||
};
|
||||
},
|
||||
["firstName"] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
"Bob"
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
["zipCode"] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
90210
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
["isAdmin"] = (JsonElement)JsonSerializer.Deserialize("""
|
||||
false
|
||||
""", McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(JsonElement)))!,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -243,16 +237,13 @@ public partial class ElicitationTypedTests : ClientServerTestBase
|
|||
{
|
||||
await using McpClient client = await CreateMcpClientForServer(new McpClientOptions
|
||||
{
|
||||
Capabilities = new()
|
||||
Handlers = new()
|
||||
{
|
||||
Elicitation = new()
|
||||
// Handler should never be invoked because the exception occurs before the request is sent.
|
||||
ElicitationHandler = async (req, ct) =>
|
||||
{
|
||||
// Handler should never be invoked because the exception occurs before the request is sent.
|
||||
ElicitationHandler = async (req, ct) =>
|
||||
{
|
||||
Assert.Fail("Elicitation handler should not be called for unsupported schema test.");
|
||||
return new ElicitResult { Action = "cancel" };
|
||||
},
|
||||
Assert.Fail("Elicitation handler should not be called for unsupported schema test.");
|
||||
return new ElicitResult { Action = "cancel" };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
@ -268,18 +259,15 @@ public partial class ElicitationTypedTests : ClientServerTestBase
|
|||
{
|
||||
await using McpClient client = await CreateMcpClientForServer(new McpClientOptions
|
||||
{
|
||||
Capabilities = new()
|
||||
Handlers = new()
|
||||
{
|
||||
Elicitation = new()
|
||||
// Handler should never be invoked because the exception occurs before the request is sent.
|
||||
ElicitationHandler = async (req, ct) =>
|
||||
{
|
||||
// Handler should never be invoked because the exception occurs before the request is sent.
|
||||
ElicitationHandler = async (req, ct) =>
|
||||
{
|
||||
Assert.Fail("Elicitation handler should not be called for unsupported schema test.");
|
||||
return new ElicitResult { Action = "cancel" };
|
||||
},
|
||||
Assert.Fail("Elicitation handler should not be called for unsupported schema test.");
|
||||
return new ElicitResult { Action = "cancel" };
|
||||
},
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
var ex = await Assert.ThrowsAsync<McpException>(async () =>
|
||||
|
|
@ -291,18 +279,15 @@ public partial class ElicitationTypedTests : ClientServerTestBase
|
|||
{
|
||||
await using McpClient client = await CreateMcpClientForServer(new McpClientOptions
|
||||
{
|
||||
Capabilities = new()
|
||||
Handlers = new()
|
||||
{
|
||||
Elicitation = new()
|
||||
// Should not be invoked
|
||||
ElicitationHandler = async (req, ct) =>
|
||||
{
|
||||
// Should not be invoked
|
||||
ElicitationHandler = async (req, ct) =>
|
||||
{
|
||||
Assert.Fail("Elicitation handler should not be called for non-object generic type test.");
|
||||
return new ElicitResult { Action = "cancel" };
|
||||
},
|
||||
Assert.Fail("Elicitation handler should not be called for non-object generic type test.");
|
||||
return new ElicitResult { Action = "cancel" };
|
||||
},
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
var ex = await Assert.ThrowsAsync<McpException>(async () =>
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ public class McpServerLoggingLevelTests
|
|||
var server = provider.GetRequiredService<McpServer>();
|
||||
|
||||
Assert.NotNull(server.ServerOptions.Capabilities?.Logging);
|
||||
Assert.NotNull(server.ServerOptions.Capabilities.Logging.SetLoggingLevelHandler);
|
||||
Assert.NotNull(server.ServerOptions.Handlers.SetLoggingLevelHandler);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -263,24 +263,24 @@ public class McpServerTests : LoggedTest
|
|||
public async Task Can_Handle_Completion_Requests()
|
||||
{
|
||||
await Can_Handle_Requests(
|
||||
new()
|
||||
new ServerCapabilities
|
||||
{
|
||||
Completions = new()
|
||||
{
|
||||
CompleteHandler = async (request, ct) =>
|
||||
new CompleteResult
|
||||
{
|
||||
Completion = new()
|
||||
{
|
||||
Values = ["test"],
|
||||
Total = 2,
|
||||
HasMore = true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
method: RequestMethods.CompletionComplete,
|
||||
configureOptions: null,
|
||||
configureOptions: options =>
|
||||
{
|
||||
options.Handlers.CompleteHandler = async (request, ct) =>
|
||||
new CompleteResult
|
||||
{
|
||||
Completion = new()
|
||||
{
|
||||
Values = ["test"],
|
||||
Total = 2,
|
||||
HasMore = true
|
||||
}
|
||||
};
|
||||
},
|
||||
assertResult: (_, response) =>
|
||||
{
|
||||
var result = JsonSerializer.Deserialize<CompleteResult>(response, McpJsonUtilities.DefaultOptions);
|
||||
|
|
@ -298,26 +298,26 @@ public class McpServerTests : LoggedTest
|
|||
new ServerCapabilities
|
||||
{
|
||||
Resources = new()
|
||||
{
|
||||
ListResourceTemplatesHandler = async (request, ct) =>
|
||||
{
|
||||
return new ListResourceTemplatesResult
|
||||
{
|
||||
ResourceTemplates = [new() { UriTemplate = "test", Name = "Test Resource" }]
|
||||
};
|
||||
},
|
||||
ListResourcesHandler = async (request, ct) =>
|
||||
{
|
||||
return new ListResourcesResult
|
||||
{
|
||||
Resources = [new() { Uri = "test", Name = "Test Resource" }]
|
||||
};
|
||||
},
|
||||
ReadResourceHandler = (request, ct) => throw new NotImplementedException(),
|
||||
}
|
||||
},
|
||||
RequestMethods.ResourcesTemplatesList,
|
||||
configureOptions: null,
|
||||
configureOptions: options =>
|
||||
{
|
||||
options.Handlers.ListResourceTemplatesHandler = async (request, ct) =>
|
||||
{
|
||||
return new ListResourceTemplatesResult
|
||||
{
|
||||
ResourceTemplates = [new() { UriTemplate = "test", Name = "Test Resource" }]
|
||||
};
|
||||
};
|
||||
options.Handlers.ListResourcesHandler = async (request, ct) =>
|
||||
{
|
||||
return new ListResourcesResult
|
||||
{
|
||||
Resources = [new() { Uri = "test", Name = "Test Resource" }]
|
||||
};
|
||||
};
|
||||
options.Handlers.ReadResourceHandler = (request, ct) => throw new NotImplementedException();
|
||||
},
|
||||
assertResult: (_, response) =>
|
||||
{
|
||||
var result = JsonSerializer.Deserialize<ListResourceTemplatesResult>(response, McpJsonUtilities.DefaultOptions);
|
||||
|
|
@ -334,19 +334,19 @@ public class McpServerTests : LoggedTest
|
|||
new ServerCapabilities
|
||||
{
|
||||
Resources = new()
|
||||
{
|
||||
ListResourcesHandler = async (request, ct) =>
|
||||
{
|
||||
return new ListResourcesResult
|
||||
{
|
||||
Resources = [new() { Uri = "test", Name = "Test Resource" }]
|
||||
};
|
||||
},
|
||||
ReadResourceHandler = (request, ct) => throw new NotImplementedException(),
|
||||
}
|
||||
},
|
||||
RequestMethods.ResourcesList,
|
||||
configureOptions: null,
|
||||
configureOptions: options =>
|
||||
{
|
||||
options.Handlers.ListResourcesHandler = async (request, ct) =>
|
||||
{
|
||||
return new ListResourcesResult
|
||||
{
|
||||
Resources = [new() { Uri = "test", Name = "Test Resource" }]
|
||||
};
|
||||
};
|
||||
options.Handlers.ReadResourceHandler = (request, ct) => throw new NotImplementedException();
|
||||
},
|
||||
assertResult: (_, response) =>
|
||||
{
|
||||
var result = JsonSerializer.Deserialize<ListResourcesResult>(response, McpJsonUtilities.DefaultOptions);
|
||||
|
|
@ -369,19 +369,19 @@ public class McpServerTests : LoggedTest
|
|||
new ServerCapabilities
|
||||
{
|
||||
Resources = new()
|
||||
{
|
||||
ReadResourceHandler = async (request, ct) =>
|
||||
{
|
||||
return new ReadResourceResult
|
||||
{
|
||||
Contents = [new TextResourceContents { Text = "test" }]
|
||||
};
|
||||
},
|
||||
ListResourcesHandler = (request, ct) => throw new NotImplementedException(),
|
||||
}
|
||||
},
|
||||
method: RequestMethods.ResourcesRead,
|
||||
configureOptions: null,
|
||||
configureOptions: options =>
|
||||
{
|
||||
options.Handlers.ReadResourceHandler = async (request, ct) =>
|
||||
{
|
||||
return new ReadResourceResult
|
||||
{
|
||||
Contents = [new TextResourceContents { Text = "test" }]
|
||||
};
|
||||
};
|
||||
options.Handlers.ListResourcesHandler = (request, ct) => throw new NotImplementedException();
|
||||
},
|
||||
assertResult: (_, response) =>
|
||||
{
|
||||
var result = JsonSerializer.Deserialize<ReadResourceResult>(response, McpJsonUtilities.DefaultOptions);
|
||||
|
|
@ -406,19 +406,19 @@ public class McpServerTests : LoggedTest
|
|||
new ServerCapabilities
|
||||
{
|
||||
Prompts = new()
|
||||
{
|
||||
ListPromptsHandler = async (request, ct) =>
|
||||
{
|
||||
return new ListPromptsResult
|
||||
{
|
||||
Prompts = [new() { Name = "test" }]
|
||||
};
|
||||
},
|
||||
GetPromptHandler = (request, ct) => throw new NotImplementedException(),
|
||||
},
|
||||
},
|
||||
method: RequestMethods.PromptsList,
|
||||
configureOptions: null,
|
||||
configureOptions: options =>
|
||||
{
|
||||
options.Handlers.ListPromptsHandler = async (request, ct) =>
|
||||
{
|
||||
return new ListPromptsResult
|
||||
{
|
||||
Prompts = [new() { Name = "test" }]
|
||||
};
|
||||
};
|
||||
options.Handlers.GetPromptHandler = (request, ct) => throw new NotImplementedException();
|
||||
},
|
||||
assertResult: (_, response) =>
|
||||
{
|
||||
var result = JsonSerializer.Deserialize<ListPromptsResult>(response, McpJsonUtilities.DefaultOptions);
|
||||
|
|
@ -441,13 +441,13 @@ public class McpServerTests : LoggedTest
|
|||
new ServerCapabilities
|
||||
{
|
||||
Prompts = new()
|
||||
{
|
||||
GetPromptHandler = async (request, ct) => new GetPromptResult { Description = "test" },
|
||||
ListPromptsHandler = (request, ct) => throw new NotImplementedException(),
|
||||
}
|
||||
},
|
||||
method: RequestMethods.PromptsGet,
|
||||
configureOptions: null,
|
||||
configureOptions: options =>
|
||||
{
|
||||
options.Handlers.GetPromptHandler = async (request, ct) => new GetPromptResult { Description = "test" };
|
||||
options.Handlers.ListPromptsHandler = (request, ct) => throw new NotImplementedException();
|
||||
},
|
||||
assertResult: (_, response) =>
|
||||
{
|
||||
var result = JsonSerializer.Deserialize<GetPromptResult>(response, McpJsonUtilities.DefaultOptions);
|
||||
|
|
@ -469,19 +469,19 @@ public class McpServerTests : LoggedTest
|
|||
new ServerCapabilities
|
||||
{
|
||||
Tools = new()
|
||||
{
|
||||
ListToolsHandler = async (request, ct) =>
|
||||
{
|
||||
return new ListToolsResult
|
||||
{
|
||||
Tools = [new() { Name = "test" }]
|
||||
};
|
||||
},
|
||||
CallToolHandler = (request, ct) => throw new NotImplementedException(),
|
||||
}
|
||||
},
|
||||
method: RequestMethods.ToolsList,
|
||||
configureOptions: null,
|
||||
configureOptions: options =>
|
||||
{
|
||||
options.Handlers.ListToolsHandler = async (request, ct) =>
|
||||
{
|
||||
return new ListToolsResult
|
||||
{
|
||||
Tools = [new() { Name = "test" }]
|
||||
};
|
||||
};
|
||||
options.Handlers.CallToolHandler = (request, ct) => throw new NotImplementedException();
|
||||
},
|
||||
assertResult: (_, response) =>
|
||||
{
|
||||
var result = JsonSerializer.Deserialize<ListToolsResult>(response, McpJsonUtilities.DefaultOptions);
|
||||
|
|
@ -504,19 +504,19 @@ public class McpServerTests : LoggedTest
|
|||
new ServerCapabilities
|
||||
{
|
||||
Tools = new()
|
||||
{
|
||||
CallToolHandler = async (request, ct) =>
|
||||
{
|
||||
return new CallToolResult
|
||||
{
|
||||
Content = [new TextContentBlock { Text = "test" }]
|
||||
};
|
||||
},
|
||||
ListToolsHandler = (request, ct) => throw new NotImplementedException(),
|
||||
}
|
||||
},
|
||||
method: RequestMethods.ToolsCall,
|
||||
configureOptions: null,
|
||||
configureOptions: options =>
|
||||
{
|
||||
options.Handlers.CallToolHandler = async (request, ct) =>
|
||||
{
|
||||
return new CallToolResult
|
||||
{
|
||||
Content = [new TextContentBlock { Text = "test" }]
|
||||
};
|
||||
};
|
||||
options.Handlers.ListToolsHandler = (request, ct) => throw new NotImplementedException();
|
||||
},
|
||||
assertResult: (_, response) =>
|
||||
{
|
||||
var result = JsonSerializer.Deserialize<CallToolResult>(response, McpJsonUtilities.DefaultOptions);
|
||||
|
|
@ -703,14 +703,12 @@ public class McpServerTests : LoggedTest
|
|||
var options = CreateOptions();
|
||||
|
||||
var notificationReceived = new TaskCompletionSource<JsonRpcNotification>();
|
||||
options.Capabilities = new()
|
||||
{
|
||||
NotificationHandlers = [new(NotificationMethods.ProgressNotification, (notification, cancellationToken) =>
|
||||
options.Handlers.NotificationHandlers =
|
||||
[new(NotificationMethods.ProgressNotification, (notification, cancellationToken) =>
|
||||
{
|
||||
notificationReceived.TrySetResult(notification);
|
||||
return default;
|
||||
})],
|
||||
};
|
||||
})];
|
||||
|
||||
var server = McpServer.Create(transport, options, LoggerFactory);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue