I server MCP remoti sono risorse protette, e i server che le persone effettivamente mettono in produzione si trovano dietro un authorization server. Fino ad ora il client MCP di sgcWebSockets poteva inviare una API key o un header personalizzato, il che andava bene per un token statico ma significava che qualsiasi cosa avesse una scadenza diventava un problema tuo: chiamavi tu l'endpoint del token, ne analizzavi la risposta, tenevi d'occhio l'orologio e rinnovavi il token prima che scadesse.
sgcWebSockets 2026.7 sposta quel lavoro dentro il componente. Il client MCP ora può autenticarsi con OAuth2, quindi ottiene il token di accesso, lo memorizza in cache e lo riutilizza a ogni richiesta finché non scade. La stessa release aggiunge un nuovo tipo di grant, l'Identity Assertion Authorization Grant, che concatena un'identità da un dominio a un altro senza far passare l'utente attraverso un secondo login interattivo.
Client, non server
Prima una precisazione, perché è facile confondere i due lati. sgcWebSockets 2026.5 ha introdotto OAuth 2.1 sul server MCP: endpoint di discovery, PKCE e registrazione dinamica dei client, in modo che un client MCP basato su browser potesse autorizzarsi contro il tuo server Delphi. Quel tema è già stato trattato. Questo articolo riguarda l'altro capo del filo, il client MCP, che ora ottiene un proprio token e lo presenta al server MCP remoto a cui si connette.
Cosa poteva già fare il client MCP
Due opzioni, entrambe ancora disponibili ed entrambe invariate. Una API key, inviata come Authorization: Bearer <value>, e un header personalizzato per il routing per tenant o per regione. L'articolo precedente MCP Authentication Delphi tratta entrambe nel dettaglio.
MCPClient.MCPOptions.AuthenticationOptions.ApiKey.Enabled := True;
MCPClient.MCPOptions.AuthenticationOptions.ApiKey.Value := 'YOUR_API_KEY';
MCPClient.MCPOptions.AuthenticationOptions.CustomHeader.Enabled := True;
MCPClient.MCPOptions.AuthenticationOptions.CustomHeader.Header := 'X-Tenant';
MCPClient.MCPOptions.AuthenticationOptions.CustomHeader.Value := 'Retail';
Quello che mancava è tutto ciò che sta nel mezzo: un endpoint per il token, un client id e un secret, uno scope, una scadenza. È esattamente ciò che aggiunge il nuovo oggetto OAuth2.
OAuth2 sul client MCP
L'autenticazione ora ha un terzo ramo, MCPOptions.AuthenticationOptions.OAuth2, di tipo TsgcWSMCPClientAuthenticationOAuth2_Options. Le sue proprietà sono quelle che ti aspetteresti da un client OAuth2: Enabled, TokenURL, ClientId, ClientSecret, Scope, un GrantType e un oggetto IdentityAssertionOptions usato dal nuovo grant. C'è un solo metodo, GetBearerToken, e normalmente non lo chiami mai: il client lo chiama per te a ogni richiesta MCP e inserisce il risultato nell'header Authorization.
Il grant si seleziona con GrantType, di tipo TsgcOAuth2GrantTypes:
TsgcOAuth2GrantTypes = (auth2Code, auth2CodePKCE, auth2ClientCredentials,
auth2ResourceOwnerPassword, auth2DeviceCode, auth2IdentityAssertion);
L'ultimo valore, auth2IdentityAssertion, è nuovo nella 2026.7. Gli altri erano già supportati dal client HTTP OAuth2 e ora sono raggiungibili anche dal client MCP.
Client credentials: il caso più comune
Un servizio che dialoga con un server MCP remoto, senza un utente nel mezzo, usa il grant client credentials. Compila l'endpoint del token e le credenziali, abilitalo e connettiti:
uses
sgcAI, sgcAI_MCP_Client, sgcHTTP_OAuth_Types;
var
MCPClient: TsgcWSAPIClient_MCP;
begin
MCPClient := TsgcWSAPIClient_MCP.Create(nil);
MCPClient.MCPOptions.HttpOptions.URL := 'https://mcp.example.com/mcp';
MCPClient.MCPOptions.AuthenticationOptions.OAuth2.Enabled := True;
MCPClient.MCPOptions.AuthenticationOptions.OAuth2.GrantType := auth2ClientCredentials;
MCPClient.MCPOptions.AuthenticationOptions.OAuth2.TokenURL := 'https://auth.example.com/oauth2/token';
MCPClient.MCPOptions.AuthenticationOptions.OAuth2.ClientId := 'YOUR_CLIENT_ID';
MCPClient.MCPOptions.AuthenticationOptions.OAuth2.ClientSecret := 'YOUR_CLIENT_SECRET';
MCPClient.MCPOptions.AuthenticationOptions.OAuth2.Scope := 'mcp.read mcp.tools';
MCPClient.Initialize;
MCPClient.ListTools;
end;
Nessuna gestione del token nel tuo codice. La prima richiesta che ha bisogno di un token attiva il grant, e ogni richiesta successiva riutilizza quanto è in cache.
Cache del token e priorità sulla API key
Il token viene acquisito in modo lazy, alla prima richiesta, e conservato in memoria con la sua scadenza. Finché è ancora valido il client lo riutilizza. Quando scade, oppure quando il server non ha mai comunicato una scadenza e il token torna vuoto, il grant viene eseguito di nuovo. Il client rinnova anche leggermente in anticipo rispetto alla scadenza dichiarata, così un token prossimo a decadere non viene inviato con una richiesta che verrebbe rifiutata nel momento in cui arriva.
La priorità è semplice e vale la pena dirla chiaramente: quando OAuth2.Enabled vale True, vince OAuth2. L'header Authorization trasporta il bearer token ottenuto dal grant, e ApiKey viene ignorata anche se è abilitata a sua volta. Questo ti permette di lasciare configurata una API key esistente durante la migrazione, per poi attivare OAuth2.Enabled senza cambiare nient'altro. CustomHeader è ortogonale e continua a essere inviato in entrambi i casi.
Opzioni TLS sul client MCP
Il trasporto HTTP del client MCP ora espone la propria configurazione TLS tramite MCPOptions.HttpOptions.TLSOptions, così la connessione verso il server MCP e verso l'endpoint del token può essere fissata sui parametri richiesti dal tuo ambiente invece che sui valori predefiniti.
MCPClient.MCPOptions.HttpOptions.TLSOptions.Version := tls1_3;
MCPClient.MCPOptions.HttpOptions.TLSOptions.VerifyCertificate := True;
MCPClient.MCPOptions.HttpOptions.TLSOptions.RootCertFile := 'C:\certs\ca-bundle.pem';
MCPClient.MCPOptions.HttpOptions.TLSOptions.IOHandler := iohOpenSSL; // or iohSChannel on Windows
Il grant Identity Assertion
Ora la parte interessante. auth2IdentityAssertion è un grant OAuth2 definito dall'IETF per concatenare un'identità tra domini diversi, ed è un flusso in stile token exchange.
La situazione che risolve è questa. La tua applicazione possiede già un token che dimostra chi è l'utente nel dominio A, tipicamente un id token dell'identity provider con cui l'utente ha effettuato l'accesso. Ora devi chiamare un server MCP nel dominio B, che non accetterà il token del dominio A: si fida del proprio authorization server, non di quello di qualcun altro. L'utente è lì, già autenticato, quindi mandarlo attraverso un secondo login interattivo è esattamente l'attrito che vuoi evitare.
Il grant Identity Assertion è la via d'uscita. Presenti il token del dominio A come subject token, e lo scambio restituisce un token con lo scope dell'audience o della resource che hai richiesto, un token che il dominio B accetta. Ecco perché le opzioni sono strutturate così: SubjectToken e SubjectTokenType descrivono ciò che possiedi, RequestedTokenType, Audience e Resource descrivono ciò che vuoi, e ActorToken / ActorTokenType ti permettono di indicare il servizio che agisce per conto dell'utente quando la destinazione lo richiede.
È uno scambio a più passi, e il client lo percorre per te. Il subject token viene inviato all'authorization server della requesting party (RequestingPartyTokenURL, con RequestingPartyClientId e RequestingPartyClientSecret), che restituisce un'assertion cross-domain. Quell'assertion viene poi presentata al resource authorization server indicato in TokenURL, che emette il token di accesso che il client MCP invia realmente. Se hai già l'assertion da un'altra fonte, imposta Assertion e la prima tratta viene saltata.
with MCPClient.MCPOptions.AuthenticationOptions.OAuth2 do
begin
Enabled := True;
GrantType := auth2IdentityAssertion;
// resource authorization server (domain B): issues the token the MCP server accepts
TokenURL := 'https://auth.domain-b.com/oauth2/token';
ClientId := 'YOUR_CLIENT_ID';
ClientSecret := 'YOUR_CLIENT_SECRET';
Scope := 'mcp.tools';
// the identity you already hold in domain A
IdentityAssertionOptions.SubjectToken := vIdTokenFromDomainA;
IdentityAssertionOptions.SubjectTokenType := 'urn:ietf:params:oauth:token-type:id_token';
IdentityAssertionOptions.RequestedTokenType := 'urn:ietf:params:oauth:token-type:id-jag';
// where the exchange happens, and who you are exchanging as
IdentityAssertionOptions.RequestingPartyTokenURL := 'https://auth.domain-a.com/oauth2/token';
IdentityAssertionOptions.RequestingPartyClientId := 'DOMAIN_A_CLIENT_ID';
IdentityAssertionOptions.RequestingPartyClientSecret := 'DOMAIN_A_CLIENT_SECRET';
// what the resulting token is for
IdentityAssertionOptions.Audience := 'https://mcp.domain-b.com';
IdentityAssertionOptions.Resource := 'https://mcp.domain-b.com/mcp';
end;
MCPClient.Initialize;
Dal codice chiamante restano una proprietà e una connessione. Tutto quanto sopra avviene dentro GetBearerToken, e il token di accesso risultante viene messo in cache come qualsiasi altro.
Seguire lo scambio
Il grant non è limitato a MCP. TsgcHTTPComponentClient_OAuth2 lo supporta direttamente, ed è quello che ti serve quando vuoi vedere ogni passo, registrarlo a log o riutilizzare il token altrove. Seleziona il grant su OAuth2Options.GrantType, compila IdentityAssertionOptions e chiama Start. L'assertion intermedia viene lasciata nella proprietà di sola lettura IdentityAssertion, e DoIdentityAssertionGrant è a disposizione se vuoi guidare il flusso esplicitamente.
Gli eventi riportano ogni tratta. OnBeforeTokenExchange, OnAfterTokenExchange e OnErrorTokenExchange coprono lo scambio cross-domain, mentre i già esistenti OnAuthToken e OnAuthTokenError riportano il risultato finale.
uses
sgcHTTP_OAuth2_Client, sgcHTTP_OAuth_Types;
procedure TForm1.OnAfterTokenExchange(Sender: TObject; const Access_Token,
Issued_Token_Type, Token_Type, Expires_In, Scope, RawParams: String;
var Handled: Boolean);
begin
// ... leg 1 done: we now hold the cross-domain assertion
Log('assertion issued, type: ' + Issued_Token_Type);
end;
procedure TForm1.OnAuthToken(Sender: TObject; const TokenType, Token,
Data: String);
begin
// ... leg 2 done: this is the token domain B accepts
Log('access token: ' + TokenType + ' ' + Token);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
oOAuth2: TsgcHTTPComponentClient_OAuth2;
begin
oOAuth2 := TsgcHTTPComponentClient_OAuth2.Create(nil);
try
oOAuth2.OAuth2Options.GrantType := auth2IdentityAssertion;
oOAuth2.OAuth2Options.ClientId := 'YOUR_CLIENT_ID';
oOAuth2.OAuth2Options.ClientSecret := 'YOUR_CLIENT_SECRET';
oOAuth2.AuthorizationServerOptions.TokenURL := 'https://auth.domain-b.com/oauth2/token';
oOAuth2.IdentityAssertionOptions.SubjectToken := vIdTokenFromDomainA;
oOAuth2.IdentityAssertionOptions.SubjectTokenType := 'urn:ietf:params:oauth:token-type:id_token';
oOAuth2.IdentityAssertionOptions.RequestedTokenType := 'urn:ietf:params:oauth:token-type:id-jag';
oOAuth2.IdentityAssertionOptions.RequestingPartyTokenURL := 'https://auth.domain-a.com/oauth2/token';
oOAuth2.IdentityAssertionOptions.Audience := 'https://mcp.domain-b.com';
oOAuth2.OnAfterTokenExchange := OnAfterTokenExchange;
oOAuth2.OnErrorTokenExchange := OnErrorTokenExchange;
oOAuth2.OnAuthToken := OnAuthToken;
oOAuth2.OnAuthTokenError := OnAuthTokenError;
oOAuth2.Start;
finally
oOAuth2.Free;
end;
end;
Se una tratta fallisce, l'evento di errore trasporta il codice di errore OAuth2 e la descrizione così come li ha restituiti l'authorization server, quindi un'audience configurata male o un tipo di subject token non accettato sono visibili subito invece di emergere più tardi come un 401 dal server MCP.
Disponibilità
L'autenticazione OAuth2 sul client MCP, il grant Identity Assertion e le opzioni TLS del client MCP sono disponibili in sgcWebSockets 2026.7 per Delphi da 7 a 13 e C++Builder, su Win32/Win64, Linux64, macOS, Android e iOS. Per il codice esistente non cambia nulla: OAuth2 resta disattivato finché non imposti Enabled, e l'autenticazione con API key e header personalizzato si comporta esattamente come prima.
I clienti con un abbonamento attivo possono scaricare la nuova build dall'area clienti. Chi usa la versione di prova può scaricare l'installer aggiornato su esegece.com/products/websockets/download.
Domande, commenti o aiuto per integrare un grant nella tua applicazione? Contattaci, riceverai una risposta dalle persone che hanno scritto il codice.
