MCP sicuro in Copilot Studio: quale identità sta davvero eseguendo le azioni?
Gli agenti di intelligenza artificiale hanno smesso di limitarsi a rispondere alle domande. Possono interrogare un database, leggere documenti, aprire ticket, modificare record, eseguire chiamate API e avviare processi aziendali. Con l’introduzione del Model Context Protocol in Copilot Studio, collegare queste capacità a un agente è diventato molto più semplice: un server MCP può pubblicare strumenti e risorse che vengono rilevati dalla piattaforma e resi disponibili all’orchestratore.
La facilità dell’integrazione, però, nasconde una domanda che dovrebbe precedere qualsiasi configurazione: Quale identità vede realmente la risorsa quando l’agente esegue un’azione? Non è una distinzione accademica.
Un agente che legge una pratica con i privilegi dell’utente che ha formulato la richiesta ha un determinato perimetro. Lo stesso agente, se utilizza un’identità applicativa con accesso a tutte le pratiche dell’organizzazione, ne ha uno completamente diverso.
In entrambi i casi, dalla chat potrebbe apparire la stessa risposta. La differenza emerge soltanto osservando i token, i claim, i log di Microsoft Entra e i controlli applicati dal resource server.
Microsoft Copilot Studio consente di collegare un server MCP attraverso il proprio wizard oppure mediante un custom connector. Attualmente supporta il trasporto Streamable HTTP; il precedente trasporto SSE non è più supportato da Copilot Studio dall’agosto 2025. Una volta collegato il server, strumenti e risorse pubblicati vengono rilevati dinamicamente e possono essere resi disponibili all’agente.
La semplificazione è notevole. La sicurezza, invece, rimane una nostra responsabilità.
Che cosa fa realmente MCP
Il Model Context Protocol definisce un modello standardizzato attraverso il quale un’applicazione basata su intelligenza artificiale può scoprire e utilizzare capacità esterne.
Un server MCP può pubblicare:
-
tools, cioè funzioni che l’agente può invocare;
-
resources, cioè dati utilizzabili come contesto;
-
prompts, cioè modelli di prompt predefiniti.
Copilot Studio supporta attualmente tool e resource MCP. Quando il server aggiunge, modifica o rimuove una capacità, Copilot Studio riflette dinamicamente la nuova configurazione. Il server fornisce il nome dello strumento, la descrizione, gli input e gli output; l’orchestratore utilizza queste informazioni per decidere se e quando invocarlo.
Supponiamo che un server MCP esponga questi strumenti:
get_my_orders
get_order
cancel_order
approve_order
Dal punto di vista dell’agente sono quattro funzioni disponibili. Dal punto di vista della sicurezza sono quattro operazioni su un sistema aziendale. La descrizione dello strumento può aiutare il modello a scegliere correttamente, ma non rappresenta una policy di autorizzazione. Un prompt del tipo: “Non mostrare ordini appartenenti ad altri utenti. Non annullare un ordine senza autorizzazione.” può orientare il comportamento dell’agente, ma non deve essere considerato un controllo di sicurezza.
L’autorizzazione deve essere applicata dal server MCP, dall’API downstream o da un servizio di policy attendibile. Il modello può decidere quale strumento proporre o invocare; non deve decidere da solo se l’utente sia autorizzato a utilizzarlo.
Il primo equivoco: la connessione di Copilot Studio non racconta tutta la storia
Quando si aggiunge un server MCP attraverso il wizard di Copilot Studio, è possibile selezionare tre tipi di autenticazione:
-
nessuna autenticazione;
-
API key;
-
OAuth 2.0.
Per OAuth 2.0 sono disponibili configurazioni con dynamic discovery, dynamic client registration o configurazione manuale. Nel caso manuale vengono richiesti client ID, client secret, authorization URL, token URL, refresh URL ed eventuali scope. Il flusso descritto da Microsoft prevede che l’utente venga indirizzato al provider di identità, conceda il consenso e permetta all’agente di ottenere un access token e, quando previsto, un refresh token per il server MCP.
Questo ci dice quale identità viene utilizzata nel primo tratto:
Copilot Studio → server MCP
Non ci dice automaticamente quale identità il server MCP utilizzerà nel tratto successivo:
server MCP → API aziendale
Il server può infatti:
-
ottenere un nuovo token downstream mantenendo il contesto dell’utente;
-
ottenere un token applicativo usando una propria workload identity;
-
usare una credenziale tecnica verso un sistema legacy;
-
applicare l’autorizzazione localmente e poi invocare un backend con un’identità di servizio.
È quindi necessario separare chiaramente i confini di autenticazione.
Due confini, due token
L’architettura corretta non dovrebbe essere pensata come un’unica catena nella quale lo stesso token viene passato da un componente al successivo.
Esistono almeno due risorse protette:
-
il server MCP;
-
l’API downstream.
Ciascuna dovrebbe ricevere un token emesso espressamente per sé.

