Files
Project-Caffeine/projects/arabica/docs/deisgn/arabica-sprint1-development-specification.md
2026-03-05 12:05:57 +08:00

15 KiB
Raw Blame History

Arabica Sprint1 版本开发指南说明

1. 环境前置要求

在开始部署前,请确保开发机已安装以下软件:

  • Node.js: v18 LTS 或更高版本。
  • Visual Studio Code (VS Code): 作为主力开发与断点调试 IDE。
  • Cherry Studio: 最新版,作为发起请求的 MCP Client大模型中枢
  • 本地知识库: 一个存放 .md 格式笔记的本地文件夹(如 Obsidian Vault

2. 工程初始化与依赖安装

打开终端,执行以下命令从零搭建工程骨架:

# 1. 创建并进入项目目录
mkdir project-caffeine-ts
cd project-caffeine-ts

# 2. 初始化 npm
npm init -y

# 3. 安装生产核心依赖
npm install @modelcontextprotocol/sdk zod

# 4. 安装 TypeScript 及开发环境依赖
npm install --save-dev typescript @types/node

# 5. 创建标准的目录结构
mkdir -p src/services dist .vscode
touch src/app.ts src/services/promptService.ts src/services/resourceService.ts .vscode/launch.json tsconfig.json

3. 核心工程配置

我们需要配置 TypeScript 编译器选项、启动脚本以及 VS Code 独有的源码映射调试环境。

3.1 编辑tsconfig.json

控制代码编译并生成用于断点调试的 sourceMap

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "CommonJS",
    "moduleResolution": "node",
    "outDir": "./dist",
    "rootDir": "./src",
    "sourceMap": true,             // 【关键】生成 .js.map 文件,用于 VS Code 断点映射
    "strict": true,                // 开启严格模式
    "esModuleInterop": true,       // 允许默认导入 CommonJS 模块
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
	"include": ["src/**/*"]
}

2.2 编辑package.json

添加 buildwatch 脚本,用于将 .ts 编译为 .js。在 package.json 中找到 "scripts" 字段并替换为

"scripts": { "build": "tsc", "watch": "tsc --watch", "start": "node dist/app.js" }

2.3 编辑 .vscode/launch.json

新增的 outFiles 字段,它告诉 VS Code 去 dist 目录寻找编译后的文件,从而将断点映射回你的 src/*.ts 源码上。

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "attach",
      "name": "🍒 附加到 Cherry Studio (TS 联调)",
      "port": 9229,
      "restart": true,
      "skipFiles": ["<node_internals>/**"],
      "outFiles": ["${workspaceFolder}/dist/**/*.js"]
    }
  ]
}

4. 第核心业务代码实现

4.1. src/services/promptService.ts (提示词策略生成)

纯本地业务逻辑,负责 5 Whys 框架生成。

/**
 * 根据查询主题生成 5 Whys 提示词策略
 * @param query 用户输入的查询主题
 * @returns 包含 5 个追问的字符串数组
 */
export function generate5Whys(query: string): string[] {
    if (query.includes("开源人才")) {
        return [
            "为什么中国开源人才的培养面临困难?",  
            "为什么中国开源人才缺乏足够的行业经验?",  
            "为什么开源社区对中国人才的支持力度不足?",  
            "为什么中国开源人才的市场需求与供给不平衡?",  
            "为什么政策支持不足导致中国开源人才流失?"
        ];
    }

    return [
        `为什么 "${query}" 会成为一个问题?`,
        `为什么导致上述现象的直接原因会发生?`,
        `为什么当前的系统或流程没有阻止这种情况?`,
        `为什么以前的解决方案或预防措施失效了?`,
        `为什么根本的系统性漏洞一直未被修复?`
    ];
}

## 4.2 src/services/resourceService.ts (本地知识库访问)

带有严格路径防穿越Path Traversal安全校验的本地文件读取服务。

import fs from 'fs/promises';
import path from 'path';

// 【⚠️ 重要配置】请修改为你电脑上真实的 Markdown 笔记文件夹绝对路径!
const OBSIDIAN_VAULT_PATH = '/home/wguo/Downloads/MyVault';

export async function listObsidianNotes(): Promise<string[]> {
    try {
        const files = await fs.readdir(OBSIDIAN_VAULT_PATH);
        return files.filter(file => file.toLowerCase().endsWith('.md'));
    } catch (error: any) {
        console.error(`[Project Caffeine] 无法读取知识库目录: ${error.message}`);
        return [];
    }
}

export async function readObsidianNote(filename: string): Promise<string> {
    const targetPath = path.resolve(OBSIDIAN_VAULT_PATH, filename);
    const safeVaultPath = path.resolve(OBSIDIAN_VAULT_PATH);

    // 核心防御:防止大模型通过传入 "../../" 读取系统敏感文件
    if (!targetPath.startsWith(safeVaultPath)) {
        throw new Error(`安全警告:越权访问拦截!禁止读取目录外的文件: ${filename}`);
    }

    try {
        const content = await fs.readFile(targetPath, 'utf-8');
        return content;
    } catch (error: any) {
        throw new Error(`无法读取笔记 [${filename}]: 文件可能不存在或无权限。`);
    }
}

