OAuth2 dla klienta MCP oraz grant Identity Assertion | Blog eSeGeCe

OAuth2 dla klienta MCP oraz grant Identity Assertion

· Komponenty

Zdalne serwery MCP są zasobami chronionymi, a te, które ludzie faktycznie wdrażają, stoją za serwerem autoryzacji. Do tej pory klient MCP w sgcWebSockets mógł wysłać klucz API albo własny nagłówek, co wystarczało przy statycznym tokenie, ale oznaczało, że wszystko, co ma czas życia, było Twoim problemem: sam wywoływałeś punkt końcowy tokenu, parsowałeś odpowiedź, pilnowałeś zegara i odświeżałeś token przed jego wygaśnięciem.

sgcWebSockets 2026.7 przenosi tę pracę do komponentu. Klient MCP może teraz uwierzytelniać się przez OAuth2, więc pobiera token dostępu, zapisuje go w pamięci podręcznej i używa go ponownie przy każdym żądaniu aż do wygaśnięcia. To samo wydanie dodaje nowy typ grantu, Identity Assertion Authorization Grant, który przenosi tożsamość z jednej domeny do drugiej bez przeprowadzania użytkownika przez drugie interaktywne logowanie.

Klient, nie serwer

Najpierw jedno wyjaśnienie, bo te dwie strony łatwo pomylić. sgcWebSockets 2026.5 dostarczyło OAuth 2.1 na serwerze MCP: punkty końcowe odkrywania, PKCE i dynamiczną rejestrację klienta, dzięki czemu klient MCP działający w przeglądarce mógł autoryzować się wobec Twojego serwera w Delphi. To zostało już omówione wcześniej. Ten wpis dotyczy drugiego końca przewodu, czyli klienta MCP, który teraz sam zdobywa token i przedstawia go zdalnemu serwerowi MCP, z którym się łączy.

Co klient MCP potrafił wcześniej

Dwie opcje, obie nadal dostępne i obie niezmienione. Klucz API wysyłany jako Authorization: Bearer <value> oraz własny nagłówek do kierowania ruchu według dzierżawcy lub regionu. Wcześniejszy wpis Uwierzytelnianie MCP w Delphi opisuje oba szczegółowo.

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';

Brakowało wszystkiego, co jest pomiędzy: punktu końcowego tokenu, identyfikatora i sekretu klienta, zakresu, czasu wygaśnięcia. Właśnie to dodaje nowy obiekt OAuth2.

OAuth2 w kliencie MCP

Uwierzytelnianie ma teraz trzecią gałąź, MCPOptions.AuthenticationOptions.OAuth2, typu TsgcWSMCPClientAuthenticationOAuth2_Options. Jej właściwości to dokładnie to, czego oczekujesz od klienta OAuth2: Enabled, TokenURL, ClientId, ClientSecret, Scope, GrantType oraz obiekt IdentityAssertionOptions używany przez nowy grant. Jest jedna metoda, GetBearerToken, i zwykle nigdy jej nie wywołujesz: klient robi to za Ciebie przy każdym żądaniu MCP i umieszcza wynik w nagłówku Authorization.

Grant wybiera się właściwością GrantType typu TsgcOAuth2GrantTypes:

TsgcOAuth2GrantTypes = (auth2Code, auth2CodePKCE, auth2ClientCredentials,
  auth2ResourceOwnerPassword, auth2DeviceCode, auth2IdentityAssertion);

Ostatnia wartość, auth2IdentityAssertion, jest nowa w 2026.7. Pozostałe były już obsługiwane przez klienta HTTP OAuth2, a teraz są dostępne również z poziomu klienta MCP.

Client credentials, czyli najczęstszy przypadek

Usługa, która rozmawia ze zdalnym serwerem MCP bez udziału użytkownika, to grant client credentials. Wypełnij punkt końcowy tokenu i dane uwierzytelniające, włącz go i połącz się:

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;

Żadnej obsługi tokenu w Twoim kodzie. Pierwsze żądanie, które potrzebuje tokenu, uruchamia grant, a każde kolejne żądanie korzysta z tego, co znajduje się w pamięci podręcznej.

Pamięć podręczna tokenu i pierwszeństwo przed kluczem API

Token jest pobierany leniwie, przy pierwszym żądaniu, i przechowywany w pamięci wraz z czasem wygaśnięcia. Dopóki jest ważny, klient używa go ponownie. Gdy wygaśnie albo gdy serwer nigdy nie podał czasu wygaśnięcia, a token wraca pusty, grant uruchamia się ponownie. Klient odświeża token także nieco przed podanym czasem wygaśnięcia, więc token, który zaraz straci ważność, nie zostanie wysłany w żądaniu, które w chwili dotarcia byłoby już odrzucone.

Pierwszeństwo jest proste i warto je jasno powiedzieć: gdy OAuth2.Enabled ma wartość True, wygrywa OAuth2. Nagłówek Authorization niesie token bearer z grantu, a ApiKey jest ignorowany, nawet jeśli również jest włączony. Dzięki temu możesz zostawić skonfigurowany dotychczasowy klucz API na czas migracji, a potem przełączyć OAuth2.Enabled i nie zmieniać nic więcej. CustomHeader jest niezależny i nadal jest wysyłany w obu przypadkach.