Figura 1 – Flusso token
Nel primo hop, Copilot Studio chiama il server MCP con un token destinato al server MCP.
Se il server deve chiamare una seconda API mantenendo l’identità dell’utente, può utilizzare l’On-Behalf-Of flow. In questo modello il token A ha come audience l’API intermedia, mentre l’API intermedia chiede a Microsoft Entra un token B destinato alla risorsa downstream. Microsoft specifica che OBO utilizza esclusivamente delegated scope e che non deve essere usato per scambiare token app-only.
Se invece il server MCP deve eseguire un’operazione come applicazione, può ottenere un token attraverso il client credentials flow, una managed identity o una federated workload identity.
Il punto essenziale è che il token A non deve essere inoltrato direttamente all’API downstream.
La specifica MCP richiede che il parametro OAuth resource identifichi il server MCP destinatario e che il server verifichi che il token sia stato emesso specificamente per lui. Il token passthrough è espressamente vietato perché rompe la separazione delle audience, indebolisce i controlli e rende più ambiguo l’audit trail.
Token A
aud = api://contoso-mcp-orders
Token B
aud = api://contoso-orders-api
Stesso utente, eventualmente. Token differenti. Risorse differenti. Decisioni autorizzative differenti.
Delegated flow: l’agente opera nel contesto dell’utente
Nel modello delegato, l’applicazione agisce per conto di un utente autenticato.
Il resource server riceve un token che contiene gli scope concessi e un contesto riferibile all’utente. L’API può quindi applicare controlli basati sull’identità della persona, sui gruppi, sui ruoli applicativi assegnati e sulla proprietà dell’oggetto richiesto.
Il claim scp contiene gli scope delegati concessi ed è incluso nei token utente. L’API deve verificare che lo scope ricevuto corrisponda a uno degli scope esposti e accettati.
Un flusso end-to-end può apparire così:

Figura 2 – Token End-To-End
Se Domenico è autorizzato a leggere soltanto i propri ordini, il server o l’API possono applicare una regola come:
order.ownerObjectId == token.oid
Se il record appartiene a un altro utente, la risposta dovrà essere:
HTTP/1.1 403 Forbidden
Il vantaggio del modello delegato è che l’identità umana rimane presente lungo la catena.
Questo facilita:
-
l’applicazione delle autorizzazioni individuali;
-
l’attribuzione delle operazioni;
-
la revoca dell’accesso;
-
l’applicazione delle policy di Conditional Access;
-
l’eventuale richiesta di autenticazione step-up;
-
il rispetto delle ACL già esistenti sulla risorsa.
Il limite emerge quando il processo deve essere eseguito senza una sessione utente: job schedulati, elaborazioni batch, sincronizzazioni, attività notturne o operazioni guidate da eventi. In quei casi è normalmente necessaria un’identità applicativa.
Application identity: l’agente non sta più agendo come l’utente
Il client credentials flow permette a un servizio di autenticarsi con le proprie credenziali e chiamare una risorsa senza impersonare un utente. I permessi vengono concessi direttamente all’applicazione, generalmente attraverso application permissions o app role approvati da un amministratore. Microsoft lo indica come il modello tipico per daemon, servizi backend e processi che devono funzionare senza interazione immediata con una persona.
Il flusso diventa:

Figura 3 – Flusso token
La chiamata iniziale può ancora essere associata a Domenico. La chiamata downstream, tuttavia, è autorizzata sulla base dell’identità applicativa del server MCP. Per la Orders API, l’esecutore tecnico è l’applicazione.
Il resource server non deve assumere che l’applicazione possieda gli stessi limiti dell’utente che ha scritto nella chat. Se l’app role consente di leggere tutti gli ordini, il token permette di leggere tutti gli ordini. È qui che nasce il rischio. Un utente con accesso limitato può utilizzare un intermediario molto più privilegiato di lui.
Service principal e managed identity non sono modelli autorizzativi contrapposti
Una managed identity non rappresenta un’alternativa concettuale all’identità applicativa. Rimane una workload identity ed è rappresentata in Microsoft Entra da un service principal. La differenza principale consiste nel modo in cui il workload ottiene e gestisce la propria credenziale.
Con un client secret occorre creare, proteggere, distribuire e ruotare un segreto.
Con un certificato occorre proteggere e rinnovare la chiave privata.
Con workload identity federation si utilizza una relazione di fiducia con un’identità esterna.
Con una managed identity, Azure gestisce il ciclo di vita della credenziale e permette alla risorsa di richiedere token senza conservare secret nel codice o nella configurazione.
La tassonomia corretta è quindi:

