Doop
Canvas de design multiplayer de código aberto (alternativa ao Paper.design): agentes criam e transmitem artboards HTML ao vivo ao lado de colaboradores humanos, fazem auto-revisão por meio de capturas de tela e seguem diretrizes de design compartilhadas.
Documentação
Doop é a alternativa open-source ao Paper.design — uma tela
de design multiplayer para humanos e agentes de IA. Cada design vive em um Canvas
(/c/<id>) compartilhável que contém Frames — pranchetas que renderizam HTML real em
iframes isolados. Pessoas editam no navegador; agentes de IA editam por meio do servidor MCP
integrado, transmitindo seus designs ao vivo. Todos veem tudo conforme acontece: cursores,
presença, edições de frames, status de agentes e um feed de atividades.
- Projete com agentes, não com prompts-e-atualização — conecte o Claude Code (ou qualquer cliente MCP) uma vez e veja-o esboçar, transmitir e auto-revisar designs no seu canvas, ao lado do seu cursor.
- Um Agente Doop integrado — enfileire um cartão ou mencione um papel com @ e ele projeta
sozinho, sem precisar conectar um cliente. Ele roda no
ANTHROPIC_API_KEYdo servidor para um punhado de tarefas gratuitas, depois na assinatura do ChatGPT (ou chave OpenAI) que cada usuário conecta (configuração); a performance de boas-vindas do primeiro canvas é roteirizada e roda sem nada disso. - Multijogador de verdade — cursores ao vivo, presença, indicadores de edição por frame, desfazer/refazer, comentários fixados em elementos e um feed de atividades, tudo em uma única sala WebSocket.
- Memória de design — fixe frames exemplares, capture decisões e deixe o destilador propor regras de estilo duráveis que todo agente segue.
- Privado por padrão — convide colaboradores por e-mail ou ative o compartilhamento por link em cada canvas; agentes herdam exatamente o acesso de seu humano.
- Auto-hospedagem em um comando —
docker compose up, oubun run devcom zero configuração (Postgres embutido, sem serviços externos necessários).
Início rápido
git clone https://github.com/kgoedecke/doop && cd doop
bun install
bun run dev
O Doop compila e instala com bun (bun.lock é o único
lockfile); o servidor em si roda em Node.
- Aplicativo web: http://localhost:4300
- API + WebSocket + servidor MCP: http://localhost:4400 (a porta web faz proxy de
/api,/ws,/mcppara ele)
Tudo funciona sem configuração: os dados persistem em um Postgres embutido (PGlite) em data/pg,
e toda integração opcional (SMTP, fotos de banco de imagens, armazenamento de objetos, análise)
degrada graciosamente até que sua variável em .env.example seja definida. A que
você provavelmente vai querer é ANTHROPIC_API_KEY, que ativa o Agente Doop
integrado — agentes que você conecta via MCP não precisam de chave.
Ou auto-hospede a build de produção com Docker:
BETTER_AUTH_SECRET=$(openssl rand -hex 32) docker compose up -d # app + Postgres on :4400
Build de produção sem Docker: bun run build && bun run start (servidor único na :4400 servindo
tudo). Defina DATABASE_URL para usar um Postgres real — mesmo caminho de código que o PGlite.
Prefere não rodar nada? doop.design é a versão hospedada.
Conecte o Claude Code
Um comando conecta o Claude Code (ou qualquer cliente MCP) ao seu canvas:
claude mcp add --transport http doop http://localhost:4300/mcp
Isso dispara o fluxo OAuth padrão do MCP — uma janela do navegador abre, você aprova e, a partir
daí, o agente trabalha como você. Peça para ele projetar algo no seu id de canvas e veja
acontecer ao vivo. Tudo nesta captura é o fluxo real: o Claude Code se anunciou com set_status, criou um
frame e está transmitindo a seção de preços — avatar de presença, atribuição "para Kai Moreno",
o chip do frame, a faixa de trabalho e a tarefa no painel de Agentes.
Veja um agente projetar
O primeiro canvas após o cadastro vem com uma performance: o Agente Doop transmite um design de boas-vindas enquanto você assiste — status na faixa de trabalho, uma tarefa no painel, uma borda pulsante no frame que ele está construindo.
Essa performance de boas-vindas é roteirizada (server/demo.ts) — um frame pré-escrito
reproduzido pela mesma maquinaria que agentes reais usam, então roda sem nenhuma configuração. O
Agente Doop de verdade precisa de uma chave.
O Agente Doop
O Doop traz uma equipe de design integrada que vive no servidor e pega trabalho sozinha: enfileire
um cartão no quadro, @mention um papel em um comentário de elemento ou deixe feedback em uma
tarefa, e ela roda sem um humano no loop. Papéis (o Doop constrói; especialistas têm uma passada
cada — UX, copy, marca, acessibilidade) são definidos em shared/agents.ts, e um
cartão pode ser roteado por vários em ordem.
O servidor paga pelo nível gratuito, na Anthropic por padrão:
ANTHROPIC_API_KEY=sk-ant-... # in .env, or the environment of your deployment
A mesma chave controla o destilador de diretrizes (server/distill.ts), que
propõe regras de estilo duráveis do seu canvas.
O nível gratuito pode rodar no Azure OpenAI — útil quando os créditos ou regras de conformidade da sua organização estão lá:
DOOP_AGENT_PROVIDER=azure
AZURE_OPENAI_ENDPOINT=https://my-resource.openai.azure.com
AZURE_OPENAI_API_KEY=...
AZURE_OPENAI_DEPLOYMENT=my-deployment
O destilador permanece em ANTHROPIC_API_KEY de qualquer forma e desliga silenciosamente sem ele.
Além das tarefas gratuitas: conecte seu próprio ChatGPT
Quando as RESIDENT_TASK_LIMIT tarefas gratuitas de um usuário acabam, ele não perde o agente — ele
conecta uma conta de modelo e o Agente Doop continua rodando nela. Uma conta conectada assume
imediatamente, desde a próxima tarefa: o nível gratuito é um teste que leva as pessoas até aqui,
não um saldo para gastar primeiro, e conectar para de custar qualquer coisa ao servidor a partir
desse momento. A conexão é no nível da conta, então fica em /settings (Início → Configurações);
a parede do nível gratuito linka para lá em vez de ter sua própria cópia, e "Conectar um agente de
IA" em um canvas continua sendo só sobre clientes MCP. Dois tipos de conta:
- Assinatura do ChatGPT — OAuth contra
auth.openai.com, depois inferência pelo backend Codex que os planos Plus/Pro/Business incluem. Tokens ficam emmodel_accountse nunca chegam a um navegador. - Chave da API OpenAI — pague conforme o uso na conta OpenAI do próprio usuário, sem assinatura envolvida.
O Azure OpenAI deliberadamente não é um tipo de conta conectável: um endpoint fornecido pelo usuário seria uma URL que o servidor busca com o contexto completo da execução — um vetor SSRF — então o Azure permanece apenas como provedor no nível do servidor.
De qualquer forma, o usuário escolhe seu nível de modelo nas Configurações — gpt-5.6-sol
(principal), gpt-5.6-terra (o padrão de trabalho) ou gpt-5.6-luna (barato e rápido). Eles estão
pagando por isso, então a escolha é deles; DOOP_AGENT_OPENAI_MODEL só define o padrão com que começam. Note
que gpt-5.4 e gpt-5.4-mini se aposentam do Codex autenticado pelo ChatGPT em 31 de
agosto de 2026, então fixar um id 5.4 via essa variável de ambiente quebrará o caminho da
assinatura após essa data.
O OpenAI não registra URI de redirecionamento para um aplicativo hospedado, então conectar o ChatGPT assume uma de três formas e o Doop escolhe a mais barata disponível:
| Onde o Doop roda | Fluxo | O que o usuário faz |
|---|---|---|
| Mesma máquina que o navegador (dev, auto-hospedado) | Captura de loopback — o Doop segura 127.0.0.1:1455 | Aprovar na aba do OpenAI. Nada para copiar, sem configuração |
| Hospedado (doop.design) | Código de dispositivo (/api/accounts/deviceauth/*) | Digitar um código curto em auth.openai.com/codex/device |
| Códigos de dispositivo desativados | Redirecionamento do navegador + colar | Colar o endereço da página localhost:1455 morta de volta no Doop |
O fluxo de dispositivo precisa de autorização de código de dispositivo ativada em ChatGPT → Configurações → Segurança (membros do workspace precisam que um admin permita) — é por isso que o fluxo de loopback, que não precisa de nenhuma configuração, continua sendo o padrão quando o Doop é local. Todos os três terminam na mesma troca PKCE no lado do servidor.
Antes de ativar isso para usuários reais: dirigir uma assinatura do ChatGPT a partir de um servidor de terceiros não é algo que os termos do OpenAI sancionam, e uso pesado pode fazer uma conta ser limitada ou suspensa. O caminho da chave de API é a alternativa totalmente suportada e compartilha todo o mesmo código.
CHATGPT_CONNECT_DISABLED=1desliga o caminho da assinatura e deixa o caminho da chave.
As execuções são atribuídas ao humano cujo cartão, comentário ou feedback elas pegaram, então a
pessoa que pediu o trabalho é a pessoa cuja conta o executa. A tradução entre o loop em formato
Anthropic do agente e a API Responses do OpenAI vive em
server/openaiAgent.ts; qual credencial uma execução recebe é decidido em
server/agentModel.ts.
Sem chave de servidor e sem conta conectada, o Agente Doop está desligado e falha silenciosamente
por design — cartões enfileirados e @mentions simplesmente esperam algum agente reivindicá-los. O
banner de inicialização informa em qual estado você está.
Tudo isso é separado de conectar seu próprio agente. O Claude Code e qualquer outro cliente MCP autenticam via OAuth e dirigem o canvas de fora, na sua própria assinatura — nunca medido. Três caminhos, mesmo canvas: o Agente Doop na nossa chave (nível gratuito), o Agente Doop na sua chave ou seu próprio agente via MCP.
| Variável | Padrão | O que faz |
|---|---|---|
DOOP_AGENT_PROVIDER | anthropic | No que o nível gratuito roda: anthropic | azure |
ANTHROPIC_API_KEY | não definido | Paga pelo nível gratuito do Agente Doop (provedor padrão) e pelo destilador |
AZURE_OPENAI_ENDPOINT | não definido | O recurso Azure OpenAI do nível gratuito, quando DOOP_AGENT_PROVIDER=azure |
AZURE_OPENAI_API_KEY | não definido | Uma chave desse recurso |
AZURE_OPENAI_DEPLOYMENT | não definido | O deployment em que o nível gratuito roda |
AZURE_OPENAI_API_VERSION | não definido | Fixa um parâmetro de consulta api-version; a superfície v1 não precisa de nenhum |
AZURE_OPENAI_REASONING_EFFORT | não definido | Esforço de raciocínio em execuções do Azure; não definido não envia nenhum (não seguro para raciocínio) |
RESIDENT_TASK_LIMIT | 0 | Tarefas gratuitas do Agente Doop por conta; 0 significa uma conta conectada desde a primeira tarefa |
DOOP_AGENT_MODEL | claude-opus-5 | Modelo para o Agente Doop na chave Anthropic do servidor |
DOOP_AGENT_OPENAI_MODEL | gpt-5.6-terra | Nível padrão na conta de um usuário; cada usuário pode escolher outro nas Configurações |
CHATGPT_CONNECT_DISABLED | não definido | 1 esconde o fluxo do ChatGPT, deixando o caminho da chave de API |
DOOP_DISTILL_MODEL | claude-haiku-4-5-20251001 | Modelo para o destilador de diretrizes |
RESIDENT_TASK_LIMIT é o medidor do plano gratuito. Por padrão, ele é 0: o Agente Doop só é executado | ||
| depois que o usuário conecta uma conta de modelo (a assinatura do ChatGPT ou uma chave da OpenAI) — uma conta | ||
| conectada nunca é medida. Conectar seu próprio agente MCP não remove o medidor: ele é executado no seu | ||
| modelo quando ele projeta, mas tarefas residentes ainda cobram uma credencial. Defina o limite acima de 0 para | ||
| conceder esse número de tarefas gratuitas na chave do servidor; tudo o que aciona trabalho residente conta, | ||
| incluindo feedback e novas tentativas. Não existe valor "ilimitado": ao fazer self-hosting com sua própria chave, defina | ||
| um número grande, já que você está pagando a Anthropic diretamente de qualquer forma. |
Contas
O aplicativo web exige uma conta (better-auth, e-mail/senha — cadastro aberto). O nome da sua conta é a sua identidade em todos os lugares: cursores, presença, feed de atividades e atribuição de feedback são todos autoritativos no servidor a partir da sessão, e o WebSocket rejeita conexões não autenticadas. As telas (canvases) são privadas por padrão, no estilo Figma: apenas o proprietário e as pessoas que ele convida (Compartilhar → convite por e-mail, contas doop existentes) podem abrir uma. O modal Compartilhar também pode ativar o compartilhamento por link por tela ("qualquer pessoa com o link pode editar"), o que restaura a colaboração por link para aquela tela. Sua tela inicial lista suas próprias telas mais as compartilhadas com você (além de legadas sem proprietário, reivindicáveis lá). Agentes conectados via MCP agem sob a conta que os aprovou e obtêm exatamente o acesso desse usuário.
Com SMTP configurado (SMTP_HOST etc. — veja .env.example), os cadastros exigem verificação
de e-mail e "esqueci minha senha" envia links de redefinição reais. Sem isso, o cadastro permanece aberto e cada
e-mail é impresso no log do servidor, incluindo os links — os fluxos ainda funcionam em desenvolvimento.
Defina SIGNUP_EMAIL_DOMAINS=jointhetroops.com para restringir novas contas a um domínio de e-mail, ou use uma
lista separada por vírgulas para vários domínios. A correspondência não diferencia maiúsculas de minúsculas e é exata; contas existentes
não são afetadas. Deixe em branco para manter o cadastro público aberto.
Defina REQUIRE_EMAIL_VERIFICATION=false para permitir que as pessoas entrem antes de verificar — o link ainda é
enviado por e-mail, apenas deixa de bloquear o login. A promoção de administrador não faz parte dessa troca:
ADMIN_EMAILS só promove um endereço verificado (veja abaixo).
Se o cadastro ou a redefinição de senha travar em vez de falhar, a causa quase sempre é um host que
bloqueia SMTP de saída: Railway e a maioria dos PaaS bloqueiam 25/465/587. A Resend também atende 2465/2587, então
SMTP_PORT=2587 é a correção usual.
Env: BETTER_AUTH_SECRET (obrigatório em produção), TRUSTED_ORIGINS (separado por vírgulas,
padrão para as origens de desenvolvimento localhost).
Administradores da instância
ADMIN_EMAILS (separado por vírgulas) nomeia as contas que recebem o papel admin, aplicado no
cadastro, na verificação de e-mail e na inicialização — então você pode nomear um administrador antes ou depois de ele
ter uma conta. Isso exige SMTP em produção: um endereço só identifica alguém
depois que ele prova que o possui, e sem um serviço de e-mail o cadastro é aberto, então qualquer pessoa poderia
se cadastrar com o seu endereço e assumir o papel com ele. Uma instância de produção sem SMTP não promove
ninguém e avisa na inicialização; defina o papel diretamente no banco de dados se essa for a sua configuração.
Administradores recebem /admin: todas as telas e contas da instância, e "ver como", que lhes dá
uma sessão real, mas somente leitura, de 15 minutos como aquele usuário. Ser administrador não amplia o acesso
às telas por si só: a porta em server/access.ts é compartilhada com o MCP,
então uma leitura privilegiada ali daria a cada agente com o token de um administrador o controle total da
instância. Sessões "ver como" não podem escrever, não podem conectar agentes e registram quem está por trás
delas em session.impersonated_by.
SSO (OIDC)
Login opcional contra um provedor OIDC externo (Zitadel, Okta, Authentik, Keycloak,
etc.), junto com e-mail/senha — não é um substituto para isso. Defina OIDC_ISSUER,
OIDC_CLIENT_ID e OIDC_CLIENT_SECRET juntos para ativá-lo; um conjunto parcial se recusa a
inicializar em vez de rodar com SSO meio configurado. OIDC_SCOPES (padrão
openid email profile) e OIDC_PROVIDER_NAME (padrão SSO, exibido no botão
de login — por exemplo, Zitadel) são opcionais. Entrar via SSO vincula a uma conta existente
de e-mail/senha quando os e-mails coincidem e o provedor marca o e-mail como
verificado, e isso funciona mesmo em uma instância sem SMTP configurado, onde uma
conta local nunca poderia verificar por conta própria. O SSO sozinho nunca concede
o papel de administrador, mesmo para um endereço listado em ADMIN_EMAILS — um IdP não é
confiável como fonte de promoção de administrador, apenas como verificação de propriedade de e-mail;
a promoção ainda exige o caminho normal de ADMIN_EMAILS (cadastro verificado, ou
syncAdmins na inicialização para uma conta que o SSO verificou desde então).
Env: veja o bloco OIDC em .env.example.
Entrar com o Google
Opcional, junto com e-mail/senha e SSO. Crie um cliente OAuth (aplicativo Web) no
console do Google Cloud, adicione
<BETTER_AUTH_URL>/api/auth/callback/google como URI de redirecionamento autorizado e defina
GOOGLE_CLIENT_ID e GOOGLE_CLIENT_SECRET juntos (um sem o outro se recusa a
inicializar). A página de login mostra um botão "Entrar com o Google" sempre que ambos estão definidos. O
vínculo de conta e a promoção de administrador seguem as mesmas regras do SSO acima; SIGNUP_EMAIL_DOMAINS
se aplica a cadastros do Google (e SSO) exatamente como se aplica a e-mail/senha.
Entrar com a Microsoft
Mesmo formato do Google. Registre um aplicativo no Microsoft Entra
(Registros de aplicativos, plataforma Web) com <BETTER_AUTH_URL>/api/auth/callback/microsoft como URI de
redirecionamento, crie um segredo de cliente e defina MICROSOFT_CLIENT_ID e
MICROSOFT_CLIENT_SECRET juntos. MICROSOFT_TENANT_ID (padrão common, qualquer conta
Microsoft) pode ser organizations, consumers ou o ID do seu locatário para tornar o botão uma
porta exclusiva da organização. A Microsoft não afirma a propriedade do e-mail a menos que o token de ID do
registro do aplicativo inclua as declarações opcionais email e verified_primary_email; sem elas, um
login da Microsoft ainda funciona, mas só vincula a uma conta existente que já esteja
verificada. Todo o resto (lista de permissões, promoção de administrador) segue as regras de SSO acima.
Autenticação de agente (MCP OAuth)
O endpoint /mcp exige OAuth. Adicionar o servidor no Claude Code / Codex aciona
o fluxo padrão de OAuth do MCP: uma janela do navegador abre, você entra no Doop e aprova,
e o cliente armazena um token de portador. Cada chamada de ferramenta então carrega sua identidade —
tarefas de agente mostram "para ⟨você⟩" no painel de Tarefas, e dicas de presença nomeiam o proprietário.
Chamadas não autenticadas recebem um 401 com ponteiros de descoberta WWW-Authenticate
(/.well-known/oauth-authorization-server + oauth-protected-resource), que é o que
inicia o fluxo. O registro dinâmico de clientes está habilitado, então não é necessária configuração manual de cliente.
Em produção, defina também BETTER_AUTH_URL para a origem pública — as URLs de OAuth são construídas nela.
Implantação
O repositório inclui uma Dockerfile de produção (build do cliente + Chromium para capturas de tela de frames).
Qualquer host de contêiner funciona; Railway/Fly são os de menor atrito:
- Crie o aplicativo a partir deste repositório (ambos detectam automaticamente o Dockerfile).
- Adicione um Postgres gerenciado e defina
DATABASE_URL. Não pule isso em implantações reais — o fallback PGlite é embutido/processo único e só serve para uma única instância com um volume persistente montado em/app/data. - Defina
BETTER_AUTH_SECRET(string aleatória longa) eBETTER_AUTH_URL(a origem pública, por exemplo,https://doop.example.com). Origens adicionais permitidas:TRUSTED_ORIGINS(separadas por vírgulas). - Verificação de saúde:
GET /healthz. O servidor confia em um salto de proxy (trust proxy), então a terminação de TLS na borda da plataforma funciona imediatamente.
Verificação local da imagem de produção exata:
docker build -t doop .
docker run -p 4400:4400 -e BETTER_AUTH_URL=http://localhost:4400 -e BETTER_AUTH_SECRET=dev-only doop
Conecte um agente de IA
O endpoint MCP (HTTP transmitível, sem estado) está em:
http://localhost:4300/mcp
Claude Code:
claude mcp add --transport http doop http://localhost:4300/mcp
Configuração MCP genérica:
{ "mcpServers": { "doop": { "type": "http", "url": "http://localhost:4300/mcp" } } }
Depois diga ao agente algo como:
Trabalhe na tela
<canvas-id>(mostrada na barra superior). Chameget_canvaspara ver os frames existentes. Para projetar, crie um frame comcreate_frame, depois transmita o design para ele comappend_frame_htmlem blocos de ~300–500 caracteres (start=trueno primeiro,done=trueno último) para que as pessoas vejam a construção ao vivo. HTML completo com CSS inline. Depois de terminar, chameget_frame_screenshotpara vê-lo, corrija o que parecer errado e verifique novamente. Escolha umagent_namee reutilize-o em cada chamada.
As capturas de tela são renderizadas no seu Chrome/Chromium do sistema via puppeteer-core (defina CHROME_PATH se não for
detectado automaticamente). Humanos podem acessar o mesmo renderizador em GET /api/frames/:id/screenshot.png?scale=2.
Para visualização/importação de sites, definir CONTEXT_DEV_API_KEY faz o Context.dev adquirir o HTML
renderizado enquanto o Doop ainda o sanitiza e renderiza a prévia localmente; sem a chave, o Doop navega
para a página pública diretamente no Chromium.
Sincronização de design: envie as telas ao vivo de um aplicativo para uma tela
A importação no lado do servidor não alcança aplicativos atrás de SSO ou VPN. O snippet doop-sync inverte a captura para o navegador do usuário: crie uma chave somente escrita no diálogo Compartilhar de uma tela, adicione uma tag ao aplicativo —
<script async src="https://your-doop-origin/doop-sync.js?key=dk_…"></script>
— e cada tela distinta que as pessoas visitam cai naquela tela como um frame (uma linha por aplicativo), importada
uma vez: uma janela de graça curta permite que a primeira captura se estabilize (revelações de rolagem, imagens tardias), depois o frame
congela para que visitas posteriores — viewports diferentes, dados de outros usuários, menus abertos — nunca o alterem. Excluir
um frame o reimporta na próxima visita; as contagens de navegação continuam acumulando independentemente. As rotas são
normalizadas (/orders/8231 → /orders/:id) para que cada tela mapeie para um
frame; as capturas são serializadas do CSSOM (para que a saída de styled-components/emotion sobreviva), e
fontes da web de mesma origem e pequenas imagens são embutidas como URIs de dados — fontes exigem CORS dentro do
frame em sandbox, e URLs de intranet nunca renderizariam para espectadores fora da rede. Scripts são
removidos no cliente e no servidor, valores de entrada são sempre descartados, e qualquer coisa marcada como data-doop-mask
é redigida antes do upload (data-doop-sync-ignore exclui um elemento inteiramente). A chave é a credencial
inteira: ela só pode escrever frames na sua única tela, então revogá-la no diálogo Compartilhar corta o
aplicativo instantaneamente. Endpoint: POST /ingest/<key> (CORS aberto, sem cookies).
Como a transmissão se parece (suavização no lado do servidor)
O HTML do agente chega ao armazenamento imediatamente, mas os espectadores o veem através de uma revelação de máquina de escrever: o servidor
transmite o HTML acumulado a uma taxa constante (~500 caracteres/s, acelerando para limpar atrasos em ~8s),
então mesmo um agente que envia poucos blocos grandes — ou um set_frame_html / create_frame de uma vez com
HTML completo — reproduz como uma transmissão ao vivo suave. O HTML no meio da revelação é curado antes da transmissão: uma tag
incompleta no final é descartada, um <script> não fechado é cortado (nunca executa JS pela metade), e um
<style> não fechado é fechado para que o conteúdo seja pintado em vez de ficar em branco. Edições humanas do inspetor ignoram a
revelação (e uma edição de HTML humana cancela qualquer revelação aberta — o humano assume o controle).
Enquanto uma transmissão/revelação está aberta, o frame recebe uma borda tracejada pulsante e um selo "✦ está projetando…";
"projeto concluído" é registrado quando a revelação termina. Uma transmissão obsoleta fecha automaticamente após 30s.
Também há um equivalente REST: POST /api/frames/:id/append com { html_chunk, start?, done?, actor? }.
Como os agentes aprendem o fluxo de trabalho
A orientação acontece em três camadas (a mesma arquitetura que o paper.design usa, além de incentivos de resultado):
- Servidor
instructionsna inicialização do MCP — um contrato compacto: carregue o guia, obtenha o contexto primeiro, transmita designs, revise com capturas de tela, mantenha umagent_name. - Ferramenta
get_guide— o manual aprofundado (checkpoints de revisão obrigatórios, fluxo de transmissão, dimensionamento de quadros, doutrina de qualidade de design, etiqueta multijogador), carregado uma vez por sessão e recarregável após compactação de contexto. Fonte:server/guide.ts. - Empurrões de resultado — resultados de
create_frame/set_frame_html/ finalappend_frame_htmlinformam ao agente que ele ainda não viu seu design e que deve chamarget_frame_screenshotantes de continuar.
Ferramentas MCP
| Ferramenta | O que faz |
|---|---|
get_guide | O manual do agente — os agentes são instruídos a carregar isso primeiro |
set_status | Transmitir uma linha "no que estou trabalhando" — exibida ao vivo na faixa de trabalho atual, dica do avatar e feed de atividades |
get_feedback | Buscar e reivindicar solicitações de feedback humano abertas — para agentes cuja função é verificar o canvas periodicamente |
get_comments | Ler comentários e respostas fixados em elementos, opcionalmente filtrados por quadro ou estado de resolução, sem reivindicar trabalho |
list_canvases | Listar todos os canvases |
create_canvas | Criar um canvas, retorna seu id compartilhável |
get_canvas | Layout do canvas: posição/tamanho/meta de cada quadro |
view_website | Inspecionar uma página pública somente leitura; retorna uma captura de tela do desktop e texto visível sem alterar o canvas |
import_webpage | Importar uma URL pública para um canvas como um snapshot/quadro HTML editável |
create_frame | Adicionar um quadro com HTML (posicionado automaticamente se não houver x/y) |
get_frame | Ler um quadro incluindo seu HTML |
get_frame_screenshot | Renderizar o quadro sem cabeçalho e retornar um PNG — permite que os agentes vejam e iterem em seu design |
set_frame_html | Substituir o design de um quadro de uma só vez — renderiza ao vivo para todos |
append_frame_html | Transmitir um design em partes (start=true primeiro, done=true por último) — os espectadores veem a construção |
edit_frame_html | Substituição exata e direcionada de localizar/substituir no HTML de um quadro — transforma-se no render no local |
update_frame | Renomear / mover / redimensionar um quadro |
delete_frame | Remover um quadro |
Ferramentas de mutação aceitam agent_name; o agente então aparece na pilha de presença (avatar quadrado pulsante),
recebe um anel "editando" + etiqueta no quadro que tocou, e suas ações aparecem no feed de atividades. Os agentes
expiram da presença após ~20s de inatividade (~60s enquanto têm um status publicado, já que um status
geralmente significa que o agente está pensando entre chamadas de ferramenta).
A propriedade agente-para-humano vem do token OAuth: o token portador identifica quem aprovou a conexão, e esse usuário aparece como o proprietário do agente em tarefas e presença.
Narração de tarefas ao vivo
Os agentes são orientados (instruções + guia) a chamar set_status com um resumo de uma linha, no presente,
quando iniciam uma tarefa e sempre que seu foco muda — ex.: "Esboçando um fluxo de onboarding mobile".
Os status aparecem em uma faixa flutuante trabalhando agora no canto inferior esquerdo do canvas (ponto pulsante na
cor do agente), na dica do avatar de presença e como uma entrada no feed de atividades, para que você sempre saiba o que
cada agente está fazendo mesmo enquanto pensa silenciosamente. Uma string vazia limpa o status; ele também
expira com a presença do agente.
Cada status também se torna uma tarefa: postar um novo status completa a anterior, limpar (ou
ficar em silêncio) encerra a tarefa aberta. Agentes que nunca chamam set_status ainda aparecem: o servidor
infere uma tarefa do que eles fazem visivelmente ("Projetando 'Hero'", em itálico no painel), fecha-a
quando a transmissão termina e os incentiva nos resultados da ferramenta a começar a anunciar — para que o painel funcione
mesmo para sessões que se conectaram antes da ferramenta existir ou pularam o guia. O painel lateral é dividido em duas abas — Tarefas mostra o histórico
por agente (tarefa ativa pulsando com duração em execução, concluídas marcadas com o tempo que
levaram), estilo painel do agente Cursor; Atividade é o feed de eventos bruto. O histórico de tarefas sobrevive à saída
dos agentes e é enviado para quem entra tarde.
Orientando agentes: feedback em tarefas
Passe o mouse sobre qualquer tarefa na aba Tarefas e pressione ↩ para deixar feedback (ex.: "deixe o tom mais quente").
Cada nota se torna uma solicitação aberta no canvas — um item de trabalho, não um e-mail para o agente cuja
tarefa era. O MCP é baseado em pull, então a entrega viaja na camada de empurrão de resultado: a próxima chamada de agente identificada
no canvas que suporta entrega de feedback (carregando um agent_name, seja quem for) retorna um
bloco HUMAN FEEDBACK citando a nota, dizendo de quem é o trabalho que ela concerne e instruindo o agente
a resolvê-la antes de continuar — incluindo editar o quadro de outro agente (uma solicitação humana
sobrepõe a etiqueta de não tocar). Pegá-la reivindica: a interface muda de "→ aguardando um
agente…" para "✓ pego por ⟨agente⟩", e cada nota é reivindicada exatamente uma vez.
Os agentes não ficam esperando respostas — as sessões terminam quando o trabalho deles termina. Solicitações abertas simplesmente
aguardam o próximo agente aparecer: o agente original em uma sessão posterior, um agente diferente
já no canvas ou um novo que você cria ("verifique o canvas ⟨id⟩"). Para um
cuidador dedicado, aponte um agente para get_feedback — uma busca e reivindicação não bloqueante projetada para um
loop de "verifique o canvas a cada poucos minutos, resolva o que os humanos solicitaram".
Equivalente REST: POST /api/tasks/:id/feedback com { text, from }.
Lendo comentários de elementos via MCP
Chame get_comments({ canvas_id }) para ler os comentários e respostas de elementos retidos do canvas
(até 100 entradas, mais recentes primeiro). Cada entrada inclui seu ID, ID do quadro, autor, texto, timestamp,
seletor CSS, trecho de HTML e quaisquer metadados de reivindicação, falha ou resolução. Respostas carregam um
parentId apontando para seu comentário raiz.
Passe frame_id para ler apenas comentários em um quadro pertencente a esse canvas, ou
include_resolved: false para excluir entradas resolvidas. Entradas resolvidas são incluídas por padrão
para que o contexto da conversa permaneça disponível. Um resultado vazio é []. A ferramenta aplica as mesmas
permissões de acesso ao canvas que outras leituras MCP; agent_name opcional anuncia presença.
Ela não reivindica feedback de tarefas ou comentários, nem marca nada como resolvido.
O que está incluso
- Canvas infinito — role para mover,
⌘/ctrl+ rolagem (ou pinça) para zoom, arraste o fundo para mover, zoom para ajustar; grade de pontos acompanha a viewport. - Quadros — arraste para mover, alça de canto para redimensionar, clique para selecionar. O inspetor à direita edita
nome/posição/tamanho e o HTML bruto com salvamentos ao vivo com debounce.
⌫exclui o quadro selecionado. - Multijogador — cursores ao vivo com etiquetas de nome, avatares de presença, indicadores "quem está editando" por quadro, flash colorido quando um ator remoto altera um quadro, posições de arrasto transmitidas ao vivo, reconexão automática.
- Feed de atividades — cada criar/editar/renomear/excluir, por quem (usuário ou agente), com timestamps.
- Compartilhamento — a URL do canvas é o link de compartilhamento (o botão
Sharecopia). - Modal Conectar IA — copie e cole as instruções de configuração MCP do próprio aplicativo.
Arquitetura
server/ Node (tsx) — one process on :4400
index.ts Express REST API + ws rooms + presence + static serving (prod)
store.ts In-memory canvas/frame state (hot path), write-through to the DB
db/ Drizzle schema + PGlite/Postgres connection + write-through persistence
actions.ts Shared mutations: broadcast + activity log + agent presence
mcp.ts MCP server (@modelcontextprotocol/sdk), stateless streamable HTTP at /mcp
seed.ts Demo canvas on first run
shared/types.ts Store + ws protocol types shared by server and client
src/ React + Vite + zustand client on :4300
components/ui/ The component system — every styled primitive lives here
styles.css Design tokens, the base reset, and keyframes. Nothing else.
Estilo
A aparência do Doop é um sistema de componentes, não uma folha de estilos. src/components/ui/ contém os
primitivos — Button, Input, Badge, Card, Panel, Modal, Menu, Toolbar,
Segmented, Dash* e o restante — cada um uma receita Tailwind + CVA vinculada
aos tokens em styles.css. As telas compõem esses; elas não redescrevem bordas,
sombras ou escalas de tipo. Se um padrão aparece duas vezes, ele pertence a ui/.
src/styles.css é deliberadamente pequeno: os tokens :root (--ink, --paper, --brand…),
seu mapeamento @theme inline para nomes Tailwind, o reset base e as
utilitárias @keyframes não podem expressar. Os componentes referenciam essas animações por nome, então os nomes são
API. --breakpoint-md (900px) é o limite mobile e useIsMobile() corresponde a ele em JS —
altere-os juntos.
O HTML do quadro renderiza em <iframe sandbox="allow-scripts"> — scripts são executados, mas sem acesso de mesma origem e
sem alcance ao aplicativo. Cada iframe carrega um pequeno bootstrap uma vez; novo HTML é postMessaged e
DOM-morfado no local (src/lib/frameRuntime.ts), então atualizações e ticks de transmissão nunca piscam em branco
o quadro com um recarregamento completo do documento. <script>s alterados re-executam; estilos/fontes inalterados são
intocados. A camada em tempo real é JSON simples sobre um socket WebSocket por canvas;
mutações REST/MCP são transmitidas para a sala pela camada de ações compartilhada, então edições humanas e de agente passam
pelo mesmo encanamento.
Contribuindo
PRs são bem-vindos — veja CONTRIBUTING.md para convenções de commit e estilo de código.
bun run test executa a suíte de integração (ela inicia o servidor real contra um banco de dados descartável);
mudanças de esquema passam por migrações drizzle (npx drizzle-kit generate após editar
server/db/schema.ts). Problemas de segurança: veja SECURITY.md — por favor, reporte em particular.
Licença
Doop é open source sob a GNU AGPL v3. Em resumo: use, hospede você mesmo, modifique — mas se você oferecer uma versão modificada como serviço, deve publicar suas alterações sob a mesma licença.
O nome e logotipo do Doop são marcas registradas e não são cobertos pela licença de código — por favor, renomeie serviços derivados.