Skip to content

Commit 470643f

Browse files
[Diagnostics] Add GraphQL request spans to adapters (#9684)
1 parent d37fcea commit 470643f

28 files changed

Lines changed: 3843 additions & 69 deletions

File tree

src/HotChocolate/Adapters/src/Adapters.Mcp.Core/Handlers/CallToolHandler.cs

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Security.Claims;
55
using System.Text;
66
using System.Text.Json;
7+
using HotChocolate.AspNetCore.Instrumentation;
78
using HotChocolate.Buffers;
89
using HotChocolate.Execution;
910
using HotChocolate.Transport.Formatters;
@@ -61,6 +62,7 @@ public static async ValueTask<CallToolResult> HandleAsync(
6162
var requestExecutor = services.GetRequiredService<IRequestExecutor>();
6263
var rootServiceProvider = services.GetRequiredService<IRootServiceProviderAccessor>().ServiceProvider;
6364
var httpContext = rootServiceProvider.GetRequiredService<IHttpContextAccessor>().HttpContext!;
65+
var diagnosticEvents = requestExecutor.Schema.Services.GetRequiredService<IServerDiagnosticEvents>();
6466
var requestBuilder = CreateRequestBuilder(httpContext);
6567

6668
if (context.Params?.Arguments is { Count: > 0 } arguments)
@@ -69,24 +71,28 @@ public static async ValueTask<CallToolResult> HandleAsync(
6971
}
7072

7173
var request = requestBuilder.SetDocument(tool.DocumentNode).Build();
72-
var result = await requestExecutor.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
73-
var operationResult = result.ExpectOperationResult();
7474

75-
using var writer = new PooledArrayWriter();
75+
using (diagnosticEvents.ExecuteHttpRequest(httpContext, HttpRequestKind.HttpPost))
76+
{
77+
var result = await requestExecutor.ExecuteAsync(request, cancellationToken).ConfigureAwait(false);
78+
var operationResult = result.ExpectOperationResult();
7679

77-
JsonResultFormatter.Indented.Format(operationResult, writer);
78-
var jsonOperationResult = Encoding.UTF8.GetString(writer.WrittenSpan);
80+
using var writer = new PooledArrayWriter();
7981

80-
return new CallToolResult
81-
{
82-
// https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content
83-
// For backwards compatibility, a tool that returns structured content SHOULD
84-
// also return functionally equivalent unstructured content. (For example,
85-
// serialized JSON can be returned in a TextContent block.)
86-
Content = [new TextContentBlock { Text = jsonOperationResult }],
87-
StructuredContent = JsonElement.Parse(jsonOperationResult),
88-
IsError = !operationResult.Errors.IsEmpty
89-
};
82+
JsonResultFormatter.Indented.Format(operationResult, writer);
83+
var jsonOperationResult = Encoding.UTF8.GetString(writer.WrittenSpan);
84+
85+
return new CallToolResult
86+
{
87+
// https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content
88+
// For backwards compatibility, a tool that returns structured content SHOULD
89+
// also return functionally equivalent unstructured content. (For example,
90+
// serialized JSON can be returned in a TextContent block.)
91+
Content = [new TextContentBlock { Text = jsonOperationResult }],
92+
StructuredContent = JsonElement.Parse(jsonOperationResult),
93+
IsError = !operationResult.Errors.IsEmpty
94+
};
95+
}
9096
}
9197

9298
#if !NET9_0_OR_GREATER

src/HotChocolate/Adapters/src/Adapters.Mcp.Core/HotChocolate.Adapters.Mcp.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
<ItemGroup>
2121
<ProjectReference Include="..\Adapters.Mcp.Abstractions\HotChocolate.Adapters.Mcp.Abstractions.csproj" />
22+
<ProjectReference Include="..\..\..\AspNetCore\src\AspNetCore.Pipeline\HotChocolate.AspNetCore.Pipeline.csproj" />
2223
<ProjectReference Include="..\..\..\AspNetCore\src\Transport.Formatters\HotChocolate.Transport.Formatters.csproj" />
2324
<ProjectReference Include="..\..\..\Core\src\Execution.Abstractions\HotChocolate.Execution.Abstractions.csproj" />
2425
<ProjectReference Include="..\..\..\Core\src\Types.Abstractions\HotChocolate.Types.Abstractions.csproj" />

src/HotChocolate/Adapters/src/Adapters.OpenApi.Core/Execution/DynamicEndpointMiddleware.cs

Lines changed: 59 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using Microsoft.AspNetCore.Http;
66
using Microsoft.AspNetCore.Routing;
77
using Microsoft.Extensions.DependencyInjection;
8+
using HotChocolate.AspNetCore.Instrumentation;
89
using HotChocolate.Buffers;
910
using HotChocolate.Execution;
1011
using HotChocolate.Language;
@@ -61,75 +62,81 @@ await Results.Problem(
6162
.Get(schemaName)
6263
.ExecutorProxy;
6364
var session = await proxy.GetOrCreateSessionAsync(context.RequestAborted);
65+
var requestKind = HttpMethods.IsGet(context.Request.Method)
66+
? HttpRequestKind.HttpGet
67+
: HttpRequestKind.HttpPost;
6468

65-
using var variableBuffer = new PooledArrayWriter();
66-
using var variables = await BuildVariablesAsync(
67-
endpointDescriptor,
68-
context,
69-
variableBuffer,
70-
cancellationToken);
69+
using (session.DiagnosticEvents.ExecuteHttpRequest(context, requestKind))
70+
{
71+
using var variableBuffer = new PooledArrayWriter();
72+
using var variables = await BuildVariablesAsync(
73+
endpointDescriptor,
74+
context,
75+
variableBuffer,
76+
cancellationToken);
7177

72-
var requestBuilder = OperationRequestBuilder.New()
73-
.SetDocument(endpointDescriptor.Document)
74-
.SetErrorHandlingMode(ErrorHandlingMode.Propagate)
75-
.SetVariableValues(variables);
78+
var requestBuilder = OperationRequestBuilder.New()
79+
.SetDocument(endpointDescriptor.Document)
80+
.SetErrorHandlingMode(ErrorHandlingMode.Propagate)
81+
.SetVariableValues(variables);
7682

77-
await session.OnCreateAsync(context, requestBuilder, cancellationToken);
83+
await session.OnCreateAsync(context, requestBuilder, cancellationToken);
7884

79-
var executionResult = await session.ExecuteAsync(
80-
requestBuilder.Build(),
81-
cancellationToken).ConfigureAwait(false);
85+
var executionResult = await session.ExecuteAsync(
86+
requestBuilder.Build(),
87+
cancellationToken).ConfigureAwait(false);
8288

83-
// If the request was canceled, we do not attempt to write a response.
84-
if (cancellationToken.IsCancellationRequested)
85-
{
86-
return;
87-
}
89+
// If the request was canceled, we do not attempt to write a response.
90+
if (cancellationToken.IsCancellationRequested)
91+
{
92+
return;
93+
}
8894

89-
// If we do not have an operation result, something went wrong, and we return HTTP 500.
90-
if (executionResult is not OperationResult operationResult)
91-
{
95+
// If we do not have an operation result, something went wrong, and we return HTTP 500.
96+
if (executionResult is not OperationResult operationResult)
97+
{
9298
#if NET9_0_OR_GREATER
93-
await Results.InternalServerError().ExecuteAsync(context);
99+
await Results.InternalServerError().ExecuteAsync(context);
94100
#else
95-
await Results.StatusCode(500).ExecuteAsync(context);
101+
await Results.StatusCode(500).ExecuteAsync(context);
96102
#endif
97-
return;
98-
}
99-
100-
// If the request had validation errors or execution didn't start, we return HTTP 400.
101-
if (operationResult.ContextData.ContainsKey(ExecutionContextData.ValidationErrors)
102-
|| !operationResult.Data.HasValue)
103-
{
104-
var firstErrorMessage = operationResult.Errors.FirstOrDefault()?.Message;
103+
return;
104+
}
105105

106-
if (!string.IsNullOrEmpty(firstErrorMessage))
106+
// If the request had validation errors or execution didn't start, we return HTTP 400.
107+
if (operationResult.ContextData.ContainsKey(ExecutionContextData.ValidationErrors)
108+
|| !operationResult.Data.HasValue)
107109
{
108-
await Results.Problem(
109-
detail: firstErrorMessage,
110-
statusCode: StatusCodes.Status400BadRequest).ExecuteAsync(context);
110+
var firstErrorMessage = operationResult.Errors.FirstOrDefault()?.Message;
111+
112+
if (!string.IsNullOrEmpty(firstErrorMessage))
113+
{
114+
await Results.Problem(
115+
detail: firstErrorMessage,
116+
statusCode: StatusCodes.Status400BadRequest).ExecuteAsync(context);
117+
}
118+
else
119+
{
120+
await Results.BadRequest().ExecuteAsync(context);
121+
}
122+
123+
return;
111124
}
112-
else
125+
126+
// If execution started, and we produced GraphQL errors,
127+
// we return HTTP 500 or 401/403 for authorization errors.
128+
if (!operationResult.Errors.IsEmpty)
113129
{
114-
await Results.BadRequest().ExecuteAsync(context);
115-
}
130+
var result = GetResultFromErrors(operationResult.Errors);
116131

117-
return;
118-
}
132+
await result.ExecuteAsync(context);
133+
return;
134+
}
119135

120-
// If execution started, and we produced GraphQL errors,
121-
// we return HTTP 500 or 401/403 for authorization errors.
122-
if (!operationResult.Errors.IsEmpty)
123-
{
124-
var result = GetResultFromErrors(operationResult.Errors);
136+
var formatter = session.Schema.Services.GetRequiredService<IOpenApiResultFormatter>();
125137

126-
await result.ExecuteAsync(context);
127-
return;
138+
await formatter.FormatResultAsync(operationResult, context, endpointDescriptor, cancellationToken);
128139
}
129-
130-
var formatter = session.Schema.Services.GetRequiredService<IOpenApiResultFormatter>();
131-
132-
await formatter.FormatResultAsync(operationResult, context, endpointDescriptor, cancellationToken);
133140
}
134141
catch (BadRequestException badRequestException)
135142
{

src/HotChocolate/Adapters/test/Adapters.Mcp.Tests/CoreIntegrationTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ protected override Task<TestServer> CreateTestServerAsync(
180180

181181
var builder =
182182
services
183-
.AddGraphQL()
183+
.AddGraphQLServer()
184184
.AddAuthorization()
185185
.AddMcp(configureMcpServerOptions, configureMcpServer)
186186
.AddMcpStorage(storage)

src/HotChocolate/Diagnostics/test/Diagnostics.Tests/HotChocolate.Diagnostics.Tests.csproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,14 @@
1111
<ProjectReference Include="..\..\..\CostAnalysis\src\CostAnalysis\HotChocolate.CostAnalysis.csproj" />
1212
</ItemGroup>
1313

14+
<ItemGroup>
15+
<ProjectReference Include="..\..\..\Adapters\src\Adapters.Mcp\HotChocolate.Adapters.Mcp.csproj" />
16+
</ItemGroup>
17+
18+
<ItemGroup Condition="'$(TargetFramework)' != 'net8.0'">
19+
<ProjectReference Include="..\..\..\Adapters\src\Adapters.OpenApi\HotChocolate.Adapters.OpenApi.csproj" />
20+
</ItemGroup>
21+
1422
<ItemGroup>
1523
<PackageReference Include="OpenTelemetry" />
1624
<PackageReference Include="OpenTelemetry.Exporter.InMemory" />

0 commit comments

Comments
 (0)