Skip to content

Commit 291ac37

Browse files
author
a-panizza_globant
committed
- Extract Mcp code to a different source for incremental loading and code separation.
1 parent 26d7343 commit 291ac37

File tree

2 files changed

+101
-73
lines changed

2 files changed

+101
-73
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using System;
2+
using System.Linq;
3+
using System.Reflection;
4+
using System.Security.Policy;
5+
using ModelContextProtocol.AspNetCore;
6+
using GeneXus.Utils;
7+
using Microsoft.Extensions.DependencyInjection;
8+
using Microsoft.AspNetCore;
9+
using Microsoft.AspNetCore.Routing;
10+
using Microsoft.AspNetCore.Builder;
11+
12+
namespace GeneXus.Application
13+
{
14+
public class StartupMcp
15+
{
16+
static readonly IGXLogger log = GXLoggerFactory.GetLogger(typeof(StartupMcp).FullName);
17+
public static void AddService(IServiceCollection services)
18+
{
19+
Console.Out.WriteLine("Starting MCP Server...");
20+
var mcp = services.AddMcpServer(options =>
21+
{
22+
options.ServerInfo = new ModelContextProtocol.Protocol.Implementation
23+
{
24+
Name = "GxMcpServer",
25+
Version = Assembly.GetExecutingAssembly()
26+
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? "1.0.0"
27+
};
28+
})
29+
.WithHttpTransport(transportOptions =>
30+
{
31+
// SSE endpoints (/sse, /message) require STATEFUL sessions to support server-to-client push
32+
transportOptions.Stateless = false;
33+
transportOptions.IdleTimeout = TimeSpan.FromMinutes(5);
34+
GXLogging.Debug(log, "MCP HTTP Transport configured: Stateless=false (SSE enabled), IdleTimeout=5min");
35+
});
36+
37+
try
38+
{
39+
var mcpAssemblies = FileTools.MCPFileTools(Startup.LocalPath).ToList();
40+
foreach (var assembly in mcpAssemblies)
41+
{
42+
try
43+
{
44+
mcp.WithToolsFromAssembly(assembly);
45+
GXLogging.Debug(log, $"Successfully loaded MCP tools from assembly: {assembly.FullName}");
46+
}
47+
catch (Exception assemblyEx)
48+
{
49+
GXLogging.Error(log, $"Failed to load MCP tools from assembly: {assembly.FullName}", assemblyEx);
50+
}
51+
}
52+
}
53+
catch (Exception ex)
54+
{
55+
GXLogging.Error(log, "Error discovering MCP tool assemblies", ex);
56+
}
57+
}
58+
59+
public static void MapEndpoints(IEndpointRouteBuilder endpoints)
60+
{
61+
// Register MCP endpoints at root, exposing /sse and /message
62+
endpoints.MapMcp();
63+
GXLogging.Debug(log, "MCP Routing configured.");
64+
65+
}
66+
}
67+
}

dotnet/src/dotnetcore/GxNetCoreStartup/Startup.cs

Lines changed: 34 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
using Microsoft.Extensions.Logging;
4242
using Microsoft.Identity.Client;
4343

44-
using ModelContextProtocol.AspNetCore;
4544
using StackExchange.Redis;
4645

4746
namespace GeneXus.Application
@@ -74,7 +73,6 @@ public static void Main(string[] args)
7473

7574
}
7675

77-
7876
if (port == DEFAULT_PORT)
7977
{
8078
BuildWebHost(null).Run();
@@ -88,7 +86,7 @@ public static void Main(string[] args)
8886
{
8987
Console.Error.WriteLine("ERROR:");
9088
Console.Error.WriteLine("Web Host terminated unexpectedly: {0}", e.Message);
91-
}
89+
}
9290
}
9391

9492
public static IWebHost BuildWebHost(string[] args) =>
@@ -287,43 +285,7 @@ public void ConfigureServices(IServiceCollection services)
287285
}
288286
if (Startup.IsMcp)
289287
{
290-
var mcp = services.AddMcpServer(options =>
291-
{
292-
options.ServerInfo = new ModelContextProtocol.Protocol.Implementation
293-
{
294-
Name = "GxMcpServer",
295-
Version = Assembly.GetExecutingAssembly()
296-
.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? "1.0.0"
297-
};
298-
})
299-
.WithHttpTransport(transportOptions =>
300-
{
301-
// SSE endpoints (/sse, /message) require STATEFUL sessions to support server-to-client push
302-
transportOptions.Stateless = false;
303-
transportOptions.IdleTimeout = TimeSpan.FromSeconds(30);
304-
GXLogging.Debug(log, "MCP HTTP Transport configured: Stateless=false (SSE enabled), IdleTimeout=10min");
305-
});
306-
307-
try
308-
{
309-
var mcpAssemblies = FileTools.MCPFileTools(Startup.LocalPath).ToList();
310-
foreach (var assembly in mcpAssemblies)
311-
{
312-
try
313-
{
314-
mcp.WithToolsFromAssembly(assembly);
315-
GXLogging.Debug(log, $"Successfully loaded MCP tools from assembly: {assembly.FullName}");
316-
}
317-
catch (Exception assemblyEx)
318-
{
319-
GXLogging.Error(log, $"Failed to load MCP tools from assembly: {assembly.FullName}", assemblyEx);
320-
}
321-
}
322-
}
323-
catch (Exception ex)
324-
{
325-
GXLogging.Error(log, "Error discovering MCP tool assemblies", ex);
326-
}
288+
StartupMcp.AddService(services);
327289
}
328290