Figura 4 – Tassonomia dei modelli autorizzativi
Se il server MCP viene ospitato su Azure Functions, App Service, Container Apps o un’altra risorsa compatibile, la managed identity dovrebbe essere la prima opzione da valutare per il tratto app-only.
Non riduce i privilegi assegnati all’applicazione. Riduce il rischio legato alla gestione delle credenziali.
Il laboratorio: Contoso Orders
Per rendere il problema osservabile utilizziamo un caso di laboratorio semplice.
L’organizzazione Contoso dispone di una piccola API per la gestione degli ordini. Vogliamo costruire un agente Copilot Studio che permetta agli utenti di:
-
visualizzare i propri ordini;
-
leggere il dettaglio di un ordine;
-
annullare un ordine;
-
avviare un processo amministrativo notturno.
Il server MCP pubblica tre strumenti interattivi e uno applicativo:
get_my_orders
get_order
cancel_order
reconcile_pending_orders
Nel dataset sono presenti questi record:
| Ordine | Proprietario | Reparto | Stato |
| ORD-001 | Domenico Caldarelli | Finance | Pending |
| ORD-002 | Laura Bianchi | Finance | Approved |
| ORD-003 | Direzione | Executive | Confidential |
Gli utenti di laboratorio sono:
|
Utente |
Autorizzazioni |
|
Domenico Caldarelli |
Legge e annulla solo i propri ordini |
|
Laura Bianchi |
Legge gli ordini Finance e può approvarli |
|
Amministratore |
Configura applicazioni e consensi |
L’obiettivo è confrontare tre comportamenti:
-
accesso delegated end-to-end;
-
accesso applicativo downstream senza controllo sull’utente;
-
accesso applicativo downstream con authorization near resource.

Figura 5 – Architettura laboratorio
Registrazione delle applicazioni in Microsoft Entra
Il laboratorio richiede tre identità logiche:
-
un client OAuth utilizzato dalla connessione Copilot Studio;
-
il server MCP, registrato come API protetta;
-
la Orders API downstream.
Registrazione del server MCP
Nel Microsoft Entra admin center apriamo:
Identity → Applications → App registrations → New registration
Creiamo l’applicazione:
Name: Contoso MCP Orders Server
Supported account types: Accounts in this organizational directory only
Nella sezione Expose an API impostiamo:
Application ID URI:
api://<MCP-SERVER-APPLICATION-ID>
Aggiungiamo lo scope delegato:
Scope name: mcp.invoke
Who can consent: Admins and users
Admin consent display name: Invoke Contoso MCP Orders Server
User consent display name: Use the Contoso Orders agent
State: Enabled
Lo scope sarà utilizzato nel token che Copilot Studio presenta al server MCP.

Figura 6 – Registrazione dell’API MCP
Registrazione della Orders API
Creiamo una seconda app registration:
Name: Contoso Orders API
Application ID URI:
api://<ORDERS-API-APPLICATION-ID>
Esponiamo due delegated scope:
Orders.Read
Orders.Cancel
Creiamo inoltre un app role destinato esclusivamente alle applicazioni:
Display name: Reconcile orders as application
Allowed member types: Applications
Value: Orders.Reconcile.AsApp
Description: Allows the application to reconcile pending orders
State: Enabled
Limitare Allowed member types ad Applications evita che il medesimo ruolo venga assegnato accidentalmente a utenti. Microsoft Entra utilizza gli scope per le autorizzazioni delegate e gli app role come application permissions nei flussi app-only. Il resource server deve verificare esplicitamente i permessi che si aspetta.

Figura 7 – Scope e app role della Orders API
Il client OAuth usato da Copilot Studio
Per la configurazione OAuth manuale del wizard MCP occorre una registrazione client:
Name: Copilot Studio MCP Orders Client
Platform: Web
Il redirect URI verrà fornito da Copilot Studio durante il wizard. Per questo conviene creare prima la registrazione, annotare client ID e creare una credenziale temporanea, quindi completare il redirect URI dopo che Copilot Studio avrà mostrato il callback.
Nel client aggiungiamo i delegated permission verso il server MCP:
api://<MCP-SERVER-APPLICATION-ID>/mcp.invoke
Per il laboratorio possiamo utilizzare un client secret a breve durata.
In produzione è necessario ricordare che il secret appartiene al client OAuth usato dal connettore. Non è la credenziale con la quale il server MCP accede alla Orders API.
Sono due ruoli differenti:
Copilot MCP Client
→ ottiene Token A per il server MCP
MCP workload identity
→ ottiene Token B per la Orders API
Collegamento del server MCP a Copilot Studio
Apriamo l’agente in Copilot Studio.
La generative orchestration deve essere abilitata, perché è il componente che consente all’agente di selezionare e utilizzare dinamicamente i tool MCP.
Accediamo a: Tools → Add a tool → New tool → Model Context Protocol
Inseriamo:
Server name: Contoso Orders MCP
Server description: Allows authorized Contoso users to view and manage orders.
Server URL: https://mcp-orders.contoso.com/mcp
La descrizione è importante perché l’orchestratore la utilizza per comprendere quando richiamare il server. Non deve contenere istruzioni generiche o ambigue e non deve promettere capacità che il server non possiede.
Selezioniamo: Authentication type: OAuth 2.0
OAuth type: Manual
Configuriamo:
Client ID:
<CLIENT-ID-COPILOT-MCP>
Client secret: <CLIENT-SECRET>
Authorization URL:
https://login.microsoftonline.com/<TENANT-ID>/oauth2/v2.0/authorize
Token URL:
https://login.microsoftonline.com/<TENANT-ID>/oauth2/v2.0/token
Refresh URL:
https://login.microsoftonline.com/<TENANT-ID>/oauth2/v2.0/token
Scopes:
openid profile offline_access
api://<MCP-SERVER-APPLICATION-ID>/mcp.invoke
Copilot Studio mostra un callback URL. Copiamolo nella sezione Authentication della registrazione Copilot Studio MCP Orders Client.
Torniamo nel wizard, creiamo la connessione e aggiungiamo il server all’agente.
La procedura e i campi possono cambiare nell’interfaccia nel tempo; la documentazione Microsoft aggiornata a maggio 2026 descrive però esattamente questo percorso, incluse le modalità Dynamic discovery, Dynamic e Manual.

