using System.Collections.Generic; using System.Text.Json.Serialization; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Sozsoft.Mcp.Dtos; using Volo.Abp.AspNetCore.Mvc; namespace Sozsoft.Mcp.Controllers; /// /// Uzak MCP istemcilerinin baglanti kesfi. Istemci /mcp'den 401 alir, WWW-Authenticate /// basligindaki meta veri adresinden yetkilendirme sunucusunu ogrenir, kayit ucundan istemci /// kimligini alir ve kullaniciyi platformun giris ekranina yonlendirir. /// Adresler ve yanit bicimi RFC'lerle sabittir; bu yuzden ABP'nin konvansiyonel uclari yerine /// acik rotali bir controller kullanilir. /// [AllowAnonymous] [IgnoreAntiforgeryToken] [ApiExplorerSettings(IgnoreApi = true)] public class McpOAuthController( IOptions options, IMcpOAuthAppService oAuthAppService) : AbpControllerBase { /// Korumali kaynak meta verisi (RFC 9728). [HttpGet(McpConsts.ProtectedResourceMetadataPath)] [HttpGet(McpConsts.ProtectedResourceMetadataPath + McpConsts.EndpointPath)] public IActionResult GetProtectedResourceMetadata() { var value = options.Value; return new JsonResult(new ProtectedResourceMetadata( value.ResourceUrl, [value.AuthorizationServer], McpConsts.OAuthScopes, ["header"], McpServerInfoDefaults.DisplayName)); } /// Dinamik istemci kaydi (RFC 7591). [HttpPost(McpConsts.RegistrationEndpointPath)] public async Task RegisterAsync([FromBody] ClientRegistrationRequest request) { var result = await oAuthAppService.RegisterClientAsync(new McpClientRegistrationRequestDto { ClientName = request.ClientName, RedirectUris = request.RedirectUris ?? [] }); if (result.Error is not null) { return new JsonResult(new ClientRegistrationError(result.Error, result.ErrorDescription)) { StatusCode = StatusCodes.Status400BadRequest }; } return new JsonResult(new ClientRegistrationResponse( result.ClientId!, result.ClientIdIssuedAt, result.ClientName, result.RedirectUris, result.GrantTypes, result.ResponseTypes, result.TokenEndpointAuthMethod, result.Scope)) { StatusCode = StatusCodes.Status201Created }; } // Protokol sozlesmeleri snake_case alan adlariyla sabittir; ABP'nin camelCase JSON // politikasina birakilmaz. public sealed record ClientRegistrationRequest( [property: JsonPropertyName("client_name")] string? ClientName, [property: JsonPropertyName("redirect_uris")] List? RedirectUris); private sealed record ClientRegistrationResponse( [property: JsonPropertyName("client_id")] string ClientId, [property: JsonPropertyName("client_id_issued_at")] long ClientIdIssuedAt, [property: JsonPropertyName("client_name")] string? ClientName, [property: JsonPropertyName("redirect_uris")] IReadOnlyList RedirectUris, [property: JsonPropertyName("grant_types")] IReadOnlyList GrantTypes, [property: JsonPropertyName("response_types")] IReadOnlyList ResponseTypes, [property: JsonPropertyName("token_endpoint_auth_method")] string TokenEndpointAuthMethod, [property: JsonPropertyName("scope")] string? Scope); private sealed record ClientRegistrationError( [property: JsonPropertyName("error")] string Error, [property: JsonPropertyName("error_description")] string? ErrorDescription); private sealed record ProtectedResourceMetadata( [property: JsonPropertyName("resource")] string Resource, [property: JsonPropertyName("authorization_servers")] IReadOnlyList AuthorizationServers, [property: JsonPropertyName("scopes_supported")] IReadOnlyList ScopesSupported, [property: JsonPropertyName("bearer_methods_supported")] IReadOnlyList BearerMethodsSupported, [property: JsonPropertyName("resource_name")] string ResourceName); }