e-naxosMAF COURSE / COURS
English

MICROSOFT AGENT FRAMEWORK · 0.16.0-rc.1

Demo400 — Local MCP

Découvrir deux outils par stdio, comparer Found, NotFound et erreur, puis distinguer exécution directe et agent natif.

Objectifs

Préparer les deux projets

Ouvrir PowerShell à la racine de la distribution complète du dépôt canonique, avec SDK .NET 10.0.402 et paquets verrouillés. Conserver shared et les deux projets. Le lancement par défaut est déterministe et ne nécessite ni Ollama ni clé API. Le build de la solution compile aussi le serveur ; démarrer seulement le client ensuite.

dotnet build BaseTechnique.slnx -m:1 -c Release
Composition et chemins depuis la racine du dépôt
MécanismePortée
demos/Demo400_LocalMcp/Server/Demo400.Server.csprojServeur et fixtures SupportTools ; DLL ServeurMcpLocal.dll
demos/Demo400_LocalMcp/Client/Demo400.Client.csprojHôte et LocalMcpSession ; DLL ClientAgentMcp.dll

Lancer le client qui possède le serveur

dotnet run --project demos/Demo400_LocalMcp/Client/Demo400.Client.csproj -c Release --no-build

Il n’y a aucun port MCP à ouvrir. Le client lance la DLL serveur précompilée via stdio, imprime Discovered avec les JsonSchema, appelle INC-318, INC-999 puis list_priority_customers et termine avec Status=Completed, Mode=Protocol. stdout appartient au protocole dans le processus serveur ; le client imprime ses propres résultats JSON.

var builder = Host.CreateApplicationBuilder(args);
builder.Logging.ClearProviders();
builder.Logging.AddConsole(options => options.LogToStandardErrorThreshold = LogLevel.Trace);
builder.Services.AddMcpServer().WithStdioServerTransport().WithToolsFromAssembly();
await builder.Build().RunAsync();

demos/Demo400_LocalMcp/Server/Program.cs · L6–L10 · 9a895c94534c

Code source · Demo400 · Dossier

