arrow-return

Como criar um MCP e submetê-lo ao Claude + Open AI

Como criar um MCP e submetê-lo ao Claude + Open AI
An image of Welton Silva, the author of this post
13 min de leitura

Cole um URL no Claude ou no ChatGPT. Aprove um ecrã de consentimento. A partir desse momento o assistente responde com os dados da própria conta no Semantika: como o ChatGPT, o Claude, o Gemini, o Perplexity e as AI Overviews da Google falam de uma marca, e o que corrigir primeiro. Sem API key para gerar, copiar ou colar. Nada para instalar. O Semantika é a plataforma de visibilidade em pesquisa por IA que construímos na Buzzvel. Os dois diretórios listam-no desde este mês.

Este artigo é a receita: o que os clientes esperam do seu servidor, as quatro peças que constrói por si, o passo que morde, e o que as revisões da Anthropic e da OpenAI pediram antes de o listar. É também um retrato justo do que custa pôr o seu próprio produto dentro do Claude e do ChatGPT. Parte do princípio de que sabe o que é o MCP: o protocolo que o Claude, o ChatGPT e outros assistentes usam para chamar ferramentas num servidor remoto por HTTP.

Stack, em setembro de 2026: Laravel 13, Passport 13, laravel/mcp 0.7.

O que um cliente MCP espera do seu servidor

Quando um utilizador cola o seu URL, o Claude e o ChatGPT fazem quatro coisas:

  1. Vão buscar um documento de discovery em um caminho .well-known no seu domínio. Diz onde registar, para onde enviar o utilizador para autorizar, e onde trocar códigos por tokens.

  2. Registam-se como cliente OAuth, na hora, com Dynamic Client Registration (RFC 7591). Não há nenhum painel onde se cole um client ID.

  3. Correm o fluxo authorization code com PKCE. O utilizador vê o seu ecrã de consentimento e aprova.

  4. Chamam o seu endpoint MCP com um token bearer e listam as suas ferramentas.

Os dois diretórios também leem as anotações das ferramentas. Uma ferramenta só de leitura passa por uma revisão mais leve do que uma que escreve.

Figura 1. O fluxo completo. Os passos a ciano vêm com o Passport e o laravel/mcp. Os passos a laranja são seus.

Passo 1. O Passport 13 é o servidor de autorização

O Passport 13 fala OAuth 2.1 de origem: authorization code com PKCE, e os grants password e implicit ficam desligados a menos que os ative. Não os ative. Instale o Passport, corra as migrações e defina um scope para o endpoint MCP.

// app/Providers/AppServiceProvider.php

use Laravel\Passport\Passport;

Passport::tokensCan([

'mcp:use' => 'Use the MCP tools for the selected workspaces',

]);

Um scope chega. A restrição de tenant vive no token, não no scope. Isso é o passo 3.

Passo 2. Discovery e registo de clientes, zero código

O laravel/mcp traz o documento de discovery e o endpoint de registo. Uma chamada publica os dois, uma segunda monta o servidor.

// routes/ai.php

use Laravel\Mcp\Facades\Mcp;

Mcp::oauthRoutes();

Mcp::web('/mcp', AppMcpServer::class)

->middleware(AuthenticateMcpRequest::class);

Depois disto, GET /.well-known/oauth-authorization-server responde, POST /oauth/register cria um cliente Passport, e o Claude e o ChatGPT encontram o caminho sozinhos. Resista a registar os dois clientes à mão. Qualquer outro cliente MCP que apareça no próximo ano deixaria de funcionar.

Passo 3. O ecrã de consentimento e a armadilha

O Passport dá-lhe o /oauth/authorize. O ecrã é seu. Numa aplicação single-tenant é um botão. No Semantika, um workspace guarda as marcas que uma equipa monitoriza, por isso o utilizador escolhe que workspaces o assistente pode ler. Um ou mais.

Aqui está a armadilha. O consentimento acontece no browser, dentro da sessão do utilizador. A troca de token que vem a seguir é um POST /oauth/token de servidor para servidor, vindo do backend da Anthropic ou da OpenAI. Sem cookie, sem sessão, sem memória do que o utilizador escolheu. Guarde a escolha na sessão e leia-a quando o token é criado, e não lê nada.

A solução é carimbar a escolha no código de autorização, no momento do consentimento, no pedido que cria o código.

// app/Http/Controllers/OAuth/ApproveAuthorizationController.php

public function approve(Request $request)