Figura 8 – MCP onboarding wizard
Limitare gli strumenti disponibili
Quando un server MCP viene aggiunto a un agente, tutti i tool risultano inizialmente abilitati attraverso l’opzione Allow all.
Per un ambiente enterprise questa impostazione merita attenzione. Se domani il proprietario del server pubblica un nuovo tool chiamato:
delete_all_orders
lasciare ogni strumento automaticamente disponibile aumenterebbe il perimetro dell’agente senza un’esplicita revisione. Copilot Studio permette di disattivare Allow all e abilitare singolarmente i tool. Quando l’opzione è disabilitata, i nuovi strumenti aggiunti successivamente al server restano spenti per impostazione predefinita.
Nel laboratorio abilitiamo:
get_my_orders
get_order
cancel_order
Lasciamo disabilitato:
reconcile_pending_orders
Quest’ultimo sarà destinato a un processo backend app-only e non a una richiesta conversazionale generica.

Figura 9 – Selezione tool MCP
Protezione del server MCP
Il server MCP deve validare il token ricevuto da Copilot Studio.
Per una web API ASP.NET Core possiamo utilizzare Microsoft.Identity.Web.
Configurazione indicativa:
|
1 2 3 4 5 6 7 8 9 |
{ "AzureAd": { "Instance": "https://login.microsoftonline.com/", "TenantId": "<TENANT-ID>", "ClientId": "<MCP-SERVER-APPLICATION-ID>", "Audience": "api://<MCP-SERVER-APPLICATION-ID>" } } |
Nel Program.cs:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.Identity.Web; var builder = WebApplication.CreateBuilder(args); builder.Services .AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApi( builder.Configuration.GetSection("AzureAd")); builder.Services.AddAuthorization(options => { options.AddPolicy("McpInvoke", policy => { policy.RequireAuthenticatedUser(); policy.RequireClaim("scp", "mcp.invoke"); }); }); builder.Services.AddControllers(); var app = builder.Build(); app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run(); |
In un’implementazione reale, lo scope può contenere più valori separati da spazi. È quindi preferibile utilizzare i metodi di verifica forniti da Microsoft Identity Web oppure un authorization handler che analizzi correttamente l’insieme degli scope.
Microsoft ricorda inoltre che un’applicazione non dovrebbe costruire dipendenze fragili sulla presenza o sull’ordine di singoli claim. scp identifica un token utente con scope delegati, mentre roles può contenere app role assegnati a un’applicazione oppure a un utente. Il claim opzionale idtyp può aiutare a distinguere token app-only e app+user, ma il modello più sicuro consiste nel definire endpoint e policy che sappiano in anticipo quale tipo di token accettare.
Primo test: delegated end-to-end
Domenico apre l’agente e chiede:
Mostrami i miei ordini in attesa.
Copilot Studio seleziona il tool:
get_my_orders
Il server MCP riceve il token A:
|
1 2 3 4 5 6 7 8 |
{ "aud": "api://<MCP-SERVER-APPLICATION-ID>", "scp": "mcp.invoke", "oid": "<DOMENICO-OBJECT-ID>", "tid": "<TENANT-ID>", "azp": "<COPILOT-MCP-CLIENT-ID>" } |
Il server valida:
-
firma;
-
issuer;
-
tenant;
-
scadenza;
-
audience;
-
scope.
A questo punto deve interrogare la Orders API. Non può inoltrare il token appena ricevuto, perché la sua audience è il server MCP. Utilizza quindi OBO per ottenere un token B:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
using Microsoft.Identity.Web; public sealed class OrdersTokenProvider { private readonly ITokenAcquisition _tokenAcquisition; public OrdersTokenProvider(ITokenAcquisition tokenAcquisition) { _tokenAcquisition = tokenAcquisition; } public Task<string> GetDelegatedOrdersTokenAsync() { return _tokenAcquisition.GetAccessTokenForUserAsync( new[] { "api://<ORDERS-API-APPLICATION-ID>/Orders.Read" }); } } |
Il token B sarà destinato alla Orders API:
|
1 2 3 4 5 6 7 |
{ "aud": "<ORDERS-API-APPLICATION-ID>", "scp": "Orders.Read", "oid": "<DOMENICO-OBJECT-ID>", "tid": "<TENANT-ID>" } |
La Orders API usa oid per limitare i risultati:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
[Authorize] [HttpGet("my")] public async Task<IActionResult> GetMyOrders() { HttpContext.VerifyUserHasAnyAcceptedScope("Orders.Read"); var userObjectId = User.FindFirst("oid")?.Value; if (string.IsNullOrWhiteSpace(userObjectId)) { return Forbid(); } var orders = await repository.GetOrdersByOwnerAsync(userObjectId); return Ok(orders); } |
Il risultato atteso è:
|
1 2 3 4 5 6 7 8 9 |
[ { "orderId": "ORD-001", "owner": "Domenico Caldarelli", "department": "Finance", "status": "Pending" } ] |

