diff --git a/lazy52API.sln b/lazy52API.sln
new file mode 100644
index 0000000..08767b3
--- /dev/null
+++ b/lazy52API.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.13.35931.197 d17.13
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "lazy52API", "lazy52API\lazy52API.csproj", "{13E3BF0F-6CBC-465C-A566-FB635D87DCA9}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {13E3BF0F-6CBC-465C-A566-FB635D87DCA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {13E3BF0F-6CBC-465C-A566-FB635D87DCA9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {13E3BF0F-6CBC-465C-A566-FB635D87DCA9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {13E3BF0F-6CBC-465C-A566-FB635D87DCA9}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {0CA45D20-65DD-42F9-9F9D-382FA58B5226}
+ EndGlobalSection
+EndGlobal
diff --git a/lazy52API/McpEndpointRouteBuilderExtensions.cs b/lazy52API/McpEndpointRouteBuilderExtensions.cs
new file mode 100644
index 0000000..4170b21
--- /dev/null
+++ b/lazy52API/McpEndpointRouteBuilderExtensions.cs
@@ -0,0 +1,156 @@
+using Microsoft.AspNetCore.Http.Features;
+using Microsoft.AspNetCore.Routing.Patterns;
+using Microsoft.AspNetCore.WebUtilities;
+using Microsoft.Extensions.Options;
+using ModelContextProtocol.Protocol.Messages;
+using ModelContextProtocol.Protocol.Transport;
+using ModelContextProtocol.Server;
+using ModelContextProtocol.Utils.Json;
+using System.Collections.Concurrent;
+using System.Diagnostics.CodeAnalysis;
+using System.Security.Cryptography;
+
+namespace WebSSE
+{
+ ///
+ /// Extension methods for to add MCP endpoints.
+ /// https://github.com/modelcontextprotocol/csharp-sdk/tree/main
+ ///
+ public static class McpEndpointRouteBuilderExtensions
+ {
+ ///
+ /// Sets up endpoints for handling MCP HTTP Streaming transport.
+ ///
+ /// The web application to attach MCP HTTP endpoints.
+ /// The route pattern prefix to map to.
+ /// Configure per-session options.
+ /// Provides an optional asynchronous callback for handling new MCP sessions.
+ /// Returns a builder for configuring additional endpoint conventions like authorization policies.
+ public static IEndpointConventionBuilder MapMcp(
+ this IEndpointRouteBuilder endpoints,
+ [StringSyntax("Route")] string pattern = "",
+ Func? configureOptionsAsync = null,
+ Func? runSessionAsync = null)
+ => endpoints.MapMcp(RoutePatternFactory.Parse(pattern), configureOptionsAsync, runSessionAsync);
+
+ ///
+ /// Sets up endpoints for handling MCP HTTP Streaming transport.
+ ///
+ /// The web application to attach MCP HTTP endpoints.
+ /// The route pattern prefix to map to.
+ /// Configure per-session options.
+ /// Provides an optional asynchronous callback for handling new MCP sessions.
+ /// Returns a builder for configuring additional endpoint conventions like authorization policies.
+ public static IEndpointConventionBuilder MapMcp(this IEndpointRouteBuilder endpoints,
+ RoutePattern pattern,
+ Func? configureOptionsAsync = null,
+ Func? runSessionAsync = null)
+ {
+ ConcurrentDictionary _sessions = new(StringComparer.Ordinal);
+
+ var loggerFactory = endpoints.ServiceProvider.GetRequiredService();
+ var optionsSnapshot = endpoints.ServiceProvider.GetRequiredService>();
+ var optionsFactory = endpoints.ServiceProvider.GetRequiredService>();
+ var hostApplicationLifetime = endpoints.ServiceProvider.GetRequiredService();
+
+ var routeGroup = endpoints.MapGroup(pattern);
+
+ routeGroup.MapGet("/sse", async context =>
+ {
+ // If the server is shutting down, we need to cancel all SSE connections immediately without waiting for HostOptions.ShutdownTimeout
+ // which defaults to 30 seconds.
+ using var sseCts = CancellationTokenSource.CreateLinkedTokenSource(context.RequestAborted, hostApplicationLifetime.ApplicationStopping);
+ var cancellationToken = sseCts.Token;
+
+ var response = context.Response;
+ response.Headers.ContentType = "text/event-stream";
+ response.Headers.CacheControl = "no-cache,no-store";
+
+ // Make sure we disable all response buffering for SSE
+ context.Response.Headers.ContentEncoding = "identity";
+ context.Features.GetRequiredFeature().DisableBuffering();
+
+ var sessionId = MakeNewSessionId();
+ await using var transport = new SseResponseStreamTransport(response.Body, $"/message?sessionId={sessionId}");
+ if (!_sessions.TryAdd(sessionId, transport))
+ {
+ throw new Exception($"Unreachable given good entropy! Session with ID '{sessionId}' has already been created.");
+ }
+
+ var options = optionsSnapshot.Value;
+ if (configureOptionsAsync is not null)
+ {
+ options = optionsFactory.Create(Options.DefaultName);
+ await configureOptionsAsync.Invoke(context, options, cancellationToken);
+ }
+
+ try
+ {
+ var transportTask = transport.RunAsync(cancellationToken);
+
+ try
+ {
+ await using var mcpServer = McpServerFactory.Create(transport, options, loggerFactory, endpoints.ServiceProvider);
+ context.Features.Set(mcpServer);
+
+ runSessionAsync ??= RunSession;
+ await runSessionAsync(context, mcpServer, cancellationToken);
+ }
+ finally
+ {
+ await transport.DisposeAsync();
+ await transportTask;
+ }
+ }
+ catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
+ {
+ // RequestAborted always triggers when the client disconnects before a complete response body is written,
+ // but this is how SSE connections are typically closed.
+ }
+ finally
+ {
+ _sessions.TryRemove(sessionId, out _);
+ }
+ });
+
+ routeGroup.MapPost("/message", async context =>
+ {
+ if (!context.Request.Query.TryGetValue("sessionId", out var sessionId))
+ {
+ await Results.BadRequest("Missing sessionId query parameter.").ExecuteAsync(context);
+ return;
+ }
+
+ if (!_sessions.TryGetValue(sessionId.ToString(), out var transport))
+ {
+ await Results.BadRequest($"Session ID not found.").ExecuteAsync(context);
+ return;
+ }
+
+ var message = (IJsonRpcMessage?)await context.Request.ReadFromJsonAsync(McpJsonUtilities.DefaultOptions.GetTypeInfo(typeof(IJsonRpcMessage)), 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;
+ }
+
+ private static Task RunSession(HttpContext httpContext, IMcpServer session, CancellationToken requestAborted)
+ => session.RunAsync(requestAborted);
+
+ private static string MakeNewSessionId()
+ {
+ // 128 bits
+ Span buffer = stackalloc byte[16];
+ RandomNumberGenerator.Fill(buffer);
+ return WebEncoders.Base64UrlEncode(buffer);
+ }
+ }
+}
diff --git a/lazy52API/Program.cs b/lazy52API/Program.cs
new file mode 100644
index 0000000..f9248a2
--- /dev/null
+++ b/lazy52API/Program.cs
@@ -0,0 +1,30 @@
+using WebSSE;
+
+var builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+
+builder.Services.AddControllers();
+// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen();
+builder.Services.AddMcpServer().WithToolsFromAssembly();
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+//if (app.Environment.IsDevelopment())
+{
+ app.UseSwagger();
+ app.UseSwaggerUI();
+}
+
+app.UseHttpsRedirection();
+
+app.UseAuthorization();
+
+app.MapControllers();
+//app.MapMcpSse();
+app.MapMcp();
+
+app.Run();
diff --git a/lazy52API/Properties/launchSettings.json b/lazy52API/Properties/launchSettings.json
new file mode 100644
index 0000000..78a1005
--- /dev/null
+++ b/lazy52API/Properties/launchSettings.json
@@ -0,0 +1,52 @@
+{
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true,
+ "applicationUrl": "http://localhost:5010"
+ },
+ "https": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true,
+ "applicationUrl": "https://localhost:7110;http://localhost:5010"
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "Container (.NET SDK)": {
+ "commandName": "SdkContainer",
+ "launchBrowser": true,
+ "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
+ "environmentVariables": {
+ "ASPNETCORE_HTTPS_PORTS": "8081",
+ "ASPNETCORE_HTTP_PORTS": "8080"
+ },
+ "publishAllPorts": true,
+ "useSSL": true
+ }
+ },
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:2486",
+ "sslPort": 44340
+ }
+ }
+}
\ No newline at end of file
diff --git a/lazy52API/Tool/doubao.cs b/lazy52API/Tool/doubao.cs
new file mode 100644
index 0000000..c4572b4
--- /dev/null
+++ b/lazy52API/Tool/doubao.cs
@@ -0,0 +1,53 @@
+using Flurl;
+using Flurl.Http;
+using ModelContextProtocol.Server;
+using System.ComponentModel;
+
+namespace lazy52API.Tool
+{
+ [McpServerToolType]
+ public static class Painting
+ {
+ ///
+ /// 调用豆包绘图接口
+ ///
+ ///
+ [McpServerTool(Name = "DouBaoPainting"), Description("调用豆包绘图接口;输出image_url数组(一般是四个)")]
+ public static string DouBao(
+ [Description("要生成图片的描述")] string description,
+ [Description("绘画风格")] string type,
+ [Description("绘画比例,可选9:16,2:3,3:4,4:3,1:1,3:2,16:9。不填默认16:9")] string ratio = "16:9"
+ )
+ {
+ Console.WriteLine($"接收到绘图任务:{description}");
+ var Painting = new
+ {
+ description,
+ type,
+ ratio
+ };
+ var filteredPainting = new System.Collections.Generic.Dictionary();
+ foreach (var prop in Painting.GetType().GetProperties())
+ {
+ var value = prop.GetValue(Painting);
+ if (value != null)
+ {
+ filteredPainting[prop.Name] = value;
+ }
+ }
+ var request = new FlurlRequest("https://npi.lazy52.com/api/doubao")
+ .WithTimeout(Timeout.InfiniteTimeSpan);
+ var response = request
+ .SetQueryParams(filteredPainting).GetAsync().GetAwaiter().GetResult();
+ if (response != null)
+ {
+ var responseString = response.GetStringAsync().GetAwaiter().GetResult();
+ //JToken rootToken = JToken.Parse(responseString);
+ //var resultPath = "$.image_url[*]";
+ //IEnumerable resultTokens = rootToken.SelectTokens(resultPath);
+ return responseString;
+ }
+ return "";
+ }
+ }
+}
diff --git a/lazy52API/appsettings.Development.json b/lazy52API/appsettings.Development.json
new file mode 100644
index 0000000..0c208ae
--- /dev/null
+++ b/lazy52API/appsettings.Development.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
diff --git a/lazy52API/appsettings.json b/lazy52API/appsettings.json
new file mode 100644
index 0000000..4009aa3
--- /dev/null
+++ b/lazy52API/appsettings.json
@@ -0,0 +1,16 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ },
+ "AllowedHosts": "*",
+ "Kestrel": {
+ "EndPoints": {
+ "Http": {
+ "Url": "http://*:9090" //
+ }
+ }
+ }
+}
diff --git a/lazy52API/lazy52API.csproj b/lazy52API/lazy52API.csproj
new file mode 100644
index 0000000..31dc20a
--- /dev/null
+++ b/lazy52API/lazy52API.csproj
@@ -0,0 +1,31 @@
+
+
+
+ net8.0
+ enable
+ enable
+ linux-x64
+ linux-x64
+ True
+ mcr.microsoft.com/dotnet/aspnet:8.0
+ e1ff1491-e3f4-4af6-822c-2ca2d8aeb211
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+