{

// Only workspaces this user can actually access.

$allowed = $request->user()->workspaces()

->whereIn('id', $request->input('workspace_ids', []))

->pluck('id')

->all();

abort_if($allowed === [], 422, 'Select at least one workspace.');

$request->session()->put('oauth.consent.workspace_ids', $allowed);

return parent::approve($request);

}
// app/Models/Passport/AuthCode.php

protected static function booted(): void

{

static::creating(function (self $code) {

$code->workspace_ids = session('oauth.consent.workspace_ids', []);

});

}

Registe o modelo com Passport::useAuthCodeModel(AuthCode::class) e acrescente uma coluna JSON. A linha do código passa a transportar a lista de tenants, e o código é a única coisa que o cliente devolve. Valide os ids contra o acesso real do utilizador, no servidor. O formulário pode ser editado.

Figura 2. A armadilha. Só o código atravessa do pedido de consentimento para a troca de token.

Passo 4. Copiar a escolha para o access token

Quando o Passport cria um access token, dispara AccessTokenCreated com os ids do token, do utilizador e do cliente. Escute-o, encontre o código que iniciou a troca e copie os ids.

// app/Listeners/AttachWorkspacesToAccessToken.php

public function handle(AccessTokenCreated $event): void

{

$code = $this->authCodeFromTokenRequest($event);

Token::find($event->tokenId)

?->forceFill(['workspace_ids' => $code?->workspace_ids ?? []])

->save();

}

O pedido de token transporta o código no campo code, encriptado com a chave da aplicação. Desencripte-o, leia auth_code_id, carregue a linha. Mantenha um fallback para o código não revogado mais recente do mesmo utilizador e cliente, e copie do access token anterior no refresh. Salte este passo e todos os tokens ficam cegos ao seu tenant.

Passo 5. Um middleware, três formatos de bearer

Se a sua aplicação já tem uma API, tem tokens à solta. O Semantika tinha personal access tokens criados na interface e API keys de workspace mais antigas, usadas por integrações headless como o Looker Studio. O endpoint MCP tinha de aceitar ambos mais os novos tokens OAuth, ou a mudança partiria alguma coisa a alguém. Um middleware, despacho por prefixo, OAuth como ramo por omissão.

public function handle(Request $request, Closure $next): Response

{

$bearer = (string) $request->bearerToken();

return match (true) {

str_starts_with($bearer, 'pat_') => $this->personalAccessToken($bearer, $request, $next),

str_starts_with($bearer, 'key_') => $this->legacyApiKey($bearer, $request, $next),

default => $this->oauthToken($request, $next),

};

}

O ramo OAuth autentica pelo guard api, verifica tokenCan('mcp:use'), lê workspace_ids da linha do token e liga-os ao pedido. Cada ferramenta lê os workspaces ligados e nada mais.

Figura 3. Um middleware, três formatos de bearer.

Passo 6. Ferramentas que um modelo e uma pessoa conseguem ler

As ferramentas são classes simples. Anote cada uma.

use Laravel\Mcp\Server\Tool;

use Laravel\Mcp\Server\Tools\Annotations\IsReadOnly;

#[IsReadOnly]

class ListRecommendedActions extends Tool

{

protected string $description = 'List the open recommendations for the selected workspaces, newest first.';

public function handle(Request $request): Response

{

$lines = $this->actions()->map(fn ($a) =>

"- [{$a->title}]({$a->url}) · {$a->impact} · due {$a->due_at->toDateString()}"

);

return Response::text($lines->implode("\n"));

}

}

Duas coisas neste excerto custaram mais tempo do que todo o fluxo OAuth. A descrição é escrita para o modelo: diz o que a ferramenta devolve e por que ordem, porque o modelo escolhe ferramentas a ler estas strings. E cada linha leva um link markdown de volta para a aplicação, [label](url), nunca um URL nu e nunca um ID solto. Sem isso, a pessoa acaba a conversa com três IDs numéricos na mão e sítio nenhum onde clicar. Essa veio de observar uma sessão real, não de uma especificação.

As seis ferramentas do Semantika são só de leitura: listar marcas, a visibilidade de uma marca por motor, os prompts monitorizados, as ações recomendadas, uma ação em detalhe, e o relatório mais recente. Uma decisão de produto, e encurtou as duas revisões.

Figura 4. A mesma pergunta, antes e depois dos links markdown.

Passo 7. Entrar nas listagens

Neste ponto, qualquer pessoa pode usar o servidor como conector personalizado. Pôr o Semantika nos dois diretórios foi um trabalho à parte, e a maior parte não foi código. O que se segue vem da documentação oficial em setembro de 2026, mais a nossa própria submissão.