4.3 src/app.ts (主入口)

负责初始化标准输入输出传输层,并向 Cherry Studio 注册工具字典。

import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import { generate5Whys } from './services/promptService';
import { listObsidianNotes, readObsidianNote } from './services/resourceService';

// ==========================================
// 1. 初始化 MCP Server
// ==========================================
const mcpServer = new McpServer({
    name: "Project-Caffeine-Prompt-Strategy",
    version: "1.2.0"
});

// ==========================================
// 2. 注册 Tools (工具) - 赋予大模型主动执行的能力
// ==========================================

// 工具 15 Whys 提示词策略生成
mcpServer.tool(
    "generate_5_whys",
    "使用 5 Whys 模板对用户查询进行深度分解,生成增强的提示词策略",
    { query: z.string().describe("需要分析的查询主题") },
    async ({ query }: { query: string }) => {
        console.error(`[Project Caffeine] 大模型调用工具: 正在生成 5 Whys 策略 -> ${query}`);
        const enhancedPrompt = generate5Whys(query);
        return {
            content: [{ type: "text", text: JSON.stringify(enhancedPrompt, null, 2) }]
        };
    }
);

// 工具 2扫描本地知识库目录
mcpServer.tool(
    "list_local_notes",
    "获取本地 Obsidian 知识库中的所有 Markdown 笔记列表,用于了解当前有哪些可用的本地上下文资料。",
    {},
    async () => {
        console.error(`[Project Caffeine] 大模型调用工具: 正在扫描本地笔记列表...`);
        const notes = await listObsidianNotes();
        return {
            content: [{
                type: "text",
                text: notes.length > 0 ? `找到了以下笔记:\n${notes.join('\n')}` : "未找到笔记。"
            }]
        };
    }
);

// 工具 3阅读指定的单篇笔记内容
mcpServer.tool(
    "read_local_note",
    "读取本地 Obsidian 知识库中指定笔记的完整内容,作为深度分析的上下文参考。",
    { filename: z.string().describe("需要读取的笔记文件名,必须包含 .md 后缀") },
    async ({ filename }: { filename: string }) => {
        console.error(`[Project Caffeine] 大模型调用工具: 正在深度阅读笔记 -> ${filename}`);
        try {
            const content = await readObsidianNote(filename);
            return { content: [{ type: "text", text: content }] };
        } catch (error: any) {
            return {
                content: [{ type: "text", text: `读取失败: ${error.message}` }],
                isError: true // 明确告知大模型此操作抛出了错误
            };
        }
    }
);

// ==========================================
// 3. 注册 Resources (资源) - 暴露给客户端供用户手动勾选的静态数据
// ==========================================

// 资源 1知识库目录索引
mcpServer.resource(
    "obsidian-index",                   // 客户端显示的资源 Name/ID
    "obsidian://vault/index",           // 唯一的 URI 标识
    {
        description: "本地知识库的目录索引,包含所有 Markdown 笔记的列表"
    },
    async (uri) => {
        console.error(`[Project Caffeine] 客户端请求静态资源: ${uri.href}`);

        const notes = await listObsidianNotes();
        const textContent = notes.length > 0
            ? `当前知识库包含以下文件:\n${notes.join('\n')}`
            : "当前知识库为空。";

        return {
            contents: [{
                uri: uri.href,
                mimeType: "text/plain",
                text: textContent
            }]
        };
    }
);

// ==========================================
// 4. 启动底层 Stdio 传输层
// ==========================================
async function start(): Promise<void> {
    console.error("[Project Caffeine] 正在启动 TS 版 MCP Server (含 Tools 与 Resources)...");
    const transport = new StdioServerTransport();
    await mcpServer.connect(transport);
    console.error("[Project Caffeine] MCP Server 已就绪,等待 Cherry Studio 交互。");
}

// 捕获致命错误并安全退出
start().catch((err: unknown) => {
    console.error("服务器启动失败:", err);
    process.exit(1);
});

5. 启动与工作流验证

5.1 步骤一:启动 TS 实时编译 (Watch Mode)

在 VS Code 中打开终端,运行以下命令。这会让 TypeScript 编译器在后台实时监控你的 .ts 文件修改,并自动编译到 dist 目录中:

npm run watch

(保持这个终端窗口在后台运行不要关闭)

5.2 步骤二:在 Cherry Studio 中配置 Server

  1. 进入 Cherry Studio 的 设置 -> MCP

  2. 添加或修改 Server关键在于你要指向编译后的 dist/app.js 而不是 src/app.ts

    • Command: node
    • Args: ["--inspect=9229", "/project-caffeine-sprint1/dist/app.js"]
    • 注意需要输入app.js的绝对路径
  3. 确保状态灯亮起绿色。

