Pular para o conteúdo

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

  1. O cliente chama o servidor sem token. O servidor responde 401 com um cabeçalho WWW-Authenticate: Bearer cujo resource_metadata aponta para os metadados de recurso protegido dele.
  2. O cliente lê esse documento (RFC 9728), servido em /.well-known/oauth-protected-resource seguido do caminho do endpoint. Ele indica o realm como servidor de autorização.
  3. 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.
  4. 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.
  5. 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 em aud, inútil em qualquer outro servidor.
  6. 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.
GET /.well-known/oauth-protected-resource/mcpJSON
{
  "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

Servidores de recurso e os tokens emitidos para eles exigem a integração MCP no plano da sua conta, e contas conectadas exigem um recurso próprio. Veja Planos e cobrança.
  1. 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 como mcp:tools. O identificador vira o aud de 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.

  2. 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.

  3. 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 e S256 permitido.
  • O redirect URI de cada cliente MCP: o Claude Code usa http://localhost:<callback-port>/callback, o MCP Inspector http://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 openid també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:

TerminalBash
claude mcp add --transport http --client-id acme-claude-code --callback-port 8765 acme-tools https://mcp.example.com/mcp

Client 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 https público; *.example.com cobre os subdomínios de example.com, mas não o próprio example.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: true enquanto 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.

TerminalBash
npm install authio-mcp @modelcontextprotocol/sdk express

createAuthio 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#.

TerminalBash
dotnet add package Authio.Mcp.AspNetCore
dotnet add package ModelContextProtocol.AspNetCore

Guarde 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 NodeConfiguração no .NETSignificado
baseUrlBaseUrlOnde o Authio responde, sem o realm. Precisa ser a base que o Authio publica no emissor.
realmRealmO realm em que o servidor de recurso está.
resourceUrlResourceUrlA URL pública do endpoint MCP: o identificador do recurso e o aud de todo token.
clientId, clientSecretClientId, ClientSecretO client de introspecção do servidor de recurso; usado também nas trocas por contas conectadas.
requiredScopesRequiredScopesEscopos que todo token precisa ter. Padrão mcp:tools.
resourceNameResourceNameO nome que os metadados dão ao servidor.
introspectionTtlMsIntrospectionCacheDurationPor quanto tempo uma resposta ativa vale. Padrão de 30 segundos.

O que o servidor responde

SituaçãoStatusWWW-Authenticate leva
Sem token401resource_metadata
Token inválido, expirado, revogado ou emitido para outro recurso401error="invalid_token", uma descrição e resource_metadata
Token sem os escopos exigidos403error="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.

ResultadoO que dizer à pessoa
ConcedidoNada: use o access token do provedor. O Authio nunca entrega o refresh token do provedor.
Conexão necessáriaO endereço em que ela conecta a conta (connectUri).
Escolha necessáriaAs contas para escolher; chame de novo com um connectionId.
RecusadoO 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

TerminalBash
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/acme

A 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 aparecePor quêO que fazer
invalid_target no pedido de autorizaçãoO 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 openidO 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 URIO 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 emissorbaseUrl 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 recursoresourceUrl difere do identificador do servidor de recurso.Deixe os dois idênticos, com esquema, host e caminho.
Toda requisição falha com 500O Authio recusou o pedido de introspecção.Confira o ID e o segredo do client de introspecção.

Próximos passos

Cookies de analytics

Gostaríamos de medir como o console é usado, com o Google Analytics, para enxergar em quais telas as pessoas travam. Isso grava um cookie que reconhece este navegador na sua próxima visita.

Recusar não muda mais nada. Os cookies que mantêm você conectado e lembram o seu idioma e o seu tema são necessários para o console funcionar, e ficam de qualquer forma. Ler a Política de Privacidade