diff --git a/worker/index.js b/worker/index.js
index 97496c6..bbe919f 100644
--- a/worker/index.js
+++ b/worker/index.js
@@ -9,7 +9,9 @@
 // - /api/ev-stats        その記録を週ごと・ページごとに数えて見せる（数だけ。誰かは分からない）
 // - ページ・llms.txt・全文の Markdown など  静的ファイルを返す。検索エンジン・AI のクローラーが読みに来たら、R2（p2m-messages の cr/）に1件ずつ残す
 // - /api/cr-stats        その記録を、どのクローラーが・いつ・どのページを読んだかで数えて見せる（2026-10-25）
-// - 5分ごとの scheduled  SNS の予約投稿の時刻が来ていたら GitHub Actions を起こす（worker/sns_cron.js）
+// - /api/kpi-stats       主に見る3つの数字（AI の依頼読み・AI のリンクからの来訪・サイト名の検索）を日ごと・週ごとに見せる（worker/kpi.js。2026-11-06）
+// - /mcp/{鍵}            サイトの計測を Claude につなぐ部品（MCP サーバー。worker/mcp.js。2026-11-06）
+// - 5分ごとの scheduled  SNS の予約投稿の時刻が来ていたら GitHub Actions を起こす（worker/sns_cron.js）。前の日の3つの数字がまだなければ集計して残す（worker/kpi.js）
 //
 // 守り（2026-10-23）
 // - 数の表（/api/hk-stats・/api/ev-stats・/api/cr-stats）は、鍵（Cloudflare の秘密の値 STATS_KEY）を付けたときだけ見られる。鍵がなければ 404
@@ -19,10 +21,14 @@
 // - このファイルが返す応答には、内容の種類を推測させない指定（nosniff）を付ける。ページ側の指定は public/_headers
 import { EmailMessage } from 'cloudflare:email';
 import { snsCron } from './sns_cron.js';
