diff --git a/mcpdotnet.sln b/mcpdotnet.sln index 375a103..5781724 100644 --- a/mcpdotnet.sln +++ b/mcpdotnet.sln @@ -17,6 +17,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "McpDotNet.Extensions.AI", " EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "McpDotNet.Extensions.AI.Tests", "tests\McpDotNet.Extensions.AI.Tests\McpDotNet.Extensions.AI.Tests.csproj", "{67BEF596-AD39-4BDD-B505-31DE7FF0C8D2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestServerWithHosting", "samples\TestServerWithHosting\TestServerWithHosting.csproj", "{6499876E-2F76-44A8-B6EB-5B889C6E9B7F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -51,10 +55,19 @@ Global {67BEF596-AD39-4BDD-B505-31DE7FF0C8D2}.Debug|Any CPU.Build.0 = Debug|Any CPU {67BEF596-AD39-4BDD-B505-31DE7FF0C8D2}.Release|Any CPU.ActiveCfg = Release|Any CPU {67BEF596-AD39-4BDD-B505-31DE7FF0C8D2}.Release|Any CPU.Build.0 = Release|Any CPU + {6499876E-2F76-44A8-B6EB-5B889C6E9B7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6499876E-2F76-44A8-B6EB-5B889C6E9B7F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6499876E-2F76-44A8-B6EB-5B889C6E9B7F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6499876E-2F76-44A8-B6EB-5B889C6E9B7F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {76E295FA-9E85-7B99-332A-2CDBFCD5860A} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {CA0BB450-1903-2F92-A68D-1285976551D6} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {6499876E-2F76-44A8-B6EB-5B889C6E9B7F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {384A3888-751F-4D75-9AE5-587330582D89} EndGlobalSection diff --git a/samples/TestServerWithHosting/Program.cs b/samples/TestServerWithHosting/Program.cs new file mode 100644 index 0000000..d0e3f81 --- /dev/null +++ b/samples/TestServerWithHosting/Program.cs @@ -0,0 +1,37 @@ +using McpDotNet; +using Microsoft.Extensions.Hosting; +using Serilog; + +Log.Logger = new LoggerConfiguration() + .MinimumLevel.Verbose() // Capture all log levels + .WriteTo.File(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs", "TestServer_.log"), + rollingInterval: RollingInterval.Day, + outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}") + .WriteTo.Debug() + .WriteTo.Console() + .CreateLogger(); + +try +{ + Log.Information("Starting server..."); + + var builder = Host.CreateApplicationBuilder(args); + builder.Services.AddSerilog(); + builder.Services.AddMcpServer() + .WithStdioServerTransport() + .WithCallToolHandler(async (r, ct) => new McpDotNet.Protocol.Types.CallToolResponse()); + + var app = builder.Build(); + + await app.RunAsync(); + return 0; +} +catch (Exception ex) +{ + Log.Fatal(ex, "Host terminated unexpectedly"); + return 1; +} +finally +{ + await Log.CloseAndFlushAsync(); +} \ No newline at end of file diff --git a/samples/TestServerWithHosting/TestServerWithHosting.csproj b/samples/TestServerWithHosting/TestServerWithHosting.csproj new file mode 100644 index 0000000..34fb993 --- /dev/null +++ b/samples/TestServerWithHosting/TestServerWithHosting.csproj @@ -0,0 +1,22 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + + + + + + + + + diff --git a/src/mcpdotnet/Configuration/DefaultMcpServerBuilder.cs b/src/mcpdotnet/Configuration/DefaultMcpServerBuilder.cs new file mode 100644 index 0000000..5559d71 --- /dev/null +++ b/src/mcpdotnet/Configuration/DefaultMcpServerBuilder.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace McpDotNet.Configuration; + +/// +/// Default implementation of . +/// +internal class DefaultMcpServerBuilder : IMcpServerBuilder +{ + /// + public IServiceCollection Services { get; } + + /// + /// Initializes a new instance of the class. + /// + /// + /// + public DefaultMcpServerBuilder(IServiceCollection services) + { + Services = services ?? throw new ArgumentNullException(nameof(services)); + } +} diff --git a/src/mcpdotnet/Configuration/IMcpServerBuilder.cs b/src/mcpdotnet/Configuration/IMcpServerBuilder.cs new file mode 100644 index 0000000..163b039 --- /dev/null +++ b/src/mcpdotnet/Configuration/IMcpServerBuilder.cs @@ -0,0 +1,15 @@ +using McpDotNet.Server; +using Microsoft.Extensions.DependencyInjection; + +namespace McpDotNet.Configuration; + +/// +/// Builder for configuring instances. +/// +public interface IMcpServerBuilder +{ + /// + /// Gets the service collection. + /// + IServiceCollection Services { get; } +} diff --git a/src/mcpdotnet/Configuration/McpServerBuilderExtensions.cs b/src/mcpdotnet/Configuration/McpServerBuilderExtensions.cs new file mode 100644 index 0000000..acfb9ea --- /dev/null +++ b/src/mcpdotnet/Configuration/McpServerBuilderExtensions.cs @@ -0,0 +1,132 @@ +using McpDotNet.Configuration; +using McpDotNet.Protocol.Transport; +using McpDotNet.Protocol.Types; +using McpDotNet.Server; +using Microsoft.Extensions.DependencyInjection; + +namespace McpDotNet; + +/// +/// Extension to configure the MCP server +/// +public static class McpServerBuilderExtensions +{ + /// + /// Adds a server transport that uses stdin/stdout for communication. + /// + /// The builder instance. + /// + public static IMcpServerBuilder WithStdioServerTransport(this IMcpServerBuilder builder) + { + builder.Services.AddSingleton(); + return builder; + } + + /// + /// Sets the handler for list tools requests. + /// + /// The builder instance. + /// The handler. + /// + public static IMcpServerBuilder WithListToolsHandler(this IMcpServerBuilder builder, Func> handler) + { + builder.Services.Configure(s => s.ListToolsHandler = handler); + return builder; + } + + /// + /// Sets the handler for call tool requests. + /// + /// The builder instance. + /// The handler. + /// + public static IMcpServerBuilder WithCallToolHandler(this IMcpServerBuilder builder, Func> handler) + { + builder.Services.Configure(s => s.CallToolHandler = handler); + return builder; + } + + /// + /// Sets the handler for list prompts requests. + /// + /// The builder instance. + /// The handler. + /// + public static IMcpServerBuilder WithListPromptsHandler(this IMcpServerBuilder builder, Func> handler) + { + builder.Services.Configure(s => s.ListPromptsHandler = handler); + return builder; + } + + /// + /// Sets the handler for get prompt requests. + /// + /// The builder instance. + /// The handler. + /// + public static IMcpServerBuilder WithGetPromptHandler(this IMcpServerBuilder builder, Func> handler) + { + builder.Services.Configure(s => s.GetPromptHandler = handler); + return builder; + } + + /// + /// Sets the handler for list resources requests. + /// + /// The builder instance. + /// The handler. + /// + public static IMcpServerBuilder WithListResourcesHandler(this IMcpServerBuilder builder, Func> handler) + { + builder.Services.Configure(s => s.ListResourcesHandler = handler); + return builder; + } + + /// + /// Sets the handler for read resources requests. + /// + /// The builder instance. + /// The handler. + /// + public static IMcpServerBuilder WithReadResourceHandler(this IMcpServerBuilder builder, Func> handler) + { + builder.Services.Configure(s => s.ReadResourceHandler = handler); + return builder; + } + + /// + /// Sets the handler for get resources requests. + /// + /// The builder instance. + /// The handler. + /// + public static IMcpServerBuilder WithGetCompletionHandler(this IMcpServerBuilder builder, Func> handler) + { + builder.Services.Configure(s => s.GetCompletionHandler = handler); + return builder; + } + + /// + /// Sets the handler for subscribe to resources messages. + /// + /// The builder instance. + /// The handler. + /// + public static IMcpServerBuilder WithSubscribeToResourcesHandler(this IMcpServerBuilder builder, Func handler) + { + builder.Services.Configure(s => s.SubscribeToResourcesHandler = handler); + return builder; + } + + /// + /// Sets or sets the handler for subscribe to resources messages. + /// + /// The builder instance. + /// The handler. + /// + public static IMcpServerBuilder WithUnsubscribeFromResourcesHandler(this IMcpServerBuilder builder, Func handler) + { + builder.Services.Configure(s => s.UnsubscribeFromResourcesHandler = handler); + return builder; + } +} diff --git a/src/mcpdotnet/Configuration/McpServerServiceCollectionExtension.cs b/src/mcpdotnet/Configuration/McpServerServiceCollectionExtension.cs new file mode 100644 index 0000000..476efbd --- /dev/null +++ b/src/mcpdotnet/Configuration/McpServerServiceCollectionExtension.cs @@ -0,0 +1,63 @@ +using System.Reflection; +using McpDotNet.Configuration; +using McpDotNet.Hosting; +using McpDotNet.Protocol.Types; +using McpDotNet.Server; +using Microsoft.Extensions.DependencyInjection; + +namespace McpDotNet; + +/// +/// Extension to host the MCP server +/// +public static class McpServerServiceCollectionExtension +{ + /// + /// Adds the MCP server to the service collection with default options. + /// + /// + /// + /// + public static IMcpServerBuilder AddMcpServer(this IServiceCollection services, Action? configureOptions = null) + { + var options = CreateDefaultServerOptions(); + configureOptions?.Invoke(options); + + return AddMcpServer(services, options); + } + + /// + /// Adds the MCP server to the service collection with the provided options. + /// + /// + /// + /// + public static IMcpServerBuilder AddMcpServer(this IServiceCollection services, McpServerOptions serverOptions) + { + services.AddSingleton(serverOptions); + services.AddSingleton(); + services.AddHostedService(); + services.AddOptions(); + + services.AddSingleton(sp => sp.GetRequiredService().CreateServer()); + + return new DefaultMcpServerBuilder(services); + } + + private static McpServerOptions CreateDefaultServerOptions() + { + var assemblyName = Assembly.GetEntryAssembly()?.GetName(); + + return new McpServerOptions() + { + ServerInfo = new Implementation() { Name = assemblyName?.Name ?? "McpServer", Version = assemblyName?.Version?.ToString() ?? "1.0.0" }, + Capabilities = new ServerCapabilities() + { + Tools = new(), + Resources = new(), + Prompts = new(), + }, + ProtocolVersion = "2024-11-05" + }; + } +} diff --git a/src/mcpdotnet/Hosting/McpServerHostedService.cs b/src/mcpdotnet/Hosting/McpServerHostedService.cs new file mode 100644 index 0000000..d9b0b0f --- /dev/null +++ b/src/mcpdotnet/Hosting/McpServerHostedService.cs @@ -0,0 +1,28 @@ +using McpDotNet.Server; +using Microsoft.Extensions.Hosting; + +namespace McpDotNet.Hosting; + +/// +/// Hosted service for the MCP server. +/// +public class McpServerHostedService : BackgroundService +{ + private readonly IMcpServer _server; + + /// + /// Creates a new instance of the McpServerHostedService. + /// + /// The MCP server instance + /// + public McpServerHostedService(IMcpServer server) + { + _server = server ?? throw new ArgumentNullException(nameof(server)); + } + + /// + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + await _server.StartAsync(stoppingToken); + } +} diff --git a/src/mcpdotnet/Protocol/Transport/StdioServerTransport.cs b/src/mcpdotnet/Protocol/Transport/StdioServerTransport.cs index a9820dc..7a7feb7 100644 --- a/src/mcpdotnet/Protocol/Transport/StdioServerTransport.cs +++ b/src/mcpdotnet/Protocol/Transport/StdioServerTransport.cs @@ -1,8 +1,9 @@ using System.Text.Json; -using Microsoft.Extensions.Logging; -using McpDotNet.Protocol.Messages; -using McpDotNet.Utils.Json; using McpDotNet.Logging; +using McpDotNet.Protocol.Messages; +using McpDotNet.Server; +using McpDotNet.Utils.Json; +using Microsoft.Extensions.Logging; namespace McpDotNet.Protocol.Transport; @@ -19,6 +20,16 @@ public sealed class StdioServerTransport : TransportBase, IServerTransport private string EndpointName => $"Server (stdio) ({_serverName})"; + /// + /// Initializes a new instance of the StdioServerTransport class. + /// + /// The server options. + /// A logger factory for creating loggers. + public StdioServerTransport(McpServerOptions serverOptions, ILoggerFactory loggerFactory) + : this(serverOptions.ServerInfo.Name, loggerFactory) + { + } + /// /// Initializes a new instance of the StdioServerTransport class. /// @@ -64,7 +75,7 @@ public sealed class StdioServerTransport : TransportBase, IServerTransport { var json = JsonSerializer.Serialize(message, _jsonOptions); _logger.TransportSendingMessage(EndpointName, id, json); - + using (Console.OpenStandardOutput()) { await Console.Out.WriteLineAsync(json.AsMemory(), cancellationToken); @@ -159,7 +170,7 @@ public sealed class StdioServerTransport : TransportBase, IServerTransport private async Task CleanupAsync(CancellationToken cancellationToken) { _logger.TransportCleaningUp(EndpointName); - + _shutdownCts?.Cancel(); _shutdownCts?.Dispose(); _shutdownCts = null; diff --git a/src/mcpdotnet/Server/IMcpServerFactory.cs b/src/mcpdotnet/Server/IMcpServerFactory.cs new file mode 100644 index 0000000..a8c1e70 --- /dev/null +++ b/src/mcpdotnet/Server/IMcpServerFactory.cs @@ -0,0 +1,13 @@ +namespace McpDotNet.Server; + +/// +/// Factory for creating instances. +/// +public interface IMcpServerFactory +{ + /// + /// Creates a new server instance. + /// + /// + IMcpServer CreateServer(); +} diff --git a/src/mcpdotnet/Server/McpServer.cs b/src/mcpdotnet/Server/McpServer.cs index 4355c9b..c253255 100644 --- a/src/mcpdotnet/Server/McpServer.cs +++ b/src/mcpdotnet/Server/McpServer.cs @@ -1,11 +1,11 @@  using System.Text.Json.Nodes; -using Microsoft.Extensions.Logging; -using McpDotNet.Protocol.Types; -using McpDotNet.Shared; -using McpDotNet.Protocol.Transport; using McpDotNet.Logging; using McpDotNet.Protocol.Messages; +using McpDotNet.Protocol.Transport; +using McpDotNet.Protocol.Types; +using McpDotNet.Shared; +using Microsoft.Extensions.Logging; namespace McpDotNet.Server; @@ -22,22 +22,22 @@ internal class McpServer : McpJsonRpcEndpoint, IMcpServer /// /// Transport to use for the server /// Configuration options for this server, including capabilities. - /// Make sure to accurately reflect exactly what capabilities the server supports and does not support. - /// Optional server instructions to send to clients + /// Make sure to accurately reflect exactly what capabilities the server supports and does not support. /// Logger factory to use for logging /// - public McpServer(IServerTransport transport, McpServerOptions options, string? serverInstructions, ILoggerFactory loggerFactory) + public McpServer(IServerTransport transport, McpServerOptions options, ILoggerFactory loggerFactory) : base(transport, loggerFactory) { _serverTransport = transport; _options = options; _logger = loggerFactory.CreateLogger(); - ServerInstructions = serverInstructions; + ServerInstructions = options.ServerInstructions; if (options.Capabilities?.Tools != null) { SetRequestHandler("tools/list", - async (request) => { + async (request) => + { if (ListToolsHandler == null) { // Setting the capability, but not a handler means we have nothing to return to the server @@ -49,7 +49,8 @@ internal class McpServer : McpJsonRpcEndpoint, IMcpServer }); SetRequestHandler("tools/call", - async (request) => { + async (request) => + { if (CallToolHandler == null) { // Setting the capability, but not a handler means we have nothing to return to the server @@ -265,7 +266,7 @@ internal class McpServer : McpJsonRpcEndpoint, IMcpServer if (ClientCapabilities?.Roots == null) { throw new McpServerException("Client does not support roots"); - } + } return await SendRequestAsync( new JsonRpcRequest diff --git a/src/mcpdotnet/Server/McpServerDelegates.cs b/src/mcpdotnet/Server/McpServerDelegates.cs new file mode 100644 index 0000000..fc5b691 --- /dev/null +++ b/src/mcpdotnet/Server/McpServerDelegates.cs @@ -0,0 +1,88 @@ +using McpDotNet.Protocol.Types; + +namespace McpDotNet.Server; + +/// +/// Container for delegates that can be applied to an MCP server. +/// +public class McpServerDelegates +{ + /// + /// Gets or sets the handler for list tools requests. + /// + public Func>? ListToolsHandler { get; set; } + + /// + /// Gets or sets the handler for call tool requests. + /// + public Func>? CallToolHandler { get; set; } + + /// + /// Gets or sets the handler for list prompts requests. + /// + public Func>? ListPromptsHandler { get; set; } + + /// + /// Gets or sets the handler for get prompt requests. + /// + public Func>? GetPromptHandler { get; set; } + + /// + /// Gets or sets the handler for list resources requests. + /// + public Func>? ListResourcesHandler { get; set; } + + /// + /// Gets or sets the handler for read resources requests. + /// + public Func>? ReadResourceHandler { get; set; } + + /// + /// Gets or sets the handler for get resources requests. + /// + public Func>? GetCompletionHandler { get; set; } + + /// + /// Gets or sets the handler for subscribe to resources messages. + /// + public Func? SubscribeToResourcesHandler { get; set; } + + /// + /// Gets or sets the handler for subscribe to resources messages. + /// + public Func? UnsubscribeFromResourcesHandler { get; set; } + + /// + /// Applies the delegates to the server. + /// + /// + public void Apply(IMcpServer server) + { + if (ListToolsHandler != null) + server.ListToolsHandler = ListToolsHandler; + + if (CallToolHandler != null) + server.CallToolHandler = CallToolHandler; + + if (ListPromptsHandler != null) + server.ListPromptsHandler = ListPromptsHandler; + + if (GetPromptHandler != null) + server.GetPromptHandler = GetPromptHandler; + + if (ListResourcesHandler != null) + server.ListResourcesHandler = ListResourcesHandler; + + if (ReadResourceHandler != null) + server.ReadResourceHandler = ReadResourceHandler; + + if (GetCompletionHandler != null) + server.GetCompletionHandler = GetCompletionHandler; + + if (SubscribeToResourcesHandler != null) + server.SubscribeToResourcesHandler = SubscribeToResourcesHandler; + + if (UnsubscribeFromResourcesHandler != null) + server.UnsubscribeFromResourcesHandler = UnsubscribeFromResourcesHandler; + } +} diff --git a/src/mcpdotnet/Server/McpServerFactory.cs b/src/mcpdotnet/Server/McpServerFactory.cs index 858fd4f..37cfd93 100644 --- a/src/mcpdotnet/Server/McpServerFactory.cs +++ b/src/mcpdotnet/Server/McpServerFactory.cs @@ -1,5 +1,6 @@ -using Microsoft.Extensions.Logging; -using McpDotNet.Protocol.Transport; +using McpDotNet.Protocol.Transport; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace McpDotNet.Server; @@ -13,12 +14,12 @@ namespace McpDotNet.Server; /// /// You must register handlers for all supported capabilities on the server instance, before calling BeginListeningAsync. /// -public class McpServerFactory +public class McpServerFactory : IMcpServerFactory { private readonly IServerTransport _serverTransport; private readonly McpServerOptions _options; private readonly ILoggerFactory _loggerFactory; - private readonly string? _serverInstructions; + private readonly McpServerDelegates? _serverDelegates; /// /// Initializes a new instance of the class. @@ -26,15 +27,14 @@ public class McpServerFactory /// Transport to use for the server /// Configuration options for this server, including capabilities. /// Make sure to accurately reflect exactly what capabilities the server supports and does not support. - /// Optional server instructions to send to clients /// Logger factory to use for logging - public McpServerFactory(IServerTransport serverTransport, McpServerOptions options, ILoggerFactory loggerFactory, - string? serverInstructions = null) + /// + public McpServerFactory(IServerTransport serverTransport, McpServerOptions options, ILoggerFactory loggerFactory, IOptions? serverDelegates = null) { - _serverTransport = serverTransport; - _options = options; - _loggerFactory = loggerFactory; - _serverInstructions = serverInstructions; + _serverTransport = serverTransport ?? throw new ArgumentNullException(nameof(serverTransport)); + _options = options ?? throw new ArgumentNullException(nameof(options)); + _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); + _serverDelegates = serverDelegates?.Value; } /// @@ -44,6 +44,10 @@ public class McpServerFactory /// public IMcpServer CreateServer() { - return new McpServer(_serverTransport, _options, _serverInstructions, _loggerFactory); + var server = new McpServer(_serverTransport, _options, _loggerFactory); + + _serverDelegates?.Apply(server); + + return server; } } diff --git a/src/mcpdotnet/Server/McpServerOptions.cs b/src/mcpdotnet/Server/McpServerOptions.cs index 37b577c..76f5e1d 100644 --- a/src/mcpdotnet/Server/McpServerOptions.cs +++ b/src/mcpdotnet/Server/McpServerOptions.cs @@ -29,4 +29,9 @@ public record McpServerOptions /// Timeout for initialization sequence. /// public TimeSpan InitializationTimeout { get; init; } = TimeSpan.FromSeconds(60); + + /// + /// Optional server instructions to send to clients + /// + public string ServerInstructions { get; init; } = string.Empty; } diff --git a/src/mcpdotnet/mcpdotnet.csproj b/src/mcpdotnet/mcpdotnet.csproj index f0cfde9..99af1e7 100644 --- a/src/mcpdotnet/mcpdotnet.csproj +++ b/src/mcpdotnet/mcpdotnet.csproj @@ -20,7 +20,7 @@ MIT mcpdotnet - mcpdotnet + McpDotNet true true snupkg @@ -35,6 +35,7 @@ + diff --git a/tests/mcpdotnet.TestServer/Program.cs b/tests/mcpdotnet.TestServer/Program.cs index 9bd5b4b..3c942ea 100644 --- a/tests/mcpdotnet.TestServer/Program.cs +++ b/tests/mcpdotnet.TestServer/Program.cs @@ -1,10 +1,9 @@ -using McpDotNet.Protocol.Transport; +using System.Text; +using McpDotNet.Protocol.Transport; using McpDotNet.Protocol.Types; using McpDotNet.Server; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Serilog; -using System.Text; internal class Program { @@ -38,11 +37,11 @@ internal class Program Resources = new(), Prompts = new(), }, - ProtocolVersion = "2024-11-05" + ProtocolVersion = "2024-11-05", + ServerInstructions = "This is a test server with only stub functionality", }; var loggerFactory = CreateLoggerFactory(); - McpServerFactory factory = new McpServerFactory(new StdioServerTransport("TestServer", loggerFactory), options, loggerFactory, - "This is a test server with only stub functionality"); + McpServerFactory factory = new McpServerFactory(new StdioServerTransport("TestServer", loggerFactory), options, loggerFactory); IMcpServer server = factory.CreateServer(); Console.WriteLine("Server object created, registering handlers."); @@ -51,7 +50,7 @@ internal class Program static CreateMessageRequestParams CreateRequestSamplingParams(string context, string uri, int maxTokens = 100) { return new CreateMessageRequestParams() - { + { Messages = [new SamplingMessage() { Role = Role.User, @@ -61,10 +60,10 @@ internal class Program Text = $"Resource {uri} context: {context}" } }], - SystemPrompt = "You are a helpful test server.", - MaxTokens = maxTokens, - Temperature = 0.7f, - IncludeContext = ContextInclusion.ThisServer + SystemPrompt = "You are a helpful test server.", + MaxTokens = maxTokens, + Temperature = 0.7f, + IncludeContext = ContextInclusion.ThisServer }; } #endregion @@ -74,9 +73,9 @@ internal class Program { return Task.FromResult(new ListToolsResult() { - Tools = + Tools = [ - new Tool() + new Tool() { Name = "echo", Description = "Echoes the input back to the client.", @@ -122,8 +121,8 @@ internal class Program } else if (request.Name == "sampleLLM") { - if (request.Arguments is null || - !request.Arguments.TryGetValue("prompt", out var prompt) || + if (request.Arguments is null || + !request.Arguments.TryGetValue("prompt", out var prompt) || !request.Arguments.TryGetValue("maxTokens", out var maxTokens)) { throw new McpServerException("Missing required arguments 'prompt' and 'maxTokens'"); @@ -200,10 +199,10 @@ internal class Program throw new McpServerException("Invalid cursor"); } } - + int endIndex = Math.Min(startIndex + pageSize, resources.Count); string? nextCursor = null; - + if (endIndex < resources.Count) { nextCursor = Convert.ToBase64String(Encoding.UTF8.GetBytes(endIndex.ToString())); diff --git a/tests/mcpdotnet.TestServer/mcpdotnet.TestServer.csproj b/tests/mcpdotnet.TestServer/mcpdotnet.TestServer.csproj index 6451d08..ef2c91a 100644 --- a/tests/mcpdotnet.TestServer/mcpdotnet.TestServer.csproj +++ b/tests/mcpdotnet.TestServer/mcpdotnet.TestServer.csproj @@ -9,6 +9,7 @@ + diff --git a/tests/mcpdotnet.Tests/Server/McpServerDelegatesTests.cs b/tests/mcpdotnet.Tests/Server/McpServerDelegatesTests.cs new file mode 100644 index 0000000..3516eb5 --- /dev/null +++ b/tests/mcpdotnet.Tests/Server/McpServerDelegatesTests.cs @@ -0,0 +1,38 @@ +using McpDotNet.Protocol.Types; +using McpDotNet.Server; +using Moq; + +namespace mcpdotnet.Tests.Server; + +public class McpServerDelegatesTests +{ + [Fact] + public void Applies_All_Given_Delegates() + { + var container = new McpServerDelegates(); + var server = new Mock(); + server.SetupAllProperties(); + + container.ListToolsHandler = (p, c) => Task.FromResult(new ListToolsResult()); + container.CallToolHandler = (p, c) => Task.FromResult(new CallToolResponse()); + container.ListPromptsHandler = (p, c) => Task.FromResult(new ListPromptsResult()); + container.GetPromptHandler = (p, c) => Task.FromResult(new GetPromptResult()); + container.ListResourcesHandler = (p, c) => Task.FromResult(new ListResourcesResult()); + container.ReadResourceHandler = (p, c) => Task.FromResult(new ReadResourceResult()); + container.GetCompletionHandler = (p, c) => Task.FromResult(new CompleteResult()); + container.SubscribeToResourcesHandler = (s, c) => Task.CompletedTask; + container.UnsubscribeFromResourcesHandler = (s, c) => Task.CompletedTask; + + container.Apply(server.Object); + + Assert.Equal(container.ListToolsHandler, server.Object.ListToolsHandler); + Assert.Equal(container.CallToolHandler, server.Object.CallToolHandler); + Assert.Equal(container.ListPromptsHandler, server.Object.ListPromptsHandler); + Assert.Equal(container.GetPromptHandler, server.Object.GetPromptHandler); + Assert.Equal(container.ListResourcesHandler, server.Object.ListResourcesHandler); + Assert.Equal(container.ReadResourceHandler, server.Object.ReadResourceHandler); + Assert.Equal(container.GetCompletionHandler, server.Object.GetCompletionHandler); + Assert.Equal(container.SubscribeToResourcesHandler, server.Object.SubscribeToResourcesHandler); + Assert.Equal(container.UnsubscribeFromResourcesHandler, server.Object.UnsubscribeFromResourcesHandler); + } +}