
Adicionei suporte a MCP ao meu SaaS em uma tarde. Aqui está tudo.
Eu construí o Imagcon porque eu sempre precisava dele. Gerador de ícones PWA — descreva o que você quer, receba todos os 19 tamanhos de volta, além de telas de splash e um manifest.json. Todo aplicativo que eu envio precisa de um novo conjunto de ícones e eu fiquei cansado de fazer isso manualmente.
Há algumas semanas, eu estava no terminal com Claude Code trabalhando em algo, eu preciso de ícones, e estou prestes a abrir uma aba do navegador para fazer isso. O que significa: descrever o ícone, esperar ele renderizar, baixar o ZIP, arrastar os arquivos para o projeto. Enquanto eu já estou em uma sessão onde eu poderia apenas dizer o que quero.
Então eu perguntei ao Claude: há uma maneira de tornar isso chamável de dentro do terminal, sem sair do editor?
O MCP voltou como a resposta. Eu disse vamos construir isso. Nós trocamos ideias por uma tarde, encontramos um problema de URL que me custou uma hora sólida, e no final estava empacotado e instalável com um comando. Esta é toda a tarde.
o que é o MCP
Claude me explicou assim: você expõe um conjunto de funções, dá a elas nomes e descrições, e a IA pode chamá-las da mesma forma que chama qualquer ferramenta interna. O usuário nunca precisa sair do editor.
antes: "gerar ícones para meu aplicativo" → trocar de abas, descrever, esperar, baixar ZIP, arrastar arquivos.
depois:
generate_pwa_icons("bússola azul minimalista em gradiente escuro", "./public/icons")
Claude Code executa isso, extrai o ZIP, os ícones vão para o seu projeto. Você continua digitando.
(Eu também estive trabalhando em algo que se encaixa ao lado disso — uma maneira de dizer aos agentes como realmente navegar pelo seu aplicativo, não apenas chamar a API. Me aprofundei nisso antes mesmo de saber que o MCP existia. Post diferente.)
a estrutura
três arquivos. Claude organizou isso antes de escrever qualquer coisa:
imagcon_mcp/
client.py # wrapper httpx em torno da API REST do Imagcon
server.py # definições de ferramentas FastMCP
main.py # ponto de entrada, autenticação, argparse
aqui está o que há em cada um.
o cliente
nada inteligente — cada método mapeia para uma chamada de API:
import io
import zipfile
from pathlib import Path
import httpx
BASE_URL = "https://imagcon.app"
class ImagconClient:
def init(self, api_key: str, timeout: float = 300.0) -> None:
self._client = httpx.Client(
base_url=BASE_URL,
headers={"Authorization": f"Bearer {api_key}"},
timeout=timeout,
)
def generate_image(self, description: str) -> dict:
r = self._client.post(
"/routes/generate-image",
json={"description": description, "aspect_ratio": "1:1", "number_of_images": 1},
)
r.raise_for_status()
return r.json()
def generate_pwa_icons(self, image_key: str) -> dict:
r = self._client.post("/routes/generate-pwa-icons", json={"image_key": image_key})
r.raise_for_status()
return r.json()
@staticmethod
def extract_zip_to_dir(zip_bytes: bytes, output_dir: Path) -> None:
output_dir.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(io.BytesIO(zip_bytes)) as zf:
zf.extractall(output_dir)o tempo limite de 300 segundos — eu descobri por que isso importa na primeira vez que uma solicitação falhou no meio da geração. A geração de imagens leva tempo real de computação e o tempo limite padrão do httpx corta antes que termine. Claude aumentou isso depois que isso aconteceu.
o tratamento de erro de autenticação foi Claude pensando à frente:
def _request_error_message(self, response: httpx.Response) -> str:
if response.status_code in (401, 403):
return (
f"A API do Imagcon retornou {response.status_code}. "
f"Verifique sua chave de API em https://imagcon.app/api-keys"
)
return f"Erro da API do Imagcon {response.status_code}: {response.text[:500]}"
sem algo assim, uma chave de API ruim apenas retorna um 401 sem mensagem útil e você passa vinte minutos convencido de que é um problema de rede.
o servidor
FastMCP torna isso quase simples demais. você decorre uma função e ela se torna uma ferramenta chamável:
from mcp.server.fastmcp import FastMCP
from imagcon_mcp.client import ImagconClient
mcp = FastMCP("imagcon")
_client: ImagconClient | None = None
def set_client(client: ImagconClient) -> None:
global _client
_client = client
def _require_client() -> ImagconClient:
if _client is None:
raise RuntimeError("O cliente Imagcon não está configurado.")
return _client
Eu perguntei por que o cliente era global em vez de passado por aí. Claude explicou: o servidor MCP roda como um processo de longa duração, não um manipulador de solicitações — você configura a autenticação uma vez na inicialização e reutiliza-a em cada chamada de ferramenta. Não tinha pensado nisso. Faz sentido uma vez que você vê.
a ferramenta principal — a que faz todo o pipeline:
@mcp.tool()
def generate_pwa_icons(description: str, output_dir: str = "./public/icons") -> str:
client = _require_client()
out = Path(output_dir)
# Passo 1: gerar a imagem base
gen = client.generate_image(description)
image_key = gen["image_key"]
# Passo 2: redimensionar para todos os 19 tamanhos PWA
pwa = client.generate_pwa_icons(image_key)
raw_icons = pwa["icons"]
# Passo 3: salvar na galeria para que possa ser baixado novamente
save = client.save_pwa_icon_set(
set_name=description[:99],
original_image_key=image_key,
icons=[{"size": ic["size"], "image_key": ic["image_key"]} for ic in raw_icons],
prompt=description,
)
# Passo 4: baixar o ZIP e extrair
zip_bytes = client.download_pwa_icon_set_zip(save["set_id"])
ImagconClient.extract_zip_to_dir(zip_bytes, out)
return f"Gerados {len(raw_icons)} ícones em {out.resolve()}. manifest.json incluído."quatro chamadas de API sob o capô, mas da perspectiva da IA é uma ferramenta com dois parâmetros. ela não precisa saber sobre chaves de imagem ou IDs de conjuntos — isso é apenas encanamento. o usuário diz o que quer, a ferramenta cuida de tudo.
as ferramentas menores seguem o mesmo padrão:
@mcp.tool()
def get_credit_balance() -> str:
data = _require_client().get_credit_balance()
return f"Você tem {data['remaining_credits']} créditos restantes."
@mcp.tool()
def list_saved_icon_sets() -> str:
data = _require_client().list_pwa_icon_sets()
sets = data.get("icon_sets", [])
if not sets:
return "Nenhum conjunto de ícones salvo encontrado."
return "\n".join(
f"{s['id']}: {s['set_name']} (criado {s['created_at'][:10]})"
for s in sets
)
retorne strings, não JSON. a IA lê o valor de retorno e decide o que dizer ao usuário — texto simples é transmitido diretamente. JSON faz a IA resumir o JSON, o que adiciona ruído.
o ponto de entrada
Eu não sabia como a autenticação era tipicamente tratada para algo assim. Claude configurou dois padrões — variável de ambiente e flag CLI:
import argparse, os, sys
from imagcon_mcp.client import ImagconClient
from imagcon_mcp.server import mcp, set_client
def resolve_api_key(cli_key: str | None) -> str:
key = (cli_key or os.environ.get("IMAGCON_API_KEY") or "").strip()
if not key:
print(
"Chave de API do Imagcon ausente. Defina IMAGCON_API_KEY ou passe --api-key.\n"
"Crie uma chave em https://imagcon.app/api-keys",
file=sys.stderr,
)
raise SystemExit(2)
A implementação do MCP permite que empresas brasileiras integrem funções de IA diretamente em seus fluxos de trabalho, aumentando a eficiência e reduzindo o tempo de desenvolvimento. Isso pode ser especialmente útil para startups e desenvolvedores que buscam otimizar processos repetitivos.