+import { kpiCron, kpiRange, todayJst, addDays } from './kpi.js';
+import { handleMcp } from './mcp.js';
+import { gscRefresh } from './gsc.js';
 
 export default {
   async fetch(request, env, ctx) {
     const url = new URL(request.url);
+    if (url.pathname.startsWith('/mcp/')) return secured(await handleMcp(request, env, url, { evStats, crStats, hkStats }));
     if (url.pathname.startsWith('/api/') || url.pathname.startsWith('/media/') || url.pathname === '/pixelstomotors' || url.pathname.startsWith('/pixelstomotors/')) {
       return secured(await route(request, env, url));
     }
@@ -59,6 +65,11 @@ export default {
   // 5分ごと（wrangler.jsonc の triggers）：SNS の予約投稿の時刻が来ていたら、GitHub Actions を起こす（worker/sns_cron.js）
   async scheduled(event, env, ctx) {
     ctx.waitUntil(snsCron(env, event.scheduledTime).then((m) => console.log('sns-cron:', m), (e) => console.error('sns-cron:', e.message)));
+    // 前の日の分を作ったとき（1日1回）だけ、Search Console からサイト名の検索の数も取り直す（worker/gsc.js）
+    ctx.waitUntil(kpiCron(env).then(async (m) => {
+      console.log(m);
+      if (m.startsWith('kpi: made')) console.log('gsc:', JSON.stringify(await gscRefresh(env)));
+    }).catch((e) => console.error('kpi-cron:', e.message)));
   },
 };
 
@@ -101,6 +112,7 @@ async function route(request, env, url) {
     if (url.pathname === '/api/hk-stats') return (await statsAllowed(request, env, url)) ? hkStats(request, env, url) : notFound();
     if (url.pathname === '/api/ev-stats') return (await statsAllowed(request, env, url)) ? evStats(request, env, url) : notFound();
     if (url.pathname === '/api/cr-stats') return (await statsAllowed(request, env, url)) ? crStats(request, env, url) : notFound();
+    if (url.pathname === '/api/kpi-stats') return (await statsAllowed(request, env, url)) ? kpiStats(request, env, url) : notFound();
     return env.ASSETS.fetch(request);
 }
 
@@ -308,15 +320,6 @@ async function hkStats(request, env, url) {
   const byDay = new Map(), byWeek = new Map(), byItem = new Map(), byW = new Map();
   for (const r of rows) { add(byDay, r.day, r.e); add(byWeek, weekOf(r.day), r.e); add(byItem, r.id, r.e); add(byW, r.w, r.e); }
   const KIND_JA = { n: '機械の印なし', b: '機械', o: '印なし（10/28より前）' };
-  // 「AIで読む」経由（?via=）。押した AI ごとに、実際に読みに来たのはだれか（2026-11-01）
-  const viaMap = new Map();
-  for (const r of rows) {
-    if (!r.v) continue;
-    const e = viaMap.get(r.v) || { via: r.v, visits: 0, records: new Set(), bots: new Map(), last: '' };
-    e.visits++; e.records.add(r.p); count(e.bots, r.b); if (r.day > e.last) e.last = r.day; viaMap.set(r.v, e);
-  }
-  const viaWeeks = new Map();
-  for (const r of rows) { if (!r.v) continue; const w = viaWeeks.get(r.week) || new Map(); count(w, r.v); viaWeeks.set(r.week, w); }
   const data = {
     total: rows.length,
     who: who || null,
@@ -346,7 +349,6 @@ h1{font-size:20px}h2{font-size:16px;margin-top:28px}table{border-collapse:collap
 <h2>日ごと</h2>${table('日付', data.byDay, 'day')}
 <h2>更新ごと</h2>${table('更新', data.byItem, 'id')}
 <h2>画面の幅（pc＝広い／sp＝スマホ）</h2>${table('幅', data.byWidth, 'w')}
-<h2>返せなかったページ（400以上）</h2>${table([['path', 'ページ'], ['status', '返した結果'], ['visits', '回数'], ['bots', 'クローラー'], ['last', '最後の日']], data.errors)}
 <p>記録の件数：${data.total}</p></body></html>`;
   return new Response(html, { headers: { 'content-type': 'text/html; charset=utf-8', 'x-robots-tag': 'noindex', 'cache-control': 'no-store' } });
 }
@@ -501,15 +503,6 @@ async function evStats(request, env, url) {
     }
   }
   const top = (map, n, key) => [...map].sort((a, b) => b[1] - a[1]).slice(0, n).map(([k, v]) => ({ [key]: k, count: v }));
-  // 「AIで読む」経由（?via=）。押した AI ごとに、実際に読みに来たのはだれか（2026-11-01）
-  const viaMap = new Map();
-  for (const r of rows) {
-    if (!r.v) continue;
-    const e = viaMap.get(r.v) || { via: r.v, visits: 0, records: new Set(), bots: new Map(), last: '' };
-    e.visits++; e.records.add(r.p); count(e.bots, r.b); if (r.day > e.last) e.last = r.day; viaMap.set(r.v, e);
-  }
-  const viaWeeks = new Map();
-  for (const r of rows) { if (!r.v) continue; const w = viaWeeks.get(r.week) || new Map(); count(w, r.v); viaWeeks.set(r.week, w); }
   const data = {
     total: rows.length, counted: counted.length, truncated, since: since || null, week: week || null, who: who || null,
     byWeek,
@@ -672,3 +665,32 @@ ${table([['week', '週（月曜）'], ['total', '合計'], ['via', '内訳']], d
 <p>記録の件数：${data.total}</p></body></html>`;
   return new Response(html, { headers: { 'content-type': 'text/html; charset=utf-8', 'x-robots-tag': 'noindex', 'cache-control': 'no-store' } });
 }
+
+// ───────── 主に見る3つの数字（2026-11-06。集計は worker/kpi.js）─────────
+async function kpiStats(request, env, url) {
+  if (request.method !== 'GET') return new Response('Method Not Allowed', { status: 405 });
+  const day = (k, d) => (/^\d{4}-\d{2}-\d{2}$/.test(url.searchParams.get(k) || '') ? url.searchParams.get(k) : d);
+  const today = todayJst();
+  // ?sc=refresh で Search Console からサイト名の検索の数を取り直す（?since= の週から。最初の埋め戻し用）
+  if (url.searchParams.get('sc') === 'refresh') {
+    try { return json(await gscRefresh(env, { since: day('since', '') })); } catch (e) { return json({ ok: false, error: e.message }, 500); }
+  }
+  const data = await kpiRange(env, day('since', addDays(today, -28)), day('until', today));
+  if (url.searchParams.get('format') === 'json') {
+    return new Response(JSON.stringify(data), { headers: { 'content-type': 'application/json; charset=utf-8', 'x-robots-tag': 'noindex', 'cache-control': 'no-store' } });
+  }
+  const esc = (s) => String(s).replace(/[&<>"]/g, (ch) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[ch]));
+  const kv = (o) => Object.entries(o || {}).sort((a, b) => b[1] - a[1]).map(([k, v]) => `${k} ${v}`).join('、');
+  const table = (cols, list) => list.length
+    ? `<div class="wrap"><table><tr>${cols.map(([, h]) => `<th>${h}</th>`).join('')}</tr>${list.map((r) => `<tr>${cols.map(([k, , f]) => `<td>${esc(f ? f(r[k], r) : (r[k] ?? '-'))}</td>`).join('')}</tr>`).join('')}</table></div>`
+    : '<p>まだ記録がありません。</p>';
+  const html = `<!doctype html><html lang="ja"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex">
+<title>3つの数字</title><style>body{font:14px/1.7 -apple-system,'Noto Sans JP',sans-serif;margin:24px;color:#242424;background:#f7f7f7}
+h1{font-size:20px}h2{font-size:16px;margin-top:28px}.wrap{overflow-x:auto}table{border-collapse:collapse;background:#fff}th,td{border:1px solid #ddd;padding:6px 10px;text-align:left;white-space:nowrap}th{background:#eee}p{color:#555}</style></head><body>
+<h1>3つの数字</h1><p>AI に調べられることで、人の来訪や知名度がいつ増えはじめるかを見るための数。日付は日本時間、週は月曜はじまり。${esc(data.since)} 〜 ${esc(data.until)}（?since=・?until= で変える）。${data.pendingDays.length ? ` 集計がまだの日が ${data.pendingDays.length} 日ある（開き直すと作る）。` : ''}</p>
+<p>AI の依頼読み：ChatGPT-User・Claude-User・Perplexity-User などが読んだページの数（「AIで読む」ボタン経由はエイイチ・Claude の試しが混ざりうる）。AI のリンクから：chatgpt.com などのリンクから来た、人の操作があった表示。サイト名の検索：Search Console から週ごとに記録したクリック数。</p>
+<h2>週ごと</h2>${table([['week', '週（月曜）'], ['days', '日数'], ['aiReads', 'AI の依頼読み'], ['aiReadsViaButton', 'うちボタン経由'], ['aiReferrals', 'AI のリンクから来た人'], ['nameSearch', 'サイト名の検索（クリック）'], ['humanViews', '人の表示（参考）'], ['aiReadsByBot', '依頼読みの内訳', kv], ['aiReferralsBySource', 'リンクの内訳', kv]], [...data.byWeek].reverse())}
+<h2>日ごと</h2>${table([['day', '日付', (v, r) => v + (r.partial ? '（途中）' : '')], ['aiReads', 'AI の依頼読み'], ['aiReadsViaButton', 'うちボタン経由'], ['aiReferrals', 'AI のリンクから来た人'], ['humanViews', '人の表示（参考）'], ['aiReadsByBot', '依頼読みの内訳', kv], ['aiReferralsBySource', 'リンクの内訳', kv]], [...data.byDay].reverse())}
+</body></html>`;
+  return new Response(html, { headers: { 'content-type': 'text/html; charset=utf-8', 'x-robots-tag': 'noindex', 'cache-control': 'no-store' } });
+}
