OAuth istemcileri
Makine çevirisi
Bu sayfa İngilizce dokümantasyondan otomatik olarak çevrildi; esas alınması gereken sürüm İngilizce sayfadır. Yanlış görünen bir şey varsa, nasıl bildireceğinizi Çeviriler sayfası açıklar.
Bazı MCP sunucuları korumalıdır. Onlara token'sız bir istek gönderin, 401 Unauthorized yanıtını verirler.
Token'ı edinmenin yolu OAuthClientProvider'dır. Bu bir MCP nesnesi bile değildir. Bir httpx2.Auth'tur; httpx2'nin "her isteğe bir şey yap" için sunduğu standart kancadır. Onu bir httpx2.AsyncClient'a takarsınız, o istemciyi Streamable HTTP aktarımına verirsiniz ve konuyu unutursunuz.
Bu sayfa istemci tarafını anlatır. Kendi sunucunuzun token talep etmesini sağlamak Yetkilendirme sayfasının konusudur.
Sağlayıcı
from urllib.parse import parse_qs, urlparse
import httpx2
from pydantic import AnyUrl
from mcp import Client
from mcp.client.auth import AuthorizationCodeResult, OAuthClientProvider
from mcp.client.streamable_http import streamable_http_client
from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
class InMemoryTokenStorage:
def __init__(self) -> None:
self.tokens: OAuthToken | None = None
self.client_info: OAuthClientInformationFull | None = None
async def get_tokens(self) -> OAuthToken | None:
return self.tokens
async def set_tokens(self, tokens: OAuthToken) -> None:
self.tokens = tokens
async def get_client_info(self) -> OAuthClientInformationFull | None:
return self.client_info
async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
self.client_info = client_info
async def open_browser(authorization_url: str) -> None:
print(f"Visit: {authorization_url}")
async def wait_for_callback() -> AuthorizationCodeResult:
redirect_url = input("Paste the URL you were redirected to: ")
params = parse_qs(urlparse(redirect_url).query)
return AuthorizationCodeResult(
code=params["code"][0],
state=params["state"][0],
iss=params["iss"][0] if "iss" in params else None,
)
oauth = OAuthClientProvider(
server_url="http://localhost:8001/mcp",
client_metadata=OAuthClientMetadata(
client_name="Bookshop Agent",
redirect_uris=[AnyUrl("http://localhost:3030/callback")],
scope="user",
),
storage=InMemoryTokenStorage(),
redirect_handler=open_browser,
callback_handler=wait_for_callback,
)
async def main() -> None:
async with httpx2.AsyncClient(auth=oauth, follow_redirects=True) as http_client:
transport = streamable_http_client("http://localhost:8001/mcp", http_client=http_client)
async with Client(transport) as client:
result = await client.list_tools()
print([tool.name for tool in result.tools])
Ona dört şey verirsiniz:
server_url: bağlandığınız MCP endpoint'i. Sağlayıcı geri kalan her şeyi buradan keşfeder.client_metadata: bir yetkilendirme sunucusunun "uygulama kaydet" formuna yazacağınız bilgiler.storage: token'ların çalıştırmalar arasında saklandığı yer.redirect_handlervecallback_handler: bir insanın devreye girdiği iki an.
Dosyada OAuth'tan söz eden başka hiçbir şey yok. main() hiçbir zaman bir token görmez.
İstemci metadatası
OAuthClientMetadata, gerçek RFC 7591 kayıt belgesinin Pydantic modeli hâlidir.
Üç alan ayarlarsınız. Gerisini varsayılanlar doldurur: grant_types zaten ["authorization_code", "refresh_token"], response_types ise zaten ["code"]; bu sağlayıcının çalıştırdığı akış da tam olarak budur.
Check
Bir Pydantic modeli olduğu için doğrulamayı ağa tek bir bayt bile gitmeden yapar.
redirect_uris alanını atlarsanız oluşturma, alanın adını veren bir ValidationError ile
anında başarısız olur:
redirect_uris
Field required [type=missing, input_value={'client_name': 'Bookshop Agent'}, input_type=dict]
Ne bir tarayıcı açılır ne de yetkilendirme sunucusunda yarım kalmış bir kayıt bırakılır.
Token deposu
TokenStorage, dört asenkron metodu olan bir Protocol'dür. Hiçbir şeyden kalıtım almazsınız; metotları yazın, herhangi bir sınıf bir token deposu olur:
get_tokens/set_tokens,OAuthToken'ı tutar: erişim token'ı, yenileme token'ı, geçerlilik süresi, kapsam.get_client_info/set_client_info, sağlayıcı sizi kaydettiğinde yetkilendirme sunucusunun verdiğiOAuthClientInformationFull'u tutar;client_id'niz de bunun içindedir.
Yukarıdaki bellek içi sürüm çalışır. Ancak süreç sona erdiğinde her şeyi unutur; bu yüzden bir sonraki çalıştırma bütün süreci baştan yapar. Onu bir dosyada ya da platformunuzun anahtarlığında kalıcı hâle getirin, bir sonraki çalıştırma sessiz geçer.
Tip
Yalnızca token'ları değil, client_info'yu da saklayın. Sağlayıcı, depoda client_info
bulamadığı ilk seferde dinamik olarak kayıt yaptırır. Onu atarsanız her çalıştırmada yeni bir
kayıt üretirsiniz.
İki işleyici
Yetkilendirme kodu akışı bir insana tam olarak bir kez ihtiyaç duyar: birinin oturum açıp "allow" düğmesine tıklaması gerekir.
redirect_handler, tamamen hazırlanmış yetkilendirme URL'siyle await edilir.client_id,redirect_uri,stateve PKCE challenge'ı zaten içindedir. Tek işiniz bir tarayıcıyı oraya götürmektir. Bir masaüstü uygulamasıwebbrowser.open'ı çağırır; bu dosya URL'yi yazdırır.- Ardından
callback_handlerawait edilir. Kullanıcıredirect_uri'nize geri dönene kadar bekler ve o yönlendirmenin sorgu parametrelerini birAuthorizationCodeResultolarak döndürür.
Gerçek bir istemci input() çağırmak yerine yönlendirme URI'si üzerinde küçük bir yerel HTTP sunucusu çalıştırır. Biçim aynıdır: yönlendirilin, code, state ve iss değerlerini geri verin.
Warning
state ve iss değerlerini tam geldikleri gibi aktarın. Sağlayıcı state'i kendi ürettiğiyle,
iss'i de keşfettiği yayıncıyla karşılaştırır ve uyuşmazlığı reddeder. Bunlar CSRF ve
sunucu karışıklığı (mix-up) savunmalarıdır.
Client'a bağlama
main()'e bakın. Sağlayıcı httpx2 istemcisine takılır, httpx2 istemcisi streamable_http_client(url, http_client=...)'a verilir, bu aktarım da Client'a gider.
streamable_http_client'ın auth= diye bir anahtar sözcük argümanı yoktur. HTTP düzeyindeki her şey (kimlik doğrulama, başlıklar, zaman aşımları, vekil sunucular) sizin getirdiğiniz httpx2.AsyncClient'a aittir. Bu katmanlama İstemci aktarımları sayfasında anlatılır.
Sağlayıcının sizin için yaptıkları
Client ilk kez bir istek gönderdiğinde sunucu 401 yanıtını verir. Sağlayıcı devralır:
- Keşif.
WWW-Authenticatebaşlığını okur, sunucunun Protected Resource Metadata belgesini/.well-known/oauth-protected-resourceadresinden alır, bu kaynağı hangi yetkilendirme sunucusunun koruduğunu öğrenir ve o sunucunun metadatasını alır. - Kayıt. Depoda bir şey yok mu?
OAuthClientMetadata'nızla sizi dinamik olarak kaydeder ve sonucu saklar. - Yetkilendirme. PKCE çiftini ve bir
stateüretir, yetkilendirme URL'sini oluşturur,redirect_handler'ınızı await eder, ardından kod içincallback_handler'ınızı await eder. - Değişim. Kodu bir
OAuthTokenile takas eder, onu saklar ve özgün isteğiniziAuthorization: Bearer ...ile yeniden gönderir.
Bundan sonra sessizdir. Token'lar depodan gelir, süresi dolmuş bir erişim token'ı yenileme token'ıyla yenilenir ve ancak bunların hiçbiri işe yaramadığında akışı yeniden çalıştırır.
Bunların hiçbirini siz yazmadınız. Geriye iki anahtar sözcük argümanı kalır (client_metadata_url ve validate_resource_url) ve bu dosyanın ikisine de ihtiyacı yoktur. Bilmeye değer olanı client_metadata_url'dir; aşağıda kendi bölümü var.
Deneyin
Bu belgelerdeki örneklerin çoğunu bellek içi bir Client(server) ile sınayabilirsiniz. Bunu değil: akışın bütün amacı bir HTTP 401'idir ve bellek içi bir istemci ile sunucusu arasında HTTP yoktur.
Depo canlı sürümü içerir. examples/servers/simple-auth/ bağımsız bir yetkilendirme sunucusu ile korumalı bir MCP sunucusu çalıştırır; examples/clients/simple-auth-client/ ise bu sayfadaki istemcinin küçük bir CLI'a dönüşmüş hâlidir. README'sinde iki komut var: sunucuları başlatın, istemciyi onlara karşı çalıştırın ve dört adımın geçişini izleyin.
Client ID Metadata Documents
Belirtimin 2026-07-28 sürümü, dinamik istemci kaydını Client ID Metadata Documents (CIMD) lehine kullanım dışı bırakır. İstemciniz karşılaştığı her yetkilendirme sunucusuna yeni bir kayıt POST etmek yerine, kendisi hakkında tek bir JSON belgesini kararlı bir HTTPS URL'sinde yayımlar ve client_id'si bu URL'nin ta kendisidir. Belgeyi yetkilendirme sunucusu alır; sağlayıcı ona hiç dokunmaz.
SDK bunu zaten destekler: sağlayıcıyı oluştururken URL'yi client_metadata_url= olarak geçirin. Yetkilendirme sunucusunun metadatası client_id_metadata_document_supported: true bildiriyorsa sağlayıcı /register isteğini tamamen atlar: URL akışa client_id olarak girer ve client_secret yoktur. Sunucu bunu bildirmiyorsa (çoğu henüz bildirmiyor) ya da hiç URL geçirmediyseniz sağlayıcı sessizce dinamik kayda geri döner ve yukarıdaki her şey tam anlatıldığı gibi çalışır. Saklanmış client_info yine de ikisinin de önüne geçer.
URL, kök olmayan bir yola sahip HTTPS olmalıdır; başka her şey, herhangi bir ağ trafiği olmadan oluşturma sırasında bir ValueError'dır. Depodaki examples/clients/simple-auth-client/ bunu MCP_CLIENT_METADATA_URL ortam değişkeni olarak alır.
Makineden makineye
Bir gece görevi, bir CI adımı, başka bir servis. Tarayıcı yok, "allow" düğmesine tıklayacak kimse de yok. Bu client credentials yetkilendirme türüdür: elinizde zaten bir client_id ve bir client_secret vardır, akışın tamamı da token endpoint'idir.
ClientCredentialsOAuthProvider aynı httpx2.Auth'tur, insan hariç:
import httpx2
from mcp import Client
from mcp.client.auth.extensions.client_credentials import ClientCredentialsOAuthProvider
from mcp.client.streamable_http import streamable_http_client
from mcp.shared.auth import OAuthClientInformationFull, OAuthToken
class InMemoryTokenStorage:
def __init__(self) -> None:
self.tokens: OAuthToken | None = None
self.client_info: OAuthClientInformationFull | None = None
async def get_tokens(self) -> OAuthToken | None:
return self.tokens
async def set_tokens(self, tokens: OAuthToken) -> None:
self.tokens = tokens
async def get_client_info(self) -> OAuthClientInformationFull | None:
return self.client_info
async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
self.client_info = client_info
oauth = ClientCredentialsOAuthProvider(
server_url="http://localhost:8001/mcp",
storage=InMemoryTokenStorage(),
client_id="reporting-agent",
client_secret="...",
scope="user",
)
async def main() -> None:
async with httpx2.AsyncClient(auth=oauth, follow_redirects=True) as http_client:
transport = streamable_http_client("http://localhost:8001/mcp", http_client=http_client)
async with Client(transport) as client:
result = await client.list_tools()
print([tool.name for tool in result.tools])
Neler değişti:
OAuthClientMetadatayok, işleyiciler yok.client_idveclient_secretgeçirirsiniz; sağlayıcı bunların etrafında asgari birclient_credentialskaydı oluşturur ve dinamik kaydı tamamen atlar.scope, boşlukla ayrılmış bir dizedir; OAuth'un iletilen verideki biçimi budur.- Bundan sonraki her şey aynıdır: aynı
TokenStorage, aynıhttpx2.AsyncClient(auth=...), aynıstreamable_http_client.
Varsayılan olarak sır, token isteğinde HTTP Basic kimlik doğrulaması olarak gider (client_secret_basic). Onu bunun yerine form gövdesine koymak için token_endpoint_auth_method="client_secret_post" geçirin. Bazı yetkilendirme sunucuları ikisinden yalnızca birini kabul eder.
Tip
client_secret'ı ortamdan ya da bir sır yöneticisinden okuyun, asla kaynak kontrolünden değil.
Info
mcp.client.auth.extensions.client_credentials içinde bir sağlayıcı daha var:
paylaşılan bir sır yerine JWT ile kimlik doğrulayan istemciler için PrivateKeyJWTOAuthProvider
(private_key_jwt; anahtar çifti ve iş yükü kimliği türü). Aynı kalıbı izler:
bir tane oluşturun, auth='a koyun. Aynı modül, onun assertion'ını oluşturan iki yardımcıyı da
sunar: SignedJWTParameters ve static_assertion_provider.
İnsansız bir durum daha var: istemci, hangi MCP sunucularına erişebileceğine kullanıcının değil kimlik sağlayıcısının karar verdiği bir kuruluşa aittir. Bu, kendi güven modeli ve kendi sayfası olan farklı bir yetkilendirme türüdür: Kimlik beyanı.
Başarısız olduğunda
OAuth akışı ters gittiğinde sağlayıcı, mcp.client.auth içinden bir OAuthFlowError fırlatır. İki alt sınıfı vardır. OAuthRegistrationError, kaydın kullanabileceğiniz bir istemci üretmediği anlamına gelir: yetkilendirme sunucusu sizi kaydetmeyi reddetti ya da kaydetti ama bu akışın kullanamayacağı kimlik bilgileriyle (örneğin uygulamadığı bir kimlik doğrulama yöntemiyle). OAuthTokenError ise bir token alınamadığı anlamına gelir: token endpoint'i hayır dedi ya da saklanan bir istemci kaydı bu istemcinin uygulayamayacağı bir kimlik doğrulama yöntemi taşıyor; bu durum gönderilmek yerine token isteği oluşturulurken bildirilir. Tek bir except OAuthFlowError: keşfi, kaydı, yetkilendirmeyi ve değişimi kapsar.
Her şey bir akış hatası değildir. Ağ yine de başarısız olabilir; bunlar sıradan httpx2 istisnalarıdır ve dokunulmadan geçer.
Özet
OAuthClientProviderbirhttpx2.Auth'tur. Onu birhttpx2.AsyncClient'a koyun, bunustreamable_http_client(url, http_client=...)'a geçirin;ClientOAuth'un gerçekleştiğini hiç bilmez.- Dört şey sağlarsınız: sunucu URL'si, bir
OAuthClientMetadata, birTokenStorageve redirect/callback işleyici çifti. TokenStoragebirProtocol'dür: dört asenkron metot, taban sınıf yok. Token'ların yanı sıraclient_info'yu da kalıcı hâle getirin.- Keşif, kayıt (dinamik ya da bir Client ID Metadata Document aracılığıyla), PKCE,
stateveissdenetimleri ile token yenileme sağlayıcının işidir, sizin değil. ClientCredentialsOAuthProviderinsansız sürümdür:client_id+client_secret, işleyici yok, tarayıcı yok.- Her OAuth hatası bir
OAuthFlowError'dır;OAuthRegistrationErrorveOAuthTokenErroronun alt sınıflarıdır.
Bu el sıkışmanın diğer yarısı, yani sunucunuzun token talep etmesini sağlamak Yetkilendirme sayfasındadır.