sozsoft-platform/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpHttpApiModule.cs

104 lines
4.9 KiB
C#
Raw Permalink Normal View History

using System;
using System.Linq;
2026-09-17 09:01:14 +00:00
using Microsoft.Extensions.DependencyInjection;
2026-10-09 17:02:27 +00:00
using ModelContextProtocol;
2026-09-17 09:01:14 +00:00
using ModelContextProtocol.AspNetCore;
2026-10-09 17:02:27 +00:00
using ModelContextProtocol.Protocol;
2026-10-17 22:31:09 +00:00
using ModelContextProtocol.Server;
2026-09-17 09:01:14 +00:00
using Sozsoft.Mcp.Tools;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Modularity;
namespace Sozsoft.Mcp;
/// <summary>
/// /mcp ucunun sunum katmani. Araclar yalnizca uygulama sozlesmesini tanir; kimlik dogrulama,
/// kiraci cozumu, unit of work ve yetki kontrolu istegin gectigi ABP ardisik duzeninden gelir.
/// Ucun kendisi host tarafinda <c>MapMcp</c> ile baglanir.
/// </summary>
[DependsOn(
typeof(McpApplicationContractsModule),
typeof(AbpAspNetCoreMvcModule)
)]
public class McpHttpApiModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
var configuration = context.Services.GetConfiguration();
Configure<McpOAuthOptions>(options =>
{
options.BaseUrl = (configuration["App:SelfUrl"] ?? string.Empty).TrimEnd('/');
// OpenIddict keşif belgesindeki issuer'ı Uri.AbsoluteUri ile (sonda eğik çizgiyle) yazar;
// RFC 8414 §3.3 gereği istemci iki değeri birebir karşılaştırır.
var authority = configuration["AuthServer:Authority"];
options.AuthorizationServer = string.IsNullOrWhiteSpace(authority)
? string.Empty
: new Uri(authority).AbsoluteUri;
2026-09-17 09:01:14 +00:00
});
context.Services
.AddMcpServer(options =>
{
options.ServerInfo = new() { Name = McpServerInfoDefaults.Name, Version = McpServerInfoDefaults.Version };
options.ServerInstructions = McpServerInfoDefaults.Instructions;
})
.WithHttpTransport(options =>
{
// Durumsuz kip: her istek kendi ABP kapsaminda kosar, boylece kimlik, kiraci
// cozumu ve unit of work istekle birlikte dogru kurulur. Durumlu kipte oturum
// kapsami initialize istegine baglanir ve sonraki cagrilar yanlis kiracinin
// baglaminda kalabilir; ayrica ters vekil arkasinda oturum yapisakligi gerekmez.
options.SessionMode = HttpServerSessionMode.Stateless;
})
.WithTools<McpSchemaTools>()
.WithTools<McpQueryTools>()
.WithTools<McpDataTools>()
2026-09-18 13:47:23 +00:00
.WithTools<McpDevelopTools>()
.WithTools<McpReportTools>()
.WithPrompts(typeof(McpDevelopPrompts))
.WithRequestFilters(filters =>
2026-09-17 09:01:14 +00:00
{
filters.AddListToolsFilter(next => async (requestContext, cancellationToken) =>
{
var result = await next(requestContext, cancellationToken);
2026-09-17 09:01:14 +00:00
// Arac listesi kiracinin sunucu kaydina ve kullanicinin uygulamadaki yetkilerine
// gore suzulur. Cagri aninda da ayni kural uygulanir
// (McpAppService.EnsureToolAllowedAsync): istemci listede olmayan bir araci da
// cagirabilir, suzme tek basina bir yetki siniri degildir.
var schemaAppService = requestContext.Services!.GetRequiredService<IMcpSchemaAppService>();
try
{
var available = (await schemaAppService.GetAvailableToolsAsync()).ToHashSet(System.StringComparer.Ordinal);
result.Tools = [.. result.Tools.Where(tool => available.Contains(tool.Name))];
}
catch (System.Exception ex) when (McpToolErrorTranslator.CanTranslate(ex))
{
2026-10-09 17:02:27 +00:00
throw new McpException(await requestContext.Services!.GetRequiredService<McpToolErrorTranslator>().TranslateAsync(ex, null), ex);
}
2026-09-17 09:01:14 +00:00
return result;
});
2026-09-17 09:01:14 +00:00
2026-10-10 22:03:36 +00:00
// MCP'nin kendi reddi istisna degildir (McpProblem). Arkadaki platform servisinin yetki
// reddi ve is kurali hatasi ise istisnadir; firlatilirsa SDK onu yigin izli ERR olarak
// loglar. Hata sonucu (IsError) olarak donulur; model nedeni okuyup devam eder.
filters.AddCallToolFilter(next => async (requestContext, cancellationToken) =>
{
2026-10-17 22:31:09 +00:00
McpToolArgumentNormalizer.Normalize(requestContext.Params, requestContext.MatchedPrimitive as McpServerTool);
try
{
return await next(requestContext, cancellationToken);
}
catch (System.Exception ex) when (McpToolErrorTranslator.CanTranslate(ex))
{
2026-10-09 17:02:27 +00:00
var message = await requestContext.Services!.GetRequiredService<McpToolErrorTranslator>().TranslateAsync(ex, requestContext.Params?.Name);
2026-10-10 22:03:36 +00:00
return McpToolResult.Error(message);
}
});
});
2026-09-17 09:01:14 +00:00
}
}