feat(fixed):更新Sprint2 package.json和app.ts源代码中的版本号
Signed-off-by: gzkoala <guohao@gitconomy.org>
This commit is contained in:
@@ -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 连接');
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user