6 ขั้นตอนปฏิบัติให้เว็บเดียวรองรับทั้งผู้อ่านและ AI agent พร้อมเช็กลิสต์พร้อมคัดลอก
สี่วันที่ผ่านมา ผมกับ Claude Code เปลี่ยน smarts.md จากเว็บเอกสารสมาร์ตคอนแทรกต์ที่สร้างขึ้นสำหรับมนุษย์ ให้กลายเป็นเว็บที่สร้างขึ้นสำหรับ AI agent
ฟังดูเหมือนกัน แต่จริง ๆ ไม่ใช่เลย เว็บสำหรับมนุษย์ปรับให้อ่านง่าย ค้นได้ และโหลดไว เว็บสำหรับ AI agent ต้องตอบคำถามคนละชุด:
robots.txt ดีฟอลต์ล็อกพวกมันไว้ข้างนอก?ต่อไปนี้คือ 6 อย่างที่ผมให้ Claude ทำ แต่ละอย่างใช้อิสระได้และคุณเอาไปใส่เว็บของตัวเองทีละอย่างได้
robots.txtเว็บส่วนใหญ่สืบ robots.txt มาจากเทมเพลตเก่า ๆ ที่ปิดกั้น User-Agent ที่ไม่รู้จักโดยปริยาย แต่หลังปี 2025 AI crawler หลายตัวเป็น User-Agent ใหม่ — GPTBot, ClaudeBot, PerplexityBot, Applebot-Extended, Google-Extended, CCBot, meta-externalagent และอื่น ๆ ถ้าอยากให้มันเข้า ต้อง Allow ให้ชัด
robots.txt ของ smarts เปิดด้วยบันทึกนโยบาย แล้วไล่ User-agent + Allow: / ให้ crawler 12 ตัว ครอบคลุม OpenAI, Anthropic, Perplexity, Google Gemini, Apple, Common Crawl, Meta และ Cohere จุดยืนตรงไปตรงมา: เว็บนี้มีอยู่เพื่อให้ AI อ่าน
llms.txt — เมนูสำหรับ LLMllms.txt เป็น convention ที่ llmstxt.org เสนอ: วาง Markdown สั้น ๆ ไว้ที่รากของเว็บ บอก LLM ว่าเว็บนี้คืออะไร ใช้ยังไง และจุดเข้าหลักอยู่ไหน เหมือน elevator pitch ของเว็บคุณ
llms.txt ของ smarts ประมาณ 60 บรรทัด ประกอบด้วย:
/{chain}/{address} vs slug สั้น)คีย์เวิร์ด: LLM จ่ายเป็น token เพื่ออ่านเอกสารของคุณ จึงไม่ crawl ทั้งเว็บ ยื่น index ที่กลั่นแล้วให้มัน อัตราเข้าเป้าจะสูงขึ้นอย่างชัดเจน
.well-known/mcp.json — วางเดิมพันแต่เนิ่น ๆโปรโตคอล MCP ยังไม่ตั้งมาตรฐาน discovery แบบ /.well-known/openid-configuration ตอนนี้ไม่มี client สาธารณะตัวไหนเข้าคราว path well-known ตายตัว
แต่การเผยแพร่ 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
วันที่ convention discovery โผล่มา ผมไม่ต้องแก้แม้แต่บรรทัดเดียว และ manifest ชิ้นนี้ก็ยัง เป็น self-documentation ให้เดฟ ด้วย — ใครสงสัย MCP ของผม ก็แค่ curl smarts.md/.well-known/mcp.json
เคล็ดลับ: tools array ดึงมาจากค่า constant MCP_TOOLS ซึ่งเป็นแหล่งความจริงเดียว test เชิงโครงสร้างที่มีอยู่บังคับให้ทุก app/tools/*_tool.rb ต้องปรากฏใน constant นั้น manifest จึงได้รับประกัน "tool ใหม่ไม่ตกหล่น" ฟรี ๆ
.md: สรุปที่เป็นมิตรกับ WebFetch ของทุกหน้าตัวโปรดผม WebFetch tool ของ Claude Code ต้องทำความสะอาดและดึงเนื้อหาจาก HTML เองซึ่งเปลืองโทเค็นและไม่เสถียร แล้วถ้าส่ง .md ให้เลยล่ะ?
respond_to ของ Rails รองรับ native อยู่แล้ว:
# 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 คืน Markdown 40 บรรทัด — ที่อยู่, chain, classification, 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"
Fetch ครั้งเดียว เนื้อหาที่มีโครงสร้างทุกอย่างที่ AI agent ต้องการอยู่ในนั้น ไม่ต้องใช้ DOM parser Claude จัดการเรื่องนี้เร็วมาก — controller หนึ่งตัว template .md.erb สองอัน เพิ่ม (.:format) ใน routes แค่นั้น
นี่เป็นการ discover ทิศทางกลับกัน: visitor บางส่วนใช้ Claude Code / Cursor / Windsurf อยู่แล้ว พวกเขาเจอหน้า contract แล้วก้าวต่อไปคือ "ให้ AI ของฉันลองดูหน่อย"
ดังนั้นทุกหน้า contract จะ render การ์ดนี้:
usdc-eth) หรือ chain/address พร้อมปุ่ม Copymcp.smarts.md (setup บรรทัดเดียว)ระหว่าง "ผมกำลังดู contract นี้" กับ "ผมกำลังถาม AI ของผม" เหลือแรงเสียดทานแค่คลิกเดียว การ์ดใช้ card ของ DaisyUI กับ Stimulus controller copy แบบทั่วไป — erb 50 บรรทัด test regression ล็อก attribute data-copy-text-value ไว้ เพื่อไม่ให้ refactor ในอนาคตแอบวางข้อความผิดเข้าคลิปบอร์ดผู้ใช้
OpenGraph, Twitter Card, JSON-LD — ชั้นค้นหาและ social card แบบเดิม ทำไว้คุ้ม เพราะ AI crawler ก็อ่านตรงนี้เหมือนกัน
ที่น่าสนใจคือ JSON-LD ควรอธิบายสมาร์ตคอนแทรกต์ยังไง Claude เลือก WebPage ห่อ SoftwareApplication:
{
"@type": "WebPage",
"about": {
"@type": "SoftwareApplication",
"name": "USD Coin",
"applicationCategory": "SmartContract",
"operatingSystem": "Ethereum",
"identifier": "0xa0b8..."
}
}
ใส่ชื่อ chain ใน operatingSystem, ที่อยู่ใน identifier, ใส่ classification ใน additionalType (เช่น "Uniswap V3 Pool") schema.org ไม่มี type เฉพาะสำหรับ SmartContract แต่ SoftwareApplication + field เหล่านี้เพียงพอให้ LLM เข้าใจว่า "นี่คือ contract บน Ethereum ที่มีที่อยู่เฉพาะเจาะจง"
รูป OG เป็น summary_large_image ขนาด 1200×630; Twitter Card, breadcrumb, softwareVersion, license ถูกเติมให้ครบ
หกข้อข้างบนมาจาก PR เจ็ดตัวในสี่วัน:
| PR | ไฟล์ | ขนาด |
|---|---|---|
feat/ai-crawler-discovery |
2 | ~126 loc |
feat/well-known-mcp-manifest |
4 | ~129 loc |
feat/markdown-contract-pages |
6 | ~298 loc |
feat/contract-mcp-card |
3 | ~126 loc |
feat/seo-meta-tags |
8 | ~292 loc |
feat/seo-enrichments |
8 | ~189 loc |
feat/og-card |
— | — |
แต่ละตัวเป็น PR แยก พร้อม test ของตัวเองและ commit message ของตัวเอง จังหวะการหั่นให้ละเอียดแบบนี้คือสิ่งที่มีค่าที่สุดตอนทำงานกับ Claude — แต่ละ PR เล็กพอให้ review diff ด้วยตาเดียวได้ แต่พอเอามารวมกัน เว็บงอกพื้นที่ AI ครบชุด
ถ้ามีเว็บแนวเนื้อหา แล้วอยากให้เป็นมิตรกับ AI ทำตามลำดับนี้:
robots.txt/llms.txt (เมนูที่กลั่นแล้ว).md ให้หน้าเนื้อหาหลัก/.well-known/mcp.jsonห้าข้อแรก SEO engineer ส่วนใหญ่ไม่บอก — เพราะไม่อยู่ใน playbook แบบเดิม แต่จากข้อมูลสี่วันที่ผมมี เส้นกราฟทราฟฟิก AI กับทราฟฟิกมนุษย์เป็นสองเส้นที่อิสระต่อกันโดยสิ้นเชิง ต้องให้บริการทั้งสอง