diff --git a/src/AAB.EBA.MCP/AAB.EBA.MCP.csproj b/src/AAB.EBA.MCP/AAB.EBA.MCP.csproj index efed1ccd..039bd802 100644 --- a/src/AAB.EBA.MCP/AAB.EBA.MCP.csproj +++ b/src/AAB.EBA.MCP/AAB.EBA.MCP.csproj @@ -1,4 +1,4 @@ - + Exe @@ -14,7 +14,8 @@ - + + diff --git a/src/AAB.EBA.MCP/Infrastructure/Startup.cs b/src/AAB.EBA.MCP/Infrastructure/Startup.cs index b98d57c9..c38911a7 100644 --- a/src/AAB.EBA.MCP/Infrastructure/Startup.cs +++ b/src/AAB.EBA.MCP/Infrastructure/Startup.cs @@ -5,19 +5,52 @@ using AAB.EBA.Graph.Db.Neo4jDb; using AAB.EBA.GraphDb; using AAB.EBA.MCP.Blockchains.Bitcoin; -using Microsoft.Extensions.Configuration; using Serilog; +using Serilog.Sinks.SystemConsole.Themes; namespace AAB.EBA.MCP.Infrastructure; public class Startup { - public static HostBuilder GetHostBuilder(Options options) + /// + /// Builds and configures a that exposes the MCP server + /// over HTTP using the Streamable-HTTP (SSE) transport. + /// Call app.Run() on the returned instance to start Kestrel. + /// + public static WebApplication GetWebApplication(string[] args, Options options) { - var hostBuilder = new HostBuilder(); + ConfigureSerilog(options); - var logFilename = options.Logger.LogFilename; + var builder = WebApplication.CreateBuilder(args); + builder.Host.UseSerilog(); + + builder.Configuration.Sources.Clear(); + builder.Configuration + .SetBasePath(builder.Environment.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true); + + builder.Configuration.GetSection(nameof(Options)).Bind(options); + + ConfigureCommonServices(builder.Services, options); + + builder.Services + .AddMcpServer() + .WithHttpTransport(options => { options.Stateless = true; }) + .WithToolsFromAssembly(); + + var app = builder.Build(); + + app.MapMcp(); + + app.MapGet("/healthz", () => Results.Ok(new { status = "healthy" })); + + return app; + } + + private static void ConfigureSerilog(Options options) + { Log.Logger = new LoggerConfiguration() .MinimumLevel.Information() @@ -26,55 +59,17 @@ public static HostBuilder GetHostBuilder(Options options) Serilog.Events.LogEventLevel.Warning) .Enrich.FromLogContext() .WriteTo.File( - path: logFilename, + path: options.Logger.LogFilename, rollingInterval: RollingInterval.Hour, outputTemplate: options.Logger.MessageTemplate, shared: true, retainedFileCountLimit: null) - /*.WriteTo.Console( - theme: AnsiConsoleTheme.Code)*/ + .WriteTo.Console( + theme: AnsiConsoleTheme.Code) .CreateLogger(); - hostBuilder.UseSerilog(); - - hostBuilder.ConfigureAppConfiguration( - (hostingContext, configuration) => - { - ConfigureApp(hostingContext, configuration, options); - }); - - hostBuilder.ConfigureServices( - services => - { - ConfigureServices(services, options); - }); - - return hostBuilder; } - private static void ConfigureApp( - HostBuilderContext context, - IConfigurationBuilder config, - Options options) - { - config.Sources.Clear(); - var env = context.HostingEnvironment; - - config - .SetBasePath(env.ContentRootPath) - .AddJsonFile( - $"appsettings.json", - optional: true, - reloadOnChange: true) - .AddJsonFile( - $"appsettings.{env.EnvironmentName}.json", - optional: true, - reloadOnChange: true); - - var configRoot = config.Build(); - configRoot.GetSection(nameof(Options)).Bind(options); - } - - private static void ConfigureServices(IServiceCollection services, Options options) + private static void ConfigureCommonServices(IServiceCollection services, Options options) { services.AddSingleton(options); services.AddSingleton(); @@ -82,12 +77,8 @@ private static void ConfigureServices(IServiceCollection services, Options optio services.AddSingleton(); // TODO: this is a hack. Need it to access strategy factory from the service - services.AddSingleton, BitcoinNeo4jDb>(); + services.AddSingleton, BitcoinNeo4jDb>(); services.AddHttpClient(); - - services.AddMcpServer() - .WithStdioServerTransport() - .WithToolsFromAssembly(); } } \ No newline at end of file diff --git a/src/AAB.EBA.MCP/Orchestrator.cs b/src/AAB.EBA.MCP/Orchestrator.cs index 1cc2382c..f0855e83 100644 --- a/src/AAB.EBA.MCP/Orchestrator.cs +++ b/src/AAB.EBA.MCP/Orchestrator.cs @@ -19,21 +19,13 @@ public Orchestrator(CancellationToken cT) public async Task InvokeAsync(string[] args) { var options = new Options(); - var host = await SetupAndGetHostAsync(options); - await host.RunAsync(_cT); - - return 0; - } - - private async Task SetupAndGetHostAsync(Options options) - { Directory.CreateDirectory(options.WorkingDir); - var hostBuilder = Startup.GetHostBuilder(options); - var host = hostBuilder.Build(); - _logger = host.Services.GetRequiredService>(); + var app = Startup.GetWebApplication(args, options); + _logger = app.Services.GetRequiredService>(); + await app.RunAsync(_cT); - return host; + return 0; } public void Dispose() diff --git a/src/AAB.EBA.MCP/README.md b/src/AAB.EBA.MCP/README.md index efae9246..b5f446cc 100644 --- a/src/AAB.EBA.MCP/README.md +++ b/src/AAB.EBA.MCP/README.md @@ -1,3 +1,7 @@ -``` -npx @modelcontextprotocol/inspector dotnet AAB.EBA.MCP.dll +```shell +# in 1st terminal, start the mcp service: +dotnet .\src\AAB.EBA.MCP\bin\Debug\net10.0\AAB.EBA.MCP.dll + +# in 2nd terminal, run inspector: +npx @modelcontextprotocol/inspector http://localhost:5000 ``` \ No newline at end of file