Introduction
Découvrez comment il est possible d’auditer les performances et la stabilité d’une application Azure existante à l’aide du langage naturel. Grâce à cette automatisation, il deviendra possible de récupérer des données précises sur le comportement de vos charges en production, et de poser des questions permettant de savoir par exemple “Quels sont les endpoints les plus lents” ou encore “Quelles ont été les régressions suite à la dernière release”, et tout cela sans utiliser un langage de query complexe (par exemple KQL).
Que sont les serveurs MCP ?
Le Model Context Protocol (MCP) est un standard open‑source introduit par Anthropic le 25 novembre 2024. Il vise à unifier la communication entre agents d’IA (LLM clients) et sources de données/outil externes (MCP servers), sur une base de JSON‑RPC 2.0. On le compare souvent à un port USB‑C pour l’IA lorsqu’il s’agit de connecter de multiples systèmes de manière standardisée. L'avantage de ce standard est son universalité, du fait de son succès immédiat, de nombreux systèmes et langages de programmation ont intégrés le support de cette fonctionalité.
Configuration de l'environnement dotnet et n8n
Nous allons pour cela utiliser n8n pour orchestrer l’automatisation et implémenter l’agent IA, et un serveur MCP connecté à cet agent, implémenté en dotnet, ce qui permettra de communiquer avec Azure. Côté implémentation, nous aurons besoin des commandes suivantes pour créer un projet de serveur MCP et installer les dépendances nécessaires:
dotnet new mcpserver -n AzureInsightsMcpServer
cd AzureInsightsMcpServer
dotnet add package ModelContextProtocol --prerelease
dotnet add package Azure.Monitor.Query
dotnet add package Azure.Identity
Il faudra également se connecter à Azure dans le cadre de cette intégration avec les commandes suivantes:
az login # dev local
az account set --subscription <id>
Implémentation
L’implémentation commence par l’écriture du code (il est possible de le vibe-coder) correspondant au serveur en lui-même (Program.cs)
using Azure.Identity;
using Azure.Monitor.Query;
using Microsoft.Extensions.Hosting;
using ModelContextProtocol.Server;
var builder = Host.CreateApplicationBuilder(args);
// 1 singleton pour réutiliser les clients Azure Monitor
builder.Services.AddSingleton(sp =>
{
var cred = new DefaultAzureCredential();
return new MonitorClients(
new LogsQueryClient(cred),
new MetricsQueryClient(cred));
});
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithToolsFromAssembly(); // ← scanne [McpServerTool]
await builder.Build().RunAsync();
record MonitorClients(LogsQueryClient Logs, MetricsQueryClient Metrics);
Ce code enregistre les dépendances nécessaires (incluant des clients permettant de récupérer des informations sur Azure monitor) et permet de lancer le serveur.
Ensuite le but est d’implémenter plusieurs outils permettant d’analyser les performances, les tendances d’erreur, et d’avoir une vue globale sous forme de rapport:
using System.ComponentModel;
using Azure;
using Azure.Core;
using Azure.Identity;
using Azure.Monitor.Query;
using Azure.Monitor.Query.Models;
using ModelContextProtocol.Server;
using System.Text.Json;
[McpServerToolType]
public static class AzureInsightsTools
{
/* ------------------------------------------------------------------
* 1) RAPPORT DE PERFORMANCE (métriques)
* ------------------------------------------------------------------*/
[McpServerTool,
Description("Résume la latence et le taux de succès pour une Web App.")]
public static async Task<PerfSummary> GetPerfSummaryAsync(
[Description("ID complet de la ressource WebApp /subscriptions/.../sites/<name>")]
string resourceId,
[Description("Plage en heures"), DefaultValue(24)] int hours = 24,
[FromServices] MonitorClients? clients = null)
{
var range = TimeSpan.FromHours(hours);
var mqClient = clients?.Metrics ?? new MetricsQueryClient(new DefaultAzureCredential());
Response<MetricsQueryResult> resp =
await mqClient.QueryResourceAsync(
resourceId,
new[] { "AverageResponseTime", "Http2xx", "Http5xx" },
new MetricsQueryOptions
{
Aggregations =
{
MetricAggregationType.Average,
MetricAggregationType.Count
},
TimeRange = range,
Interval = TimeSpan.FromMinutes(5)
});
var result = resp.Value;
double avgLatencyMs = result.Metrics.First(m => m.Name == "AverageResponseTime")
.TimeSeries.SelectMany(ts => ts.Values)
.Where(v => v.Average.HasValue)
.Average(v => v.Average!.Value);
double requests = result.Metrics.First(m => m.Name == "Http2xx")
.TimeSeries.SelectMany(ts => ts.Values)
.Sum(v => v.Count ?? 0);
double failures = result.Metrics.First(m => m.Name == "Http5xx")
.TimeSeries.SelectMany(ts => ts.Values)
.Sum(v => v.Count ?? 0);
return new(avgLatencyMs, requests, failures);
}
/* ------------------------------------------------------------------
* 2) TENDANCE DES ERREURS (logs KQL)
* ------------------------------------------------------------------*/
[McpServerTool,
Description("Retourne les 10 erreurs les plus fréquentes sur la période.")]
public static async Task<ErrorTrend[]> GetErrorTrendsAsync(
[Description("ID de l’Application Insights (ressource)")] string appInsightsId,
[Description("Plage en heures"), DefaultValue(24)] int hours = 24,
[FromServices] MonitorClients? clients = null)
{
string kql = $@"
exceptions
| where timestamp >= ago({hours}h)
| summarize hits = count() by problemId, outerMessage
| top 10 by hits desc";
var lqClient = clients?.Logs ?? new LogsQueryClient(new DefaultAzureCredential());
Response<LogsQueryResult> res = await lqClient.QueryResourceAsync(
new ResourceIdentifier(appInsightsId),
kql,
new QueryTimeRange(TimeSpan.FromHours(hours)));
var table = res.Value.Table;
return table.Rows.Select(r => new ErrorTrend(
r["problemId"].ToString()!,
r["outerMessage"].ToString()!,
Convert.ToInt32(r["hits"])))
.ToArray();
}
/* ------------------------------------------------------------------
* 3) RAPPORT GLOBAL — fusionne métriques + erreurs
* ------------------------------------------------------------------*/
[McpServerTool, Description("Rapport JSON prêt pour un LLM")]
public static async Task<JsonDocument> BuildHealthReportAsync(
string resourceId, string appInsightsId, int hours = 24,
[FromServices] MonitorClients? clients = null)
{
var perf = await GetPerfSummaryAsync(resourceId, hours, clients);
var errors = await GetErrorTrendsAsync(appInsightsId, hours, clients);
using var stream = new MemoryStream();
await JsonSerializer.SerializeAsync(stream, new
{
GeneratedAtUtc = DateTime.UtcNow,
WindowHours = hours,
Performance = perf,
TopErrors = errors
}, new JsonSerializerOptions { WriteIndented = true });
stream.Position = 0;
return await JsonDocument.ParseAsync(stream);
}
public record PerfSummary(double AvgLatencyMs, double Requests, double Failures);
public record ErrorTrend(string ProblemId, string Message, int Hits);
}
Enfin il suffit d’appeler cette commande dans le répertoire du projet pour le lancer (il est aussi possible de créer un conteneur docker pour une utilisation facilitée):
dotnet run
Désormais, il devient possible d’utiliser cette partie de la solution dans un workflow d'automatisation n8n simple (attention à bien configurer l'url du serveur MCP dans le module "MCP client" avec le port utilisé par le serveur):
Et il devient possible de demander toute sorte de requête en langage naturel. Cette intégration peut bien-sûr être améliorée en récupérant d’autres données depuis d’autres outils Azure (par exemple sur les crashs applicatifs ou la sécurité) ou encore en ajoutant d’autres outils à l’automation n8n, afin d’avoir des données enrichies et un rapport le plus complet possible.
Conclusion
Avec l’essor du protocole MCP et sa disponibilité dans un nombre croissant d’outils, il devient très simple de connecter des outils ayant des finalités différentes (code, no-code) pour en faire un système unique, et ouvrir des possibilités jusqu’alors impossibles. Si vous êtes intéressés par ce sujets et en savoir plus, ou bien que vous souhaitez avoir ce système implémenté dans votre entreprise, n’hésitez pas à nous contacter pour en discuter.