Saltar para o conteúdo
Blume
Português
Esc
navegarabrir⌘Jpré-visualizar
Nesta página

IA

Torne sua documentação legível por máquinas com llms.txt e uma API JSON descrita por OpenAPI, adicione um assistente Ask AI opcional na página e exponha um servidor MCP hospedado para agentes de programação.

O Blume tem alguns recursos de IA: documentação legível por máquinas para ferramentas externas (llms.txt e uma API JSON com uma descrição OpenAPI, ambos ativados por padrão), um assistente Ask AI na página e um servidor MCP hospedado para agentes de programação. O Ask AI e o MCP são opcionais, e a documentação estática continua totalmente estática até você ativar um recurso.

llms.txt

O Blume emite versões legíveis por máquinas da sua documentação que agentes de programação e assistentes de chat podem consumir. Isso vem ativado por padrão; defina llmsTxt: false para desativar:

ai: {
  llmsTxt: false,
}

Enquanto estiver ativado, blume build escreve dois arquivos na raiz do seu site:

  • /llms.txt — um índice compacto: o título e a descrição do seu site, depois uma lista com links de todas as páginas com seus resumos, organizada em seções que espelham a sua barra lateral — pastas e grupos viram cabeçalhos, para que um agente veja a estrutura da documentação, e não um bloco único e plano.
  • /llms-full.txt — o corpus completo: o corpo Markdown inteiro de cada página, com sua URL de origem, em um único arquivo.

Páginas em rascunho são excluídas. Defina deployment.site para que os links e as URLs de origem resolvam para endereços absolutos.

llmsTxt também aceita uma forma de objeto com opções para o que os arquivos incluem. Se a sua referência de API documenta uma especificação de exemplo ou provisória, defina openapi: false para manter as páginas geradas dela fora dos dois arquivos:

ai: {
  llmsTxt: {
    enabled: true, // default
    openapi: false, // exclude generated API reference pages
  },
}

A forma de objeto também aceita details: Markdown colocado logo depois do título e do resumo no llms.txt, antes das seções de páginas — o bloco livre de “details” da especificação do llms.txt. É o lugar para dizer aos agentes quando recorrer ao seu produto e como chamá-lo, algo que scanners de prontidão procuram explicitamente; um comando de instalação e o nome do pacote também cabem aqui:

ai: {
  llmsTxt: {
    details: [
      "## When to use Acme",
      "",
      "Reach for Acme when a project needs hosted feature flags. Install the CLI with `npm install -g acme`; the API reference below covers every endpoint.",
    ].join("\n"),
  },
}

O llms.txt termina com duas seções geradas que não precisam de configuração. Agent skills lista cada skill publicada por meio de ai.skills com a descrição dela (onde uma skill diz quando deve ser usada). Agent resources traz links para todo artefato legível por máquinas que o build emite — llms-full.txt, o espelho de Markdown bruto por página, o servidor MCP e seu documento de descoberta, o índice de skills, o catálogo de API, o agent-readability.json e o sitemap — cada um só quando existe, para que um agente que não lê nada além do llms.txt ainda encontre a superfície inteira.

Para manter uma página individual fora dos dois arquivos, defina ai.exclude no frontmatter dela:

---
title: Internal notes
ai:
  exclude: true
---

A página continua sendo renderizada, permanece na busca e mantém seu lugar no sitemap — só os arquivos llms.txt a pulam.

Para assumir o controle total de qualquer um dos arquivos, adicione seu próprio llms.txt ou llms-full.txt à sua pasta public/. Como um favicon personalizado, ele é detectado automaticamente e entra no lugar do arquivo gerado — substitua um e o Blume continua gerando o outro.

Markdown bruto

Adicione .md ou .mdx à URL de qualquer página para buscar seu código-fonte Markdown bruto — perfeito para LLMs, agentes de programação e fluxos de trabalho de “copiar como Markdown”. Está disponível para todas as páginas, em desenvolvimento e em produção, sem nenhuma configuração.

URL Retorna
/quickstart A página renderizada
/quickstart.md Markdown simples, com os componentes convertidos
/quickstart.mdx O código-fonte MDX bruto, exatamente como foi escrito

As rotas aninhadas funcionam do mesmo jeito (/content/syntax.md), e a página inicial é servida em /index.md.

A variante .md reduz os componentes a Markdown simples para consumidores que não conseguem interpretar JSX: <TypeTable> vira uma tabela Markdown, <Callout> uma citação rotulada, <Steps> uma lista ordenada, <Tabs> seções com rótulos em negrito, <Card> seu título como um link acima do corpo (e <CardGroup> os cards que ele contém) e <YouTube> um link. As props são avaliadas com o frontmatter da página no escopo, então uma prop como title={frontmatter.status} resolve para o mesmo valor que a página renderizada mostra. Tudo que não puder ser convertido fielmente — um componente personalizado ou uma prop calculada a partir de um import — é deixado como está, e a marcação de componentes dentro de blocos de código cercados nunca é tocada. A mesma conversão se aplica ao llms-full.txt e à ferramenta get_page do servidor MCP, para que toda superfície voltada a agentes leia Markdown limpo. Quando quiser o código-fonte não transformado, use a variante .mdx.

Negociação de conteúdo

Os agentes não precisam conhecer a convenção .md: pedir a própria URL de uma página com um cabeçalho Accept: text/markdown serve a variante Markdown no mesmo endereço, com Vary: Accept para que os caches mantenham as duas separadas. O servidor de desenvolvimento respeita o cabeçalho de fábrica, e um build de servidor na Vercel ou na Cloudflare integra a mesma negociação ao deploy automaticamente — regras de roteamento na Vercel, um Worker gerado na Cloudflare — sem precisar de configuração. A página inicial sempre negocia, mesmo quando é uma landing page personalizada em vez de uma página de conteúdo: seu espelho Markdown recorre ao índice llms.txt, então um agente que pede Markdown à raiz do site recebe o mapa legível por máquinas do site. As respostas Markdown também carregam um cabeçalho x-markdown-tokens — uma contagem estimada de tokens (~4 caracteres por token), seguindo a convenção do Markdown for Agents da Cloudflare — em toda superfície onde o Blume controla os cabeçalhos de resposta: o servidor de desenvolvimento, as respostas renderizadas no servidor e a página inicial negociada na Vercel e na Cloudflare. Outros destinos de deploy servem páginas pré-renderizadas a partir de uma camada estática sem nenhum hook em tempo de requisição, então os agentes ali buscam a URL .md diretamente; o manifesto de legibilidade para agentes anuncia contentNegotiation só em deploys que respeitam o cabeçalho.

Páginas inexistentes também negociam. Todo build emite uma página 404 em Markdown em /404.md — a mensagem de não encontrado seguida de links de recuperação para cada seção de nível superior, o sitemap e o llms.txt — e, na Vercel, uma requisição a uma URL inexistente que prefere Markdown, ou qualquer URL .md sem página por trás, recebe esse corpo com um status 404 de verdade, em vez do shell HTML.

Serializadores de componentes personalizados

Dê aos seus próprios componentes uma forma Markdown com ai.markdownComponents — um mapa de nome JSX para serializador. Cada serializador recebe as props do componente (avaliadas estaticamente a partir dos atributos MDX, com o frontmatter da página no escopo), seus children (já reduzidos a Markdown) e os dados de frontmatter da página, e retorna a substituição — ou null para deixar o JSX como está:

import { defineConfig } from "blume";
import type { ComponentMarkdown } from "blume";

const chart: ComponentMarkdown = ({ props }) =>
  `![${props.title}](/charts/${props.slug}.png)`;

export default defineConfig({
  ai: {
    markdownComponents: {
      Chart: chart,
    },
  },
});

Para componentes de contêiner, childComponents("Name") extrai os filhos diretos por tag — do mesmo jeito que o serializador integrado de <Steps> coleta seus itens <Step> — e childBlocks() retorna todos os filhos diretos em ordem, componentes e prosa igualmente, cada um já reduzido a um bloco de Markdown (o serializador integrado de <CardGroup> é só esses blocos unidos por linhas em branco). Uma entrada com o mesmo nome substitui um serializador integrado, então você pode reformular como <Callout> é reduzido — ou retornar null para deixá-lo de fora por completo.

Os serializadores moram no blume.config.ts, não no components.tsx: o arquivo de configuração é executado em tempo de build, enquanto o arquivo de componentes é apenas analisado estaticamente (ele pode importar arquivos .astro, que não podem rodar fora do build do site). Seus componentes em si continuam registrados no components.tsx exatamente como antes — markdownComponents só adiciona a forma Markdown deles voltada a agentes.

Copiar como Markdown

Toda página traz uma ação Copiar como Markdown — nas ações da página abaixo do sumário — que copia o Markdown bruto da página para a área de transferência. É o mesmo código-fonte servido na URL .md acima, pronto para colar em um LLM, uma issue ou nas suas anotações. Está disponível em todas as páginas, em desenvolvimento e em produção, sem nenhuma configuração.

Onde a Clipboard API não está disponível ou o navegador a nega — navegadores dentro de apps, WebViews, origens inseguras — a ação recorre ao comando de cópia legado e, se nada chegar à área de transferência, o botão informa Falha ao copiar (localizado via actions.copyFailed) em vez de ficar em silêncio. O mesmo fallback sustenta todo botão de copiar que o Blume renderiza.

Abrir no chat

A ação Abrir no chat abre a página atual em um assistente de IA — v0, ChatGPT, Claude, T3 Chat, Scira ou Cursor — pré-preenchida com um prompt que o aponta para o Markdown bruto da página, para que ele possa responder perguntas sobre o que você está lendo:

Leia https://your-site/this-page.md para que eu possa te fazer perguntas sobre esta página.

Assim como Copiar como Markdown, não precisa de configuração. O assistente busca a página pela URL pública dela, então funciona assim que a página estiver publicada.

O prompt faz parte do dicionário de interface (actions.openInChatPrompt), então sites localizados o enviam no idioma deles, e i18n.ui pode sobrescrever o texto — mantenha o placeholder {url}, que é substituído pela URL do Markdown bruto da página.

Para ajustar a ação, defina ai.openInChat. false a esconde por completo, e um array de chaves de provedor — "v0", "chatgpt", "claude", "t3", "scira", "cursor" — mostra apenas esses provedores, na ordem em que você os listar:

ai: {
  openInChat: ["claude", "chatgpt", "cursor"],
}

Para incorporar um prompt pronto para copiar direto no seu conteúdo — em vez de uma ação de página inteira — use o componente Prompt, que renderiza uma linha rotulada com um botão Copiar prompt e um link opcional para abrir no Cursor.

Ask AI

Adicione um assistente que responde às perguntas dos leitores em um painel de chat na página, sustentado por um endpoint de servidor com streaming e pelo AI SDK:

ai: {
  ask: {
    enabled: true,
    provider: "gateway", // default
    model: "openai/gpt-5.5",
  },
}

Perguntas sugeridas

Preencha o estado vazio com alguns prompts iniciais. Cada um é renderizado como uma sugestão clicável — clique em uma para enviá-la — com um ícone Lucide opcional ao lado do rótulo:

ai: {
  ask: {
    enabled: true,
    suggestions: [
      { label: "What is Blume?", icon: "rocket" },
      { label: "How do I write a docs page?", icon: "file-text" },
      { label: "How do I configure the theme?", icon: "settings" },
    ],
  },
}

label é a pergunta que é feita; icon é opcional. Deixe suggestions sem definir (ou vazio) e o painel abre com um campo de entrada simples.

Instruções personalizadas

Adicione seu próprio texto de prompt de sistema com instructions — identidade, idioma, tom ou qualquer outra coisa que o assistente deva ter em mente:

ai: {
  ask: {
    enabled: true,
    instructions:
      "You are Bloomy, the Acme docs assistant. Answer in the language the question was asked in, and keep answers under three paragraphs.",
  },
}

Seu texto é acrescentado às instruções integradas, em vez de substituí-las: a parte integrada carrega o contrato de fundamentação — responder só a partir das páginas recuperadas, citá-las como links Markdown — do qual dependem as citações do painel de chat, então ela permanece intacta, não importa o que você adicione.

Fundamentação

O Ask AI é fundamentado na sua documentação. Para cada pergunta, ele recupera as páginas mais relevantes — usando o mesmo índice lexical do Orama que alimenta a busca na página — e as injeta no prompt de sistema do modelo, para que as respostas venham do seu conteúdo, e não do conhecimento próprio do modelo. O assistente é instruído a responder só a partir das páginas recuperadas, a dizer quando algo não está coberto e a citar as páginas de onde tirou a informação.

A página em que o leitor está é adicionada primeiro ao contexto e usada para restringir a recuperação ao idioma dessa página, para que as respostas continuem relevantes para o ponto em que ele está na documentação. A recuperação roda em tempo de requisição a partir de um snapshot embutido no build, então funciona independentemente do seu provedor de busca — mesmo quando a busca está definida como none — e não precisa de configuração.

A fundamentação fica ativa em todos os backends, exceto o Inkeep, que executa a própria recuperação sobre o conteúdo que você indexou no painel dele.

Tamanho da recuperação

Quanta documentação uma pergunta carrega é a maior alavanca sobre quanto tempo o leitor espera pela primeira palavra: o modelo lê cada caractere injetado antes de emitir um token. Em um modelo de fronteira hospedado isso é invisível, mas em um backend auto-hospedado é o fator dominante. retrieval dimensiona isso:

ai: {
  ask: {
    enabled: true,
    retrieval: {
      maxResults: 3, // fewer pages retrieved per question
      excerptChars: 1200, // shorter excerpt from each one
      contextBudget: 3000, // smaller total injection
    },
  },
}
Opção Padrão Descrição
maxResults 6 Documentos recuperados por pergunta.
excerptChars 2000 Caracteres mantidos de cada página recuperada.
contextBudget 10000 Total de caracteres injetados, somando os trechos.

Os três não são intercambiáveis. contextBudget limita a injeção inteira, excerptChars decide quão fundo o trecho de uma única página longa chega — aumente quando uma página guarda a resposta inteira e o trecho a corta — e maxResults limita quantas páginas a recuperação adiciona. A página que o leitor está vendo é injetada além das recuperadas, então uma resposta pode citar até uma página a mais do que maxResults.

Os padrões servem bem a um modelo hospedado. Reduza-os quando você estiver servindo a partir do seu próprio hardware e o tempo até o primeiro token importar mais que a abrangência; as respostas continuam fundamentadas nos dois casos, e o assistente é instruído a dizer quando algo não está coberto, em vez de preencher a lacuna.

Endpoint externo

Já tem um backend de API para IA? Aponte o painel para ele e mantenha o build da documentação estático:

ai: {
  ask: {
    enabled: true,
    endpoint: "https://api.example.com/v1/docs/ask",
  },
}

O Blume envia o mesmo corpo POST que a rota integrada:

{
  "messages": [{ "role": "user", "content": "How do I deploy?" }],
  "page": { "path": "/deployment" }
}

Retorne uma resposta bem-sucedida cujo corpo seja um stream de texto UTF-8 simples. Se o endpoint estiver em outra origem, libere a origem da documentação com CORS: aceite OPTIONS e POST, permita o cabeçalho de requisição content-type e retorne os cabeçalhos CORS tanto no preflight quanto na resposta em streaming. Com endpoint definido, o Blume gera a interface de chat, mas nenhuma rota de servidor, snapshot de fundamentação, dependência de provedor ou aviso de segredo de provedor; o seu backend cuida de recuperação, autenticação, limitação de taxa, acesso ao modelo e citações.

Saída de servidor obrigatória

O backend integrado do Ask AI no Blume é uma rota de servidor (POST /api/ask), então ele não pode rodar em um build estático. Mude para saída de servidor e escolha um adaptador:

deployment: {
  output: "server",
  adapter: "vercel",
}

Um build estático com o Ask AI ativado e sem endpoint externo falha logo de cara, com uma mensagem dizendo para definir deployment.output como server. Veja Deploy para conhecer os adaptadores.

Backends

Por padrão, o Ask AI é roteado pelo Vercel AI Gateway: model é uma string provider/model, então você troca de modelo alterando essa string (openai/gpt-5.5, anthropic/claude-sonnet-4-5, e assim por diante) sem nenhum SDK de provedor para instalar. O gateway lê AI_GATEWAY_API_KEY do seu ambiente e é configurado automaticamente quando você faz o deploy na Vercel.

Defina provider para apontar o Ask AI para outro lugar. Cada backend lê sua chave de API de uma variável de ambiente e faz o streaming por um SDK de provedor que você instala no seu projeto — só o que você usar:

provider model Variável de ambiente da chave de API SDK a instalar
gateway (padrão) uma string provider/model via AI Gateway AI_GATEWAY_API_KEY nenhum — já vem com o Blume
openrouter qualquer modelo do OpenRouter OPENROUTER_API_KEY @openrouter/ai-sdk-provider
llmgateway qualquer modelo do LLMGateway LLMGATEWAY_API_KEY @ai-sdk/openai-compatible
inkeep um modelo QA do Inkeep INKEEP_API_KEY @ai-sdk/openai-compatible
openai-compatible o que quer que o seu endpoint sirva definida com apiKeyEnv @ai-sdk/openai-compatible

Os SDKs são dependências de par opcionais, então adicione ao seu projeto aquele de que o seu backend precisa (por exemplo, npm install @openrouter/ai-sdk-provider). Se ele estiver faltando, o build avisa com o nome exato do pacote antes que o Vite falhasse ao resolver o import.

Por exemplo, para usar o OpenRouter:

ai: {
  ask: {
    enabled: true,
    provider: "openrouter",
    model: "anthropic/claude-sonnet-4-5",
  },
}

Qualquer endpoint compatível com OpenAI funciona via openai-compatible — informe o baseUrl e a variável de ambiente que guarda a chave:

ai: {
  ask: {
    enabled: true,
    provider: "openai-compatible",
    baseUrl: "https://my-gateway.example.com/v1",
    apiKeyEnv: "MY_GATEWAY_API_KEY",
    model: "gpt-4o",
  },
}

Defina apiKeyEnv (e, para os provedores nomeados, baseUrl) em qualquer backend para apontar para outra variável de ambiente ou proxy.

As chaves são lidas com process.env, o que cobre os adaptadores Node, Vercel e Netlify. Na Cloudflare, exponha a chave pelo binding de runtime da plataforma. Ativar o Ask AI também liga o React para a ilha na página — veja Personalização.

Limitação de taxa

O endpoint POST /api/ask é não autenticado — precisa ser, para que o assistente na página consiga chamá-lo. O Blume valida cada requisição — rejeitando corpos malformados, limitando a 1–40 mensagens e aceitando só os papéis user/assistant, para que quem chama não consiga injetar o próprio prompt de sistema e transformar a rota em um proxy genérico de LLM — para limitar quanto uma única chamada pode gastar do seu modelo, mas não consegue impedir alguém de chamar o endpoint repetidamente. Se abuso de custos for uma preocupação, coloque a rota atrás de um rate limiter — a limitação de taxa na edge do seu host (por exemplo, a da Vercel), um middleware ou os limites de gasto por chave do seu provedor de modelos.

Servidor MCP

Hospede um servidor Model Context Protocol para que agentes de programação (Claude Code, Cursor, VS Code, conectores do claude.ai) possam buscar e ler sua documentação diretamente — sem scraping:

ai: {
  mcp: {
    enabled: true,
    route: "/mcp", // where the server is mounted
  },
}
Opção Padrão Descrição
enabled false Gerar e hospedar o servidor MCP.
route /mcp Caminho onde o endpoint Streamable-HTTP é montado.
name título Nome do servidor mostrado aos clientes (por padrão, o título).
instructions Dica de sistema opcional passada aos agentes que se conectam.

O servidor expõe ferramentas somente leitura — search_docs, get_page, list_pages e get_navigation — e cada página como um recurso MCP (resources/list enumera as páginas nas URLs em que são servidas com o tipo text/markdown; resources/read retorna o Markdown da página voltado a agentes, a mesma saída de get_page), para que clientes que anexam contexto por URI possam navegar pela documentação sem chamar uma ferramenta. Ele publica documentos de descoberta em /.well-known/mcp.json e /.well-known/mcp/server-card.json. O cartão do servidor segue o esquema da extensão Server Card SEP-2127 (name em DNS reverso, endpoints de transporte remotes), com campos de compatibilidade no formato do initialize (serverInfo, capabilities, transports) para scanners construídos com base na revisão anterior da proposta. O menu Conectar ao MCP de cada página oferece instalação de copiar e usar para Claude Code, Cursor, VS Code e Codex (mostrado assim que deployment.site estiver definido).

search_docs roda seu próprio índice de texto completo, então funciona independentemente do seu provedor de busca — e mesmo quando a busca está definida como none. O servidor MCP é um recurso separado da busca na página.

Tanto search_docs quanto list_pages aceitam um filtro opcional contentTypes, que restringe os resultados a páginas dos types de frontmatter indicados — ["rfc"], ["blog", "changelog"] — para que um agente trabalhando com um site que mistura documentação com RFCs, runbooks ou políticas possa restringir a recuperação ao tipo de página de que precisa. Todo resultado informa seu tipo de conteúdo, e a saída do list_pages mostra os tipos em uso.

As duas ferramentas também aceitam um objeto filters que casa com as facetas que um site declara por tipo de conteúdo (content.types.<type>.facets) — chaves de frontmatter personalizadas cujos valores viram metadados filtráveis:

{
  "query": "OpenAPI request schemas",
  "contentTypes": ["rfc"],
  "filters": { "domain": "architecture", "status": "enforced" }
}

Toda entrada de filters precisa casar (os resultados carregam seus valores de faceta, e o list_pages mostra os de cada página), então uma base de conhecimento pode conduzir fluxos de trabalho de agentes com divulgação progressiva — enumerar os padrões em vigor, buscar só dentro deles — sem nenhum servidor próprio.

Saída de servidor obrigatória

O servidor MCP é um endpoint ativo (/mcp), então ele não pode rodar em um build estático. Mude para saída de servidor e escolha um adaptador:

deployment: {
  output: "server",
  adapter: "node", // or "vercel" | "netlify" | "cloudflare"
  site: "https://docs.example.com",
}

Um build estático com ai.mcp.enabled falha logo de cara, com uma mensagem dizendo para definir deployment.output como server. Veja Deploy para conhecer os adaptadores. Depois do deploy, conecte a partir do Claude Code com:

claude mcp add --transport http my-docs https://docs.example.com/mcp

API JSON

Todo site Blume também serve sua documentação como uma pequena API JSON somente leitura — a irmã REST das ferramentas do servidor MCP, sobre o mesmo snapshot de páginas, para agentes e frameworks de function calling que falam HTTP simples em vez de MCP. Vem ativada por padrão e não precisa de configuração:

Endpoint Retorna
/api/docs/pages.json Todas as páginas com sua rota, título, descrição, tipo de conteúdo, idioma, facetas e as URLs das suas formas renderizada, Markdown e JSON.
/api/docs/pages/{route}.json Uma página: sua entrada de índice mais o Markdown voltado a agentes (o mesmo corpo que get_page retorna). {route} é a rota da página sem a barra inicial, index para a inicial.
/api/docs/navigation.json A árvore de navegação — abas do cabeçalho e a hierarquia da barra lateral.
/api/docs/search?q= Busca de texto completo, com a mesma restrição por limit, contentTypes, locale, version e filters[key] do search_docs. Só com saída de servidor.
/openapi.json A descrição OpenAPI 3.1 de toda a superfície legível por máquinas.

O índice de páginas, os documentos por página e a navegação são pré-renderizados, então um site estático os serve como arquivos a partir de qualquer host. A busca é um endpoint ativo e existe apenas com saída de servidor, onde ela roda o mesmo índice que o search_docs. Os erros são problem details da RFC 9457 (application/problem+json) com um code estável, um detail e uma dica de resolution dizendo ao agente para onde ir em seguida — uma página inexistente, uma consulta de busca vazia ou, com saída de servidor, qualquer URL /api/… que nenhum endpoint responda:

{
  "code": "API_ROUTE_NOT_FOUND",
  "detail": "No API route exists at /api/nope.",
  "instance": "/api/nope",
  "links": [
    {
      "href": "https://docs.example.com/openapi.json",
      "label": "OpenAPI description"
    },
    {
      "href": "https://docs.example.com/api/docs/pages.json",
      "label": "Page index"
    }
  ],
  "resolution": "Discover the available operations through the OpenAPI description at https://docs.example.com/openapi.json, or list every page at https://docs.example.com/api/docs/pages.json.",
  "status": 404,
  "title": "API route not found",
  "type": "about:blank"
}

O documento OpenAPI em /openapi.json é gerado a cada build a partir da sua configuração, então descreve só o que o site publicado serve: cada endpoint JSON com um operationId único, parâmetros tipados e esquemas de resposta, mais as superfícies de texto ao lado — os espelhos .md, o llms.txt e o llms-full.txt, o agent-readability.json — e o endpoint MCP quando ele está ativado. Frameworks que constroem ferramentas a partir de uma descrição OpenAPI ganham o mesmo alcance que um cliente MCP tem. O documento é referenciado pelo catálogo de API, pelo manifesto de legibilidade, pelo cabeçalho Link da página inicial como rel="service-desc" e pelo llms.txt.

Nada disso mexe na sua própria referência de API: uma especificação documentada é renderizada em páginas, nunca servida em /openapi.json, e o catálogo lista as duas. Um public/openapi.json publicado por você assume essa rota (os endpoints JSON continuam). O catch-all de /api/… sai do caminho quando uma seção da documentação é servida a partir do namespace /api (content/api/overview.md) ou quando uma página personalizada é dona de uma rota rest sob /api/, então essas páginas continuam vencendo. Defina ai.api como false para não publicar nada disso:

ai: {
  api: false,
}

Legibilidade para agentes

O Blume escreve um manifesto /agent-readability.json na raiz do seu site que indexa a superfície voltada a agentes descrita nesta página — para que um agente possa descobri-la em uma única requisição, em vez de adivinhar convenções ou raspar o HTML. Assim como o llms.txt, vem ativado por padrão:

seo: {
  agentReadability: true,
}

O manifesto lista só o que você ativou — o padrão de espelho de Markdown bruto, a API JSON e sua descrição OpenAPI, o llms.txt e o llms-full.txt, o servidor MCP e seu documento de descoberta, o endpoint do Ask AI, o sitemap e os feeds RSS — junto com o nome do seu site, a descrição, o repositório de origem e a política de uso de content-signal. As URLs são absolutas quando deployment.site está definido e relativas à raiz caso contrário:

{
  "artifacts": {
    "markdown": {
      "contentNegotiation": "text/markdown",
      "pattern": "https://docs.example.com/{route}.md"
    },
    "api": {
      "openapi": "https://docs.example.com/openapi.json",
      "pages": "https://docs.example.com/api/docs/pages.json",
      "search": "https://docs.example.com/api/docs/search"
    },
    "llmsFullTxt": "https://docs.example.com/llms-full.txt",
    "llmsTxt": "https://docs.example.com/llms.txt",
    "mcp": {
      "discovery": "https://docs.example.com/.well-known/mcp.json",
      "url": "https://docs.example.com/mcp"
    }
  },
  "description": "Docs for the Acme API.",
  "generator": "blume@1.0.0",
  "name": "Acme Docs",
  "site": "https://docs.example.com",
  "contentUsage": { "search": true, "ai-input": true, "ai-train": true },
  "repository": "https://github.com/acme/docs"
}

O campo contentNegotiation aparece só quando o site em produção realmente respeita o cabeçalho Accept: text/markdown — veja negociação de conteúdo; em todos os outros deploys, o manifesto anuncia apenas o padrão de espelho .md.

Defina seo.agentReadability como false para pulá-lo, ou publique seu próprio public/agent-readability.json para assumir o controle — o Blume nunca sobrescreve um arquivo que você coloca em public/.

Agentes que sondam um site não sabem que devem procurar o manifesto — por isso o Blume também o anuncia em um cabeçalho de resposta Link conforme a RFC 8288 na página inicial, usando tipos de relação registrados na IANA:

Link: </.well-known/api-catalog>; rel="api-catalog"; type="application/linkset+json",
  </openapi.json>; rel="service-desc"; type="application/json",
  </agent-readability.json>; rel="describedby"; type="application/json",
  </llms.txt>; rel="describedby"; type="text/plain",
  </index.md>; rel="alternate"; type="text/markdown"

Cada entrada aparece só quando o respectivo recurso está ativo. O link alternate aponta para o espelho Markdown da página inicial — o Markdown bruto da própria página quando a rota inicial é uma página de conteúdo, ou o fallback do llms.txt sintetizado quando é uma landing page. O link service-desc (RFC 8631) aponta para a descrição OpenAPI da API JSON, e o api-catalog para o catálogo de API gerado. O cabeçalho vai em todas as superfícies que o Blume controla: o servidor de desenvolvimento (confira com curl -I localhost:4321), os builds estáticos via arquivo _headers emitido (Netlify e Cloudflare) e os builds de servidor na Vercel via regras de roteamento do deploy.

Mas nem todo agente entra pela raiz — um que segue um resultado de busca ou um link compartilhado cai em uma página profunda e nunca vê o cabeçalho da página inicial. Por isso toda página renderizada também carrega os mesmos links de descoberta no <head> do seu HTML, usando as mesmas relações registradas na IANA:

<link
  rel="describedby"
  href="/agent-readability.json"
  type="application/json"
/>
<link rel="describedby" href="/llms.txt" type="text/plain" />
<link rel="alternate" href="/docs/example.md" type="text/markdown" />

Aqui o link alternate aponta para o espelho Markdown bruto daquela própria página, então um agente consegue pular direto do HTML em que caiu para a versão eficiente em tokens. Como os links no head viajam junto com o HTML pré-renderizado, eles também funcionam em hosts que ignoram _headers e não conseguem enviar cabeçalhos de resposta personalizados de jeito nenhum (GitHub Pages, S3) — não importa por qual página o agente entre.

Catálogo de API

Quando o site publica APIs, o Blume gera um catálogo de API conforme a RFC 9727 em /.well-known/api-catalog — um linkset que permite aos agentes enumerar suas APIs só a partir do domínio, servido com seu media type registrado application/linkset+json em todas as superfícies de build. Não há nada para configurar: o catálogo é derivado do que já está no blume.config.ts. Cada referência OpenAPI ou AsyncAPI vira uma entrada ancorada na sua rota de documentação renderizada, com service-doc apontando para essa documentação e service-desc para a especificação quando ela mora em uma URL acessível; a própria API JSON do site vira uma entrada descrita pelo seu /openapi.json; e o servidor MCP vira uma entrada com seu documento de descoberta como descrição do serviço:

{
  "linkset": [
    {
      "anchor": "https://docs.example.com/reference",
      "service-doc": [
        { "href": "https://docs.example.com/reference", "type": "text/html" }
      ],
      "service-desc": [{ "href": "https://api.example.com/openapi.json" }]
    },
    {
      "anchor": "https://docs.example.com/api/docs",
      "service-desc": [
        {
          "href": "https://docs.example.com/openapi.json",
          "type": "application/json"
        }
      ],
      "service-doc": [
        { "href": "https://docs.example.com/", "type": "text/html" }
      ]
    },
    {
      "anchor": "https://docs.example.com/mcp",
      "service-desc": [
        {
          "href": "https://docs.example.com/.well-known/mcp.json",
          "type": "application/json"
        }
      ],
      "service-doc": [
        { "href": "https://docs.example.com/", "type": "text/html" }
      ]
    }
  ]
}

Um site sem referências de API, sem servidor MCP e com a API JSON desativada não emite catálogo nenhum — não haveria nada nele. Como em todo lugar, um arquivo public/.well-known/api-catalog publicado por você vence o gerado.

WebMCP

O WebMCP é uma API de navegador emergente que permite a uma página registrar ferramentas diretamente em um navegador agêntico — sem precisar de uma conexão com um servidor separado. Toda página do Blume registra a superfície somente leitura da documentação no model context da página: search_docs (busca do site), get_page (o Markdown bruto de uma página) e list_pages (o índice llms.txt). O script é minúsculo, não carrega nenhuma maquinaria de busca até uma ferramenta ser de fato chamada e vira um no-op silencioso em todo navegador sem a API — o que hoje são todos, fora a prévia inicial do Chrome. Ele se registra na superfície que a spec em evolução expuser (navigator.modelContext ou document.modelContext), via provideContext ou registerTool por ferramenta.

Vem ativado por padrão; defina webmcp: false para desativar:

ai: {
  webmcp: false,
}

Descoberta de skills

Se o seu projeto inclui agent skills — o próprio repositório do Blume inclui — aponte ai.skills para o diretório que as contém, e o build as publica para descoberta conforme a RFC de Agent Skills Discovery:

ai: {
  skills: "./skills",
}

O caminho é resolvido em relação à raiz do seu projeto, e cada subdiretório com um SKILL.md vira uma skill publicada. Uma skill que é só um SKILL.md sozinho é copiada como está para /.well-known/agent-skills/<name>/SKILL.md (type: "skill-md"); uma skill com recursos de apoio (scripts/, references/, assets/) é empacotada em um .tar.gz determinístico (type: "archive") para que suas referências relativas resolvam depois da extração, com os bits de execução dos scripts preservados. O índice de descoberta em /.well-known/agent-skills/index.json carrega o $schema v0.2.0 e, por skill, o nome, o tipo, a descrição (do frontmatter do SKILL.md), a URL do artefato e o digest SHA-256 que os clientes usam para verificar os downloads.

Skills com name/description ausente ou inválido perante a spec são puladas com um aviso de build, em vez de publicadas quebradas, e um public/.well-known/agent-skills/index.json publicado por você assume o controle de toda a superfície.

Descoberta baseada em DNS (DNS-AID)

O DNS for AI Discovery é um rascunho emergente do IETF que permite aos agentes descobrir a superfície de IA de um site antes de fazer uma única requisição HTTP, consultando registros SVCB/HTTPS em ServiceMode em um ponto de entrada DNS bem conhecido. Os registros DNS moram na sua zona, não no build, então essa é a única superfície de descoberta que o Blume não pode publicar por você — em vez disso, adicione um registro no seu provedor de DNS:

_index._agents.docs.example.com. 3600 IN HTTPS 1 docs.example.com. alpn=h2

Use o tipo de registro HTTPS se o seu provedor oferecer (o Vercel DNS oferece; ele não suporta o tipo SVCB puro), ou, caso contrário, um registro SVCB em ServiceMode com os parâmetros alpn e port. O rascunho também recomenda assinar a zona com DNSSEC para que resolvedores validadores retornem respostas autenticadas — provedores como a Cloudflare ativam isso com um clique, enquanto alguns (incluindo o Vercel DNS) não suportam de jeito nenhum.

blume audit --url <origin> verifica isso por você: quando deployment.site está definido, a camada de rede consulta o ponto de entrada por DNS-over-HTTPS e informa o registro exato a publicar se não existir nenhum, além de dizer se as respostas são autenticadas por DNSSEC. Defina BLUME_DOH_URL para apontar a consulta para o seu próprio resolvedor se a sua rede bloquear os públicos (Google, Cloudflare).

Web Bot Auth

O Web Bot Auth funciona na direção contrária: não se trata de agentes lendo sua documentação, mas de os agentes da sua organização se identificarem quando fazem requisições em outros lugares. Seus agentes assinam as requisições com HTTP Message Signatures, e os sites que as recebem as verificam contra um diretório de chaves públicas publicado no seu domínio. Se a sua organização roda agentes e o seu site Blume mora no domínio com que eles se identificam, publique as chaves públicas deles:

ai: {
  webBotAuth: {
    keys: [{ kty: "OKP", crv: "Ed25519", x: "JrQLj5P_89iXES9-vFgrIy29c…" }],
  },
}

O Blume passa então a servir o JWKS em /.well-known/http-message-signatures-directory com seu media type registrado em todas as superfícies de build. O diretório é público por definição, então a configuração só admite chaves públicas — um JWK que contenha material privado (d, p, q, …) falha na validação com um erro, em vez de publicar uma credencial vazada. Gere um par Ed25519 com:

node -e 'const { generateKeyPairSync } = require("node:crypto"); const { publicKey, privateKey } = generateKeyPairSync("ed25519"); console.log("public: ", JSON.stringify(publicKey.export({ format: "jwk" }))); console.log("private:", JSON.stringify(privateKey.export({ format: "jwk" })))'

O JWK público vai na configuração acima; o privado vai para onde quer que o seu agente de assinatura rode (um gerenciador de segredos, nunca o repositório). Se a sua organização não opera agentes, pule isso — um diretório vazio não anuncia nada que valha a pena verificar.

Como o blume.config.ts é executado em tempo de build, a chave não precisa estar no código — carregue-a de uma variável de ambiente em tempo de build para manter a configuração livre de blobs de chave e rotacioná-la sem commit:

const webBotAuthKey = process.env.WEB_BOT_AUTH_PUBLIC_JWK;

export default defineConfig({
  ai: {
    webBotAuth: {
      keys: webBotAuthKey ? [JSON.parse(webBotAuthKey)] : [],
    },
  },
});

Ambientes sem a variável não publicam diretório nenhum, e uma chave carregada assim é validada exatamente como uma inline — incluindo a checagem de material privado. (A chave pública não é um segredo, então commitá-la inline é igualmente válido; a variável de ambiente é uma escolha ergonômica, não de segurança.)

Agent skill

Está construindo um site Blume com a ajuda de um agente de programação? Instale a agent skill do Blume para que ele saiba como o Blume funciona sem você precisar explicar:

npx skills add haydenbleasel/blume

A skill ensina ao agente o que é o Blume e como criar, escrever e configurar um site, e o aponta para a documentação completa incluída no pacote instalado (o diretório docs/ dentro de blume, onde quer que o seu gerenciador de pacotes o instale).

É uma das agent skills que o Blume traz, junto com uma skill para manter a documentação em sincronia com o seu produto a partir de uma execução agendada de um agente.

Esta página foi útil?