OAuth clients
मशीनी अनुवाद
यह page अंग्रेज़ी documentation से अपने-आप अनुवादित किया गया है, और अंग्रेज़ी page ही प्रामाणिक version है। अगर कुछ गलत लगे, तो अनुवाद page बताता है कि इसकी सूचना कैसे दें।
कुछ MCP servers सुरक्षित होते हैं। उन्हें बिना token के request भेजें तो जवाब 401 Unauthorized आता है।
token पाने का तरीका OAuthClientProvider है। यह कोई MCP object है ही नहीं। यह httpx2.Auth है, "हर request के साथ कुछ करो" वाला httpx2 का standard hook। इसे httpx2.AsyncClient पर लगाएँ, वह client Streamable HTTP transport को दें, और इसके बारे में सोचना बंद कर दें।
यह page client वाला हिस्सा है। अपने server से token की माँग करवाना Authorization में है।
Provider
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])
आप इसे चार चीज़ें देते हैं:
server_url: वह MCP endpoint जिससे आप जुड़ रहे हैं। बाकी सब कुछ provider इसी से खोज लेता है।client_metadata: वही जो आप किसी authorization server के "register an application" form में भरते।storage: जहाँ दो runs के बीच tokens रहते हैं।redirect_handlerऔरcallback_handler: वे दो पल जिनमें कोई इंसान शामिल होता है।
file में और कहीं OAuth का ज़िक्र नहीं है। main() को कभी कोई token दिखता ही नहीं।
Client metadata
OAuthClientMetadata असली RFC 7591 registration document है, Pydantic model के रूप में।
आप तीन fields भरते हैं। बाकी defaults भर देते हैं: grant_types पहले से ["authorization_code", "refresh_token"] है और response_types पहले से ["code"] है, ठीक वही flow जो यह provider चलाता है।
Check
चूँकि यह Pydantic model है, यह network पर एक भी byte जाने से पहले validate करता है।
redirect_uris छोड़ दें तो construction वहीं के वहीं ValidationError के साथ fail हो जाता है,
जो field का नाम बताता है:
redirect_uris
Field required [type=missing, input_value={'client_name': 'Bookshop Agent'}, input_type=dict]
न कोई browser खुला, न authorization server पर कोई अधूरी registration पीछे छूटी।
Token storage
TokenStorage चार async methods वाला Protocol है। आपको किसी से inherit नहीं करना; methods लिख दें और कोई भी class token store बन जाती है:
get_tokens/set_tokensOAuthTokenरखते हैं: access token, refresh token, expiry, scope।get_client_info/set_client_infoवहOAuthClientInformationFullरखते हैं जो authorization server ने तब जारी किया था जब provider ने आपको register किया, आपकेclient_idसमेत।
ऊपर वाला in-memory version काम करता है। यह process के ख़त्म होते ही सब कुछ भूल भी जाता है, इसलिए अगला run पूरी प्रक्रिया फिर से दोहराता है। इसे किसी file या अपने platform के keyring में persist करें और अगला run चुपचाप चल जाता है।
Tip
सिर्फ़ tokens नहीं, client_info भी store करें। provider पहली बार तब dynamically register करता है
जब उसे कोई stored client_info नहीं मिलता। इसे फेंक दें तो हर run पर एक नई registration बनती है।
दो handlers
authorization code flow को इंसान की ज़रूरत ठीक एक बार पड़ती है: किसी को sign in करके "allow" पर click करना होता है।
redirect_handlerको पूरी तरह बने हुए authorization URL के साथ await किया जाता है।client_id,redirect_uri,stateऔर PKCE challenge उसमें पहले से मौजूद हैं। आपका काम बस इतना है कि browser को वहाँ पहुँचाएँ। desktop appwebbrowser.opencall करता है; यह file उसे print कर देती है।- उसके बाद
callback_handlerawait होता है। यह तब तक इंतज़ार करता है जब तक user वापस आपकेredirect_uriपर नहीं पहुँच जाता, और उस redirect के query parameters कोAuthorizationCodeResultके रूप में लौटाता है।
असली client input() call करने के बजाय redirect URI पर एक छोटा local HTTP server चलाता है। आकार बिल्कुल वही है: redirect हों, और code, state व iss वापस दें।
Warning
state और iss को ठीक वैसे ही आगे दें जैसे वे आए थे। provider state की तुलना उससे करता है
जो उसने खुद बनाया था, और iss की तुलना खोजे गए issuer से, और मेल न खाने पर मना कर देता है।
यही CSRF और server-mix-up से बचाव हैं।
Client में
main() देखें। provider httpx2 client पर जाता है, httpx2 client streamable_http_client(url, http_client=...) में जाता है, और वह transport Client में जाता है।
streamable_http_client में auth= keyword नहीं है। HTTP स्तर की हर चीज़ (auth, headers, timeouts, proxies) उस httpx2.AsyncClient पर होती है जो आप लाते हैं। यह layering Client transports में है।
Provider आपके लिए क्या करता है
जब Client पहली बार request भेजता है, server 401 लौटाता है। provider कमान संभाल लेता है:
- Discovery. यह
WWW-Authenticateheader पढ़ता है,/.well-known/oauth-protected-resourceसे server का Protected Resource Metadata लाता है, पता करता है कि कौन सा authorization server इस resource की रक्षा करता है, और उस server का metadata लाता है। - Registration. storage में कुछ नहीं है? यह आपके
OAuthClientMetadataके साथ आपको dynamically register करता है और नतीजा store कर लेता है। - Authorization. यह PKCE pair और
stateबनाता है, authorization URL तैयार करता है, आपकेredirect_handlerको await करता है, फिर code के लिए आपकेcallback_handlerको await करता है। - Exchange. यह code के बदले
OAuthTokenलेता है, उसे store करता है, और आपकी मूल request कोAuthorization: Bearer ...के साथ दोबारा भेजता है।
उसके बाद यह शांत रहता है। tokens storage से आते हैं, expire हुआ access token refresh token से refresh हो जाता है, और सिर्फ़ तब जब इनमें से कुछ काम नहीं करता, यह flow फिर से चलाता है।
आपने इसमें से कुछ नहीं लिखा। दो keyword arguments बचते हैं (client_metadata_url और validate_resource_url), और इस file को दोनों में से किसी की ज़रूरत नहीं। client_metadata_url जानने लायक है; इसका अपना section नीचे है।
इसे आज़माएँ
इन docs के ज़्यादातर उदाहरण आप in-memory Client(server) से जाँच सकते हैं। यह नहीं: इस flow का पूरा मतलब ही HTTP 401 है, और in-memory client व उसके server के बीच कोई HTTP होता ही नहीं।
repository में live version मौजूद है। examples/servers/simple-auth/ एक standalone authorization server और एक protected MCP server चलाता है; examples/clients/simple-auth-client/ इसी page का client है, छोटी CLI में बढ़ा हुआ। उसकी README में दो commands हैं: servers शुरू करें, client को उनके सामने चलाएँ, और चारों चरण अपनी आँखों के सामने होते देखें।
Client ID Metadata Documents
spec का 2026-07-28 revision dynamic client registration को Client ID Metadata Documents (CIMD) के पक्ष में deprecated कर देता है। जिस भी authorization server से मिले उस पर नई registration POST करने के बजाय, आपका client अपने बारे में एक JSON document किसी स्थिर HTTPS URL पर publish करता है, और वही URL उसका client_id है। authorization server वह document लाता है; provider उसे कभी छूता तक नहीं।
SDK पहले से इसे समझता है: provider बनाते समय URL को client_metadata_url= के रूप में दें। जब authorization server का metadata client_id_metadata_document_supported: true बताता है, provider /register request को पूरी तरह छोड़ देता है: URL client_id बनकर flow में जाता है, और कोई client_secret नहीं होता। जब server इसे नहीं बताता (ज़्यादातर अभी नहीं बताते), या आप URL देते ही नहीं, तो provider चुपचाप dynamic registration पर लौट आता है, और ऊपर बताया सब कुछ ठीक वैसे ही काम करता है। stored client_info अब भी दोनों पर भारी पड़ती है।
URL HTTPS होना चाहिए और उसका path root न हो; इसके अलावा कुछ भी construction पर ValueError है, किसी network गतिविधि से पहले। साथ आने वाला examples/clients/simple-auth-client/ इसे MCP_CLIENT_METADATA_URL environment variable के रूप में लेता है।
Machine to machine
रात को चलने वाला job, CI का कोई step, कोई दूसरी service। न browser है, न "allow" पर click करने वाला कोई। यही client credentials grant है: आपके पास पहले से client_id और client_secret हैं, और token endpoint ही पूरा flow है।
ClientCredentialsOAuthProvider वही httpx2.Auth है, बस इंसान के बिना:
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])
क्या बदला:
- न
OAuthClientMetadata, न handlers। आपclient_idऔरclient_secretदेते हैं; provider उनके इर्द-गिर्द एक न्यूनतमclient_credentialsregistration बनाता है और dynamic registration पूरी तरह छोड़ देता है। scopespace से अलग की गई string है, OAuth का wire format।- आगे का सब कुछ बिल्कुल वही है: वही
TokenStorage, वहीhttpx2.AsyncClient(auth=...), वहीstreamable_http_client।
default रूप से secret token request पर HTTP Basic auth के रूप में जाता है (client_secret_basic)। उसे form body में डालने के लिए token_endpoint_auth_method="client_secret_post" दें। कुछ authorization servers दोनों में से सिर्फ़ एक ही स्वीकार करते हैं।
Tip
client_secret को environment या किसी secret manager से पढ़ें, source control से कभी नहीं।
Info
एक और provider mcp.client.auth.extensions.client_credentials में रहता है:
PrivateKeyJWTOAuthProvider, उन clients के लिए जो shared secret के बजाय JWT से
authenticate करते हैं (private_key_jwt, key-pair और workload-identity वाला रूप)। यह उसी
pattern पर चलता है: एक बनाएँ, auth= पर लगाएँ। उसी module में
SignedJWTParameters और static_assertion_provider भी हैं, दो helpers जो इसका assertion बनाते हैं।
बिना इंसान वाली एक और स्थिति है: client किसी enterprise का है जिसका identity provider, न कि user, तय करता है कि वह किन MCP servers तक पहुँच सकता है। वह अलग grant है, अपने trust model और अपने page के साथ, Identity assertion।
जब यह fail होता है
जब OAuth flow में गड़बड़ होती है, provider mcp.client.auth से OAuthFlowError raise करता है। इसके दो subclasses हैं। OAuthRegistrationError का मतलब है कि registration से ऐसा client नहीं मिला जिसे आप इस्तेमाल कर सकें: authorization server ने आपको register करने से मना कर दिया, या register तो किया लेकिन ऐसे credentials के साथ जो यह flow इस्तेमाल नहीं कर सकता (उदाहरण के लिए कोई authentication method जिसे यह implement नहीं करता)। OAuthTokenError का मतलब है कि token नहीं मिल सका: token endpoint ने मना कर दिया, या किसी stored client record में ऐसा authentication method है जिसे यह client लागू नहीं कर सकता, जिसकी report token request बनाते समय होती है, भेजी नहीं जाती। एक except OAuthFlowError: discovery, registration, authorization और exchange, सबको cover करता है।
हर चीज़ flow error नहीं होती। network अब भी fail हो सकता है; वे साधारण httpx2 exceptions हैं और बिना छुए आगे निकल जाते हैं।
सारांश
OAuthClientProviderhttpx2.Authहै। इसेhttpx2.AsyncClientपर लगाएँ, उसेstreamable_http_client(url, http_client=...)को दें, औरClientको कभी पता नहीं चलता कि OAuth हुआ।- आप चार चीज़ें देते हैं: server URL, एक
OAuthClientMetadata, एकTokenStorage, और redirect/callback handler की जोड़ी। TokenStorageProtocolहै: चार async methods, कोई base class नहीं। tokens के साथ-साथclient_infoभी persist करें।- discovery, registration (dynamic, या Client ID Metadata Document के ज़रिए), PKCE,
stateऔरissकी जाँच, और token refresh provider का काम हैं, आपका नहीं। ClientCredentialsOAuthProviderबिना इंसान वाला version है:client_id+client_secret, न handlers, न browser।- हर OAuth failure
OAuthFlowErrorहै;OAuthRegistrationErrorऔरOAuthTokenErrorइसके subclasses हैं।
इस handshake का दूसरा हिस्सा, अपने server से token की माँग करवाना, Authorization में है।