@xingwangzhe/stalux 1.12.0 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/layouts/Stalux.astro +7 -1
- package/src/scripts/webmcp.ts +169 -59
package/package.json
CHANGED
package/src/layouts/Stalux.astro
CHANGED
|
@@ -87,9 +87,15 @@ const llmPromote = llmPromoteRaw
|
|
|
87
87
|
siteTitle: siteConfig.title,
|
|
88
88
|
siteUrl: siteConfig.url,
|
|
89
89
|
siteDesc: siteConfig.description,
|
|
90
|
+
siteLang: siteConfig.lang,
|
|
90
91
|
}}
|
|
91
92
|
>
|
|
92
|
-
window.__STALUX_SITE_INFO__ = {
|
|
93
|
+
window.__STALUX_SITE_INFO__ = {
|
|
94
|
+
title: siteTitle,
|
|
95
|
+
url: siteUrl,
|
|
96
|
+
description: siteDesc,
|
|
97
|
+
lang: siteLang,
|
|
98
|
+
};
|
|
93
99
|
</script>
|
|
94
100
|
<script>
|
|
95
101
|
import "../scripts/webmcp.ts";
|
package/src/scripts/webmcp.ts
CHANGED
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
* /posts/{id}.md(源码导出)、/llms.txt / /llms-full.txt(站点信息镜像)、
|
|
8
8
|
* /pagefind/(全文索引)。
|
|
9
9
|
*
|
|
10
|
+
* 工具描述跟随站点语言(site.yml 的 lang):中文站显示中文说明,
|
|
11
|
+
* 英文站显示英文说明,保证 AI 代理与评审(如 webmcp.com)能读懂。
|
|
12
|
+
*
|
|
10
13
|
* 浏览器无原生 modelContext 时,由布局注入的 @mcp-b/webmcp-polyfill
|
|
11
14
|
* 提供兜底实现;两者都没有则静默跳过注册,不影响普通访问者。
|
|
12
15
|
*
|
|
@@ -23,6 +26,7 @@ declare global {
|
|
|
23
26
|
title?: string;
|
|
24
27
|
url?: string;
|
|
25
28
|
description?: string;
|
|
29
|
+
lang?: string;
|
|
26
30
|
};
|
|
27
31
|
}
|
|
28
32
|
}
|
|
@@ -48,6 +52,22 @@ interface DocumentWithModelContext extends Document {
|
|
|
48
52
|
modelContext?: ModelContext;
|
|
49
53
|
}
|
|
50
54
|
|
|
55
|
+
// ---------------------------------------------------------------------------
|
|
56
|
+
// 语言选择(跟随站点 site.yml 的 lang)
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
|
|
59
|
+
const SITE_LANG = (
|
|
60
|
+
typeof window !== "undefined" ? (window.__STALUX_SITE_INFO__?.lang ?? "zh-CN") : "zh-CN"
|
|
61
|
+
).toLowerCase();
|
|
62
|
+
|
|
63
|
+
/** 站点为英文站时为 true */
|
|
64
|
+
const IS_EN = SITE_LANG === "en" || SITE_LANG === "en-us" || SITE_LANG === "en-gb";
|
|
65
|
+
|
|
66
|
+
/** 按站点语言选择文案:pick(中文, English) */
|
|
67
|
+
function pick(zh: string, en: string): string {
|
|
68
|
+
return IS_EN ? en : zh;
|
|
69
|
+
}
|
|
70
|
+
|
|
51
71
|
// ---------------------------------------------------------------------------
|
|
52
72
|
// 工具执行辅助
|
|
53
73
|
// ---------------------------------------------------------------------------
|
|
@@ -150,28 +170,42 @@ function currentAbbrlinkFromPath(): string | null {
|
|
|
150
170
|
}
|
|
151
171
|
|
|
152
172
|
// ---------------------------------------------------------------------------
|
|
153
|
-
//
|
|
173
|
+
// 工具定义(描述中英双语,随站点语言切换)
|
|
154
174
|
// ---------------------------------------------------------------------------
|
|
155
175
|
|
|
156
176
|
function listPostsTool(): WebMCPTool {
|
|
157
177
|
return {
|
|
158
178
|
name: "stalux_list_posts",
|
|
159
|
-
title: "列出博客文章",
|
|
160
|
-
description:
|
|
179
|
+
title: pick("列出博客文章", "List blog posts"),
|
|
180
|
+
description: pick(
|
|
161
181
|
"分页列出博客的全部已发布文章,返回标题、永久链接(abbrlink)、日期、分类、标签、摘要与文章页 URL。" +
|
|
162
|
-
|
|
163
|
-
|
|
182
|
+
"需要浏览全站文章、确认某篇文章的 abbrlink 时使用;不返回正文," +
|
|
183
|
+
"只要某篇元信息用 stalux_get_post,读正文请用 stalux_read_post。" +
|
|
184
|
+
"pageSize 建议设 50 一次拿完,减少翻页;页码越界时会自动返回最后一页(clamped 标记)。",
|
|
185
|
+
"Paginated list of all published posts, returning title, permanent link (abbrlink), date, categories, tags, description and post page URL. " +
|
|
186
|
+
"Use it to browse the whole blog or find a post's abbrlink; it does not return body content — " +
|
|
187
|
+
"use stalux_get_post for one post's metadata, or stalux_read_post for the full body. " +
|
|
188
|
+
"Prefer pageSize 50 to fetch everything in one call; out-of-range pages clamp to the last page (clamped flag).",
|
|
189
|
+
),
|
|
164
190
|
annotations: { readOnlyHint: true, untrustedContentHint: false },
|
|
165
191
|
inputSchema: {
|
|
166
192
|
type: "object",
|
|
167
193
|
properties: {
|
|
168
|
-
page: {
|
|
194
|
+
page: {
|
|
195
|
+
type: "integer",
|
|
196
|
+
minimum: 1,
|
|
197
|
+
default: 1,
|
|
198
|
+
description: pick("页码,从 1 开始", "Page number, starting at 1"),
|
|
199
|
+
},
|
|
169
200
|
pageSize: {
|
|
170
201
|
type: "integer",
|
|
171
202
|
minimum: 1,
|
|
172
203
|
maximum: MAX_LIST_PAGE_SIZE,
|
|
173
204
|
default: 10,
|
|
174
|
-
description:
|
|
205
|
+
description: pick(
|
|
206
|
+
"每页条数,最大 " + MAX_LIST_PAGE_SIZE,
|
|
207
|
+
"Items per page, max " + MAX_LIST_PAGE_SIZE,
|
|
208
|
+
),
|
|
175
209
|
},
|
|
176
210
|
},
|
|
177
211
|
additionalProperties: false,
|
|
@@ -183,23 +217,23 @@ function listPostsTool(): WebMCPTool {
|
|
|
183
217
|
Math.max(1, Number(input.pageSize) || 10),
|
|
184
218
|
);
|
|
185
219
|
const list = await loadPostsMeta();
|
|
186
|
-
if (!list)
|
|
187
|
-
const total = list.length;
|
|
188
|
-
const totalPages = Math.ceil(total / pageSize) || 1;
|
|
189
|
-
const start = (page - 1) * pageSize;
|
|
190
|
-
if (start >= total) {
|
|
220
|
+
if (!list)
|
|
191
221
|
return err(
|
|
192
|
-
"
|
|
193
|
-
"
|
|
194
|
-
{
|
|
195
|
-
page,
|
|
196
|
-
totalPages,
|
|
197
|
-
total,
|
|
198
|
-
},
|
|
222
|
+
"INDEX_UNAVAILABLE",
|
|
223
|
+
pick("无法获取文章列表", "Unable to fetch post list"),
|
|
199
224
|
);
|
|
225
|
+
const total = list.length;
|
|
226
|
+
const totalPages = Math.ceil(total / pageSize) || 1;
|
|
227
|
+
// 越界时自动回退到最后一页(而非报错),agent 翻过头也能拿到数据
|
|
228
|
+
let clamped = false;
|
|
229
|
+
let resolvedPage = page;
|
|
230
|
+
if (page > totalPages) {
|
|
231
|
+
resolvedPage = totalPages;
|
|
232
|
+
clamped = true;
|
|
200
233
|
}
|
|
234
|
+
const start = (resolvedPage - 1) * pageSize;
|
|
201
235
|
const posts = list.slice(start, start + pageSize).map(briefMeta);
|
|
202
|
-
return { ok: true, page, pageSize, total, totalPages, posts };
|
|
236
|
+
return { ok: true, page: resolvedPage, pageSize, total, totalPages, clamped, posts };
|
|
203
237
|
},
|
|
204
238
|
};
|
|
205
239
|
}
|
|
@@ -207,19 +241,25 @@ function listPostsTool(): WebMCPTool {
|
|
|
207
241
|
function getPostTool(): WebMCPTool {
|
|
208
242
|
return {
|
|
209
243
|
name: "stalux_get_post",
|
|
210
|
-
title: "查看文章信息",
|
|
211
|
-
description:
|
|
244
|
+
title: pick("查看文章信息", "Get post info"),
|
|
245
|
+
description: pick(
|
|
212
246
|
"按 abbrlink 或标题关键词定位一篇文章,返回其元信息(标题、日期、分类、标签、摘要、字数、文章页 URL)。" +
|
|
213
|
-
|
|
214
|
-
|
|
247
|
+
"只要元信息时用它,比读取正文便宜得多;读正文请用 stalux_read_post。" +
|
|
248
|
+
"获取 abbrlink 可先用 stalux_list_posts 或 stalux_search_posts。",
|
|
249
|
+
"Locate a post by abbrlink or title keyword and return its metadata (title, date, categories, tags, description, word count, post page URL). " +
|
|
250
|
+
"Use it when only metadata is needed — cheaper than reading the full body; use stalux_read_post for the body. " +
|
|
251
|
+
"To get an abbrlink, first use stalux_list_posts or stalux_search_posts.",
|
|
252
|
+
),
|
|
215
253
|
annotations: { readOnlyHint: true, untrustedContentHint: false },
|
|
216
254
|
inputSchema: {
|
|
217
255
|
type: "object",
|
|
218
256
|
properties: {
|
|
219
257
|
id: {
|
|
220
258
|
type: "string",
|
|
221
|
-
description:
|
|
259
|
+
description: pick(
|
|
222
260
|
"文章 abbrlink(如 f4442947)或标题关键词(模糊匹配,取第一条命中)",
|
|
261
|
+
"Post abbrlink (e.g. f4442947) or a title keyword (fuzzy match, first hit wins)",
|
|
262
|
+
),
|
|
223
263
|
},
|
|
224
264
|
},
|
|
225
265
|
required: ["id"],
|
|
@@ -227,11 +267,23 @@ function getPostTool(): WebMCPTool {
|
|
|
227
267
|
},
|
|
228
268
|
execute: async (input) => {
|
|
229
269
|
const id = String(input.id ?? "").trim();
|
|
230
|
-
if (!id)
|
|
270
|
+
if (!id) {
|
|
271
|
+
return err(
|
|
272
|
+
"BAD_INPUT",
|
|
273
|
+
pick(
|
|
274
|
+
"请提供文章 abbrlink 或标题关键词",
|
|
275
|
+
"Provide a post abbrlink or title keyword",
|
|
276
|
+
),
|
|
277
|
+
);
|
|
278
|
+
}
|
|
231
279
|
const list = await loadPostsMeta();
|
|
232
|
-
if (!list)
|
|
280
|
+
if (!list)
|
|
281
|
+
return err(
|
|
282
|
+
"INDEX_UNAVAILABLE",
|
|
283
|
+
pick("无法获取文章索引", "Unable to fetch post index"),
|
|
284
|
+
);
|
|
233
285
|
const post = findById(list, id) ?? findByKeyword(list, id);
|
|
234
|
-
if (!post) return err("NOT_FOUND", "未找到文章: " + id);
|
|
286
|
+
if (!post) return err("NOT_FOUND", pick("未找到文章: ", "Post not found: ") + id);
|
|
235
287
|
return { ok: true, post: briefMeta(post) };
|
|
236
288
|
},
|
|
237
289
|
};
|
|
@@ -240,21 +292,35 @@ function getPostTool(): WebMCPTool {
|
|
|
240
292
|
function currentPostTool(): WebMCPTool {
|
|
241
293
|
return {
|
|
242
294
|
name: "stalux_current_post",
|
|
243
|
-
title: "当前文章信息",
|
|
244
|
-
description:
|
|
295
|
+
title: pick("当前文章信息", "Current post info"),
|
|
296
|
+
description: pick(
|
|
245
297
|
"返回用户当前正在浏览的那篇文章的元信息。当用户说「这篇文章」「当前页面」时先调用它确定上下文," +
|
|
246
|
-
|
|
298
|
+
"比读正文便宜得多。不在文章页时返回错误。",
|
|
299
|
+
"Return the metadata of the post the user is currently viewing. " +
|
|
300
|
+
"Call it first when the user says 'this post' or 'the current page' to establish context — cheaper than reading the body. " +
|
|
301
|
+
"Returns an error when not on a post page.",
|
|
302
|
+
),
|
|
247
303
|
annotations: { readOnlyHint: true, untrustedContentHint: false },
|
|
248
304
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
249
305
|
execute: async () => {
|
|
250
306
|
const id = currentAbbrlinkFromPath();
|
|
251
307
|
if (!id) {
|
|
252
|
-
return err(
|
|
308
|
+
return err(
|
|
309
|
+
"NOT_ON_POST_PAGE",
|
|
310
|
+
pick("当前不在文章页(路径: ", "Not on a post page (path: ") +
|
|
311
|
+
location.pathname +
|
|
312
|
+
")",
|
|
313
|
+
);
|
|
253
314
|
}
|
|
254
315
|
const list = await loadPostsMeta();
|
|
255
|
-
if (!list)
|
|
316
|
+
if (!list)
|
|
317
|
+
return err(
|
|
318
|
+
"INDEX_UNAVAILABLE",
|
|
319
|
+
pick("无法获取文章索引", "Unable to fetch post index"),
|
|
320
|
+
);
|
|
256
321
|
const post = findById(list, id);
|
|
257
|
-
if (!post)
|
|
322
|
+
if (!post)
|
|
323
|
+
return err("NOT_FOUND", pick("未找到当前文章: ", "Current post not found: ") + id);
|
|
258
324
|
return { ok: true, post: briefMeta(post) };
|
|
259
325
|
},
|
|
260
326
|
};
|
|
@@ -263,16 +329,22 @@ function currentPostTool(): WebMCPTool {
|
|
|
263
329
|
function randomPostTool(): WebMCPTool {
|
|
264
330
|
return {
|
|
265
331
|
name: "stalux_random_post",
|
|
266
|
-
title: "随机一篇文章",
|
|
267
|
-
description:
|
|
332
|
+
title: pick("随机一篇文章", "Random post"),
|
|
333
|
+
description: pick(
|
|
268
334
|
"随机挑选一篇文章并返回其元信息(标题、abbrlink、日期、摘要、URL)。" +
|
|
269
|
-
|
|
335
|
+
"适合推荐、探索、或不确定从哪篇开始时使用;不返回正文,不导航。",
|
|
336
|
+
"Pick a random post and return its metadata (title, abbrlink, date, description, URL). " +
|
|
337
|
+
"Useful for recommendations, discovery, or when unsure where to start; it does not return body content and does not navigate.",
|
|
338
|
+
),
|
|
270
339
|
annotations: { readOnlyHint: true, untrustedContentHint: false },
|
|
271
340
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
272
341
|
execute: async () => {
|
|
273
342
|
const list = await loadPostsMeta();
|
|
274
343
|
if (!list || list.length === 0) {
|
|
275
|
-
return err(
|
|
344
|
+
return err(
|
|
345
|
+
"INDEX_UNAVAILABLE",
|
|
346
|
+
pick("无法获取文章索引", "Unable to fetch post index"),
|
|
347
|
+
);
|
|
276
348
|
}
|
|
277
349
|
const post = list[Math.floor(Math.random() * list.length)];
|
|
278
350
|
return { ok: true, post: briefMeta(post) };
|
|
@@ -283,22 +355,29 @@ function randomPostTool(): WebMCPTool {
|
|
|
283
355
|
function searchPostsTool(): WebMCPTool {
|
|
284
356
|
return {
|
|
285
357
|
name: "stalux_search_posts",
|
|
286
|
-
title: "搜索博客文章",
|
|
287
|
-
description:
|
|
358
|
+
title: pick("搜索博客文章", "Search blog posts"),
|
|
359
|
+
description: pick(
|
|
288
360
|
"用关键词搜索博客文章的标题、标签、分类与正文全文,返回命中文章的标题、链接与摘要片段。" +
|
|
289
|
-
|
|
290
|
-
|
|
361
|
+
"适合「博客里写过 X 吗」「帮我找一下关于 Y 的文章」这类问题;" +
|
|
362
|
+
"定位到具体文章后再用 stalux_read_post 读正文、stalux_get_post 取元信息。",
|
|
363
|
+
"Search the full text of all posts (title, tags, categories, body) by keyword, returning matching posts with title, URL and excerpt snippets. " +
|
|
364
|
+
"Ideal for questions like 'has the blog covered X?' or 'find posts about Y'; " +
|
|
365
|
+
"once a post is located, use stalux_read_post for the body or stalux_get_post for metadata.",
|
|
366
|
+
),
|
|
291
367
|
annotations: { readOnlyHint: true, untrustedContentHint: false },
|
|
292
368
|
inputSchema: {
|
|
293
369
|
type: "object",
|
|
294
370
|
properties: {
|
|
295
|
-
keyword: {
|
|
371
|
+
keyword: {
|
|
372
|
+
type: "string",
|
|
373
|
+
description: pick("搜索关键词", "Search keyword"),
|
|
374
|
+
},
|
|
296
375
|
limit: {
|
|
297
376
|
type: "integer",
|
|
298
377
|
minimum: 1,
|
|
299
378
|
maximum: MAX_SEARCH_RESULTS,
|
|
300
379
|
default: 10,
|
|
301
|
-
description: "最多返回条数",
|
|
380
|
+
description: pick("最多返回条数", "Maximum number of results to return"),
|
|
302
381
|
},
|
|
303
382
|
},
|
|
304
383
|
required: ["keyword"],
|
|
@@ -306,15 +385,24 @@ function searchPostsTool(): WebMCPTool {
|
|
|
306
385
|
},
|
|
307
386
|
execute: async (input) => {
|
|
308
387
|
const keyword = String(input.keyword ?? "").trim();
|
|
309
|
-
if (!keyword)
|
|
388
|
+
if (!keyword)
|
|
389
|
+
return err("BAD_INPUT", pick("请提供搜索关键词", "Provide a search keyword"));
|
|
310
390
|
const limit = Math.min(MAX_SEARCH_RESULTS, Math.max(1, Number(input.limit) || 10));
|
|
311
391
|
const pagefind = await loadPagefind();
|
|
312
|
-
if (!pagefind)
|
|
392
|
+
if (!pagefind) {
|
|
393
|
+
return err(
|
|
394
|
+
"INDEX_UNAVAILABLE",
|
|
395
|
+
pick("全文搜索索引不可用", "Full-text search index unavailable"),
|
|
396
|
+
);
|
|
397
|
+
}
|
|
313
398
|
const res = (await pagefind.search(keyword)) as {
|
|
314
399
|
results?: Array<{ data: () => Promise<unknown> }>;
|
|
315
400
|
};
|
|
316
401
|
if (!res || !Array.isArray(res.results)) {
|
|
317
|
-
return err(
|
|
402
|
+
return err(
|
|
403
|
+
"SEARCH_FAILED",
|
|
404
|
+
pick("搜索失败,请重试", "Search failed, please retry"),
|
|
405
|
+
);
|
|
318
406
|
}
|
|
319
407
|
const items = await Promise.all(
|
|
320
408
|
res.results.slice(0, limit).map(async (r) => {
|
|
@@ -343,27 +431,44 @@ function searchPostsTool(): WebMCPTool {
|
|
|
343
431
|
function readPostTool(): WebMCPTool {
|
|
344
432
|
return {
|
|
345
433
|
name: "stalux_read_post",
|
|
346
|
-
title: "读取文章 Markdown",
|
|
347
|
-
description:
|
|
434
|
+
title: pick("读取文章 Markdown", "Read post Markdown"),
|
|
435
|
+
description: pick(
|
|
348
436
|
"按 abbrlink(文章永久链接 ID)读取一篇博客文章的原始 Markdown 全文,包含 frontmatter 与版权脚注。" +
|
|
349
|
-
|
|
350
|
-
|
|
437
|
+
"需要文章完整内容、引用或摘要时使用;只要元信息用 stalux_get_post(更便宜)。" +
|
|
438
|
+
"获取 abbrlink 可先用 stalux_list_posts 或 stalux_search_posts。",
|
|
439
|
+
"Read the full raw Markdown of a post by abbrlink (permanent link ID), including frontmatter and copyright footer. " +
|
|
440
|
+
"Use it for full content, quotes or summarization; for metadata only, stalux_get_post is cheaper. " +
|
|
441
|
+
"To get an abbrlink, first use stalux_list_posts or stalux_search_posts.",
|
|
442
|
+
),
|
|
351
443
|
annotations: { readOnlyHint: true, untrustedContentHint: false },
|
|
352
444
|
inputSchema: {
|
|
353
445
|
type: "object",
|
|
354
446
|
properties: {
|
|
355
|
-
id: {
|
|
447
|
+
id: {
|
|
448
|
+
type: "string",
|
|
449
|
+
description: pick(
|
|
450
|
+
"文章的 abbrlink,如 f4442947",
|
|
451
|
+
"Post abbrlink, e.g. f4442947",
|
|
452
|
+
),
|
|
453
|
+
},
|
|
356
454
|
},
|
|
357
455
|
required: ["id"],
|
|
358
456
|
additionalProperties: false,
|
|
359
457
|
},
|
|
360
458
|
execute: async (input) => {
|
|
361
459
|
const id = String(input.id ?? "").trim();
|
|
362
|
-
if (!id)
|
|
460
|
+
if (!id)
|
|
461
|
+
return err("BAD_INPUT", pick("请提供文章 abbrlink", "Provide a post abbrlink"));
|
|
363
462
|
const url = "/posts/" + encodeURIComponent(id) + ".md";
|
|
364
463
|
const r = await fetch(url, { headers: { Accept: "text/markdown" } });
|
|
365
|
-
if (r.status === 404)
|
|
366
|
-
|
|
464
|
+
if (r.status === 404)
|
|
465
|
+
return err("NOT_FOUND", pick("未找到文章: ", "Post not found: ") + id);
|
|
466
|
+
if (!r.ok) {
|
|
467
|
+
return err(
|
|
468
|
+
"FETCH_FAILED",
|
|
469
|
+
pick("无法获取文章 Markdown:", "Failed to fetch post Markdown: ") + url,
|
|
470
|
+
);
|
|
471
|
+
}
|
|
367
472
|
const markdown = await r.text();
|
|
368
473
|
return { ok: true, url, markdown: markdown.slice(0, 50000) };
|
|
369
474
|
},
|
|
@@ -373,11 +478,15 @@ function readPostTool(): WebMCPTool {
|
|
|
373
478
|
function siteInfoTool(): WebMCPTool {
|
|
374
479
|
return {
|
|
375
480
|
name: "stalux_site_info",
|
|
376
|
-
title: "站点信息",
|
|
377
|
-
description:
|
|
481
|
+
title: pick("站点信息", "Site info"),
|
|
482
|
+
description: pick(
|
|
378
483
|
"返回博客的站点信息(标题、简介、站点 URL),并给出更完整的机器可读内容入口:" +
|
|
379
|
-
|
|
380
|
-
|
|
484
|
+
"/llms.txt(站点导航与文章链接列表)与 /llms-full.txt(全站 Markdown 镜像,含全部文章正文)。" +
|
|
485
|
+
"适合需要了解博客主题、定位内容、或批量获取全站数据时使用。",
|
|
486
|
+
"Return the blog's site info (title, description, site URL) plus pointers to machine-readable content: " +
|
|
487
|
+
"/llms.txt (site navigation and post link list) and /llms-full.txt (full-site Markdown mirror with all post bodies). " +
|
|
488
|
+
"Use it to understand the blog's topic, orient, or bulk-fetch all content.",
|
|
489
|
+
),
|
|
381
490
|
annotations: { readOnlyHint: true, untrustedContentHint: false },
|
|
382
491
|
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
383
492
|
execute: async () => {
|
|
@@ -387,6 +496,7 @@ function siteInfoTool(): WebMCPTool {
|
|
|
387
496
|
title: info.title ?? "",
|
|
388
497
|
url: info.url ?? "",
|
|
389
498
|
description: info.description ?? "",
|
|
499
|
+
lang: info.lang ?? "",
|
|
390
500
|
llms: (info.url ?? "") + "/llms.txt",
|
|
391
501
|
llmsFull: (info.url ?? "") + "/llms-full.txt",
|
|
392
502
|
};
|