Antes de qualquer formulário. As duas revisões querem as mesmas cinco coisas: uma política de privacidade publicada que diga o que o conector lê, porquê, durante quanto tempo e como pedir a remoção (a Anthropic trata a falta dela como rejeição imediata); um contacto de suporte e uma forma de reportar problemas de segurança, no nosso caso uma caixa security@ com uma nota curta de triagem; uma página pública de documentação, um artigo chega; uma conta de teste com dados que o revisor consiga usar sem MFA, SMS ou confirmação por email; e todas as ferramentas com título e anotações explícitas. A Anthropic pede readOnlyHint ou destructiveHint. A OpenAI pede as três, incluindo openWorldHint, e aponta etiquetas erradas ou em falta como causa comum de rejeição. No laravel/mcp isso é IsReadOnly, IsDestructive e IsOpenWorld em cada classe de ferramenta. Tínhamos só a primeira e acrescentámos as outras duas antes de submeter. Corra todas as ferramentas no MCP Inspector primeiro. Os dois portais pedem-lhe para confirmar que o fez.

Anthropic Connectors Directory. A submissão acontece dentro do Claude.ai, nas definições de administração da organização, por isso precisa de uma organização Team ou Enterprise e de um Owner para submeter. O portal vai buscar as ferramentas ao servidor em produção, agrupadas por anotação, e assinala qualquer uma sem título ou hint. A listagem tem limites rígidos: nome com 100 caracteres, tagline com 55, descrição com 2.000, e um slug que fica permanente depois de publicado. Descreve casos de uso e tratamento de dados, escreve instruções de acesso para o revisor, e assina sete declarações de política, incluindo o tratamento de prompt injection e a não recolha de dados de conversas. O feedback aparece num painel de submissões. O tempo de revisão depende da fila e a documentação não se compromete com uma data, por isso submeta cedo e não amarre um lançamento a isso.

ChatGPT. O portal da OpenAI vive no dashboard da Platform, e em 2026 o diretório lista as submissões do Apps SDK como plugins. Precisa do papel Apps Management e de uma verificação de identidade concluída, que os revisores cruzam com o nome, o site e a política de privacidade da listagem. Quatro coisas diferem da Anthropic:

  • Verificação de domínio. O portal dá-lhe um token e pede-o em /.well-known/openai-apps-challenge no hostname do MCP no momento em que submete, em texto simples e nada mais. Sirva-o a partir de uma rota pequena fora do prefixo MCP e devolva 404 quando não houver token configurado, para que um deploy mau nunca pareça verificado.

  • Casos de teste. Pelo menos cinco positivos e três negativos. Um caso negativo não é um caso de erro. É um prompt perto do seu domínio em que o plugin não deve disparar, ou deve perguntar em vez de agir. Escrevemos 401s e IDs inválidos primeiro e tivemos de os refazer.

  • Scan Tools. O portal lê o tools/list do seu servidor e compara-o com o formulário. Uma discrepância é um achado de revisão.

  • Respostas das ferramentas. Retire segredos de autenticação, payloads de debug, identificadores internos e campos de utilizador não declarados do que as ferramentas devolvem, e nunca ligue a páginas de checkout ou upgrade. O passo 6 visto do outro lado.

Depois da aprovação, o plugin não entra em produção sozinho. Publica-o a partir do portal.

O que as revisões devolveram. Duas alterações, no dia da submissão ao ChatGPT, ambas metadados: as duas anotações em falta em todas as ferramentas, e as instruções do servidor, que agora dizem ao modelo para identificar cada item pelo título, nunca pelo ID numérico, e para usar apenas os links que as ferramentas devolveram. Nenhuma levou uma hora. Ambas estavam nas guidelines. Com isso feito, o Semantika foi aprovado e está listado nos dois diretórios.

Figura 5. Duas revisões, um servidor. O que cada uma pede.

Fasquia de aceitação que definimos antes de começar: um utilizador novo liga-se só com o URL em menos de 1 minuto. Aguentou.

O que não esperávamos

O código OAuth foi a metade mais pequena das duas semanas. Discovery, registo, PKCE e a troca de token são trabalho de biblioteca em 2026. As horas foram para o ecrã de consentimento, o carimbo de tenant, e tornar a saída das ferramentas legível para um modelo e para uma pessoa ao mesmo tempo.

O Semantika está nos dois diretórios. Um URL, sem API key, menos de 1 minuto entre colar e a primeira resposta. Se quer o seu produto dentro do Claude e do ChatGPT, é este o tipo de trabalho que fazemos na Buzzvel. Fale connosco.

Subscreve a
nossa newsletter

Junta-te a 1.000+ pessoas e recebe semanalmente dicas,
boas práticas e insights.