Servidores MCP
Proteja um servidor MCP para que clientes MCP como o Claude Code encontrem o seu realm, façam o login da pessoa e chamem as suas ferramentas com um token só do seu servidor.
No modelo de autorização do Model Context Protocol, o seu servidor MCP é um servidor de recurso OAuth e o Authio é o servidor de autorização dele. O servidor nunca faz o login de ninguém: um cliente MCP — o Claude Code, uma IDE, o MCP Inspector — descobre o Authio a partir do servidor, faz o login da pessoa pelo login hospedado e chama as suas ferramentas com um access token cuja audiência é o seu servidor.
Dois pacotes implementam o lado do servidor: o authio-mcp 0.1.0 para Node.js, sobre o SDK oficial do MCP, e o Authio.Mcp.AspNetCore 0.1.0 para ASP.NET Core. Os dois validam tokens, publicam os metadados que os clientes MCP procuram e respondem com os desafios que iniciam o fluxo OAuth.
Como um cliente MCP encontra o Authio
- O cliente chama o servidor sem token. O servidor responde
401com um cabeçalhoWWW-Authenticate: Bearercujoresource_metadataaponta para os metadados de recurso protegido dele. - O cliente lê esse documento (RFC 9728), servido em
/.well-known/oauth-protected-resourceseguido do caminho do endpoint. Ele indica o realm como servidor de autorização. - Ele lê os metadados do servidor de autorização do realm (RFC 8414) no endereço com o caminho inserido, que os clientes MCP tentam primeiro,
https://authio.api.hyperio.tech/.well-known/oauth-authorization-server/realms/acme; o endereço de discovery OpenID Connect do realm também responde. - Ele se identifica com um client ID que você registrou para ele, ou com a URL do próprio Client ID Metadata Document, se o realm aceitar esse tipo. O Authio não oferece registro dinâmico de clients.
- Ele roda o fluxo de código de autorização com PKCE e
resource=https://mcp.example.com/mcp(RFC 8707). O access token leva esse identificador emaud, inútil em qualquer outro servidor. - O seu servidor confere a assinatura com as chaves do realm, o emissor, a audiência, a validade e os escopos exigidos, e depois pergunta ao endpoint de introspecção do Authio se o token continua ativo — no máximo uma vez a cada 30 segundos por token.
{
"resource": "https://mcp.example.com/mcp",
"authorization_servers": ["https://authio.api.hyperio.tech/realms/acme"],
"scopes_supported": ["mcp:tools"],
"bearer_methods_supported": ["header"],
"resource_name": "Acme tools"
}Configure o realm
Registre o servidor como servidor de recurso
Em Servidores de recurso, defina o Identifier como a URL pública do endpoint MCP, por exemplo
https://mcp.example.com/mcp, e acrescente um escopo comomcp:tools. O identificador vira oaudde todo token e não pode ser trocado depois; ele é gravado com esquema e host em minúsculas, sem porta padrão e sem barra no final.Crie o client do próprio servidor
Em Clients, crie um client confidencial para o servidor (
client_secret_basic) e escolha-o como Introspection client do servidor de recurso. O ID e o segredo dele vão na configuração do servidor. Dê a ele o grant de token exchange se as ferramentas forem usar contas conectadas.Deixe os clientes MCP entrarem
Registre um client para cada cliente MCP que você usa e coloque-o em Allowed clients, ou ligue Accept CIMD clients para que clientes MCP que publicam um documento de metadados possam pedir tokens. Os dois caminhos estão descritos a seguir.
Clients pré-registrados
- Um client público com o grant
authorization_code, PKCE exigido eS256permitido. - O redirect URI de cada cliente MCP: o Claude Code usa
http://localhost:<callback-port>/callback, o MCP Inspectorhttp://localhost:6274/oauth/callback. Ligue Loopback on any port para clientes nativos que escolhem a porta. - Require OpenID Connect desligado, porque clientes MCP costumam pedir só o seu escopo — ou faça-os pedir
openidtambém. - O client listado em Allowed clients no servidor de recurso; sem isso, o pedido de autorização falha com
invalid_target.
Depois, passe ao Claude Code o client ID e a porta de callback que você registrou:
claude mcp add --transport http --client-id acme-claude-code --callback-port 8765 acme-tools https://mcp.example.com/mcpClient ID Metadata Documents
Um Client ID Metadata Document (CIMD) permite que um cliente MCP se identifique com uma URL https que serve uma descrição JSON dele — nome e redirect URIs — em vez de um client ID criado por você. A URL é a identidade: um documento que informa outro client_id é recusado.
- Ligue por realm: nas configurações MCP clients (CIMD) do realm, ative Accept clients by metadata document (CIMD). Vem desligado por padrão.
- Se quiser, limite os Allowed hosts. Vazio aceita qualquer host
httpspúblico;*.example.comcobre os subdomínios deexample.com, mas não o próprioexample.com. - Ligue Accept CIMD clients em cada servidor de recurso que esses clients podem alcançar.
- O client é registrado no primeiro uso, é sempre público, sempre mostra a tela de consentimento e acompanha o documento. Desligar a configuração, ou tirar o host dele da lista, o barra já na próxima requisição.
- O documento de discovery do realm anuncia
client_id_metadata_document_supported: trueenquanto a configuração estiver ligada.
Node.js: authio-mcp
O authio-mcp precisa do Node.js 20.9 ou mais recente e é só ESM. O express só é necessário para os helpers de authio-mcp/express.
npm install authio-mcp @modelcontextprotocol/sdk expresscreateAuthio lê os metadados do realm na partida, então base URL, realm ou emissor errados param o servidor com uma frase, em vez de todo token ser recusado depois. authioMetadataRouter publica os metadados de recurso protegido e responde ao CORS de clientes no navegador, como o MCP Inspector; monte-o na raiz. callerOf(extra.authInfo) entrega a cada ferramenta subject, clientId, scopes, organizationId, organizationAlias, expiresAt e o token bruto.
Não usa Express? authio.verifier é um OAuthTokenVerifier para o middleware bearer do SDK do MCP, e authio.metadata são os metadados do servidor de autorização do realm.
ASP.NET Core: Authio.Mcp.AspNetCore
O pacote atende .NET 8 e .NET 10 e não depende do SDK do MCP, então protege qualquer endpoint; aqui ele fica na frente do SDK oficial em C#.
dotnet add package Authio.Mcp.AspNetCore
dotnet add package ModelContextProtocol.AspNetCoreGuarde o segredo em user-secrets ou na variável de ambiente Authio__Mcp__ClientSecret. AddAuthioMcp(options => …) ajusta qualquer coisa no código depois de a configuração ser lida, e ClaimsPrincipal.HasAuthioScopes(scopes) confere um escopo por ferramenta.
| Opção no Node | Configuração no .NET | Significado |
|---|---|---|
baseUrl | BaseUrl | Onde o Authio responde, sem o realm. Precisa ser a base que o Authio publica no emissor. |
realm | Realm | O realm em que o servidor de recurso está. |
resourceUrl | ResourceUrl | A URL pública do endpoint MCP: o identificador do recurso e o aud de todo token. |
clientId, clientSecret | ClientId, ClientSecret | O client de introspecção do servidor de recurso; usado também nas trocas por contas conectadas. |
requiredScopes | RequiredScopes | Escopos que todo token precisa ter. Padrão mcp:tools. |
resourceName | ResourceName | O nome que os metadados dão ao servidor. |
introspectionTtlMs | IntrospectionCacheDuration | Por quanto tempo uma resposta ativa vale. Padrão de 30 segundos. |
O que o servidor responde
| Situação | Status | WWW-Authenticate leva |
|---|---|---|
| Sem token | 401 | resource_metadata |
| Token inválido, expirado, revogado ou emitido para outro recurso | 401 | error="invalid_token", uma descrição e resource_metadata |
| Token sem os escopos exigidos | 403 | error="insufficient_scope" e os escopos necessários |
Clientes que rodam no navegador, como o MCP Inspector, precisam ler esse cabeçalho de outra origem. Os dois pacotes expõem WWW-Authenticate pelo CORS — o router do Express por padrão, o ASP.NET Core com AddAuthioMcpPolicy e RequireCors.
Use as contas conectadas da pessoa
Uma ferramenta pode trocar o token Authio de quem chama pelo token de uma conta que a pessoa conectou no Authio — GitHub, Google Drive e outros — sem guardar nenhuma credencial de terceiros. Chame authio.exchangeConnection({ subjectToken, provider }) no Node ou IAuthioConnectionExchange.ExchangeAsync(subjectToken, provider, connectionId) no .NET, em que provider é o alias do provedor de conexão.
| Resultado | O que dizer à pessoa |
|---|---|
| Concedido | Nada: use o access token do provedor. O Authio nunca entrega o refresh token do provedor. |
| Conexão necessária | O endereço em que ela conecta a conta (connectUri). |
| Escolha necessária | As contas para escolher; chame de novo com um connectionId. |
| Recusado | O motivo, como access_denied quando o plano não inclui contas conectadas. |
No Node, describeConnectionRefusal(result, 'GitHub') transforma em frase para a pessoa qualquer resultado que não seja concedido.
Teste de ponta a ponta
curl -i -X POST https://mcp.example.com/mcp
curl https://mcp.example.com/.well-known/oauth-protected-resource/mcp
curl https://authio.api.hyperio.tech/.well-known/oauth-authorization-server/realms/acmeA primeira chamada deve responder 401 com resource_metadata, a segunda indicar o seu realm, a terceira descrevê-lo. Depois conecte um cliente de verdade: acrescente o servidor ao Claude Code como mostrado acima, ou abra o MCP Inspector com npx @modelcontextprotocol/inspector, aponte-o para o endpoint e inicie o fluxo OAuth pelas configurações de conexão.
Solução de problemas
| O que aparece | Por quê | O que fazer |
|---|---|---|
invalid_target no pedido de autorização | O cliente MCP não pode pedir este recurso. | Coloque o client dele em Allowed clients, ou ligue Accept CIMD clients para um client CIMD. |
invalid_scope — scope precisa incluir openid | O client exige OpenID Connect e o cliente MCP pediu só o seu escopo. | Desligue Require OpenID Connect nesse client. |
| O login hospedado recusa o redirect URI | O callback do cliente MCP não está registrado exatamente. | Registre-o com a porta, ou ligue Loopback on any port. |
| O servidor não sobe: o Authio informa outro emissor | baseUrl não é a base que o Authio publica no emissor. | Use o emissor sem /realms/acme. |
| Todo token recusado: não foi emitido para este recurso | resourceUrl difere do identificador do servidor de recurso. | Deixe os dois idênticos, com esquema, host e caminho. |
Toda requisição falha com 500 | O Authio recusou o pedido de introspecção. | Confira o ID e o segredo do client de introspecção. |