Opcje TLS w kliencie MCP

Transport HTTP klienta MCP udostępnia teraz własną konfigurację TLS przez MCPOptions.HttpOptions.TLSOptions, więc połączenie z serwerem MCP i z punktem końcowym tokenu można ustawić dokładnie tak, jak wymaga tego Twoje środowisko, zamiast korzystać z wartości domyślnych.

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

Grant Identity Assertion

Teraz najciekawsza część. auth2IdentityAssertion to zdefiniowany przez IETF grant OAuth2 do przenoszenia tożsamości między domenami, działający w stylu wymiany tokenów.

Sytuacja, którą rozwiązuje, wygląda tak. Twoja aplikacja ma już token, który potwierdza, kim jest użytkownik w domenie A, zwykle jest to token id od dostawcy tożsamości, u którego użytkownik się zalogował. Teraz musisz wywołać serwer MCP w domenie B, który nie przyjmie tokenu z domeny A, bo ufa własnemu serwerowi autoryzacji, a nie cudzemu. Użytkownik jest na miejscu i już uwierzytelniony, więc przeprowadzanie go przez drugie interaktywne logowanie to dokładnie ten rodzaj tarcia, którego chcesz uniknąć.

Grant Identity Assertion jest wyjściem z tej sytuacji. Przedstawiasz token z domeny A jako token podmiotu, a wymiana zwraca token o zakresie audience lub resource, o który poprosiłeś, taki, który domena B akceptuje. Dlatego opcje mają właśnie taki kształt: SubjectToken i SubjectTokenType opisują to, co posiadasz, RequestedTokenType, Audience i Resource opisują to, czego chcesz, a ActorToken / ActorTokenType pozwalają wskazać usługę działającą w imieniu użytkownika, gdy cel tego wymaga.

To wieloetapowa wymiana, a klient przechodzi ją za Ciebie. Token podmiotu trafia do serwera autoryzacji strony żądającej (RequestingPartyTokenURL, wraz z RequestingPartyClientId i RequestingPartyClientSecret), który zwraca asercję międzydomenową. Ta asercja jest następnie przedstawiana serwerowi autoryzacji zasobu pod adresem TokenURL, a on wystawia token dostępu, który klient MCP faktycznie wysyła. Jeśli masz już asercję skądinąd, ustaw Assertion, a pierwszy etap zostanie pominięty.

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;

Z perspektywy kodu wywołującego to nadal jedna właściwość i jedno połączenie. Wszystko powyższe dzieje się wewnątrz GetBearerToken, a uzyskany token dostępu jest zapisywany w pamięci podręcznej tak samo jak każdy inny.

Śledzenie wymiany

Grant nie ogranicza się do MCP. TsgcHTTPComponentClient_OAuth2 obsługuje go bezpośrednio, co przydaje się, gdy chcesz zobaczyć każdy krok, zapisać go w dzienniku albo wykorzystać token gdzie indziej. Wybierz grant we właściwości OAuth2Options.GrantType, wypełnij IdentityAssertionOptions i wywołaj Start. Pośrednia asercja zostaje w tylko do odczytu właściwości IdentityAssertion, a metoda DoIdentityAssertionGrant jest do dyspozycji, jeśli chcesz sterować przepływem jawnie.

Zdarzenia raportują każdy etap. OnBeforeTokenExchange, OnAfterTokenExchange i OnErrorTokenExchange obejmują wymianę międzydomenową, a istniejące OnAuthToken i OnAuthTokenError raportują wynik końcowy.

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;

Jeśli któryś etap zawiedzie, zdarzenie błędu niesie kod i opis błędu OAuth2 w takiej postaci, w jakiej zwrócił je serwer autoryzacji, więc źle skonfigurowany audience albo nieakceptowany typ tokenu podmiotu widać od razu, zamiast dowiadywać się o tym później z błędu 401 z serwera MCP.

Dostępność

Uwierzytelnianie OAuth2 w kliencie MCP, grant Identity Assertion oraz opcje TLS klienta MCP są dostępne w sgcWebSockets 2026.7 dla Delphi 7 do 13 i C++Builder, na Win32/Win64, Linux64, macOS, Android oraz iOS. Dla istniejącego kodu nic się nie zmienia: OAuth2 jest wyłączony, dopóki nie ustawisz Enabled, a uwierzytelnianie kluczem API i własnym nagłówkiem działa dokładnie tak jak wcześniej.

Klienci z aktywną subskrypcją mogą pobrać nową kompilację ze strefy klienta. Użytkownicy wersji próbnej mogą pobrać zaktualizowany instalator pod adresem esegece.com/products/websockets/download.

Pytania, opinie albo pomoc przy podpięciu grantu do Twojej aplikacji? Skontaktuj się z nami, odpowiedź otrzymasz od osób, które napisały ten kod.