100 lines
4.8 KiB
C#
100 lines
4.8 KiB
C#
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 }] };
|
||
}
|
||
});
|
||
});
|
||
}
|
||
}
|