| File: Mcp\Tools\ListStructuredLogsTool.cs | Web Access |
| Project: src\src\Aspire.Cli\Aspire.Cli.csproj (aspire) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Net.Http.Json; using System.Text.Json; using Aspire.Cli.Backchannel; using Aspire.Cli.Commands; using Aspire.Dashboard.Otlp.Model; using Aspire.Otlp.Serialization; using Aspire.Dashboard.Utils; using Aspire.Shared.ConsoleLogs; using Microsoft.Extensions.Logging; using ModelContextProtocol; using ModelContextProtocol.Protocol; namespace Aspire.Cli.Mcp.Tools; /// <summary> /// MCP tool for listing structured logs. /// Gets log data directly from the Dashboard telemetry API. /// </summary> internal sealed class ListStructuredLogsTool(IAuxiliaryBackchannelMonitor auxiliaryBackchannelMonitor, IHttpClientFactory httpClientFactory, ILogger<ListStructuredLogsTool> logger) : CliMcpTool { public override string Name => KnownMcpTools.ListStructuredLogs; public override string Description => "List structured logs for resources."; public override JsonElement GetInputSchema() { return JsonDocument.Parse(""" { "type": "object", "properties": { "resourceName": { "type": "string", "description": "The resource name. This limits logs returned to the specified resource. If no resource name is specified then structured logs for all resources are returned." } } } """).RootElement; } public override async ValueTask<CallToolResult> CallToolAsync(CallToolContext context, CancellationToken cancellationToken) { var arguments = context.Arguments; var (apiToken, apiBaseUrl, dashboardBaseUrl) = await McpToolHelpers.GetDashboardInfoAsync(auxiliaryBackchannelMonitor, logger, cancellationToken).ConfigureAwait(false); // Extract resourceName from arguments string? resourceName = null; if (arguments?.TryGetValue("resourceName", out var resourceNameElement) == true && resourceNameElement.ValueKind == JsonValueKind.String) { resourceName = resourceNameElement.GetString(); } try { using var client = TelemetryCommandHelpers.CreateApiClient(httpClientFactory, apiToken); // Resolve resource name to specific instances (handles replicas) var resources = await TelemetryCommandHelpers.GetAllResourcesAsync(client, apiBaseUrl, cancellationToken).ConfigureAwait(false); // If a resource was specified but not found, return error if (!TelemetryCommandHelpers.TryResolveResourceNames(resourceName, resources, out var resolvedResources)) { return new CallToolResult { Content = [new TextContentBlock { Text = $"Resource '{resourceName}' not found." }], IsError = true }; } var url = DashboardUrls.TelemetryLogsApiUrl(apiBaseUrl, resolvedResources); logger.LogDebug("Fetching structured logs from {Url}", url); var response = await client.GetAsync(url, cancellationToken).ConfigureAwait(false); response.EnsureSuccessStatusCode(); var apiResponse = await response.Content.ReadFromJsonAsync(OtlpJsonSerializerContext.Default.TelemetryApiResponse, cancellationToken).ConfigureAwait(false); var resourceLogs = apiResponse?.Data?.ResourceLogs; var (logsData, limitMessage) = SharedAIHelpers.GetStructuredLogsJson( resourceLogs, getResourceName: s => OtlpHelpers.GetResourceName(s, resources.Select(r => new SimpleOtlpResource(r.Name, r.InstanceId)).ToList()), dashboardBaseUrl: dashboardBaseUrl); var text = $""" {limitMessage} # STRUCTURED LOGS DATA {logsData} """; return new CallToolResult { Content = [new TextContentBlock { Text = text }] }; } catch (HttpRequestException ex) { logger.LogError(ex, "Failed to fetch structured logs from Dashboard API"); throw new McpProtocolException($"Failed to fetch structured logs: {ex.Message}", McpErrorCode.InternalError); } } }