diff --git a/src/HotChocolate/Adapters/src/Adapters.Mcp.Core/Extensions/EndpointRouteBuilderExtensions.cs b/src/HotChocolate/Adapters/src/Adapters.Mcp.Core/Extensions/EndpointRouteBuilderExtensions.cs index 5a5b130714b..e7d7e4d9dca 100644 --- a/src/HotChocolate/Adapters/src/Adapters.Mcp.Core/Extensions/EndpointRouteBuilderExtensions.cs +++ b/src/HotChocolate/Adapters/src/Adapters.Mcp.Core/Extensions/EndpointRouteBuilderExtensions.cs @@ -1,5 +1,6 @@ using System.Diagnostics.CodeAnalysis; using HotChocolate.Adapters.Mcp.Proxies; +using HotChocolate.Execution; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Metadata; @@ -16,6 +17,7 @@ public static IEndpointConventionBuilder MapGraphQLMcp( [StringSyntax("Route")] string pattern = "/graphql/mcp", string? schemaName = null) { + TryResolveSchemaName(endpoints.ServiceProvider, ref schemaName); schemaName ??= ISchemaDefinition.DefaultName; var streamableHttpHandler = @@ -64,4 +66,14 @@ public static IEndpointConventionBuilder MapGraphQLMcp( return mcpGroup; } + + private static void TryResolveSchemaName(IServiceProvider services, ref string? schemaName) + { + if (schemaName is null + && services.GetService() is { } provider + && provider.SchemaNames.Length == 1) + { + schemaName = provider.SchemaNames[0]; + } + } } diff --git a/src/HotChocolate/Adapters/src/Adapters.OpenApi.AspNetCore/Extensions/EndpointRouteBuilderExtensions.cs b/src/HotChocolate/Adapters/src/Adapters.OpenApi.AspNetCore/Extensions/EndpointRouteBuilderExtensions.cs index 5810f764281..84f0fd9d0d4 100644 --- a/src/HotChocolate/Adapters/src/Adapters.OpenApi.AspNetCore/Extensions/EndpointRouteBuilderExtensions.cs +++ b/src/HotChocolate/Adapters/src/Adapters.OpenApi.AspNetCore/Extensions/EndpointRouteBuilderExtensions.cs @@ -1,3 +1,4 @@ +using HotChocolate.Execution; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; @@ -9,6 +10,7 @@ public static IEndpointRouteBuilder MapOpenApiEndpoints( this IEndpointRouteBuilder endpoints, string? schemaName = null) { + TryResolveSchemaName(endpoints.ServiceProvider, ref schemaName); schemaName ??= ISchemaDefinition.DefaultName; var dataSource = endpoints.ServiceProvider.GetRequiredKeyedService(schemaName); @@ -20,4 +22,14 @@ public static IEndpointRouteBuilder MapOpenApiEndpoints( return endpoints; } + + private static void TryResolveSchemaName(IServiceProvider services, ref string? schemaName) + { + if (schemaName is null + && services.GetService() is { } provider + && provider.SchemaNames.Length == 1) + { + schemaName = provider.SchemaNames[0]; + } + } } diff --git a/src/HotChocolate/Adapters/src/Adapters.OpenApi.AspNetCore/Extensions/OpenApiOptionsExtensions.cs b/src/HotChocolate/Adapters/src/Adapters.OpenApi.AspNetCore/Extensions/OpenApiOptionsExtensions.cs index 448bd6cbe43..c2e3ee6eb05 100644 --- a/src/HotChocolate/Adapters/src/Adapters.OpenApi.AspNetCore/Extensions/OpenApiOptionsExtensions.cs +++ b/src/HotChocolate/Adapters/src/Adapters.OpenApi.AspNetCore/Extensions/OpenApiOptionsExtensions.cs @@ -1,3 +1,4 @@ +using HotChocolate.Execution; using Microsoft.AspNetCore.OpenApi; using Microsoft.Extensions.DependencyInjection; @@ -7,14 +8,26 @@ public static class OpenApiOptionsExtensions { public static OpenApiOptions AddGraphQLTransformer(this OpenApiOptions options, string? schemaName = null) { - schemaName ??= ISchemaDefinition.DefaultName; - return options.AddDocumentTransformer((document, context, ct) => { + var resolvedSchemaName = schemaName; + TryResolveSchemaName(context.ApplicationServices, ref resolvedSchemaName); + resolvedSchemaName ??= ISchemaDefinition.DefaultName; + var transformer = context.ApplicationServices - .GetRequiredKeyedService(schemaName); + .GetRequiredKeyedService(resolvedSchemaName); return transformer.TransformAsync(document, context, ct); }); } + + private static void TryResolveSchemaName(IServiceProvider services, ref string? schemaName) + { + if (schemaName is null + && services.GetService() is { } provider + && provider.SchemaNames.Length == 1) + { + schemaName = provider.SchemaNames[0]; + } + } } diff --git a/src/HotChocolate/Adapters/test/Adapters.Mcp.Tests/CoreIntegrationTests.cs b/src/HotChocolate/Adapters/test/Adapters.Mcp.Tests/CoreIntegrationTests.cs index db1d07838c6..d2fe8b996a1 100644 --- a/src/HotChocolate/Adapters/test/Adapters.Mcp.Tests/CoreIntegrationTests.cs +++ b/src/HotChocolate/Adapters/test/Adapters.Mcp.Tests/CoreIntegrationTests.cs @@ -20,6 +20,42 @@ namespace HotChocolate.Adapters.Mcp; public sealed class CoreIntegrationTests : IntegrationTestBase { + [Fact] + public async Task MapGraphQLMcp_Should_ResolveSchemaName_When_SingleNamedSchemaRegistered() + { + // arrange + var storage = new TestMcpStorage(); + await storage.AddOrUpdateToolAsync( + new OperationToolDefinition( + Utf8GraphQLParser.Parse("query GetBooks { books { title } }"))); + var builder = new WebHostBuilder() + .ConfigureServices( + services => services + .AddRouting() + .AddGraphQL("NamedSchema") + .AddAuthorization() + .AddQueryType() + .AddMutationType() + .AddInterfaceType() + .AddUnionType() + .AddObjectType() + .AddObjectType() + .AddMcp() + .AddMcpStorage(storage)) + .Configure( + app => app + .UseRouting() + .UseEndpoints(endpoints => endpoints.MapGraphQLMcp())); + var server = new TestServer(builder); + var mcpClient = await CreateMcpClientAsync(server.CreateClient()); + + // act + var tools = await mcpClient.ListToolsAsync(); + + // assert + Assert.Equal("get_books", Assert.Single(tools).Name); + } + [Fact] public async Task ListTools_AfterSchemaUpdate_ReturnsUpdatedTools() { diff --git a/src/HotChocolate/Adapters/test/Adapters.OpenApi.Tests/Endpoints/HttpEndpointIntegrationTests.cs b/src/HotChocolate/Adapters/test/Adapters.OpenApi.Tests/Endpoints/HttpEndpointIntegrationTests.cs index c3ff564234f..ffb83ef2c8b 100644 --- a/src/HotChocolate/Adapters/test/Adapters.OpenApi.Tests/Endpoints/HttpEndpointIntegrationTests.cs +++ b/src/HotChocolate/Adapters/test/Adapters.OpenApi.Tests/Endpoints/HttpEndpointIntegrationTests.cs @@ -1,4 +1,8 @@ +using System.Net; using System.Text; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; namespace HotChocolate.Adapters.OpenApi; @@ -20,6 +24,41 @@ protected override void ConfigureStorage( } } + [Fact] + public async Task MapOpenApiEndpoints_Should_ResolveSchemaName_When_SingleNamedSchemaRegistered() + { + // arrange + var storage = new TestOpenApiDefinitionStorage( + """ + query GetUsers @http(method: GET, route: "/users") { + usersWithoutAuth { + id + } + } + """); + var builder = new WebHostBuilder() + .ConfigureServices(services => + { + services.AddRouting(); + services.AddGraphQLServer("NamedSchema") + .AddOpenApiDefinitionStorage(storage) + .AddBasicServer(); + }) + .Configure(app => + { + app.UseRouting(); + app.UseEndpoints(endpoints => endpoints.MapOpenApiEndpoints()); + }); + var server = new TestServer(builder); + var client = server.CreateClient(); + + // act + var response = await client.GetAsync("/users"); + + // assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + [Fact] public async Task Http_Post_Body_Field_Has_Wrong_Type() { diff --git a/src/HotChocolate/Adapters/test/Adapters.OpenApi.Tests/OpenApi/OpenApiIntegrationTests.cs b/src/HotChocolate/Adapters/test/Adapters.OpenApi.Tests/OpenApi/OpenApiIntegrationTests.cs index 5a6502f5010..a41fea5eb06 100644 --- a/src/HotChocolate/Adapters/test/Adapters.OpenApi.Tests/OpenApi/OpenApiIntegrationTests.cs +++ b/src/HotChocolate/Adapters/test/Adapters.OpenApi.Tests/OpenApi/OpenApiIntegrationTests.cs @@ -1,3 +1,6 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; namespace HotChocolate.Adapters.OpenApi; @@ -13,4 +16,40 @@ protected override void ConfigureStorage( .AddOpenApiDefinitionStorage(storage) .AddBasicServer(); } + + [Fact] + public async Task AddGraphQLTransformer_Should_ResolveSchemaName_When_SingleNamedSchemaRegistered() + { + // arrange + var storage = new TestOpenApiDefinitionStorage( + """ + query GetUsers @http(method: GET, route: "/users") { + usersWithoutAuth { + id + } + } + """); + var builder = new WebHostBuilder() + .ConfigureServices(services => + { + services.AddRouting(); + services.AddGraphQLServer("NamedSchema") + .AddOpenApiDefinitionStorage(storage) + .AddBasicServer(); + services.AddOpenApi(options => options.AddGraphQLTransformer()); + }) + .Configure(app => + { + app.UseRouting(); + app.UseEndpoints(endpoints => endpoints.MapOpenApi()); + }); + using var server = new TestServer(builder); + var client = server.CreateClient(); + + // act + var document = await GetOpenApiDocumentAsync(client); + + // assert + Assert.Contains("/users", document); + } }