sozsoft-platform/api/modules/Sozsoft.Mcp/Sozsoft.Mcp.HttpApi/McpHttpApiModule.cs
2026-10-09 20:02:27 +03:00

100 lines
4.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
using System.Linq;
using Microsoft.Extensions.DependencyInjection;
using ModelContextProtocol;
using ModelContextProtocol.AspNetCore;
using ModelContextProtocol.Protocol;
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;
});
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>()
.WithTools<McpDevelopTools>()
.WithTools<McpReportTools>()
.WithPrompts(typeof(McpDevelopPrompts))
.WithRequestFilters(filters =>
{
filters.AddListToolsFilter(next => async (requestContext, cancellationToken) =>
{
var result = await next(requestContext, cancellationToken);
// 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))
{
throw new McpException(await requestContext.Services!.GetRequiredService<McpToolErrorTranslator>().TranslateAsync(ex, null), ex);
}
return result;
});
// Yetki reddi ve is kurali hatasi aracin beklenen sonucudur: firlatilirsa SDK onu
// yigin izli ERR olarak loglar. Hata sonucu (IsError) olarak donulur; model nedeni
// (eksik yetkiyi) okuyup devam eder.
filters.AddCallToolFilter(next => async (requestContext, cancellationToken) =>
{
try
{
return await next(requestContext, cancellationToken);
}
catch (System.Exception ex) when (McpToolErrorTranslator.CanTranslate(ex))
{
var message = await requestContext.Services!.GetRequiredService<McpToolErrorTranslator>().TranslateAsync(ex, requestContext.Params?.Name);
return new CallToolResult { IsError = true, Content = [new TextContentBlock { Text = message }] };
}
});
});
}
}