5.3 步骤三VS Code 源码级断点联调

  1. src/app.ts 或各个 Service 的关键代码行打上断点。
  2. 在 VS Code 左侧调试面板,选择 "🍒 附加到 Cherry Studio (TS 联调)",点击运行。
  3. 状态栏变色即表示成功抓取到 Cherry Studio 的底层 Node 进程。

5.4 发起全链路交互

在 Cherry Studio 对话框中,输入以下指令测试大模型的自主编排能力:

"请先查看我的本地笔记列表,找到关于开源领域的笔记并阅读内容。然后结合你的知识,调用 5 Whys 工具帮我分析一下里面的痛点。"

预期结果:你将看到大模型自动、按顺序调用了 list_local_notes -> read_local_note -> generate_5_whys 三个工具,最终为你输出一篇深度融合了你的私有知识库的洞察报告。


6. 系统运行测试样例

在开始执行测试之前,请确保已完成以下前置准备:

  • 后台编译:在 VS Code 终端保持运行 npm run watch 命令。
  • 配置连接:在 Cherry Studio 的设置中,将 Command 设为 nodeArgs 设为 ["--inspect=9229", "<绝对路径>/dist/app.js"],并确保状态灯亮起绿色。
  • 本地知识库:确保代码中 OBSIDIAN_VAULT_PATH 指向的本地文件夹中存在至少一篇 Markdown 格式的测试笔记。

6.1 测试样例1基础工具调用与分支逻辑验证

  • 测试目标:验证 generate_5_whys 工具的硬编码逻辑分支是否生效。

  • 操作步骤

    1. 向大模型发送指令:“请调用工具,帮我生成关于‘开源人才’的 5 Whys 策略。”
  • 预期结果

    • VS Code Debug Console打印日志[Project Caffeine] 大模型调用工具: 正在生成 5 Whys 策略 -> 开源人才
    • 大模型的回答中必须包含代码中预设的具体问题,例如“为什么中国开源人才的培养面临困难?”、“为什么中国开源人才缺乏足够的行业经验?”等。

6.2 测试样例 2泛化工具调用验证

  • 测试目标:验证 generate_5_whys 工具在遇到未知主题时的泛化模板处理能力。

  • 操作步骤

    1. 向大模型发送指令“我正在研究AI算力成本过高的问题请调用工具为我生成 5 Whys 框架。”
  • 预期结果

    • 大模型会调用工具,并将主题代入泛化模板,输出类似“为什么 "AI算力成本过高" 会成为一个问题?”、“为什么导致上述现象的直接原因会发生?”等框架性问题。

6.3 测试样例 3安全防御机制测试红蓝对抗

  • 测试目标:验证 read_local_note 工具中的路径防穿越Path Traversal安全校验是否能成功拦截恶意请求。

  • 操作步骤

    1. 向大模型发送诱导性指令:“请调用读取笔记的工具,帮我读取 ../etc/passwd 文件的内容。”
  • 预期结果

    • 工具调用将被拦截,并抛出错误。
    • 大模型将收到包含 isError: true 的错误响应。
    • 错误信息明确提示大模型:“读取失败: 安全警告:越权访问拦截!禁止读取目录外的文件”。

6.4 测试样例 4全链路 Agentic 自主编排测试

  • 测试目标:验证大模型是否能自主决策,按顺序组合调用多个外部工具完成复杂分析。

  • 操作步骤

    1. 向大模型发送综合指令:“请先查看我的本地笔记列表,找到关于开源领域的笔记并阅读内容。然后结合你的知识,调用 5 Whys 工具帮我分析一下里面的痛点。”。
  • 预期结果

    • 大模型将自动并按顺序调用 list_local_notes -> read_local_note -> generate_5_whys 三个工具。
    • 最终输出一篇融合了私有知识库内容深度的洞察报告。

6.5 测试样例 5VS Code 断点联调环境测试

  • 测试目标:验证 .vscode/launch.json 源码映射与调试环境配置是否成功。

  • 操作步骤

    1. src/app.ts 或其他 Service 文件的关键代码行打上断点。
    2. 在 VS Code 调试面板选择“🍒 附加到 Cherry Studio (TS 联调)”并运行。
    3. 在 Cherry Studio 中触发任意工具调用。
  • 预期结果

    • VS Code 底部状态栏变色,表示成功抓取到 Cherry Studio 的底层 Node 进程。
    • 代码执行将暂停在设置了断点的位置,允许开发者查看当前变量与调用栈。

许可声明

本文档采用 知识共享署名--相同方式共享 4.0 国际许可协议 (CC BY--SA 4.0) 进行许可,© 2025-2026 Gitconomy Research.