Figura 10 – Token delegated
Secondo test: accesso a un ordine non autorizzato
Domenico chiede:
Mostrami il dettaglio dell’ordine riservato della Direzione.
L’agente può decidere di invocare:
get_order(orderId=”ORD-003″)
Il fatto che il modello abbia selezionato il tool non significa che l’operazione sia autorizzata.
La Orders API verifica la proprietà dell’oggetto:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
[Authorize] [HttpGet("{orderId}")] public async Task<IActionResult> GetOrder(string orderId) { HttpContext.VerifyUserHasAnyAcceptedScope("Orders.Read"); var userObjectId = User.FindFirst("oid")?.Value; var order = await repository.GetOrderAsync(orderId); if (order is null) { return NotFound(); } if (!string.Equals( order.OwnerObjectId, userObjectId, StringComparison.OrdinalIgnoreCase)) { logger.LogWarning( "OrderAccessDenied correlationId={CorrelationId} " + "userObjectId={UserObjectId} orderId={OrderId}", HttpContext.TraceIdentifier, userObjectId, orderId); return Forbid(); } return Ok(order); } |
Il risultato atteso è:
HTTP/1.1 403 Forbidden
Questo test dimostra un principio importante: la descrizione del tool e le istruzioni dell’agente non sostituiscono l’autorizzazione sull’oggetto.

Figura 11 – Richiesta negata nel test chat

Figura 12 – Log di autorizzazione negata
Terzo test: app-only downstream
Il server MCP deve eseguire la riconciliazione notturna degli ordini pendenti. Non esiste un utente interattivo. Assegniamo alla managed identity del server MCP l’app role:
Orders.Reconcile.AsApp
Il server ottiene un token per la Orders API:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
using Azure.Core; using Azure.Identity; public sealed class AppOnlyOrdersTokenProvider { private readonly DefaultAzureCredential _credential = new(); public async Task<string> GetTokenAsync( CancellationToken cancellationToken = default) { var context = new TokenRequestContext( new[] { "api://<ORDERS-API-APPLICATION-ID>/.default" }); var token = await _credential.GetTokenAsync( context, cancellationToken); return token.Token; } } |
Il token risultante non contiene scp.
Dovrebbe contenere l’app role assegnato:
|
1 2 3 4 5 6 7 8 9 10 |
{ "aud": "<ORDERS-API-APPLICATION-ID>", "roles": [ "Orders.Reconcile.AsApp" ], "oid": "<MANAGED-IDENTITY-SERVICE-PRINCIPAL-ID>", "azp": "<MANAGED-IDENTITY-CLIENT-ID>", "tid": "<TENANT-ID>" } |
La Orders API espone un endpoint separato:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[Authorize(Roles = "Orders.Reconcile.AsApp")] [HttpPost("reconcile")] public async Task<IActionResult> ReconcileOrders() { var result = await reconciliationService.RunAsync(); return Ok(new { reconciled = result.Count, executionMode = "application" }); } |
La separazione degli endpoint rende evidente quale modello autorizzativo è accettato:
/api/orders/my
→ delegated
/api/orders/{id}
→ delegated
/api/orders/reconcile
→ app-only
È più sicuro di un endpoint universale che prova a dedurre il comportamento esclusivamente dalla presenza di scp o roles.

Figura 13 – Sign-in della managed identity

