/**
* CBETA MCP Server - Cloudflare Workers Entry Point
* 为 CBETA Online(中华电子佛典)提供 MCP 服务
*
* 简化架构:无状态 Worker,使用 WebStandardStreamableHTTPServerTransport
*/
/// <reference types="@cloudflare/workers-types" />
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
import { z } from "zod";
import { CBETAMcpAgent } from "./cbeta-agent.js";
// Cloudflare Workers environment interface
interface Env {
CBETA_API_BASE?: string;
CBETAMcpAgent: DurableObjectNamespace;
}
// CBETA API Base URL
const CBETA_API_BASE = "https://cbdata.dila.edu.tw/v1.2";
// ============ CBETA API Functions ============
interface SearchResult {
work_id: string;
title: string;
juan: string;
hits: number;
content_preview?: string;
}
interface WorkInfo {
work_id: string;
title: string;
byline?: string;
juan_count: number;
category?: string;
}
interface CatalogItem {
id: string;
label: string;
children?: CatalogItem[];
}
async function searchSutra(
query: string,
options: { scope?: string; page?: number; per_page?: number } = {}
): Promise<{ results: SearchResult[]; total: number }> {
const params = new URLSearchParams({
q: query,
page: String(options.page || 1),
per_page: String(options.per_page || 10),
});
if (options.scope) params.set("scope", options.scope);
try {
const response = await fetch(`${CBETA_API_BASE}/search?${params}`, {
headers: { Referer: "cbeta-mcp", Accept: "application/json" },
});
if (!response.ok) throw new Error(`Search failed: ${response.status}`);
const data = await response.json() as { results?: SearchResult[]; num_found?: number };
return { results: data.results || [], total: data.num_found || 0 };
} catch (error) {
console.error("Search API error:", error);
return { results: [], total: 0 };
}
}
async function getWorkInfo(workId: string): Promise<WorkInfo | null> {
try {
const response = await fetch(`${CBETA_API_BASE}/works/${workId}`, {
headers: { Referer: "cbeta-mcp", Accept: "application/json" },
});
if (!response.ok) return null;
return await response.json() as WorkInfo;
} catch (error) {
console.error("Get work info error:", error);
return null;
}
}
async function getJuanHtml(workId: string, juan: number = 1) {
try {
const response = await fetch(`${CBETA_API_BASE}/juans/${workId}/${juan}?work_info=1`, {
headers: { Referer: "cbeta-mcp", Accept: "application/json" },
});
if (!response.ok) return null;
const data = await response.json() as { title?: string; html?: string };
return { work_id: workId, juan, title: data.title || "", html: data.html || "" };
} catch (error) {
console.error("Get juan HTML error:", error);
return null;
}
}
async function getCatalog(catalogId: string = "CBETA"): Promise<CatalogItem[]> {
try {
const response = await fetch(`${CBETA_API_BASE}/catalog/${catalogId}`, {
headers: { Referer: "cbeta-mcp", Accept: "application/json" },
});
if (!response.ok) return getDefaultCatalog();
const data = await response.json() as { children?: CatalogItem[] };
return data.children || [];
} catch (error) {
console.error("Get catalog error:", error);
return getDefaultCatalog();
}
}
function getDefaultCatalog(): CatalogItem[] {
return [
{ id: "T", label: "大正新脩大藏經" },
{ id: "X", label: "卍新纂續藏經" },
{ id: "N", label: "漢譯南傳大藏經" },
{ id: "B", label: "大藏經補編" },
{ id: "D", label: "國家圖書館善本佛典" },
{ id: "Y", label: "印順法師佛學著作集" },
{ id: "LC", label: "呂澂佛學著作集" },
{ id: "TX", label: "太虛大師全書" },
{ id: "ZW", label: "藏外佛教文獻" },
{ id: "ZS", label: "正史佛教資料類編" },
];
}
function htmlToText(html: string): string {
return html
.replace(/<[^>]+>/g, "")
.replace(/ /g, " ")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/&/g, "&")
.replace(/\s+/g, " ")
.trim();
}
// ============ Create MCP Server ============
function createServer(): McpServer {
const server = new McpServer({
name: "cbeta-mcp",
version: "1.0.0",
});
// Tool: 搜索佛典经文
server.tool(
"search_sutra",
"搜索 CBETA 佛典经文。Search CBETA Buddhist scriptures.",
{
query: z.string().describe("搜索关键词 / Search keywords"),
scope: z.string().optional().describe("搜索范围 (T/X/N...) / Search scope"),
page: z.number().optional().describe("页码 / Page number"),
limit: z.number().optional().describe("每页数量 / Items per page"),
},
async ({ query, scope, page, limit }: { query: string; scope?: string; page?: number; limit?: number }) => {
const result = await searchSutra(query, { scope, page: page || 1, per_page: limit || 10 });
if (result.results.length === 0) {
return {
content: [{ type: "text" as const, text: `未找到与「${query}」相关的经文。No results found for "${query}".` }],
};
}
const formatted = result.results
.map((r, i) => `${i + 1}. **${r.title}** (${r.work_id}) - 卷${r.juan},${r.hits} hits`)
.join("\n\n");
return {
content: [{ type: "text" as const, text: `## 搜索结果 / Search Results\n\n共 ${result.total} 条 / Total: ${result.total}\n\n${formatted}` }],
};
}
);
// Tool: 获取经文内容
server.tool(
"get_sutra_content",
"获取指定佛典的内容。Get content of a specific Buddhist scripture.",
{
work_id: z.string().describe("作品 ID (如 T0001, T0262) / Work ID"),
juan: z.number().optional().describe("卷号 / Volume number"),
format: z.enum(["text", "html"]).optional().describe("输出格式 / Output format"),
},
async ({ work_id, juan, format }: { work_id: string; juan?: number; format?: "text" | "html" }) => {
const workInfo = await getWorkInfo(work_id);
const content = await getJuanHtml(work_id, juan || 1);
if (!content) {
return {
content: [{ type: "text" as const, text: `无法获取 ${work_id}。Cannot fetch ${work_id}.` }],
};
}
const text = format === "html" ? content.html : htmlToText(content.html);
const title = workInfo?.title || content.title || work_id;
return {
content: [{ type: "text" as const, text: `## ${title}\n\n**卷 ${content.juan}**\n\n${text}` }],
};
}
);
// Tool: 浏览藏经目录
server.tool(
"browse_catalog",
"浏览 CBETA 藏经目录。Browse CBETA scripture catalog.",
{
catalog_id: z.string().optional().describe("目录 ID (T/X/N...) / Catalog ID"),
},
async ({ catalog_id }: { catalog_id?: string }) => {
const catalog = await getCatalog(catalog_id || "CBETA");
const formatted = catalog.map((item) => `- **${item.label}** (${item.id})`).join("\n");
return {
content: [{ type: "text" as const, text: `## CBETA 藏经目录 / Scripture Catalog\n\n${formatted}` }],
};
}
);
// Tool: 获取作品信息
server.tool(
"get_work_info",
"获取佛典作品详细信息。Get detailed work information.",
{
work_id: z.string().describe("作品 ID / Work ID"),
},
async ({ work_id }: { work_id: string }) => {
const info = await getWorkInfo(work_id);
if (!info) {
return {
content: [{ type: "text" as const, text: `未找到 ${work_id}。Not found: ${work_id}.` }],
};
}
return {
content: [{
type: "text" as const,
text: `## ${info.title}\n\n- **ID**: ${info.work_id}\n- **作者/Author**: ${info.byline || "Unknown"}\n- **卷数/Volumes**: ${info.juan_count}\n- **分类/Category**: ${info.category || "N/A"}`,
}],
};
}
);
// Resource: 藏经目录
server.resource("cbeta://catalog", "CBETA 藏经目录 / Scripture Catalog", async () => {
const catalog = await getCatalog();
return {
contents: [{ uri: "cbeta://catalog", mimeType: "application/json", text: JSON.stringify(catalog, null, 2) }],
};
});
// Prompt: 解释佛经
server.prompt(
"explain_sutra",
"解释佛经段落 / Explain sutra passage",
{
text: z.string().describe("经文段落 / Sutra passage"),
style: z.enum(["简单", "学术", "现代"]).optional().describe("解释风格 / Explanation style"),
},
async ({ text, style }: { text: string; style?: "简单" | "学术" | "现代" }) => {
const styleGuide: Record<string, string> = {
简单: "请用简单易懂的语言解释",
学术: "请从佛学学术角度进行解读",
现代: "请结合现代生活场景来解释",
};
return {
messages: [{
role: "user" as const,
content: { type: "text" as const, text: `${styleGuide[style || "简单"]}以下佛经段落:\n\n「${text}」` },
}],
};
}
);
// Prompt: 佛学术语
server.prompt(
"buddhist_term",
"查询佛学术语 / Look up Buddhist term",
{ term: z.string().describe("术语 / Term") },
async ({ term }: { term: string }) => ({
messages: [{
role: "user" as const,
content: { type: "text" as const, text: `请解释佛学术语「${term}」,包括梵文原词、含义、各宗派理解和经典引用。` },
}],
})
);
return server;
}
// ============ Worker Export ============
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
// Health check
if (url.pathname === "/" || url.pathname === "/health") {
return new Response(JSON.stringify({
name: "cbeta-mcp",
version: "1.0.0",
status: "ok",
endpoints: {
mcp: "/mcp",
health: "/health",
},
}), {
headers: { "Content-Type": "application/json" },
});
}
// MCP endpoint - route to Durable Object
if (url.pathname.startsWith("/mcp")) {
// 获取或创建 Durable Object
const id = env.CBETAMcpAgent.idFromName("cbeta-mcp-agent");
const stub = env.CBETAMcpAgent.get(id);
return stub.fetch(request);
}
return new Response("Not Found", { status: 404 });
},
};
export { CBETAMcpAgent };