Add AspNetCoreSseServer sample (#81)
* Add AspNetCoreSseMcpTransport sample * Fix build errors after rebase * React to McpServerFactory changes * Make Utf8JsonWriter non thread static * Fix test errors caused by not rebasing the HttpLister changes on factory overhaul commit
This commit is contained in:
parent
b5a26e4c33
commit
90dfc35fbd
26 changed files with 635 additions and 332 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,5 +1,5 @@
|
|||
# Cake tools
|
||||
[Tt]ools/
|
||||
/[Tt]ools/
|
||||
|
||||
# Build output
|
||||
[Bb]uildArtifacts/
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{2A77AF5C
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "mcpdotnet.TestSseServer", "tests\mcpdotnet.TestSseServer\mcpdotnet.TestSseServer.csproj", "{79B94BF9-E557-33DB-3F19-B2C7D9BF8C56}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCoreSseServer", "samples\AspNetCoreSseServer\AspNetCoreSseServer.csproj", "{B6F42305-423F-56FF-090F-B7263547F924}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -61,6 +63,10 @@ Global
|
|||
{79B94BF9-E557-33DB-3F19-B2C7D9BF8C56}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{79B94BF9-E557-33DB-3F19-B2C7D9BF8C56}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{79B94BF9-E557-33DB-3F19-B2C7D9BF8C56}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{B6F42305-423F-56FF-090F-B7263547F924}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{B6F42305-423F-56FF-090F-B7263547F924}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{B6F42305-423F-56FF-090F-B7263547F924}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{B6F42305-423F-56FF-090F-B7263547F924}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
@ -71,6 +77,7 @@ Global
|
|||
{CA0BB450-1903-2F92-A68D-1285976551D6} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{7C229573-A085-4ECC-8131-958CDA2BE731} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{6499876E-2F76-44A8-B6EB-5B889C6E9B7F} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
{B6F42305-423F-56FF-090F-B7263547F924} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {384A3888-751F-4D75-9AE5-587330582D89}
|
||||
|
|
|
|||
17
samples/AspNetCoreSseServer/AspNetCoreSseServer.csproj
Normal file
17
samples/AspNetCoreSseServer/AspNetCoreSseServer.csproj
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\mcpdotnet\mcpdotnet.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.Net.ServerSentEvents" Version="10.0.0-preview.1.25080.5" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
using McpDotNet.Protocol.Messages;
|
||||
using McpDotNet.Server;
|
||||
using McpDotNet.Utils.Json;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace AspNetCoreSseServer;
|
||||
|
||||
public static class McpEndpointRouteBuilderExtensions
|
||||
{
|
||||
public static IEndpointConventionBuilder MapMcpSse(this IEndpointRouteBuilder endpoints)
|
||||
{
|
||||
IMcpServer? server = null;
|
||||
SseServerStreamTransport? transport = null;
|
||||
var loggerFactory = endpoints.ServiceProvider.GetRequiredService<ILoggerFactory>();
|
||||
var mcpServerOptions = endpoints.ServiceProvider.GetRequiredService<IOptions<McpServerOptions>>();
|
||||
|
||||
var routeGroup = endpoints.MapGroup("");
|
||||
|
||||
routeGroup.MapGet("/sse", async (HttpResponse response, CancellationToken requestAborted) =>
|
||||
{
|
||||
await using var localTransport = transport = new SseServerStreamTransport(response.Body);
|
||||
await using var localServer = server = McpServerFactory.Create(transport, mcpServerOptions.Value, loggerFactory, endpoints.ServiceProvider);
|
||||
|
||||
await localServer.StartAsync(requestAborted);
|
||||
|
||||
response.Headers.ContentType = "text/event-stream";
|
||||
response.Headers.CacheControl = "no-cache";
|
||||
|
||||
try
|
||||
{
|
||||
await transport.RunAsync(requestAborted);
|
||||
}
|
||||
catch (OperationCanceledException) when (requestAborted.IsCancellationRequested)
|
||||
{
|
||||
// RequestAborted always triggers when the client disconnects before a complete response body is written,
|
||||
// but this is how SSE connections are typically closed.
|
||||
}
|
||||
});
|
||||
|
||||
routeGroup.MapPost("/message", async (HttpContext context) =>
|
||||
{
|
||||
if (transport is null)
|
||||
{
|
||||
await Results.BadRequest("Connect to the /sse endpoint before sending messages.").ExecuteAsync(context);
|
||||
return;
|
||||
}
|
||||
|
||||
var message = await context.Request.ReadFromJsonAsync<IJsonRpcMessage>(JsonSerializerOptionsExtensions.DefaultOptions, context.RequestAborted);
|
||||
if (message is null)
|
||||
{
|
||||
await Results.BadRequest("No message in request body.").ExecuteAsync(context);
|
||||
return;
|
||||
}
|
||||
|
||||
await transport.OnMessageReceivedAsync(message, context.RequestAborted);
|
||||
context.Response.StatusCode = StatusCodes.Status202Accepted;
|
||||
await context.Response.WriteAsync("Accepted");
|
||||
});
|
||||
|
||||
return routeGroup;
|
||||
}
|
||||
}
|
||||
11
samples/AspNetCoreSseServer/Program.cs
Normal file
11
samples/AspNetCoreSseServer/Program.cs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
using McpDotNet;
|
||||
using AspNetCoreSseServer;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddMcpServer().WithTools();
|
||||
var app = builder.Build();
|
||||
|
||||
app.MapGet("/", () => "Hello World!");
|
||||
app.MapMcpSse();
|
||||
|
||||
app.Run();
|
||||
23
samples/AspNetCoreSseServer/Properties/launchSettings.json
Normal file
23
samples/AspNetCoreSseServer/Properties/launchSettings.json
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/launchsettings.json",
|
||||
"profiles": {
|
||||
"http": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "http://localhost:3001",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
},
|
||||
"https": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": true,
|
||||
"launchBrowser": true,
|
||||
"applicationUrl": "https://localhost:7133;http://localhost:3001",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
84
samples/AspNetCoreSseServer/SseServerStreamTransport.cs
Normal file
84
samples/AspNetCoreSseServer/SseServerStreamTransport.cs
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
using System.Buffers;
|
||||
using System.Net.ServerSentEvents;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Channels;
|
||||
using McpDotNet.Protocol.Messages;
|
||||
using McpDotNet.Protocol.Transport;
|
||||
using McpDotNet.Utils.Json;
|
||||
|
||||
namespace AspNetCoreSseServer;
|
||||
|
||||
public class SseServerStreamTransport(Stream sseResponseStream) : ITransport
|
||||
{
|
||||
private readonly Channel<IJsonRpcMessage> _incomingChannel = CreateSingleItemChannel<IJsonRpcMessage>();
|
||||
private readonly Channel<SseItem<IJsonRpcMessage?>> _outgoingSseChannel = CreateSingleItemChannel<SseItem<IJsonRpcMessage?>>();
|
||||
|
||||
private Task? _sseWriteTask;
|
||||
private Utf8JsonWriter? _jsonWriter;
|
||||
|
||||
public bool IsConnected => _sseWriteTask?.IsCompleted == false;
|
||||
|
||||
public Task RunAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
void WriteJsonRpcMessageToBuffer(SseItem<IJsonRpcMessage?> item, IBufferWriter<byte> writer)
|
||||
{
|
||||
if (item.EventType == "endpoint")
|
||||
{
|
||||
writer.Write("/message"u8);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonSerializer.Serialize(GetUtf8JsonWriter(writer), item.Data, JsonSerializerOptionsExtensions.DefaultOptions);
|
||||
}
|
||||
|
||||
// The very first SSE event isn't really an IJsonRpcMessage, but there's no API to write a single item of a different type,
|
||||
// so we fib and special-case the "endpoint" event type in the formatter.
|
||||
_outgoingSseChannel.Writer.TryWrite(new SseItem<IJsonRpcMessage?>(null, "endpoint"));
|
||||
|
||||
var sseItems = _outgoingSseChannel.Reader.ReadAllAsync(cancellationToken);
|
||||
return _sseWriteTask = SseFormatter.WriteAsync(sseItems, sseResponseStream, WriteJsonRpcMessageToBuffer, cancellationToken);
|
||||
}
|
||||
|
||||
public ChannelReader<IJsonRpcMessage> MessageReader => _incomingChannel.Reader;
|
||||
|
||||
public ValueTask DisposeAsync()
|
||||
{
|
||||
_incomingChannel.Writer.TryComplete();
|
||||
_outgoingSseChannel.Writer.TryComplete();
|
||||
return new ValueTask(_sseWriteTask ?? Task.CompletedTask);
|
||||
}
|
||||
|
||||
public Task SendMessageAsync(IJsonRpcMessage message, CancellationToken cancellationToken = default) =>
|
||||
_outgoingSseChannel.Writer.WriteAsync(new SseItem<IJsonRpcMessage?>(message), cancellationToken).AsTask();
|
||||
|
||||
public Task OnMessageReceivedAsync(IJsonRpcMessage message, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!IsConnected)
|
||||
{
|
||||
throw new McpTransportException("Transport is not connected");
|
||||
}
|
||||
|
||||
return _incomingChannel.Writer.WriteAsync(message, cancellationToken).AsTask();
|
||||
}
|
||||
|
||||
private static Channel<T> CreateSingleItemChannel<T>() =>
|
||||
Channel.CreateBounded<T>(new BoundedChannelOptions(1)
|
||||
{
|
||||
SingleReader = true,
|
||||
SingleWriter = false,
|
||||
});
|
||||
|
||||
private Utf8JsonWriter GetUtf8JsonWriter(IBufferWriter<byte> writer)
|
||||
{
|
||||
if (_jsonWriter is null)
|
||||
{
|
||||
_jsonWriter = new Utf8JsonWriter(writer);
|
||||
}
|
||||
else
|
||||
{
|
||||
_jsonWriter.Reset(writer);
|
||||
}
|
||||
|
||||
return _jsonWriter;
|
||||
}
|
||||
}
|
||||
14
samples/AspNetCoreSseServer/Tools/EchoTool.cs
Normal file
14
samples/AspNetCoreSseServer/Tools/EchoTool.cs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
using McpDotNet.Server;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace TestServerWithHosting.Tools;
|
||||
|
||||
[McpToolType]
|
||||
public static class EchoTool
|
||||
{
|
||||
[McpTool, Description("Echoes the input back to the client.")]
|
||||
public static string Echo(string message)
|
||||
{
|
||||
return "hello " + message;
|
||||
}
|
||||
}
|
||||
51
samples/AspNetCoreSseServer/Tools/SampleLlmTool.cs
Normal file
51
samples/AspNetCoreSseServer/Tools/SampleLlmTool.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
using McpDotNet.Protocol.Types;
|
||||
using McpDotNet.Server;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace TestServerWithHosting.Tools;
|
||||
|
||||
/// <summary>
|
||||
/// This tool uses depenency injection and async method
|
||||
/// </summary>
|
||||
[McpToolType]
|
||||
public class SampleLlmTool
|
||||
{
|
||||
private readonly IMcpServer _server;
|
||||
|
||||
public SampleLlmTool(IMcpServer server)
|
||||
{
|
||||
_server = server ?? throw new ArgumentNullException(nameof(server));
|
||||
}
|
||||
|
||||
[McpTool("sampleLLM"), Description("Samples from an LLM using MCP's sampling feature")]
|
||||
public async Task<string> SampleLLM(
|
||||
[Description("The prompt to send to the LLM")] string prompt,
|
||||
[Description("Maximum number of tokens to generate")] int maxTokens,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var samplingParams = CreateRequestSamplingParams(prompt ?? string.Empty, "sampleLLM", maxTokens);
|
||||
var sampleResult = await _server.RequestSamplingAsync(samplingParams, cancellationToken);
|
||||
|
||||
return $"LLM sampling result: {sampleResult.Content.Text}";
|
||||
}
|
||||
|
||||
private static CreateMessageRequestParams CreateRequestSamplingParams(string context, string uri, int maxTokens = 100)
|
||||
{
|
||||
return new CreateMessageRequestParams()
|
||||
{
|
||||
Messages = [new SamplingMessage()
|
||||
{
|
||||
Role = Role.User,
|
||||
Content = new Content()
|
||||
{
|
||||
Type = "text",
|
||||
Text = $"Resource {uri} context: {context}"
|
||||
}
|
||||
}],
|
||||
SystemPrompt = "You are a helpful test server.",
|
||||
MaxTokens = maxTokens,
|
||||
Temperature = 0.7f,
|
||||
IncludeContext = ContextInclusion.ThisServer
|
||||
};
|
||||
}
|
||||
}
|
||||
8
samples/AspNetCoreSseServer/appsettings.Development.json
Normal file
8
samples/AspNetCoreSseServer/appsettings.Development.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
}
|
||||
}
|
||||
9
samples/AspNetCoreSseServer/appsettings.json
Normal file
9
samples/AspNetCoreSseServer/appsettings.json
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"Logging": {
|
||||
"LogLevel": {
|
||||
"Default": "Information",
|
||||
"Microsoft.AspNetCore": "Warning"
|
||||
}
|
||||
},
|
||||
"AllowedHosts": "*"
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@ Log.Logger = new LoggerConfiguration()
|
|||
rollingInterval: RollingInterval.Day,
|
||||
outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff zzz} [{Level:u3}] {Message:lj}{NewLine}{Exception}")
|
||||
.WriteTo.Debug()
|
||||
.WriteTo.Console()
|
||||
.WriteTo.Console(standardErrorFromLevel: Serilog.Events.LogEventLevel.Verbose)
|
||||
.CreateLogger();
|
||||
|
||||
try
|
||||
|
|
@ -19,8 +19,7 @@ try
|
|||
builder.Services.AddSerilog();
|
||||
builder.Services.AddMcpServer()
|
||||
.WithStdioServerTransport()
|
||||
.WithTools()
|
||||
.WithCallToolHandler((r, ct) => Task.FromResult(new McpDotNet.Protocol.Types.CallToolResponse()));
|
||||
.WithTools();
|
||||
|
||||
var app = builder.Build();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using McpDotNet.Configuration;
|
||||
using McpDotNet.Hosting;
|
||||
using McpDotNet.Protocol.Transport;
|
||||
using McpDotNet.Utils;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
|
@ -19,6 +20,7 @@ public static partial class McpServerBuilderExtensions
|
|||
Throw.IfNull(builder);
|
||||
|
||||
builder.Services.AddSingleton<IServerTransport, StdioServerTransport>();
|
||||
builder.Services.AddHostedService<McpServerHostedService>();
|
||||
return builder;
|
||||
}
|
||||
|
||||
|
|
@ -34,6 +36,7 @@ public static partial class McpServerBuilderExtensions
|
|||
}
|
||||
|
||||
builder.Services.AddSingleton<IServerTransport, HttpListenerSseServerTransport>();
|
||||
builder.Services.AddHostedService<McpServerHostedService>();
|
||||
return builder;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
26
src/mcpdotnet/Configuration/McpServerOptionsSetup.cs
Normal file
26
src/mcpdotnet/Configuration/McpServerOptionsSetup.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using System.Reflection;
|
||||
using McpDotNet.Protocol.Types;
|
||||
using McpDotNet.Server;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace McpDotNet.Configuration;
|
||||
|
||||
internal sealed class McpServerOptionsSetup(IOptions<McpServerHandlers> serverHandlers) : IConfigureOptions<McpServerOptions>
|
||||
{
|
||||
public void Configure(McpServerOptions options)
|
||||
{
|
||||
if (options is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(options));
|
||||
}
|
||||
|
||||
var assemblyName = Assembly.GetEntryAssembly()?.GetName();
|
||||
options.ServerInfo = new Implementation
|
||||
{
|
||||
Name = assemblyName?.Name ?? "McpServer",
|
||||
Version = assemblyName?.Version?.ToString() ?? "1.0.0",
|
||||
};
|
||||
|
||||
serverHandlers.Value.OverwriteWithSetHandlers(options);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,9 @@
|
|||
using McpDotNet.Configuration;
|
||||
using McpDotNet.Hosting;
|
||||
using McpDotNet.Protocol.Transport;
|
||||
using McpDotNet.Protocol.Types;
|
||||
using McpDotNet.Server;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.Reflection;
|
||||
|
||||
namespace McpDotNet;
|
||||
|
||||
|
|
@ -23,54 +20,22 @@ public static class McpServerServiceCollectionExtension
|
|||
/// <returns></returns>
|
||||
public static IMcpServerBuilder AddMcpServer(this IServiceCollection services, Action<McpServerOptions>? configureOptions = null)
|
||||
{
|
||||
var options = CreateDefaultServerOptions();
|
||||
configureOptions?.Invoke(options);
|
||||
|
||||
return AddMcpServer(services, options);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds the MCP server to the service collection with the provided options.
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <param name="serverOptions"></param>
|
||||
/// <returns></returns>
|
||||
public static IMcpServerBuilder AddMcpServer(this IServiceCollection services, McpServerOptions serverOptions)
|
||||
{
|
||||
services.AddSingleton(serverOptions);
|
||||
services.AddHostedService<McpServerHostedService>();
|
||||
services.AddOptions();
|
||||
services.AddSingleton(services =>
|
||||
{
|
||||
IServerTransport serverTransport = services.GetRequiredService<IServerTransport>();
|
||||
McpServerOptions options = services.GetRequiredService<McpServerOptions>();
|
||||
IOptions<McpServerOptions> options = services.GetRequiredService<IOptions<McpServerOptions>>();
|
||||
ILoggerFactory? loggerFactory = services.GetService<ILoggerFactory>();
|
||||
|
||||
if (services.GetService<IOptions<McpServerHandlers>>() is { } handlersOptions)
|
||||
{
|
||||
options = handlersOptions.Value.OverwriteWithSetHandlers(options);
|
||||
}
|
||||
|
||||
return McpServerFactory.Create(serverTransport, options, loggerFactory, services);
|
||||
return McpServerFactory.Create(serverTransport, options.Value, loggerFactory, services);
|
||||
});
|
||||
|
||||
services.AddOptions();
|
||||
services.AddTransient<IConfigureOptions<McpServerOptions>, McpServerOptionsSetup>();
|
||||
if (configureOptions is not null)
|
||||
{
|
||||
services.Configure(configureOptions);
|
||||
}
|
||||
|
||||
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"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -235,10 +235,10 @@ internal class HttpListenerServerProvider : IDisposable
|
|||
response.StatusCode = 202;
|
||||
// Write "accepted" response
|
||||
// TODO: Use WriteAsync, add cancellation token and netstandard2.0 support (polyfill?)
|
||||
#pragma warning disable CA2016 // Forward the 'CancellationToken' parameter to methods
|
||||
byte[] buffer = Encoding.UTF8.GetBytes("Accepted");
|
||||
response.OutputStream.Write(buffer, 0, buffer.Length);
|
||||
#pragma warning restore CA2016 // Forward the 'CancellationToken' parameter to methods
|
||||
#pragma warning disable CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'
|
||||
await response.OutputStream.WriteAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
|
||||
#pragma warning restore CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync'
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using McpDotNet.Utils;
|
|||
using McpDotNet.Utils.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
#pragma warning disable CA2208 // Instantiate argument exceptions correctly
|
||||
#pragma warning disable CA2213 // Disposable fields should be disposed
|
||||
|
|
@ -47,6 +48,24 @@ public sealed class StdioServerTransport : TransportBase, IServerTransport
|
|||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StdioServerTransport"/> class, using
|
||||
/// <see cref="Console.In"/> and <see cref="Console.Out"/> for input and output streams.
|
||||
/// </summary>
|
||||
/// <param name="serverOptions">The server options.</param>
|
||||
/// <param name="loggerFactory">Optional logger factory used for logging employed by the transport.</param>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="serverOptions"/> is <see langword="null"/> or contains a null name.</exception>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// By default, no logging is performed. If a <paramref name="loggerFactory"/> is supplied, it must not log
|
||||
/// to <see cref="Console.Out"/>, as that will interfere with the transport's output.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public StdioServerTransport(IOptions<McpServerOptions> serverOptions, ILoggerFactory? loggerFactory = null)
|
||||
: this(GetServerName(serverOptions.Value), loggerFactory)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StdioServerTransport"/> class, using
|
||||
/// <see cref="Console.In"/> and <see cref="Console.Out"/> for input and output streams.
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ namespace McpDotNet.Server;
|
|||
/// <inheritdoc />
|
||||
internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
||||
{
|
||||
private readonly IServerTransport _serverTransport;
|
||||
private readonly IServerTransport? _serverTransport;
|
||||
private readonly McpServerOptions _options;
|
||||
private volatile bool _isInitializing;
|
||||
private readonly ILogger _logger;
|
||||
|
|
@ -26,12 +26,12 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
/// <param name="loggerFactory">Logger factory to use for logging</param>
|
||||
/// <param name="serviceProvider">Optional service provider to use for dependency injection</param>
|
||||
/// <exception cref="McpServerException"></exception>
|
||||
public McpServer(IServerTransport transport, McpServerOptions options, ILoggerFactory? loggerFactory, IServiceProvider? serviceProvider)
|
||||
public McpServer(ITransport transport, McpServerOptions options, ILoggerFactory? loggerFactory, IServiceProvider? serviceProvider)
|
||||
: base(transport, loggerFactory)
|
||||
{
|
||||
Throw.IfNull(options);
|
||||
|
||||
_serverTransport = transport;
|
||||
_serverTransport = transport as IServerTransport;
|
||||
_options = options;
|
||||
_logger = (ILogger?)loggerFactory?.CreateLogger<McpServer>() ?? NullLogger.Instance;
|
||||
ServerInstructions = options.ServerInstructions;
|
||||
|
|
@ -86,8 +86,11 @@ internal sealed class McpServer : McpJsonRpcEndpoint, IMcpServer
|
|||
{
|
||||
CancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
|
||||
// Start listening for messages
|
||||
await _serverTransport.StartListeningAsync(CancellationTokenSource.Token).ConfigureAwait(false);
|
||||
if (_serverTransport is not null)
|
||||
{
|
||||
// Start listening for messages
|
||||
await _serverTransport.StartListeningAsync(CancellationTokenSource.Token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Start processing messages
|
||||
MessageProcessingTask = ProcessMessagesAsync(CancellationTokenSource.Token);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ public static class McpServerFactory
|
|||
/// <exception cref="ArgumentNullException"><paramref name="serverTransport"/> is <see langword="null"/>.</exception>
|
||||
/// <exception cref="ArgumentNullException"><paramref name="serverOptions"/> is <see langword="null"/>.</exception>
|
||||
public static IMcpServer Create(
|
||||
IServerTransport serverTransport,
|
||||
ITransport serverTransport,
|
||||
McpServerOptions serverOptions,
|
||||
ILoggerFactory? loggerFactory = null,
|
||||
IServiceProvider? serviceProvider = null)
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ public sealed class McpServerHandlers
|
|||
/// </summary>
|
||||
/// <param name="options"></param>
|
||||
/// <returns></returns>
|
||||
internal McpServerOptions OverwriteWithSetHandlers(McpServerOptions options)
|
||||
internal void OverwriteWithSetHandlers(McpServerOptions options)
|
||||
{
|
||||
PromptsCapability? promptsCapability = options.Capabilities?.Prompts;
|
||||
if (ListPromptsHandler is not null || GetPromptHandler is not null)
|
||||
|
|
@ -118,22 +118,20 @@ public sealed class McpServerHandlers
|
|||
};
|
||||
}
|
||||
|
||||
return options with
|
||||
{
|
||||
GetCompletionHandler = GetCompletionHandler ?? options.GetCompletionHandler,
|
||||
Capabilities = options.Capabilities is null ?
|
||||
new()
|
||||
{
|
||||
Prompts = promptsCapability,
|
||||
Resources = resourcesCapability,
|
||||
Tools = toolsCapability,
|
||||
} :
|
||||
options.Capabilities with
|
||||
{
|
||||
Prompts = promptsCapability,
|
||||
Resources = resourcesCapability,
|
||||
Tools = toolsCapability,
|
||||
},
|
||||
};
|
||||
options.Capabilities = options.Capabilities is null ?
|
||||
new()
|
||||
{
|
||||
Prompts = promptsCapability,
|
||||
Resources = resourcesCapability,
|
||||
Tools = toolsCapability,
|
||||
} :
|
||||
options.Capabilities with
|
||||
{
|
||||
Prompts = promptsCapability,
|
||||
Resources = resourcesCapability,
|
||||
Tools = toolsCapability,
|
||||
};
|
||||
|
||||
options.GetCompletionHandler = GetCompletionHandler ?? options.GetCompletionHandler;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue