让内容站同时服务人类读者和 AI agent 的 6 条实操,带可抄的清单
最近 4 天,我和 Claude Code 一起把 smarts.md 从一个"给人看的智能合约文档站"改造成了一个"给 AI agent 用的智能合约文档站"。
这两者听起来像同一件事,其实差别很大。给人看的站点,你追求的是可读、可搜索、加载快;给 AI agent 用的站点,你追求的是:
这篇记一下我让 Claude 做的 6 件事,每件都能独立在你的站点上复用。
robots.txt 显式欢迎 AI 爬虫大多数站点的 robots.txt 是从一个旧模板抄来的,里面默认拒绝所有未识别的 User-Agent。但 2025 年之后,不少 AI 爬虫是新增的 User-Agent——GPTBot、ClaudeBot、PerplexityBot、Applebot-Extended、Google-Extended、CCBot、meta-externalagent……如果你想被它们抓取,得显式 Allow。
smarts 的 robots.txt 开头写了策略声明,然后逐个 User-agent + Allow: /,覆盖 OpenAI、Anthropic、Perplexity、Google Gemini、Apple、Common Crawl、Meta、Cohere 等 12 个爬虫。策略很直白:这个站本来就是给 AI 读的。
llms.txt 给 LLM 看的菜单llms.txt 是 llmstxt.org 提出的约定:在站点根目录放一份精简的 Markdown,告诉 LLM "这个站点是做什么的、怎么用、有哪些核心入口"。相当于你站点的电梯游说。
smarts 的 llms.txt 大概 60 行,包含:
/{chain}/{address} vs 友好 slug)核心思路:LLM 读文档的成本是 token,它不会整站爬。你给它一份蒸馏过的目录,它命中率会高很多。
.well-known/mcp.json 提前押注MCP 协议还没形成像 /.well-known/openid-configuration 那样的自动发现约定,没有任何公开 client 会去爬一个固定路径的 manifest。
但发布这份 manifest 只要 30 行:
def well_known_mcp
response.set_header("Cache-Control", "public, max-age=3600")
response.set_header("Access-Control-Allow-Origin", "*")
render json: {
name: "smarts",
version: "0.1.0",
description: "...",
protocol_version: "2024-11-05",
transports: [{ type: "sse", endpoint: "https://smarts.md/mcp/sse" }],
capabilities: { tools: true, resources: false, prompts: false },
tools: MCP_TOOLS.map { |t| { name: t[:name], description: t[:blurb] } }
}
end
一旦某个发现约定出现,我不用改一行代码就对接上了。同时它对开发者也是自文档化——任何人好奇你的 MCP 长什么样,curl smarts.md/.well-known/mcp.json 就够了。
关键是 tools 数组从 MCP_TOOLS 常量派生,单一数据源。既有的结构性测试强制每个 app/tools/*_tool.rb 都要出现在常量里,manifest 免费继承了"新 tool 不会被漏掉"的保证。
.md 版本:每个页面都有一份 WebFetch-friendly 的蒸馏这条是最让我满意的。Claude Code 的 WebFetch 工具拿到 HTML 后要自己做清洗、抽内容,花 token 又不稳。但如果你主动提供 .md 格式呢?
Rails 的 respond_to 天然支持:
# config/routes.rb
get ":slug(.:format)", to: "contracts#show",
constraints: { slug: ContractSlugs::ROUTE_PATTERN, format: /html|md/ }
访问 https://smarts.md/usdc-eth 返回 HTML,访问 https://smarts.md/usdc-eth.md 返回一份 40 行 Markdown——地址、链、分类、MCP endpoint、如何用 AI agent 查询、原始链接。
# USD Coin on Ethereum
- **Address:** `0xa0b8...`
- **Chain:** Ethereum
- **Classification:** ERC-20 Token
## Query via AI agent
- **MCP endpoint:** `https://smarts.md/mcp/sse`
- **Reference:** `usdc-eth`
- **Sample prompt:** "Tell me the current state of usdc-eth"
AI agent 抓一次 .md,所有它想要的结构化信息都在里面,不需要跑 DOM parser。这条 Claude 做得很快——一个 controller、两个 .md.erb 模板、routes 里加 (.:format),就这么多。
这是反过来的发现:我站点的访问者里,已经有一部分人在用 Claude Code / Cursor / Windsurf。他们看到一个合约页面,下一步往往想"让我 AI 帮我看看这个"。
那就在每个合约页面渲染一张卡片:
usdc-eth)或者 chain/address,旁边一个 Copy 按钮mcp.smarts.md 教你一行命令接入用户从"我在看这个合约"到"我在问我的 AI",只有一次点击的摩擦。这张卡片用了 DaisyUI 的 card + 一个通用 Stimulus copy controller,50 行 erb 搞定,还有个回归测试锁死 data-copy-text-value 属性,防止哪天 refactor 把错的字符串塞进用户剪贴板。
OpenGraph、Twitter Card、JSON-LD 这套是给传统搜索和社交卡片的,但值得做——AI 爬虫也会读这些。
重点在 JSON-LD 怎么描述一个智能合约。Claude 选的是 WebPage 包一个 SoftwareApplication:
{
"@type": "WebPage",
"about": {
"@type": "SoftwareApplication",
"name": "USD Coin",
"applicationCategory": "SmartContract",
"operatingSystem": "Ethereum",
"identifier": "0xa0b8..."
}
}
operatingSystem 塞链名,identifier 塞地址,additionalType 塞分类(比如"Uniswap V3 Pool")。schema.org 没有专门的 SmartContract 类型,但用 SoftwareApplication + 这几个字段,足够让 LLM "理解"这是一个跑在 Ethereum 上的合约、有明确的地址标识。
OG image 是 1200×630 的 summary_large_image,Twitter Card、面包屑、softwareVersion、license 字段一并补齐。
上面 6 件事是 4 天里 7 个 PR 的总和:
| PR | 文件数 | 大小 |
|---|---|---|
feat/ai-crawler-discovery |
2 | ~126 行 |
feat/well-known-mcp-manifest |
4 | ~129 行 |
feat/markdown-contract-pages |
6 | ~298 行 |
feat/contract-mcp-card |
3 | ~126 行 |
feat/seo-meta-tags |
8 | ~292 行 |
feat/seo-enrichments |
8 | ~189 行 |
feat/og-card |
— | — |
每一块都是独立 PR、独立测试、独立 commit message。这种切分的节奏是跟 Claude 协作最值的点——每个 PR 都小到可以一眼看完 diff,但加起来站点已经长出完整的 AI 接入面。
如果你有一个内容型站点,想让它对 AI 友好,按这个顺序做:
robots.txt 显式 Allow 主流 AI 爬虫/llms.txt(一份精简菜单).md 格式 variant/.well-known/mcp.json前 5 条大多数 SEO 工程师不会告诉你,因为它们不在传统 SEO 范畴里。但从我这 4 天的数据看,AI 流量的增长曲线和人类流量是两条完全独立的曲线——两条都得服务。