Figura 14 – Token app-only
Il caso pericoloso: app-only usato per una richiesta utente
Immaginiamo ora una configurazione differente.
Domenico chiede:
Mostrami l’ordine ORD-003.
Il server MCP riceve correttamente il token delegato di Domenico, ma invece di utilizzare OBO chiama la Orders API attraverso la managed identity.
La Orders API vede:
Contoso MCP Orders Managed Identity
Non vede Domenico come principal autorizzato.
Se l’applicazione dispone di un permesso globale come:
Orders.Read.All
la richiesta potrebbe riuscire.
Dal punto di vista dei singoli componenti:
-
Domenico è autenticato;
-
Copilot Studio ha chiamato correttamente il tool;
-
il server MCP possiede un token valido;
-
la Orders API ha autorizzato l’applicazione;
-
il database ha restituito il record.
Eppure il sistema ha violato il modello autorizzativo dell’organizzazione.
Il server MCP è diventato un intermediario privilegiato.
Questa è una forma applicativa del problema del confused deputy: un componente con privilegi propri esegue un’azione per un soggetto che non dispone degli stessi privilegi, senza verificare correttamente che quel soggetto possa richiederla.
La documentazione MCP tratta espressamente i rischi di confused deputy nei server proxy e richiede controlli di consenso e separazione per client. Nel nostro scenario aziendale il principio si estende alla decisione applicativa: il possesso di un’identità privilegiata non autorizza automaticamente ogni richiesta proveniente dall’agente.
Authorization near resource
Nel modello app-only, la decisione deve essere applicata il più vicino possibile alla risorsa.
Esistono diverse possibilità:
-
il server MCP verifica l’autorizzazione prima di chiamare il backend;
-
la Orders API riceve un’attestazione attendibile sull’utente originatore;
-
un policy decision point esterno autorizza la combinazione utente-azione-oggetto;
-
il sistema separa la proposta dell’agente dall’esecuzione effettiva;
-
un workflow umano approva le operazioni più sensibili.
Nel laboratorio possiamo applicare il controllo nel server MCP:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
public async Task<Order> GetOrderAsApplicationAsync( ClaimsPrincipal originatingUser, string orderId, CancellationToken cancellationToken) { var userObjectId = originatingUser.FindFirst("oid")?.Value; if (string.IsNullOrWhiteSpace(userObjectId)) { throw new UnauthorizedAccessException( "Missing originating user identity."); } var authorization = await authorizationService.CanReadOrderAsync( userObjectId, orderId, cancellationToken); if (!authorization.Allowed) { auditLogger.LogDenied( userObjectId, "get_order", orderId, authorization.Reason); throw new ForbiddenException(); } var appToken = await appOnlyTokenProvider.GetTokenAsync( cancellationToken); return await ordersClient.GetOrderAsync( orderId, appToken, cancellationToken); } |
È importante non ridurre questa soluzione all’aggiunta di un header:
X-User-Id: Domenico-object-id
Un header arbitrario può essere falsificato se non esiste un confine di fiducia ben definito.
Se il contesto dell’utente deve raggiungere la risorsa finale, è preferibile:
-
mantenere il flusso delegated tramite OBO;
-
usare un token interno firmato e destinato esclusivamente al backend;
-
utilizzare un servizio di autorizzazione consultato da MCP e API;
-
verificare il contesto prima di eseguire l’operazione app-only.
Che cosa cercare nei token
Per capire chi sta agendo non basta cercare un singolo claim.
Occorre osservare l’insieme del token e conoscere il modello previsto dall’endpoint.
I claim più utili sono:
| Claim |
Significato operativo |
| aud |
Risorsa alla quale il token è destinato |
| iss |
Tenant e security token service emittente |
| tid |
Tenant Microsoft Entra |
| scp |
Scope delegati; presente nei token utente |
| roles |
App role o ruoli assegnati sul resource server |
| oid |
Object ID del principal |
| azp / appid |
Applicazione client che ha richiesto il token |
| sub |
Subject del token |
| idtyp |
Claim opzionale utile a distinguere app e app+user |
| jti / token identifier |
Identificatore utile per correlazione e troubleshooting |
L’audience deve sempre essere validata. Microsoft specifica che una risorsa deve rifiutare un token il cui aud non corrisponde al destinatario previsto. La documentazione avverte inoltre di non assumere che tutti i claim siano sempre presenti e di non usare token destinati a servizi Microsoft come contratti applicativi per il proprio codice.
Una regola pratica può essere:
scp presente
+ utente atteso
+ endpoint delegated
= richiesta delegata
app role applicativo previsto
+ endpoint app-only
+ workload identity autorizzata
= richiesta applicativa
Non:
roles presente
= sicuramente service principal
Il claim roles può essere presente anche nei token utente quando alla persona è stato assegnato un app role sulla risorsa.
Test dell’audience errata
Un controllo semplice ma molto utile consiste nel presentare al server MCP un token emesso per un’altra risorsa, per esempio Microsoft Graph o la Orders API.
Il server deve rifiutarlo:
HTTP/1.1 401 Unauthorized
L’errore deve avvenire anche se:
-
il token è firmato correttamente;
-
non è scaduto;
-
appartiene allo stesso tenant;
-
contiene scope apparentemente potenti.
La firma valida dimostra che Microsoft Entra ha emesso il token.
Non dimostra che il token sia stato emesso per quel server.
La specifica MCP richiede esplicitamente che i server accettino solo token destinati a loro e vieta di usare il server come proxy di token ottenuti per altre risorse.

