---
title: Astro 博客主题 Stalux 集成 WebMCP：纯前端注册 7 个工具，让 AI 代理直接用你的博客
date: "2026-08-06 11:30:00"
updated: "2026-08-06 11:30:00"
desc: 按 W3C webmachinelearning/webmcp 草案，在 Astro 博客主题 Stalux 上纯前端注册 7 个只读工具（列表/搜索/阅读/元信息/站点信息）到 document.modelContext，让 AI 代理直接调用博客功能。记录工具从 4 个到 7 个的设计演进、中英双语描述、agent 实测踩坑与 webmcp.com 扫描评级优化全过程，零后端实现，适合所有静态 Astro 博客参考。
abbrlink: stalux-webmcp-integration
tags:
    - webmcp
    - ai代理
    - astro
    - astro博客主题
    - w3c
    - 博客主题
categories:
    - 技术
cc: CC-BY-NC-SA-4.0
---

> **AI 辅助声明**：本文的代码实现与排查过程使用了 AI 编程助手（ZCode）辅助进行源码编写、调试与文档撰写。所有结论均经过构建与浏览器实测验证。

我在给 Astro 博客主题 [Stalux](https://github.com/xingwangzhe/stalux) 做 AI 改造时，受[Mayx的博客](https://mabbs.github.io/)启发接触到了 [WebMCP](https://github.com/webmachinelearning/webmcp)——一个还在 W3C 社区组草案阶段的规范，目标是让网页直接向浏览器里的 AI 代理暴露工具。当时我的第一反应是：这是给动态网站准备的吧，我这种纯静态 Astro 博客也想凑这个热闹？

后来发现想多了。WebMCP 恰恰是**纯前端零后端**的典范——我的博客连服务器都没有，全是构建产物，反而把 WebMCP 的能力发挥得淋漓尽致。

这篇文章记录 Astro 博客主题 Stalux 从零集成 WebMCP 的完整过程：工具设计、i18n、agent 实测踩坑，以及 webmcp.com 扫描评级从 A- 的优化历程。

<!--more-->

## WebMCP 是什么

先说清楚概念。MCP（Model Context Protocol）是 Anthropic 提出的标准协议，让 AI 应用接入外部工具和数据源。而 **WebMCP 是 W3C 社区组（webmachinelearning/webmcp）推进的草案**，把类似的能力**下沉到浏览器层面**——网页通过原生 API 暴露工具给 AI 代理，浏览器作为安全边界管好权限和来源。

| 对比项 | MCP | WebMCP |
| ------ | --- | ------ |
| 协议层 | AI 应用 ↔ 工具服务器 | 网页 ↔ 浏览器代理 |
| 部署 | 需要跑 MCP server | 页面 JS 注册即可，零后端 |
| 安全边界 | server 自己管 | 浏览器权限策略 |
| 状态 | 已广泛采用 | W3C 社区组草案，Chrome 149 Origin Trial |

核心 API 是 `document.modelContext.registerTool()`，注册的 `ModelContextTool` 字典长这样：

| 字段 | 必填 | 说明 |
| ---- | ---- | ---- |
| `name` | 是 | 工具唯一标识，snake_case |
| `title` | 否 | 展示名 |
| `description` | 是 | 自然语言描述，**模型靠它决定何时调用** |
| `inputSchema` | 否 | JSON Schema 参数契约 |
| `execute` | 是 | 真正干活的函数，返回 JSON |
| `annotations` | 否 | `readOnlyHint`（是否只读）、`untrustedContentHint`（输出是否不可信） |

## 纯前端零后端：为什么静态站能实现

我的博客数据源全是**构建期静态产物**，一个后端都没有：

| 数据源 | 用途 | 形态 |
| ------ | ---- | ---- |
| `/api/posts.json` | 文章元信息索引（标题/日期/分类/标签/摘要/URL） | 构建期生成 JSON |
| `/pagefind/` | 全文搜索索引 | Pagefind 构建期生成 |
| `/posts/{abbrlink}.md` | 单篇文章原始 Markdown | 构建期导出 |
| `/llms.txt`、`/llms-full.txt` | 站点信息 + 全站 Markdown 镜像 | 构建期生成 |

这些文件都是 `public/` 或静态路由下的产物，浏览器直接 fetch 就能拿到。工具注册进 `document.modelContext` 后，execute 内部就是从这些静态端点取数据——**没有任何一个请求发到"自己的服务器"，因为压根没有**。

这就是 WebMCP 的设计初衷：一个静态博客不需要任何后端，写一段 `registerTool()` 的 JS 就能让 AI 代理直接使用。

## 工具设计：从 4 个到 7 个

第一版（v1.11.0）我注册了 4 个只读工具：

```ts title="src/scripts/webmcp.ts (v1.11.0，首批 4 工具)"
const tools = [
  listPostsTool(),    // stalux_list_posts    分页列出文章
  searchPostsTool(),  // stalux_search_posts  全文搜索
  readPostTool(),     // stalux_read_post     读文章 Markdown
  siteInfoTool(),     // stalux_site_info     站点信息
];
```

每个工具都是 `{ name, title, description, inputSchema, annotations, execute }` 六元组。以搜索为例：

```ts title="stalux_search_posts (execute 核心)"
execute: async (input) => {
    const keyword = String(input.keyword ?? "").trim();
    if (!keyword) return err("BAD_INPUT", "请提供搜索关键词");
    // 动态加载 Pagefind 索引（运行时按需，构建期产物）
    const pagefind = await loadPagefind();
    const res = await pagefind.search(keyword);
    const posts = await Promise.all(
        res.results.slice(0, limit).map(async (r) => {
            const data = await r.data();
            return { url: data.url, title: data.title, excerpt: data.excerpt.slice(0, 240) };
        }),
    );
    return { ok: true, keyword, count: posts.length, posts };
},
```

### 4 → 7 个的演进

后来提交到 [webmcp.com](https://webmcp.com) 扫描（WebMCP 目录站），拿了 **A-（79 分）**，usability 维度只有 65。对照评分方法论（usability 占 60% 权重，看"工具有用、命名清晰、描述好"），问题很明确：**工具太少、覆盖面窄**。

于是 v1.12.0 扩到 7 个，补上了元信息定位类工具：

| 工具 | 功能 | 数据源 | 成本提示 |
| ---- | ---- | ------ | -------- |
| `stalux_list_posts` | 分页列表（含元信息） | `/api/posts.json` | 不返回正文 |
| `stalux_get_post` | 按 abbrlink/关键词取单篇元信息 | `/api/posts.json` | 比读正文便宜得多 |
| `stalux_current_post` | 当前浏览文章上下文 | `/api/posts.json` | "这篇文章"场景 |
| `stalux_random_post` | 随机推荐一篇 | `/api/posts.json` | 探索用 |
| `stalux_search_posts` | 全文搜索 | Pagefind | 命中后转 read/get |
| `stalux_read_post` | 读原始 Markdown | `/posts/{id}.md` | 获取 abbrlink 先用前两个 |
| `stalux_site_info` | 站点信息 + llms 入口 | site.yml | 批量获取全站数据 |

配套新增了 `/api/posts.json` 元信息索引端点——完整字段（标题/abbrlink/日期/分类/标签/摘要/字数/URL），是 get/current/random 三个工具的廉价数据源。

## i18n：工具描述跟随站点语言

Astro 博客主题 Stalux 是 i18n 主题，中英文站都有。工具描述是给**模型**看的说明书，语言必须跟随站点：

```ts title="src/scripts/webmcp.ts (pick 双语)"
const SITE_LANG = window.__STALUX_SITE_INFO__?.lang ?? "zh-CN";
const IS_EN = SITE_LANG === "en" || SITE_LANG === "en-us" || SITE_LANG === "en-gb";

/** 按站点语言选择文案：pick(中文, English) */
function pick(zh: string, en: string): string {
    return IS_EN ? en : zh;
}

// 用法
title: pick("列出博客文章", "List blog posts"),
description: pick(
    "分页列出博客的全部已发布文章，返回标题、永久链接、日期、分类、标签、摘要与文章页 URL。",
    "Paginated list of all published posts, returning title, abbrlink, date, categories, tags, description and post page URL.",
),
```

实测验证：英文站（stalux.needhelp.icu）工具显示 "List blog posts" / "Get post info"，中文站（xingwangzhe.fun）显示 "列出博客文章" / "查看文章信息"。

## Agent 实测的坑

### 坑一：agent 反复翻页撞 OUT_OF_RANGE（v1.14.0 修复）

webmcp.com 的 "Run live agent test" 实测暴露了一个真实缺陷：agent 不知道总页数，只能一页页翻，翻过头就撞 `OUT_OF_RANGE` 报错，10 次调用耗尽还没完成任务。

修复：**越界自动回退最后一页**，而不是硬报错：

```ts title="stalux_list_posts execute (clamp 修复)"
let clamped = false;
let resolvedPage = page;
if (page > totalPages) {
    resolvedPage = totalPages;
    clamped = true;   // 标记，agent 能感知被夹取
}
const posts = list.slice((resolvedPage - 1) * pageSize, ...).map(briefMeta);
return { ok: true, page: resolvedPage, pageSize, total, totalPages, clamped, posts };
```

同时在描述里提示 agent 用 `pageSize: 50` 一次拿完，减少翻页。

### 坑二：dev 模式 pagefind import 报错（v1.17.0 顺带修复）

这个坑不是 WebMCP 迭代期间暴露的，而是 v1.17.0 升级 Astro 7.2、做增量构建确定性改造时顺带发现的——但因为它直接影响 `stalux_search_posts` 在 dev 环境的使用，一并记录在这里：

```text title="astro dev 报错"
[ERROR] [vite] Internal server error: Failed to resolve import "/pagefind/pagefind.js"
  from "src/scripts/webmcp.ts"
```

动态 `import("/pagefind/pagefind.js")` 在 build 时 `@vite-ignore` 能过，但 dev 模式 `vite:import-analysis` 还是会解析绝对路径。修复：用完整 URL 构造，让 Vite 彻底跳过：

```ts title="loadPagefind (修复后)"
const url = new URL("/pagefind/pagefind.js", location.origin).href;
const mod = await import(/* @vite-ignore */ url);
```

## 效果

| 指标 | 结果 |
| ---- | ---- |
| 工具数 | 7 个（全部 `readOnlyHint: true`） |
| webmcp.com 扫描 | 4 tools detected（早期）、可执行性 100% |
| 语言 | 中英双语跟随站点 |
| 后端 | 零（纯静态产物） |
| 增量构建兼容 | 不影响（确定性修复后 HTML 1254 restored） |

## Commit 记录

| Commit | 版本 | 内容 |
| ------ | ---- | ---- |
| `5ac6d18b` | 1.11.0 | WebMCP 工具注册（4 只读工具）+ polyfill |
| `d4774092` | 1.12.0 | 工具 4→7 + `/api/posts.json` 元信息索引 |
| `e5d74e57` | 1.13.0 | 工具描述中英双语（跟随站点 lang） |
| `62308da4` | 1.14.0 | list_posts 越界自动回退最后一页（clamped） |

## 参考文档

| 文档 | 链接 |
| ---- | ---- |
| WebMCP 规范仓库 | [github.com/webmachinelearning/webmcp](https://github.com/webmachinelearning/webmcp) |
| WebMCP polyfill | [@mcp-b/webmcp-polyfill](https://www.npmjs.com/package/@mcp-b/webmcp-polyfill) |
| WebMCP 目录站 | [webmcp.com](https://webmcp.com) |
| Chrome 149 Origin Trial | [Chrome Platform Status](https://chromestatus.com/feature/5196796094308352) |

## 总结

WebMCP 还远没到正式标准，但方向是对的：**网页自己成为 AI 代理可安全调用的工具，浏览器负责身份与权限**。对静态博客而言，它几乎是零成本的——数据源早就躺在构建产物里，只是以前只有人能读，现在 agent 也能用了。

说实话，最让我意外的是"纯前端零后端"这个特性。GitHub Pages 上挂着的静态博客，配上 WebMCP，就成了 agent 可以直接调用的接口。等 Chrome 原生支持落地（目前 Origin Trial），polyfill 自动让位，这套东西就能跑进每个访问者的浏览器里。

如果你想给自己的站点也接上，思路很直接：列出现有的静态数据源，设计工具描述（记得带成本提示），`registerTool` 一把梭。去 [webmcp.com](https://webmcp.com) 扫一下，看看评分能拿几级。


---

**作者：**xingwangzhe

**本文链接：**[https://xingwangzhe.fun/posts/stalux-webmcp-integration/](https://xingwangzhe.fun/posts/stalux-webmcp-integration/)

本文采用[知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议](https://creativecommons.org/licenses/by-nc-sa/4.0/)进行许可。