Autorização
Tradução automática
Esta página foi traduzida automaticamente a partir da documentação em inglês, e a página em inglês é a versão de referência. Se algo parecer errado, Traduções explica como avisar.
Sobre Streamable HTTP, seu servidor MCP é um serviço web comum, e você o protege como protege qualquer serviço web: com bearer tokens do OAuth 2.1.
Nos termos do OAuth, seu servidor é um resource server. Ele nunca autentica ninguém e nunca emite um token. Ele faz uma coisa só: olha o header Authorization de cada requisição e decide se o token que está ali é válido.
Esta página é o lado do servidor. Um cliente que descobre seu servidor de autorização e busca o token está em Clientes OAuth.
As três partes
- O servidor de autorização autentica as pessoas e emite tokens de acesso. Você não escreve isso. É o seu provedor de identidade (Auth0, Keycloak, Entra, o seu próprio).
- O resource server é o seu servidor MCP. Ele verifica o token em cada requisição.
- O cliente descobre em qual servidor de autorização você confia, obtém um token dele e o envia de volta para você como
Authorization: Bearer <token>.
O triângulo inteiro é esse. Tudo nesta página é o item do meio.
Um verificador de tokens
O SDK não tem opinião sobre como é um token válido. Você diz a ele, implementando TokenVerifier:
from pydantic import AnyHttpUrl
from mcp.server import MCPServer
from mcp.server.auth.provider import AccessToken, TokenVerifier
from mcp.server.auth.settings import AuthSettings
KNOWN_TOKENS = {
"alice-token": AccessToken(token="alice-token", client_id="alice", scopes=["notes:read"]),
}
class StaticTokenVerifier(TokenVerifier):
async def verify_token(self, token: str) -> AccessToken | None:
return KNOWN_TOKENS.get(token)
mcp = MCPServer(
"Notes",
token_verifier=StaticTokenVerifier(),
auth=AuthSettings(
issuer_url=AnyHttpUrl("https://auth.example.com"),
resource_server_url=AnyHttpUrl("http://127.0.0.1:8000/mcp"),
required_scopes=["notes:read"],
),
)
@mcp.tool()
def list_notes() -> list[str]:
"""List every note in the notebook."""
return ["Buy milk", "Ship the release"]
TokenVerifieré um protocolo com um único método assíncrono.verify_tokenrecebe o token bruto do headerAuthorizatione retorna umAccessTokense ele for válido,Nonese não for. Não há mais nada a implementar.- Este aqui procura o token em uma tabela. Um de verdade verifica a assinatura de um JWT ou chama o endpoint de introspecção de tokens do servidor de autorização. Esse código é seu; o SDK apenas o chama.
token_verifier=eauth=sempre andam juntos. Passe um sem o outro eMCPServer(...)levanta umValueErrorantes mesmo de atender uma requisição.
AuthSettings é a face pública do seu resource server:
issuer_url: o servidor de autorização que emite seus tokens.resource_server_url: a URL pública deste endpoint MCP. Ela indica a qual recurso um token se destina, e é onde fica o documento de descoberta.required_scopes: todo token deve conter todos eles.
Tip
examples/servers/simple-auth/ no repositório do SDK tem um IntrospectionTokenVerifier que chama
o endpoint da RFC 7662 de um servidor de autorização real. É o formato que a maioria dos verificadores de produção tem.
O que você recebe sobre HTTP
A autorização vive em headers HTTP, então só existe nos transportes HTTP. Execute-a no transporte em que você faz o deploy: mcp.run(transport="streamable-http") a coloca em http://127.0.0.1:8000/mcp, e Executando seu servidor tem o resto. O app agora tem duas rotas:
/mcp
/.well-known/oauth-protected-resource/mcp
Você registrou uma ferramenta. A segunda rota é do SDK.
Descoberta
Faça um GET nesse caminho well-known e você recebe o Protected Resource Metadata da RFC 9728, montado direto a partir do seu AuthSettings:
{
"resource": "http://127.0.0.1:8000/mcp",
"authorization_servers": ["https://auth.example.com/"],
"scopes_supported": ["notes:read"],
"bearer_methods_supported": ["header"]
}
Esse documento é como um cliente que nunca ouviu falar do seu servidor encontra o caminho de entrada: ele lê authorization_servers e vai até lá buscar um token. Você não escreveu nada disso.
Check
Chame /mcp sem token (ou com um para o qual seu verificador retornou None) e a requisição é
barrada na porta:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="invalid_token", error_description="Authentication required", resource_metadata="http://127.0.0.1:8000/.well-known/oauth-protected-resource/mcp"
{"error": "invalid_token", "error_description": "Authentication required"}
Nada foi parseado e nenhuma ferramenta foi executada. E aquele ponteiro resource_metadata em WWW-Authenticate é
o que torna a descoberta automática: 401 -> documento de metadados -> servidor de autorização -> token -> nova tentativa.
Warning
Nada disso protege o stdio. Um pipe não tem header Authorization, então token_verifier nunca é
consultado ali. A fronteira de segurança de um servidor stdio é o processo que o iniciou. O mesmo
vale para o Client(mcp) em memória que você usa nos testes: ele se conecta direto ao objeto do servidor
e pula a camada HTTP, autorização incluída.
A identidade de quem chama
Dentro de qualquer handler, get_access_token() é o AccessToken que seu verificador retornou para a requisição atual:
from pydantic import AnyHttpUrl
from mcp.server import MCPServer
from mcp.server.auth.middleware.auth_context import get_access_token
from mcp.server.auth.provider import AccessToken, TokenVerifier
from mcp.server.auth.settings import AuthSettings
KNOWN_TOKENS = {
"alice-token": AccessToken(token="alice-token", client_id="alice", scopes=["notes:read"]),
}
class StaticTokenVerifier(TokenVerifier):
async def verify_token(self, token: str) -> AccessToken | None:
return KNOWN_TOKENS.get(token)
mcp = MCPServer(
"Notes",
token_verifier=StaticTokenVerifier(),
auth=AuthSettings(
issuer_url=AnyHttpUrl("https://auth.example.com"),
resource_server_url=AnyHttpUrl("http://127.0.0.1:8000/mcp"),
required_scopes=["notes:read"],
),
)
@mcp.tool()
def whoami() -> str:
"""Report which OAuth client is calling."""
token = get_access_token()
if token is None:
return "anonymous"
return f"{token.client_id} (scopes: {', '.join(token.scopes)})"
- Funciona em ferramentas, recursos e prompts, e não há nada para passar adiante: o middleware de autenticação o guarda em uma variável de contexto por requisição.
- Você recebe de volta o mesmo objeto que seu verificador montou:
client_id,scopes,subject,expires_ate quaisquerclaimsextras que você anexou. Esse é o gancho para regras por ferramenta: leia os escopos e recuse. - Fora de uma requisição HTTP autenticada, ele retorna
None. Em memória e sobrestdio, é sempreNone.
Chame whoami com Authorization: Bearer alice-token e o modelo lê:
alice (scopes: notes:read)
A metade que o SDK não faz
O SDK entrega a metade do resource server: verificar, anunciar, recusar. Ele não entrega uma página de login, uma tela de consentimento nem um token.
Para ver as três partes em ação, execute examples/servers/simple-auth/ do repositório do SDK (um pequeno servidor de autorização e um resource server configurado exatamente como nesta página) e então aponte examples/clients/simple-auth-client/ para ele e veja a dança completa de descoberta e token.
Info
Existe um segundo argumento do construtor, auth_server_provider=, que embute um servidor de autorização
completo dentro do seu servidor MCP. Ele é anterior à separação AS/RS em torno da qual a especificação
de autorização do MCP foi construída. Servidores novos não devem recorrer a ele.
Um servidor de autorização também pode aceitar a asserção assinada de um provedor de identidade corporativo no lugar de um usuário clicando em uma tela de consentimento, e o SDK dá suporte aos dois lados dessa troca. O grant, e o cliente que o apresenta, está em Asserção de identidade.
Recapitulando
- Sobre Streamable HTTP, seu servidor é um resource server do OAuth 2.1: ele verifica tokens, nunca os emite.
TokenVerifieré toda a superfície de integração: um método assíncrono, token entra,AccessToken | Nonesai.token_verifier=eauth=AuthSettings(issuer_url=..., resource_server_url=..., required_scopes=[...])sempre andam juntos.- O SDK publica o Protected Resource Metadata da RFC 9728 em
/.well-known/oauth-protected-resource/...e responde a requisições não autenticadas com um 401 cujo headerWWW-Authenticateaponta para ele. A história da descoberta é toda essa. get_access_token()em qualquer handler diz quem está chamando.- Autorização é assunto do HTTP. O
stdioe o cliente em memória nunca a veem.
A metade do cliente (descobrir seu servidor de autorização e buscar o token para você) está em Clientes OAuth. E um cliente que afirma uma identidade em vez de pedir uma ao usuário está em Asserção de identidade.