var transport = new StdioClientTransport(new StdioClientTransportOptions
{
    Name = "LocalSupport", Command = "dotnet", Arguments = [serverPath],
    WorkingDirectory = Path.GetDirectoryName(serverPath)
});
McpClient? client = null;
try
{
    client = await McpClient.CreateAsync(transport, cancellationToken: deadline.Token);
    var tools = await client.ListToolsAsync(cancellationToken: deadline.Token);
    return new(client, tools, timeout);

demos/Demo400_LocalMcp/Client/LocalMcpSession.cs · L39–L49 · 9a895c94534c

Code source · Demo400 · Dossier

[McpServerTool(Name = "get_incident_status", ReadOnly = true)]
[Description("Gets an incident such as INC-318. Returns Found or NotFound; never invents incidents.")]
public static IncidentLookup GetIncidentStatus(
    [Description("Incident ID matching INC- followed by three digits.")] string incidentId)
{
    if (incidentId is null || !IncidentIdPattern().IsMatch(incidentId))
        throw new McpException("Invalid incident ID. Expected INC- followed by three digits.");
    var incident = Incidents.FirstOrDefault(i => i.Id.Equals(incidentId, StringComparison.OrdinalIgnoreCase));
    return new(incident is null ? "NotFound" : "Found", incident);

demos/Demo400_LocalMcp/Server/SupportTools.cs · L28–L36 · 9a895c94534c

Code source · Demo400 · Dossier

Comparer le mode agent et un serveur manquant

dotnet run --project demos/Demo400_LocalMcp/Client/Demo400.Client.csproj -c Release --no-build -- --live

--live charge la configuration partagée du fournisseur de chat, Ollama granite4:3b par défaut ; API uniquement par sélection explicite. Vérifier Calls, les succès et les trois recherches attendues avant de commenter Text. Les tests AssistantClaimWithoutToolUseIsIncomplete et SingleLookupCannotClaimWholeTaskComplete démontrent pourquoi la phrase finale ne suffit pas.

var complete = calls.All(c => c.Succeeded)
    && calls.Any(c => c.Tool == "list_priority_customers")
    && calls.Any(c => c.Tool == "get_incident_status" && c.IncidentId == "INC-318")
    && calls.Any(c => c.Tool == "get_incident_status" && c.IncidentId == "INC-999");
return new(complete ? "Completed" : "Incomplete", response.Text, calls.ToArray());

demos/Demo400_LocalMcp/Client/LocalMcpSession.cs · L132–L136 · 9a895c94534c

Code source · Demo400 · Dossier

dotnet run --project demos/Demo400_LocalMcp/Client/Demo400.Client.csproj -c Release --no-build -- --server (Join-Path $env:TEMP "maf-missing-server.dll")

Avec un chemin effectivement absent, attendre Failed / FileNotFoundException et sortie 2, sans lancement de serveur. Pour un autre binaire compilé, fournir --server avec son chemin absolu entre guillemets. Ne pas lancer un ancien script Verify-Lot011.ps1 depuis une commande publiée : il est archivé, les tests actuels sont distribués.

Observer puis redécouvrir dans une copie

dotnet test tests/LocalMcp.Tests/LocalMcp.Tests.csproj -m:1 -c Release --no-build --no-restore

Contrôler le paramètre incidentId obligatoire, Found/InProgress/Critical pour INC-318, NotFound pour INC-999 et trois clients. Les tests couvrent les entrées invalides et manquantes, le refus delete_incident, les délais, l’annulation et la fin du processus après DisposeAsync. Ils utilisent un vrai serveur enfant et des modèles scriptés quand un modèle est nécessaire.

Manipulation : dans une copie du dépôt, ajouter un outil de lecture, reconstruire la solution puis relancer. Relever le nouvel outil dans Discovered. Vérifier ensuite qu’il n’est pas offert à l’agent sans mise à jour volontaire de AllowedTools. Comparer découverte, sélection et autorisation à l’exécution. Les archives et le code canonique restent la référence ; faire cet essai dans une copie.

Observation du 24 octobre 2026 : premier et dernier enregistrements JSON d’un lancement réel du client précompilé depuis un autre dossier. Les résultats intermédiaires ont aussi été contrôlés. Aucun modèle appelé.

Sortie observée · 2026-10-24 · lot011-protocol
{"Discovered":[{"Name":"list_priority_customers","JsonSchema":{"type":"object","properties":{}}},{"Name":"get_incident_status","JsonSchema":{"type":"object","properties":{"incidentId":{"description":"Incident ID matching INC- followed by three digits.","type":"string"}},"required":["incidentId"]}}]}
{"Status":"Completed","Mode":"Protocol"}

Interpréter la preuve

Cette démo ne modifie aucune donnée métier. La politique hôte est locale et les fixtures ne prouvent pas un accès au SI. Le protocole direct est vérifiable sans modèle ; une campagne --live est distincte et ne vaut pas qualification générale du fournisseur. Aucun serveur MCP distant, aucune authentification ou isolation système n’est fourni.

Lire les composants

public async Task<CallToolResult> CallAsync(string name, Dictionary<string, object?> arguments, CancellationToken token = default)
{
    if (!AllowedTools.Contains(name, StringComparer.Ordinal)) throw new UnauthorizedAccessException("Tool is not allowed by this host.");
    using var deadline = CancellationTokenSource.CreateLinkedTokenSource(token);
    deadline.CancelAfter(requestTimeout);
    return await client.CallToolAsync(name, arguments, cancellationToken: deadline.Token);

demos/Demo400_LocalMcp/Client/LocalMcpSession.cs · L59–L64 · 9a895c94534c

Code source · Demo400 · Dossier

Prérequis

Pour poursuivre

Dossier complet de la démo · README

lot011-interfaces-1 · 2026-10-24 · 9a895c94534cd5fc6f3da883e37e5f7856e588c1