Figura 15 – Audience mismatch
Logging: ricostruire chi ha fatto cosa
I sign-in log di Microsoft Entra mostrano l’autenticazione e l’ottenimento dei token.
I log del server MCP devono mostrare:
-
quale utente ha avviato la richiesta;
-
quale agente e sessione l’hanno generata;
-
quale tool è stato selezionato;
-
quale risorsa è stata richiesta;
-
quale identità downstream è stata utilizzata;
-
quale decisione di autorizzazione è stata presa.
I log della Orders API devono mostrare:
-
principal effettivamente autorizzato;
-
scope o app role;
-
oggetto interessato;
-
esito dell’operazione.
Un evento applicativo può avere questa struttura:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
{ "timestamp": "2026-07-16T09:42:18.512Z", "environment": "lab-m365", "agentId": "contoso-orders-agent", "copilotSessionId": "cps_7f0c4f", "mcpRequestId": "mcp_2d0e6a", "correlationId": "8b1f1a5b-0e0f-41d9-8f1b-4e8f1e2ac123", "originatingActor": { "type": "user", "objectId": "user-object-id" }, "executionIdentity": { "type": "managedIdentity", "clientId": "managed-identity-client-id" }, "tool": "get_order", "resource": { "type": "order", "idHash": "sha256:..." }, "authorization": { "decision": "Deny", "reason": "User is not owner of requested order", "policyVersion": "2026-07-16.1" }, "latencyMs": 284 } |
Non è necessario registrare il prompt completo, il token o tutti i parametri. Anzi, può essere pericoloso. Prompt, output e tool argument possono contenere:
-
dati personali;
-
informazioni aziendali riservate;
-
segreti accidentalmente forniti dall’utente;
-
identificativi finanziari;
-
contenuti provenienti da documenti.
Occorre registrare ciò che serve all’audit, mascherare i riferimenti sensibili ed evitare assolutamente di salvare access token e refresh token.
Una possibile query di correlazione in Log Analytics è:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
union isfuzzy=true SigninLogs, AADServicePrincipalSignInLogs, AADManagedIdentitySignInLogs | where TimeGenerated > ago(2h) | project TimeGenerated, Type, AppDisplayName, ServicePrincipalName, ResourceDisplayName, CorrelationId, ResultType, ResultDescription | order by TimeGenerated desc |
A questa vista devono essere affiancati i log del server MCP e della Orders API utilizzando lo stesso correlationId.

Figura 16 – Correlazione end-to-end dei log
Conditional Access e step-up authentication
Nel modello delegated può essere necessario richiedere un’autenticazione più forte per una determinata operazione.
Leggere un ordine potrebbe richiedere una sessione standard.
Annullare un ordine superiore a una certa soglia potrebbe richiedere MFA recente o uno specifico authentication context.
Il middle tier non deve nascondere o aggirare la challenge. Microsoft segnala che inoltrare token tra componenti in modo improprio può impedire di soddisfare Conditional Access, token binding e scenari step-up.
Un pattern applicativo possibile è:

Figura 17 – Possibile pattern
La compatibilità concreta della challenge con l’esperienza Copilot Studio e con il connettore utilizzato deve essere verificata nel tenant. Quando non è possibile propagare correttamente lo step-up, l’alternativa più prudente consiste nel trasformare l’azione in una richiesta di approvazione separata.
Separare “decidere” da “eseguire”
Gli agenti sono particolarmente adatti a:
-
raccogliere informazioni;
-
preparare un riepilogo;
-
proporre un’azione;
-
costruire una richiesta;
-
classificare e indirizzare un’attività.
Non ogni azione proposta deve essere immediatamente eseguita. Per operazioni ad alto impatto possiamo dividere il processo:
-
L’agente prepara la richiesta.
-
L’utente verifica i dati.
-
Un approvatore conferma.
-
Un workflow applicativo esegue l’operazione.
-
Tutti i passaggi vengono registrati.
Questo modello è particolarmente indicato per:
-
disabilitazione di account;
-
modifiche ai permessi;
-
cancellazione di dati;
-
approvazione di pagamenti;
-
modifica di configurazioni;
-
operazioni tenant-wide;
-
attività irreversibili.
L’identità app-only può quindi essere usata dal workflow esecutivo senza essere esposta direttamente a una richiesta conversazionale non approvata.
Governare il server MCP in Power Platform
L’integrazione MCP di Copilot Studio utilizza l’infrastruttura dei Power Platform connector. Di conseguenza, le data policy applicate ai connector regolano anche l’accesso del server MCP e dei relativi tool.
La governance deve quindi includere almeno:
Ambienti separati
Dev, test e produzione non dovrebbero condividere:
-
connessioni;
-
service principal;
-
endpoint MCP;
-
database;
-
secret;
-
policy permissive.
Connection references
La connessione non dovrebbe dipendere dall’account personale del maker.
Occorre documentare:
-
proprietario;
-
utenti autorizzati;
-
durata delle credenziali;
-
processo di rotazione;
-
ambiente;
-
server MCP associato.
Data policy
Il connettore MCP deve essere classificato correttamente.
Un server che accede a dati aziendali non dovrebbe poter essere combinato liberamente con connector consumer o servizi esterni non autorizzati.
Inventario
Per ogni agente occorre conoscere:
-
proprietario;
-
business owner;
-
ambiente;
-
server MCP collegati;
-
tool abilitati;
-
identità downstream;
-
permessi assegnati;
-
data policy applicabile;
-
data dell’ultima revisione.
Controllo delle modifiche
Poiché tool e risorse possono essere aggiornati dinamicamente dal server, la modifica del server MCP equivale a una modifica delle capacità dell’agente.
Disabilitare Allow all riduce il rischio, ma non sostituisce:
-
code review;
-
versionamento;
-
approvazione;
-
monitoraggio dell’integrità;
-
test prima della pubblicazione.
Ciò che l’autenticazione non risolve
Una configurazione OAuth corretta non rende automaticamente sicuro il sistema.
Rimangono altri rischi.
Tool poisoning
La descrizione di un tool può essere manipolata per influenzare il modello e indurlo a selezionare una funzione in condizioni non previste.
Prompt injection indiretta
Un documento, una risposta API o una risorsa MCP può contenere istruzioni che tentano di modificare il comportamento dell’agente.
Rug pull del server
Un server approvato può cambiare successivamente descrizioni, output o comportamento.
Combinazione di strumenti
Due tool singolarmente innocui possono diventare pericolosi se utilizzati insieme. Un tool legge dati riservati, un altro invia messaggi verso l’esterno.
Output non attendibile
Il risultato restituito dal server MCP deve essere trattato come input esterno, non come istruzione attendibile.
SSRF e discovery
I flussi di metadata discovery e gli endpoint forniti da un server malevolo possono tentare di indirizzare il client verso risorse interne o metadata service. Le linee guida MCP includono esplicitamente confused deputy, token passthrough, SSRF, session hijacking e scope minimization tra i rischi da considerare.
Questo articolo si concentra sull’identità esecutiva, ma la stessa architettura deve essere inserita in un threat model più ampio.
Delegated oppure app-only?
Non esiste una scelta universalmente corretta.
La decisione dipende dall’azione.
| Scenario |
Modello consigliato |
| Leggere “i miei ordini” |
Delegated |
| Consultare file già accessibili all’utente |
Delegated |
| Annullare una richiesta personale |
Delegated con controllo sull’oggetto |
| Generare un report notturno |
App-only |
| Sincronizzare un catalogo |
App-only |
| Eseguire un’attività batch |
App-only |
| Approvare un pagamento |
Delegated con step-up o workflow approvativo |
| Modificare permessi tenant-wide |
Workflow separato e app-only fortemente limitato |
| Operazione avviata dalla chat ma eseguita come app |
Controllo esplicito dell’utente originatore |
La regola più utile è:
Delegated quando il potere dell’agente deve coincidere con quello dell’utente. App-only quando l’azione appartiene realmente al servizio e può essere delimitata, approvata e verificata come tale.
Checklist di hardening
Prima di pubblicare un agente collegato a un server MCP è opportuno verificare quanto segue.
Identità e token
-
Il server MCP valida firma, issuer, scadenza, tenant e audience.
-
I token ricevuti dal client non vengono inoltrati alle API downstream.
-
Ogni risorsa riceve un token emesso per la propria audience.
-
Gli endpoint delegated verificano gli scope attesi.
-
Gli endpoint app-only verificano app role e workload identity autorizzate.
-
Le application permission sono limitate alla singola funzione.
-
Managed identity o federated credential sono preferite ai secret.
-
Gli app role applicativi hanno Allowed member types coerente.
Autorizzazione
-
La decisione viene applicata sul singolo oggetto.
-
L’identità app-only non sostituisce i diritti dell’utente senza un controllo aggiuntivo.
-
Le operazioni sensibili richiedono step-up o approvazione.
-
Proposta ed esecuzione sono separate per le azioni irreversibili.
-
Il server rifiuta token con audience errata.
-
Un errore di autorizzazione restituisce 403, non dati parziali.
Copilot Studio e Power Platform
-
Allow all è disabilitato quando non necessario.
-
Solo i tool approvati sono abilitati.
-
Le data policy regolano il connettore MCP.
-
La connessione non appartiene a un account personale non governato.
-
Dev, test e produzione utilizzano identità separate.
-
Le modifiche ai tool passano attraverso un processo di revisione.
-
L’agente viene pubblicato soltanto dopo il test delle autorizzazioni negative.
Logging
-
Ogni richiesta possiede un correlation ID.
-
Sono distinti utente originatore e identità esecutiva.
-
I log indicano tool, risorsa, decisione ed esito.
-
Access token e refresh token non vengono registrati.
-
Prompt e output sensibili vengono minimizzati o mascherati.
-
Sign-in log, log MCP e log downstream sono correlabili.
Conclusioni
MCP riduce in modo significativo la complessità necessaria per collegare un agente a strumenti e dati aziendali. Copilot Studio rende questa integrazione ancora più accessibile: il server viene aggiunto come tool, le capacità vengono rilevate dinamicamente e l’orchestratore può selezionarle durante la conversazione.
La semplicità dell’esperienza non deve però nascondere la struttura reale del sistema. Tra l’utente e la risorsa possono esistere più hop, più token e più identità.
Nel flusso delegated, il backend può continuare a vedere e autorizzare l’utente.
Nel flusso app-only, il backend autorizza il service principal o la managed identity.
Entrambe le configurazioni possono essere legittime.
Il problema nasce quando l’organizzazione crede di aver conservato il perimetro dell’utente, mentre la risorsa sta in realtà autorizzando un’applicazione molto più privilegiata. La UI non basta a rispondere. Il nome della connessione non basta. La descrizione del tool non basta. Occorre guardare quale token arriva alla risorsa, quale audience contiene, quali scope o app role vengono valutati e quale principal compare nei log.
La regola operativa può essere riassunta in una frase: Se non sai quale token vede la risorsa, non sai quale identità sta agendo. E se non sai quale identità sta agendo, non hai ancora governato il tuo agente.
Stay tuned!