feat(fixed):更新Sprint2 package.json和app.ts源代码中的版本号

Signed-off-by: gzkoala <guohao@gitconomy.org>
This commit is contained in:
gzkoala
2026-03-08 20:33:18 +08:00
parent 9a4621ddb9
commit b310bc85e5
2 changed files with 188 additions and 122 deletions

View File

@@ -3,10 +3,11 @@
* Copyright (c) 2025-2026 Gitconomy Research
*
* SPDX-License-Identifier: MIT
*
*
* Contributors:
* - 郭晧 <guohao@gitconomy.org> (Initial Author)
*/
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
@@ -18,64 +19,65 @@ import { listObsidianNotes, readObsidianNote } from './services/resourceService'
// 初始化 MCP Server
// ==========================================
const server = new McpServer({
name: 'Project-Caffeine-S2-Prompt-Strategy',
version: '2.0.0'
name: 'Project-Caffeine-Arabica-Edition',
version: '0.0.2'
});
// ==========================================
// 辅助函数
// ==========================================
/**
* 处理可选参数的默认值转换与补全。
*
* 该函数执行两个主要任务:
* 1. 遍历传入的参数对象将空值undefined/null/"")转换为字符串 "无"。
* 2. 根据提供的 expectedKeys 列表,补全缺失的可选参数键,值为 "无",确保模板替换不会失败。
*
* @param {Record<string, any>} args - 客户端传入的原始参数对象
* @param {string[]} expectedKeys - 该框架预期的所有参数键名列表(用于补全缺失的键)
* @returns {Record<string, string>} 补全且清洗后的参数对象,所有值均为非空字符串
*/
function sanitizeArgs(args: Record<string, any>, expectedKeys: string[] = []): Record<string, string> {
const safeArgs: Record<string, string> = {};
// 处理已有的参数,确保值不为空
for (const [key, value] of Object.entries(args)) {
safeArgs[key] = value !== undefined && value !== null && value !== "" ? String(value) : "无";
}
// 补全缺失的可选参数键,确保模板替换不会失败
expectedKeys.forEach(key => {
if (!(key in safeArgs)) {
safeArgs[key] = "无";
}
});
return safeArgs;
}
// ==========================================
// 注册 Prompts 原语(多维思维框架模板)
// ==========================================
/**
* 处理 SCQA 框架的 prompts/get 请求。
* 调用 handlePromptsGet 获取消息序列,并进行清洗(过滤非法 role、补全 annotations/_meta
* @param args - 包含 situation, complication, question, answer 的对象
* @param extra - MCP 额外上下文(未使用
* @returns 符合 MCP 规范的 prompt 响应对象
* 注册并处理 SCQA 框架的 prompts/get 请求。
*
* @param {object} args - 客户端传入的框架参数
* @param {string} args.situation - 当前的客观背景或情境描述(必填
* @param {string} [args.context] - 补充的约束条件、行业背景或已有资源(可选)
* @param {string} [args.objective] - 期望达成的最终业务目标(可选)
* @returns {Promise<object>} 符合 MCP 协议规范的 prompt 响应对象,包含过滤和补全元数据后的 messages 序列
*/
server.prompt(
'scqa', // 示例SCQA 框架
'scqa',
{
situation: z.string().describe('情境'),
complication: z.string().describe('复杂性'),
question: z.string().describe('问题'),
answer: z.string().describe('答案')
situation: z.string().describe('当前的客观背景或情境描述'),
context: z.string().optional().describe('补充的约束条件、行业背景或已有资源(可选)'),
objective: z.string().optional().describe('期望达成的最终业务目标(可选)')
},
async (args, extra) => {
const result = await handlePromptsGet('scqa', args as Record<string, string>);
return {
...result,
messages: Array.isArray(result.messages)
? result.messages
.filter((msg: any) => msg.role === "user" || msg.role === "assistant")
.map((msg: any) => ({
...msg,
// Optionally ensure content has required structure
content: {
...msg.content,
// Add default annotations/_meta if missing
annotations: msg.content.annotations ?? undefined,
_meta: msg.content._meta ?? undefined
}
}))
: []
};
}
);
/**
* 处理 5 Whys 框架的 prompts/get 请求。
* 调用 handlePromptsGet 获取消息序列,并进行清洗。
* @param args - 包含 problem 的对象
* @returns 符合 MCP 规范的 prompt 响应对象
*/
server.prompt(
'5whys', // 5 Whys 框架
{ problem: z.string().describe('需要分析的问题或现象') },
async (args) => {
const result = await handlePromptsGet('5whys', args);
const result = await handlePromptsGet('scqa', sanitizeArgs(args, ['situation', 'context', 'objective']));
return {
...result,
messages: Array.isArray(result.messages)
@@ -95,17 +97,23 @@ server.prompt(
);
/**
* 处理 5W3H 框架的 prompts/get 请求。
* 调用 handlePromptsGet 获取消息序列,并进行清洗。
* @param args - 包含 topic 的对象
* @returns 符合 MCP 规范的 prompt 响应对象
* 注册并处理 5 Whys 框架的 prompts/get 请求。
*
* @param {object} args - 客户端传入的框架参数
* @param {string} args.problem - 需要分析的核心问题、故障或不良现象(必填)
* @param {string} [args.context] - 问题发生的背景信息、前置条件或受影响的范围(可选)
* @param {string} [args.goal] - 期望通过解决此问题达成的最终目标(可选)
* @returns {Promise<object>} 符合 MCP 协议规范的 prompt 响应对象
*/
server.prompt(
'5w3h', // 5W3H 框架
{ topic: z.string().describe('需要分析的主题') },
'5whys',
{
problem: z.string().describe('需要分析的核心问题、故障或不良现象'),
context: z.string().optional().describe('问题发生的背景信息、前置条件或受影响的范围(可选)'),
goal: z.string().optional().describe('期望通过解决此问题达成的最终目标(可选)')
},
async (args) => {
const result = await handlePromptsGet('5w3h', args);
const result = await handlePromptsGet('5whys', sanitizeArgs(args, ['problem', 'context', 'goal']));
return {
...result,
messages: Array.isArray(result.messages)
@@ -125,17 +133,23 @@ server.prompt(
);
/**
* 处理 SWOT 框架的 prompts/get 请求。
* 调用 handlePromptsGet 获取消息序列,并进行清洗。
* @param args - 包含 entity 的对象
* @returns 符合 MCP 规范的 prompt 响应对象
* 注册并处理 5W3H 框架的 prompts/get 请求。
*
* @param {object} args - 客户端传入的框架参数
* @param {string} args.topic - 需要分析的核心主题、事件或问题(必填)
* @param {string} [args.context] - 该主题发生的特定背景、前置条件或行业环境(可选)
* @param {string} [args.objective] - 期望通过此次分析达成的核心目标(可选)
* @returns {Promise<object>} 符合 MCP 协议规范的 prompt 响应对象
*/
server.prompt(
'swot', // SWOT 框架
{ entity: z.string().describe('分析对象(企业、项目等)') },
'5w3h',
{
topic: z.string().describe('需要分析的核心主题、事件或问题'),
context: z.string().optional().describe('该主题发生的特定背景、前置条件或行业环境(可选)'),
objective: z.string().optional().describe('期望通过此次分析达成的核心目标(可选)')
},
async (args) => {
const result = await handlePromptsGet('swot', args);
const result = await handlePromptsGet('5w3h', sanitizeArgs(args, ['topic', 'context', 'objective']));
return {
...result,
messages: Array.isArray(result.messages)
@@ -155,17 +169,59 @@ server.prompt(
);
/**
* 处理 PESTLE 框架的 prompts/get 请求。
* 调用 handlePromptsGet 获取消息序列,并进行清洗。
* @param args - 包含 domain 的对象
* @returns 符合 MCP 规范的 prompt 响应对象
* 注册并处理 SWOT 框架的 prompts/get 请求。
*
* @param {object} args - 客户端传入的框架参数
* @param {string} args.entity - 分析对象(如企业、产品、项目、个人等)(必填)
* @param {string} [args.context] - 补充的行业背景、当前阶段或面临的核心挑战(可选)
* @param {string} [args.competitors] - 主要竞争对手或对标对象(可选)
* @returns {Promise<object>} 符合 MCP 协议规范的 prompt 响应对象
*/
server.prompt(
'pestle', // PESTLE 框架
{ domain: z.string().describe('行业或领域') },
'swot',
{
entity: z.string().describe('分析对象(如企业、产品、项目、个人等)'),
context: z.string().optional().describe('补充的行业背景、当前阶段或面临的核心挑战(可选)'),
competitors: z.string().optional().describe('主要竞争对手或对标对象(可选)')
},
async (args) => {
const result = await handlePromptsGet('pestle', args);
const result = await handlePromptsGet('swot', sanitizeArgs(args, ['entity', 'context', 'competitors']));
return {
...result,
messages: Array.isArray(result.messages)
? result.messages
.filter((msg: any) => msg.role === "user" || msg.role === "assistant")
.map((msg: any) => ({
...msg,
content: {
...msg.content,
annotations: msg.content.annotations ?? undefined,
_meta: msg.content._meta ?? undefined
}
}))
: []
};
}
);
/**
* 注册并处理 PESTLE 框架的 prompts/get 请求。
*
* @param {object} args - 客户端传入的框架参数
* @param {string} args.domain - 需要分析的具体行业、市场或业务领域(必填)
* @param {string} [args.region] - 目标地域范围(可选)
* @param {string} [args.timeframe] - 分析的时间跨度(可选)
* @returns {Promise<object>} 符合 MCP 协议规范的 prompt 响应对象
*/
server.prompt(
'pestle',
{
domain: z.string().describe('需要分析的具体行业、市场或业务领域'),
region: z.string().optional().describe('目标地域范围(可选)'),
timeframe: z.string().optional().describe('分析的时间跨度(可选)')
},
async (args) => {
const result = await handlePromptsGet('pestle', sanitizeArgs(args, ['domain', 'region', 'timeframe']));
return {
...result,
messages: Array.isArray(result.messages)
@@ -185,23 +241,22 @@ server.prompt(
);
// ==========================================
// 注册工具generate_search_queries
// 注册工具Tools
// ==========================================
/**
* 处理 generate_search_queries 工具调用
* 调用 handleToolCall 获取结果,并确保返回的每个 content 项具有 type: "text"
* @param args - 包含 query 的对象
* @param extra - MCP 额外上下文(未使用)
* @returns MCP 工具响应对象
* 注册意图拆解工具 generate_search_queries。
* 接收用户的原始自然语言查询,调用意图拆解服务生成 3-5 个专业检索词
*
* @param {object} args - 工具参数对象
* @param {string} args.query - 用户的原始查询语句
* @returns {Promise<object>} 符合 MCP 协议的工具响应对象,包含 type 为 "text" 的内容格式化结果
*/
server.tool(
'generate_search_queries',
{ query: z.string().describe('用户的原始查询语句') },
async (args, extra) => {
async (args) => {
const result = await handleToolCall('generate_search_queries', args);
// Ensure each content item has type: "text" (not a generic string)
return {
...result,
content: result.content.map((item: any) => ({
@@ -212,17 +267,13 @@ server.tool(
}
);
// =====================================================
// 注册工具list_local_notes, read_local_note, save_note
// =====================================================
/**
* 处理 list_local_notes 工具调用
* 调用 handleToolCall 获取笔记列表,并标准化 content 格式
* @param args - 空对象(无需参数)
* @returns MCP 工具响应对象,包含笔记列表文本
* 注册获取本地笔记列表工具 list_local_notes。
* 允许大模型获取当前知识库目录下所有存在的 Markdown 笔记文件名
*
* @param {object} args - 无需额外参数(保留以符合 MCP 签名)
* @returns {Promise<object>} 包含笔记文件名列表的文本响应对象
*/
server.tool(
'list_local_notes',
{},
@@ -238,14 +289,14 @@ server.tool(
}
);
/**
* 处理 read_local_note 工具调用
* 调用 handleToolCall 读取指定笔记内容,并标准化 content 格式
* @param args - 包含 filename 的对象
* @returns MCP 工具响应对象,包含笔记内容文本
* 注册读取单一本地笔记工具 read_local_note。
* 允许大模型根据指定文件名获取该 Markdown 文件的具体文本内容
*
* @param {object} args - 工具参数对象
* @param {string} args.filename - 需要读取的笔记文件名,必须包含 .md 后缀
* @returns {Promise<object>} 包含目标文件全量文本内容的响应对象
*/
server.tool(
'read_local_note',
{ filename: z.string().describe('需要读取的笔记文件名,必须包含 .md 后缀') },
@@ -262,12 +313,14 @@ server.tool(
);
/**
* 处理 save_note 工具调用
* 调用 handleToolCall 保存笔记到本地知识库,并标准化 content 格式
* @param args - 包含 filename 和 content 的对象
* @returns MCP 工具响应对象,包含保存结果信息
* 注册保存本地笔记工具 save_note。
* 允许大模型将分析结果或摘要撰写并保存为本地知识库中的 Markdown 文件
*
* @param {object} args - 工具参数对象
* @param {string} args.filename - 笔记文件名,必须以 .md 结尾
* @param {string} args.content - 需要持久化写入的笔记内容Markdown 格式)
* @returns {Promise<object>} 保存成功的状态回执或报错信息的响应对象
*/
server.tool(
'save_note',
{
@@ -287,14 +340,27 @@ server.tool(
);
// ==========================================
// 注册 Resources 原语(暴露本地笔记供客户端勾选
// 注册资源(Resources
// ==========================================
// 使用 ResourceTemplate 注册动态资源
/**
* 注册 local-notes 资源模板原语。
* 提供给支持 Resources 的 MCP 客户端,使用户可以直接在界面上勾选并注入本地知识库笔记。
*
* - list 钩子:返回所有有效的 note://local/{filename} 资源列表。
* - 读取钩子:根据解析出的 filename 读取本地文件内容,组装为符合 MCP 规范的响应。
*
* @type {import('@modelcontextprotocol/sdk/server/mcp.js').ResourceTemplate}
*/
server.resource(
"local-notes", // 资源名称
new ResourceTemplate("note://local/{filename}", {
// 实现列表功能:返回所有可用的笔记资源
"local-notes",
new ResourceTemplate("note://local/{filename}", {
/**
* 列出所有可用的笔记资源。
*
* @returns {Promise<{ resources: Array<{ name: string; uri: string; mimeType: string; description: string }> }>}
* 资源列表每个资源包含名称、URI、MIME 类型和描述
*/
list: async () => {
try {
const notes = await listObsidianNotes();
@@ -312,22 +378,21 @@ server.resource(
}
}
}),
/**
/**
* 处理资源读取请求:根据 URI 中的 filename 参数读取对应笔记内容。
* @param uri - 请求的 URI 对象
* @param params - 包含从 URI 提取的 filename 参数
* @returns 符合 MCP 规范的资源内容对象
* @throws 当读取失败时抛出错误
*
* @param {URL} uri - 请求的完整 URI 对象
* @param {{ filename: string | string[] }} params - 从 URI 中提取的参数,包含 filename
* @returns {Promise<{ contents: Array<{ uri: string; mimeType: string; text: string }> }>}
* 符合 MCP 规范的内容响应对象
* @throws {Error} 当读取失败时抛出错误,错误信息将被 MCP 客户端捕获
*/
async (uri, { filename }) => {
try {
// filename 参数由 ResourceTemplate 自动从 URI 中提取
const filenameStr = Array.isArray(filename) ? filename[0] : filename;
const decodedFilename = decodeURIComponent(filenameStr);
const content = await readObsidianNote(decodedFilename);
return {
contents: [{
uri: uri.href,
@@ -336,25 +401,25 @@ server.resource(
}]
};
} catch (error: any) {
// 错误处理:返回错误信息
throw new Error(`读取笔记失败: ${error.message}`);
}
}
);
// ==========================================
// 启动 STDIO 传输层
// 启动服务器
// ==========================================
/**
* 启动 MCP 服务器,连接 STDIO 传输层
* 该函数创建 StdioServerTransport 实例并连接到 server。
* @returns {Promise<void>} 无返回值
* 启动 MCP 服务器的主入口函数
* 建立与客户端(如 Cherry Studio)的标准输入/输出 (STDIO) 传输通信通道,
* 将服务器配置挂载至进程,并就绪等待各类协议请求。
*
* @returns {Promise<void>} 异步的启动过程,无返回值
* @throws {Error} 如果连接失败,错误会被捕获并记录,进程退出
*/
async function start() {
console.error('[S2] 正在启动 MCP Server (Prompts + 检索词工具)...');
console.error('[S2] 正在启动 MCP Server (Prompts + Tools + Resources)...');
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('[S2] MCP Server 已就绪,等待 Cherry Studio 连接');

View File

@@ -1,4 +1,5 @@
{
"$schema": "https://json.schemastore.org/tsconfig", // Uncomment if you want schema validation and the URL is reachable
"compilerOptions": {
"target": "ES2022",
"module": "CommonJS",