329291
services.AddDirectoryBrowser();
@@ -332,20 +294,20 @@ public void ConfigureServices(IServiceCollection services)
332294
services.AddResponseCompression(options =>
333295
{
334296
options.MimeTypes = new[]
335-
{
336-
// Default
337-
"text/plain",
338-
"text/css",
339-
"application/javascript",
340-
"text/html",
341-
"application/xml",
342-
"text/xml",
343-
"application/json",
344-
"text/json",
345-
// Custom
346-
"application/json",
347-
"application/pdf"
348-
};
297+
{
298+
// Default
299+
"text/plain",
300+
"text/css",
301+
"application/javascript",
302+
"text/html",
303+
"application/xml",
304+
"text/xml",
305+
"application/json",
306+
"text/json",
307+
// Custom
308+
"application/json",
309+
"application/pdf"
310+
};
349311
options.EnableForHttps = true;
350312
});
351313
}
@@ -423,9 +385,9 @@ private void RegisterRestServices(IMvcBuilder mvcBuilder)
423385
try
424386
{
425387
string[] controllerAssemblyQualifiedName = new string(File.ReadLines(svcFile).First().SkipWhile(c => c != '"')
426-
.Skip(1)
427-
.TakeWhile(c => c != '"')
428-
.ToArray()).Trim().Split(',');
388+
.Skip(1)
389+
.TakeWhile(c => c != '"')
390+
.ToArray()).Trim().Split(',');
429391
string controllerAssemblyName = controllerAssemblyQualifiedName.Last();
430392
if (!serviceAssemblies.Contains(controllerAssemblyName))
431393
{
@@ -486,17 +448,17 @@ private void DefineCorsPolicy(IServiceCollection services)
486448
services.AddCors(options =>
487449
{
488450
options.AddPolicy(name: CORS_POLICY_NAME,
489-
policy =>
490-
{
491-
policy.WithOrigins(origins);
492-
if (!corsAllowedOrigins.Contains(CORS_ANY_ORIGIN))
493-
{
494-
policy.AllowCredentials();
495-
}
496-
policy.AllowAnyHeader();
497-
policy.AllowAnyMethod();
498-
policy.SetPreflightMaxAge(TimeSpan.FromSeconds(CORS_MAX_AGE_SECONDS));
499-
});
451+
policy =>
452+
{
453+
policy.WithOrigins(origins);
454+
if (!corsAllowedOrigins.Contains(CORS_ANY_ORIGIN))
455+
{
456+
policy.AllowCredentials();
457+
}
458+
policy.AllowAnyHeader();
459+
policy.AllowAnyMethod();
460+
policy.SetPreflightMaxAge(TimeSpan.FromSeconds(CORS_MAX_AGE_SECONDS));
461+
});
500462
});
501463
}
502464
}
@@ -550,7 +512,6 @@ private void ConfigureSessionService(IServiceCollection services, ISessionServic
550512
}
551513
else
552514
{
553-
554515
services.AddDistributedSqlServerCache(options =>
555516
{
556517
GXLogging.Info(log, $"Using SQLServer for Distributed session, ConnectionString:{sessionService.ConnectionString}, SchemaName: {sessionService.Schema}, TableName: {sessionService.TableName}");
@@ -640,11 +601,11 @@ public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IHos
640601
Predicate = check => check.Tags.Contains("ready")
641602
});
642603
if (Startup.IsMcp)
643-
{
644-
// Register MCP endpoints at root, exposing /sse and /message
645-
endpoints.MapMcp();
604+
{
605+
StartupMcp.MapEndpoints(endpoints);
646606
}
647607
});
608+
648609
if (log.IsCriticalEnabled && env.IsDevelopment())
649610
{
650611
try
@@ -696,7 +657,7 @@ public void Configure(IApplicationBuilder app, Microsoft.AspNetCore.Hosting.IHos
696657
},
697658
ContentTypeProvider = provider
698659
});
699-
660+
700661
app.UseExceptionHandler(new ExceptionHandlerOptions
701662
{
702663
ExceptionHandler = new CustomExceptionHandlerMiddleware().Invoke,

0 commit comments

Comments
 (0)