@zsc-glitch/knowledge-keeper-mcp 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.md +72 -0
- package/dist/core.d.ts +52 -0
- package/dist/core.js +418 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +21 -0
- package/dist/resources/index.d.ts +5 -0
- package/dist/resources/index.js +10 -0
- package/dist/tools/delete.d.ts +5 -0
- package/dist/tools/delete.js +47 -0
- package/dist/tools/get.d.ts +5 -0
- package/dist/tools/get.js +47 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/index.js +18 -0
- package/dist/tools/save.d.ts +5 -0
- package/dist/tools/save.js +45 -0
- package/dist/tools/search.d.ts +5 -0
- package/dist/tools/search.js +58 -0
- package/dist/tools/tags.d.ts +5 -0
- package/dist/tools/tags.js +47 -0
- package/dist/tools/update.d.ts +5 -0
- package/dist/tools/update.js +56 -0
- package/package.json +43 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 小影 (zsc-glitch)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Knowledge Keeper MCP Server
|
|
2
|
+
|
|
3
|
+
> 智能知识管理的 Model Context Protocol 实现
|
|
4
|
+
|
|
5
|
+
## 简介
|
|
6
|
+
|
|
7
|
+
让 **knowledge-keeper** 可被 Claude Code、Cursor、Gemini CLI、Windsurf 等工具调用。
|
|
8
|
+
|
|
9
|
+
## 安装
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @zsc-glitch/knowledge-keeper-mcp
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## 使用
|
|
16
|
+
|
|
17
|
+
### Claude Code
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
claude mcp add knowledge-keeper -- npx @zsc-glitch/knowledge-keeper-mcp
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Gemini CLI
|
|
24
|
+
|
|
25
|
+
在配置中添加:
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"knowledge-keeper": {
|
|
30
|
+
"command": "npx",
|
|
31
|
+
"args": ["@zsc-glitch/knowledge-keeper-mcp"]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## MCP Tools
|
|
38
|
+
|
|
39
|
+
| Tool | 功能 |
|
|
40
|
+
|------|------|
|
|
41
|
+
| `knowledge_save` | 保存知识点 |
|
|
42
|
+
| `knowledge_search` | 搜索知识 |
|
|
43
|
+
| `knowledge_get` | 获取单个知识点 |
|
|
44
|
+
| `knowledge_update` | 更新知识点 |
|
|
45
|
+
| `knowledge_delete` | 删除知识点 |
|
|
46
|
+
| `knowledge_tags` | 列出标签 |
|
|
47
|
+
|
|
48
|
+
## MCP Resources(计划)
|
|
49
|
+
|
|
50
|
+
| Resource | URI | 说明 |
|
|
51
|
+
|----------|-----|------|
|
|
52
|
+
| 知识点 | `knowledge:///{id}` | 单个知识点内容 |
|
|
53
|
+
| 标签索引 | `knowledge:///tags` | 所有标签 |
|
|
54
|
+
| 类型目录 | `knowledge:///type/{type}` | 按类型列出 |
|
|
55
|
+
|
|
56
|
+
## 开发
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# 构建
|
|
60
|
+
npm run build
|
|
61
|
+
|
|
62
|
+
# 本地测试
|
|
63
|
+
node dist/index.js
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## License
|
|
67
|
+
|
|
68
|
+
MIT
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
Made with 🧠 by [小影](https://github.com/zsc-glitch)
|
package/dist/core.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Keeper Core
|
|
3
|
+
* 核心逻辑,不依赖 OpenClaw SDK,可被 MCP Server 复用
|
|
4
|
+
*/
|
|
5
|
+
export type KnowledgeType = "concept" | "decision" | "todo" | "note" | "project";
|
|
6
|
+
export interface KnowledgePoint {
|
|
7
|
+
id: string;
|
|
8
|
+
type: KnowledgeType;
|
|
9
|
+
title: string;
|
|
10
|
+
content: string;
|
|
11
|
+
tags: string[];
|
|
12
|
+
links: string[];
|
|
13
|
+
created: string;
|
|
14
|
+
updated: string;
|
|
15
|
+
source: "conversation" | "manual" | "mcp";
|
|
16
|
+
}
|
|
17
|
+
export declare class KnowledgeError extends Error {
|
|
18
|
+
code: string;
|
|
19
|
+
details?: Record<string, unknown> | undefined;
|
|
20
|
+
constructor(message: string, code: string, details?: Record<string, unknown> | undefined);
|
|
21
|
+
}
|
|
22
|
+
export declare function getVaultDir(): string;
|
|
23
|
+
export declare function generateId(type: KnowledgeType): string;
|
|
24
|
+
export declare function saveKnowledge(params: {
|
|
25
|
+
type: KnowledgeType;
|
|
26
|
+
title: string;
|
|
27
|
+
content: string;
|
|
28
|
+
tags?: string[];
|
|
29
|
+
links?: string[];
|
|
30
|
+
}): Promise<KnowledgePoint>;
|
|
31
|
+
export declare function searchKnowledge(params: {
|
|
32
|
+
query: string;
|
|
33
|
+
type?: KnowledgeType;
|
|
34
|
+
tags?: string[];
|
|
35
|
+
limit?: number;
|
|
36
|
+
}): Promise<KnowledgePoint[]>;
|
|
37
|
+
export declare function getKnowledge(id: string): Promise<KnowledgePoint | null>;
|
|
38
|
+
export declare function updateKnowledge(id: string, params: {
|
|
39
|
+
title?: string;
|
|
40
|
+
content?: string;
|
|
41
|
+
tags?: string[];
|
|
42
|
+
appendTags?: string[];
|
|
43
|
+
}): Promise<KnowledgePoint | null>;
|
|
44
|
+
export declare function deleteKnowledge(id: string): Promise<boolean>;
|
|
45
|
+
export declare function listTags(): Promise<Record<string, number>>;
|
|
46
|
+
export declare function reviewKnowledge(params: {
|
|
47
|
+
period?: "today" | "week" | "month" | "all";
|
|
48
|
+
type?: KnowledgeType;
|
|
49
|
+
}): Promise<{
|
|
50
|
+
stats: Record<KnowledgeType, number>;
|
|
51
|
+
recent: KnowledgePoint[];
|
|
52
|
+
}>;
|
package/dist/core.js
ADDED
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Keeper Core
|
|
3
|
+
* 核心逻辑,不依赖 OpenClaw SDK,可被 MCP Server 复用
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from "fs/promises";
|
|
6
|
+
import * as path from "path";
|
|
7
|
+
import * as os from "os";
|
|
8
|
+
// 错误类型
|
|
9
|
+
export class KnowledgeError extends Error {
|
|
10
|
+
code;
|
|
11
|
+
details;
|
|
12
|
+
constructor(message, code, details) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.code = code;
|
|
15
|
+
this.details = details;
|
|
16
|
+
this.name = "KnowledgeError";
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// 获取知识库目录
|
|
20
|
+
export function getVaultDir() {
|
|
21
|
+
const dir = process.env.KNOWLEDGE_KEEPER_DIR || "~/.knowledge-vault";
|
|
22
|
+
return dir.replace("~", os.homedir());
|
|
23
|
+
}
|
|
24
|
+
// 生成知识点 ID(类型前缀 + 时间戳 + 随机)
|
|
25
|
+
export function generateId(type) {
|
|
26
|
+
const prefix = {
|
|
27
|
+
concept: "kp-cp",
|
|
28
|
+
decision: "kp-dc",
|
|
29
|
+
todo: "kp-td",
|
|
30
|
+
note: "kp-nt",
|
|
31
|
+
project: "kp-pj",
|
|
32
|
+
};
|
|
33
|
+
const timestamp = Date.now().toString(36);
|
|
34
|
+
const rand = Math.random().toString(36).slice(2, 8);
|
|
35
|
+
return `${prefix[type]}-${timestamp}-${rand}`;
|
|
36
|
+
}
|
|
37
|
+
// 确保目录存在
|
|
38
|
+
async function ensureDir(dir) {
|
|
39
|
+
try {
|
|
40
|
+
await fs.mkdir(dir, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
if (err.code !== "EEXIST") {
|
|
44
|
+
throw new KnowledgeError(`无法创建目录: ${dir}`, "DIR_CREATE_FAILED", { dir, error: err });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// 获取类型对应的子目录
|
|
49
|
+
function getTypeDir(type) {
|
|
50
|
+
const dirs = {
|
|
51
|
+
concept: "concepts",
|
|
52
|
+
decision: "decisions",
|
|
53
|
+
todo: "todos",
|
|
54
|
+
note: "notes",
|
|
55
|
+
project: "projects",
|
|
56
|
+
};
|
|
57
|
+
return dirs[type];
|
|
58
|
+
}
|
|
59
|
+
// 从 ID 解析类型
|
|
60
|
+
function parseTypeFromId(id) {
|
|
61
|
+
const prefix = id.slice(0, 5);
|
|
62
|
+
const map = {
|
|
63
|
+
"kp-cp": "concept",
|
|
64
|
+
"kp-dc": "decision",
|
|
65
|
+
"kp-td": "todo",
|
|
66
|
+
"kp-nt": "note",
|
|
67
|
+
"kp-pj": "project",
|
|
68
|
+
};
|
|
69
|
+
return map[prefix] || null;
|
|
70
|
+
}
|
|
71
|
+
// 格式化知识点为 Markdown
|
|
72
|
+
function formatMarkdown(kp) {
|
|
73
|
+
return `---
|
|
74
|
+
id: ${kp.id}
|
|
75
|
+
type: ${kp.type}
|
|
76
|
+
title: ${kp.title.replace(/\n/g, " ")}
|
|
77
|
+
tags: [${kp.tags.join(", ")}]
|
|
78
|
+
links: [${(kp.links || []).join(", ")}]
|
|
79
|
+
created: ${kp.created}
|
|
80
|
+
updated: ${kp.updated}
|
|
81
|
+
source: ${kp.source}
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
# ${kp.title}
|
|
85
|
+
|
|
86
|
+
${kp.content}
|
|
87
|
+
`;
|
|
88
|
+
}
|
|
89
|
+
// 解析 Markdown 为知识点
|
|
90
|
+
function parseMarkdown(content, filepath) {
|
|
91
|
+
try {
|
|
92
|
+
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
|
|
93
|
+
if (!frontmatterMatch)
|
|
94
|
+
return null;
|
|
95
|
+
const [, frontmatter, body] = frontmatterMatch;
|
|
96
|
+
const lines = frontmatter.split("\n");
|
|
97
|
+
const meta = {};
|
|
98
|
+
for (const line of lines) {
|
|
99
|
+
const colonIndex = line.indexOf(":");
|
|
100
|
+
if (colonIndex > 0) {
|
|
101
|
+
const key = line.slice(0, colonIndex).trim();
|
|
102
|
+
const value = line.slice(colonIndex + 1).trim();
|
|
103
|
+
meta[key] = value;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const titleMatch = body.match(/^#\s+(.+)$/m);
|
|
107
|
+
const title = titleMatch ? titleMatch[1].trim() : "Untitled";
|
|
108
|
+
const contentWithoutTitle = body.replace(/^#\s+.+\n/, "").trim();
|
|
109
|
+
const tagsMatch = meta.tags?.match(/\[([^\]]*)\]/);
|
|
110
|
+
const tags = tagsMatch ? tagsMatch[1].split(",").map((t) => t.trim()).filter(Boolean) : [];
|
|
111
|
+
const linksMatch = meta.links?.match(/\[([^\]]*)\]/);
|
|
112
|
+
const links = linksMatch ? linksMatch[1].split(",").map((l) => l.trim()).filter(Boolean) : [];
|
|
113
|
+
return {
|
|
114
|
+
id: meta.id || generateId(meta.type || "note"),
|
|
115
|
+
type: meta.type || "note",
|
|
116
|
+
title,
|
|
117
|
+
content: contentWithoutTitle,
|
|
118
|
+
tags,
|
|
119
|
+
links,
|
|
120
|
+
created: meta.created || new Date().toISOString(),
|
|
121
|
+
updated: meta.updated || new Date().toISOString(),
|
|
122
|
+
source: meta.source || "manual",
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// 通过 ID 查找知识点文件路径
|
|
130
|
+
async function findKnowledgeFile(vaultDir, id) {
|
|
131
|
+
// 尝试从 ID 解析类型
|
|
132
|
+
const type = parseTypeFromId(id);
|
|
133
|
+
if (type) {
|
|
134
|
+
const typeDir = path.join(vaultDir, getTypeDir(type));
|
|
135
|
+
const filepath = path.join(typeDir, `${id}.md`);
|
|
136
|
+
try {
|
|
137
|
+
await fs.access(filepath);
|
|
138
|
+
return filepath;
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// 兼容旧 ID 格式:遍历所有类型
|
|
145
|
+
const types = ["concept", "decision", "todo", "note", "project"];
|
|
146
|
+
for (const t of types) {
|
|
147
|
+
const typeDir = path.join(vaultDir, getTypeDir(t));
|
|
148
|
+
const filepath = path.join(typeDir, `${id}.md`);
|
|
149
|
+
try {
|
|
150
|
+
await fs.access(filepath);
|
|
151
|
+
return filepath;
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
// 加载索引
|
|
160
|
+
async function loadIndex(vaultDir) {
|
|
161
|
+
const indexPath = path.join(vaultDir, "index.json");
|
|
162
|
+
try {
|
|
163
|
+
const content = await fs.readFile(indexPath, "utf-8");
|
|
164
|
+
const parsed = JSON.parse(content);
|
|
165
|
+
return {
|
|
166
|
+
version: parsed.version || 1,
|
|
167
|
+
entries: parsed.entries || [],
|
|
168
|
+
tagsIndex: parsed.tagsIndex || {},
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return { version: 1, entries: [], tagsIndex: {} };
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// 更新索引
|
|
176
|
+
async function updateIndex(vaultDir, kp, mode = "add") {
|
|
177
|
+
const index = await loadIndex(vaultDir);
|
|
178
|
+
if (mode === "add") {
|
|
179
|
+
index.entries.push(kp);
|
|
180
|
+
for (const tag of kp.tags) {
|
|
181
|
+
if (!index.tagsIndex[tag])
|
|
182
|
+
index.tagsIndex[tag] = [];
|
|
183
|
+
if (!index.tagsIndex[tag].includes(kp.id))
|
|
184
|
+
index.tagsIndex[tag].push(kp.id);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
else if (mode === "remove") {
|
|
188
|
+
const removed = index.entries.find((e) => e.id === kp.id);
|
|
189
|
+
index.entries = index.entries.filter((e) => e.id !== kp.id);
|
|
190
|
+
if (removed) {
|
|
191
|
+
for (const tag of removed.tags) {
|
|
192
|
+
if (index.tagsIndex[tag]) {
|
|
193
|
+
index.tagsIndex[tag] = index.tagsIndex[tag].filter((id) => id !== kp.id);
|
|
194
|
+
if (index.tagsIndex[tag].length === 0)
|
|
195
|
+
delete index.tagsIndex[tag];
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
else if (mode === "update") {
|
|
201
|
+
const idx = index.entries.findIndex((e) => e.id === kp.id);
|
|
202
|
+
const oldKp = idx >= 0 ? index.entries[idx] : null;
|
|
203
|
+
if (idx >= 0) {
|
|
204
|
+
index.entries[idx] = kp;
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
index.entries.push(kp);
|
|
208
|
+
}
|
|
209
|
+
// 更新标签索引
|
|
210
|
+
if (oldKp) {
|
|
211
|
+
for (const tag of oldKp.tags) {
|
|
212
|
+
if (!kp.tags.includes(tag) && index.tagsIndex[tag]) {
|
|
213
|
+
index.tagsIndex[tag] = index.tagsIndex[tag].filter((id) => id !== kp.id);
|
|
214
|
+
if (index.tagsIndex[tag].length === 0)
|
|
215
|
+
delete index.tagsIndex[tag];
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
for (const tag of kp.tags) {
|
|
220
|
+
if (!index.tagsIndex[tag])
|
|
221
|
+
index.tagsIndex[tag] = [];
|
|
222
|
+
if (!index.tagsIndex[tag].includes(kp.id))
|
|
223
|
+
index.tagsIndex[tag].push(kp.id);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const indexPath = path.join(vaultDir, "index.json");
|
|
227
|
+
const tmpPath = path.join(vaultDir, "index.json.tmp");
|
|
228
|
+
await fs.writeFile(tmpPath, JSON.stringify(index, null, 2), "utf-8");
|
|
229
|
+
await fs.rename(tmpPath, indexPath);
|
|
230
|
+
}
|
|
231
|
+
// ==================== 公开 API ====================
|
|
232
|
+
// 保存知识点
|
|
233
|
+
export async function saveKnowledge(params) {
|
|
234
|
+
const vaultDir = getVaultDir();
|
|
235
|
+
const typeDir = getTypeDir(params.type);
|
|
236
|
+
const targetDir = path.join(vaultDir, typeDir);
|
|
237
|
+
await ensureDir(targetDir);
|
|
238
|
+
const kp = {
|
|
239
|
+
id: generateId(params.type),
|
|
240
|
+
type: params.type,
|
|
241
|
+
title: params.title,
|
|
242
|
+
content: params.content,
|
|
243
|
+
tags: params.tags || [],
|
|
244
|
+
links: params.links || [],
|
|
245
|
+
created: new Date().toISOString(),
|
|
246
|
+
updated: new Date().toISOString(),
|
|
247
|
+
source: "mcp",
|
|
248
|
+
};
|
|
249
|
+
const filepath = path.join(targetDir, `${kp.id}.md`);
|
|
250
|
+
await fs.writeFile(filepath, formatMarkdown(kp), "utf-8");
|
|
251
|
+
await updateIndex(vaultDir, kp, "add");
|
|
252
|
+
return kp;
|
|
253
|
+
}
|
|
254
|
+
// 搜索知识点
|
|
255
|
+
export async function searchKnowledge(params) {
|
|
256
|
+
const vaultDir = getVaultDir();
|
|
257
|
+
const index = await loadIndex(vaultDir);
|
|
258
|
+
const limit = Math.min(params.limit || 10, 50);
|
|
259
|
+
const results = [];
|
|
260
|
+
// 筛选候选 ID
|
|
261
|
+
let candidateIds = null;
|
|
262
|
+
if (params.tags && params.tags.length > 0) {
|
|
263
|
+
for (const tag of params.tags) {
|
|
264
|
+
const tagLower = tag.toLowerCase();
|
|
265
|
+
const matchingIds = Object.entries(index.tagsIndex)
|
|
266
|
+
.filter(([t]) => t.toLowerCase().includes(tagLower))
|
|
267
|
+
.flatMap(([, ids]) => ids);
|
|
268
|
+
const idSet = new Set(matchingIds);
|
|
269
|
+
if (candidateIds === null) {
|
|
270
|
+
candidateIds = idSet;
|
|
271
|
+
}
|
|
272
|
+
else {
|
|
273
|
+
const existingIds = [...candidateIds];
|
|
274
|
+
candidateIds = new Set(existingIds.filter((id) => idSet.has(id)));
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// 确定搜索范围
|
|
279
|
+
const types = params.type ? [params.type] : ["concept", "decision", "todo", "note", "project"];
|
|
280
|
+
for (const type of types) {
|
|
281
|
+
const typeDir = path.join(vaultDir, getTypeDir(type));
|
|
282
|
+
try {
|
|
283
|
+
const files = await fs.readdir(typeDir);
|
|
284
|
+
for (const file of files) {
|
|
285
|
+
if (!file.endsWith(".md"))
|
|
286
|
+
continue;
|
|
287
|
+
const id = file.replace(/\.md$/, "");
|
|
288
|
+
if (candidateIds !== null && !candidateIds.has(id))
|
|
289
|
+
continue;
|
|
290
|
+
const filepath = path.join(typeDir, file);
|
|
291
|
+
const content = await fs.readFile(filepath, "utf-8");
|
|
292
|
+
const kp = parseMarkdown(content, filepath);
|
|
293
|
+
if (!kp)
|
|
294
|
+
continue;
|
|
295
|
+
const queryLower = params.query.toLowerCase();
|
|
296
|
+
const matchesQuery = kp.title.toLowerCase().includes(queryLower) || kp.content.toLowerCase().includes(queryLower);
|
|
297
|
+
const matchesTags = !params.tags ||
|
|
298
|
+
params.tags.every((tag) => kp.tags.some((t) => t.toLowerCase().includes(tag.toLowerCase())));
|
|
299
|
+
if (matchesQuery && matchesTags) {
|
|
300
|
+
results.push(kp);
|
|
301
|
+
if (results.length >= limit)
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
catch {
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
if (results.length >= limit)
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
return results;
|
|
313
|
+
}
|
|
314
|
+
// 获取单个知识点
|
|
315
|
+
export async function getKnowledge(id) {
|
|
316
|
+
const vaultDir = getVaultDir();
|
|
317
|
+
const filepath = await findKnowledgeFile(vaultDir, id);
|
|
318
|
+
if (!filepath)
|
|
319
|
+
return null;
|
|
320
|
+
const content = await fs.readFile(filepath, "utf-8");
|
|
321
|
+
return parseMarkdown(content, filepath);
|
|
322
|
+
}
|
|
323
|
+
// 更新知识点
|
|
324
|
+
export async function updateKnowledge(id, params) {
|
|
325
|
+
const vaultDir = getVaultDir();
|
|
326
|
+
const filepath = await findKnowledgeFile(vaultDir, id);
|
|
327
|
+
if (!filepath)
|
|
328
|
+
return null;
|
|
329
|
+
const content = await fs.readFile(filepath, "utf-8");
|
|
330
|
+
const kp = parseMarkdown(content, filepath);
|
|
331
|
+
if (!kp)
|
|
332
|
+
return null;
|
|
333
|
+
if (params.title)
|
|
334
|
+
kp.title = params.title;
|
|
335
|
+
if (params.content)
|
|
336
|
+
kp.content = params.content;
|
|
337
|
+
if (params.tags) {
|
|
338
|
+
kp.tags = params.tags;
|
|
339
|
+
}
|
|
340
|
+
else if (params.appendTags) {
|
|
341
|
+
kp.tags = [...new Set([...kp.tags, ...params.appendTags])];
|
|
342
|
+
}
|
|
343
|
+
kp.updated = new Date().toISOString();
|
|
344
|
+
await fs.writeFile(filepath, formatMarkdown(kp), "utf-8");
|
|
345
|
+
await updateIndex(vaultDir, kp, "update");
|
|
346
|
+
return kp;
|
|
347
|
+
}
|
|
348
|
+
// 删除知识点
|
|
349
|
+
export async function deleteKnowledge(id) {
|
|
350
|
+
const vaultDir = getVaultDir();
|
|
351
|
+
const filepath = await findKnowledgeFile(vaultDir, id);
|
|
352
|
+
if (!filepath)
|
|
353
|
+
return false;
|
|
354
|
+
const content = await fs.readFile(filepath, "utf-8");
|
|
355
|
+
const kp = parseMarkdown(content, filepath);
|
|
356
|
+
if (!kp)
|
|
357
|
+
return false;
|
|
358
|
+
await fs.unlink(filepath);
|
|
359
|
+
await updateIndex(vaultDir, kp, "remove");
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
// 列出所有标签
|
|
363
|
+
export async function listTags() {
|
|
364
|
+
const vaultDir = getVaultDir();
|
|
365
|
+
const index = await loadIndex(vaultDir);
|
|
366
|
+
const tagsWithCount = {};
|
|
367
|
+
for (const [tag, ids] of Object.entries(index.tagsIndex)) {
|
|
368
|
+
tagsWithCount[tag] = ids.length;
|
|
369
|
+
}
|
|
370
|
+
return tagsWithCount;
|
|
371
|
+
}
|
|
372
|
+
// 回顾知识点
|
|
373
|
+
export async function reviewKnowledge(params) {
|
|
374
|
+
const vaultDir = getVaultDir();
|
|
375
|
+
const period = params.period || "week";
|
|
376
|
+
const now = new Date();
|
|
377
|
+
let startDate;
|
|
378
|
+
switch (period) {
|
|
379
|
+
case "today":
|
|
380
|
+
startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0, 0);
|
|
381
|
+
break;
|
|
382
|
+
case "week":
|
|
383
|
+
startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
|
384
|
+
break;
|
|
385
|
+
case "month":
|
|
386
|
+
startDate = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
|
|
387
|
+
break;
|
|
388
|
+
default:
|
|
389
|
+
startDate = new Date(0);
|
|
390
|
+
}
|
|
391
|
+
const types = params.type ? [params.type] : ["concept", "decision", "todo", "note", "project"];
|
|
392
|
+
const results = [];
|
|
393
|
+
for (const type of types) {
|
|
394
|
+
const typeDir = path.join(vaultDir, getTypeDir(type));
|
|
395
|
+
try {
|
|
396
|
+
const files = await fs.readdir(typeDir);
|
|
397
|
+
for (const file of files) {
|
|
398
|
+
if (!file.endsWith(".md"))
|
|
399
|
+
continue;
|
|
400
|
+
const filepath = path.join(typeDir, file);
|
|
401
|
+
const content = await fs.readFile(filepath, "utf-8");
|
|
402
|
+
const kp = parseMarkdown(content, filepath);
|
|
403
|
+
if (kp && new Date(kp.created) >= startDate) {
|
|
404
|
+
results.push(kp);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
catch {
|
|
409
|
+
continue;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
results.sort((a, b) => new Date(b.created).getTime() - new Date(a.created).getTime());
|
|
413
|
+
const stats = { concept: 0, decision: 0, todo: 0, note: 0, project: 0 };
|
|
414
|
+
for (const kp of results)
|
|
415
|
+
stats[kp.type]++;
|
|
416
|
+
return { stats, recent: results.slice(0, 10) };
|
|
417
|
+
}
|
|
418
|
+
//# sourceMappingURL=core.js.map
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Knowledge Keeper MCP Server
|
|
4
|
+
* MCP 实现 - 让 knowledge-keeper 可被 Claude Code、Cursor、Gemini CLI 等调用
|
|
5
|
+
*/
|
|
6
|
+
import { McpServer, StdioServerTransport } from "@modelcontextprotocol/server";
|
|
7
|
+
import { registerTools } from "./tools/index.js";
|
|
8
|
+
import { registerResources } from "./resources/index.js";
|
|
9
|
+
// MCP Server 配置
|
|
10
|
+
const server = new McpServer({
|
|
11
|
+
name: "knowledge-keeper",
|
|
12
|
+
version: "0.1.0-alpha.1",
|
|
13
|
+
});
|
|
14
|
+
// 注册 Tools
|
|
15
|
+
registerTools(server);
|
|
16
|
+
// 注册 Resources
|
|
17
|
+
registerResources(server);
|
|
18
|
+
// STDIO 传输(本地优先)
|
|
19
|
+
const transport = new StdioServerTransport();
|
|
20
|
+
await server.connect(transport);
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* knowledge_delete MCP Tool
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { deleteKnowledge } from "../core.js";
|
|
6
|
+
export function registerDeleteTool(server) {
|
|
7
|
+
server.registerTool("knowledge_delete", {
|
|
8
|
+
title: "删除知识点",
|
|
9
|
+
description: "删除指定知识点",
|
|
10
|
+
inputSchema: z.object({
|
|
11
|
+
id: z.string().describe("要删除的知识点 ID"),
|
|
12
|
+
}),
|
|
13
|
+
}, async (params) => {
|
|
14
|
+
try {
|
|
15
|
+
const deleted = await deleteKnowledge(params.id);
|
|
16
|
+
if (!deleted) {
|
|
17
|
+
return {
|
|
18
|
+
content: [
|
|
19
|
+
{
|
|
20
|
+
type: "text",
|
|
21
|
+
text: `❌ 未找到知识点: ${params.id}`,
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
content: [
|
|
28
|
+
{
|
|
29
|
+
type: "text",
|
|
30
|
+
text: `🗑️ 已删除知识点\n\nID: ${params.id}`,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
return {
|
|
37
|
+
content: [
|
|
38
|
+
{
|
|
39
|
+
type: "text",
|
|
40
|
+
text: `❌ 删除失败: ${error instanceof Error ? error.message : String(error)}`,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=delete.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* knowledge_get MCP Tool
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { getKnowledge } from "../core.js";
|
|
6
|
+
export function registerGetTool(server) {
|
|
7
|
+
server.registerTool("knowledge_get", {
|
|
8
|
+
title: "获取知识点",
|
|
9
|
+
description: "根据 ID 获取单个知识点",
|
|
10
|
+
inputSchema: z.object({
|
|
11
|
+
id: z.string().describe("知识点 ID"),
|
|
12
|
+
}),
|
|
13
|
+
}, async (params) => {
|
|
14
|
+
try {
|
|
15
|
+
const kp = await getKnowledge(params.id);
|
|
16
|
+
if (!kp) {
|
|
17
|
+
return {
|
|
18
|
+
content: [
|
|
19
|
+
{
|
|
20
|
+
type: "text",
|
|
21
|
+
text: `❌ 未找到知识点: ${params.id}`,
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
content: [
|
|
28
|
+
{
|
|
29
|
+
type: "text",
|
|
30
|
+
text: `📝 **${kp.title}**\n\n类型: ${kp.type}\nID: ${kp.id}\n创建: ${new Date(kp.created).toLocaleString("zh-CN")}\n标签: ${kp.tags.join(", ") || "无"}\n\n---\n\n${kp.content}`,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
return {
|
|
37
|
+
content: [
|
|
38
|
+
{
|
|
39
|
+
type: "text",
|
|
40
|
+
text: `❌ 获取失败: ${error instanceof Error ? error.message : String(error)}`,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=get.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tools 注册
|
|
3
|
+
*/
|
|
4
|
+
import { registerSaveTool } from "./save.js";
|
|
5
|
+
import { registerSearchTool } from "./search.js";
|
|
6
|
+
import { registerGetTool } from "./get.js";
|
|
7
|
+
import { registerUpdateTool } from "./update.js";
|
|
8
|
+
import { registerDeleteTool } from "./delete.js";
|
|
9
|
+
import { registerTagsTool } from "./tags.js";
|
|
10
|
+
export function registerTools(server) {
|
|
11
|
+
registerSaveTool(server);
|
|
12
|
+
registerSearchTool(server);
|
|
13
|
+
registerGetTool(server);
|
|
14
|
+
registerUpdateTool(server);
|
|
15
|
+
registerDeleteTool(server);
|
|
16
|
+
registerTagsTool(server);
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* knowledge_save MCP Tool
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { saveKnowledge } from "../core.js";
|
|
6
|
+
export function registerSaveTool(server) {
|
|
7
|
+
server.registerTool("knowledge_save", {
|
|
8
|
+
title: "保存知识点",
|
|
9
|
+
description: "保存一条新的知识到知识库",
|
|
10
|
+
inputSchema: z.object({
|
|
11
|
+
type: z.enum(["concept", "decision", "todo", "note", "project"]).describe("知识类型"),
|
|
12
|
+
title: z.string().describe("标题"),
|
|
13
|
+
content: z.string().describe("内容"),
|
|
14
|
+
tags: z.array(z.string()).optional().describe("标签列表"),
|
|
15
|
+
}),
|
|
16
|
+
}, async (params) => {
|
|
17
|
+
try {
|
|
18
|
+
const kp = await saveKnowledge({
|
|
19
|
+
type: params.type,
|
|
20
|
+
title: params.title,
|
|
21
|
+
content: params.content,
|
|
22
|
+
tags: params.tags,
|
|
23
|
+
});
|
|
24
|
+
return {
|
|
25
|
+
content: [
|
|
26
|
+
{
|
|
27
|
+
type: "text",
|
|
28
|
+
text: `✅ 知识已保存\n\n📝 **${kp.title}**\n类型: ${kp.type}\nID: ${kp.id}\n标签: ${kp.tags.join(", ") || "无"}`,
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return {
|
|
35
|
+
content: [
|
|
36
|
+
{
|
|
37
|
+
type: "text",
|
|
38
|
+
text: `❌ 保存失败: ${error instanceof Error ? error.message : String(error)}`,
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=save.js.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* knowledge_search MCP Tool
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { searchKnowledge } from "../core.js";
|
|
6
|
+
export function registerSearchTool(server) {
|
|
7
|
+
server.registerTool("knowledge_search", {
|
|
8
|
+
title: "搜索知识",
|
|
9
|
+
description: "在知识库中搜索知识点",
|
|
10
|
+
inputSchema: z.object({
|
|
11
|
+
query: z.string().describe("搜索关键词"),
|
|
12
|
+
type: z.enum(["concept", "decision", "todo", "note", "project"]).optional().describe("筛选类型"),
|
|
13
|
+
tags: z.array(z.string()).optional().describe("筛选标签"),
|
|
14
|
+
limit: z.number().optional().describe("返回数量限制"),
|
|
15
|
+
}),
|
|
16
|
+
}, async (params) => {
|
|
17
|
+
try {
|
|
18
|
+
const results = await searchKnowledge({
|
|
19
|
+
query: params.query,
|
|
20
|
+
type: params.type,
|
|
21
|
+
tags: params.tags,
|
|
22
|
+
limit: params.limit,
|
|
23
|
+
});
|
|
24
|
+
if (results.length === 0) {
|
|
25
|
+
return {
|
|
26
|
+
content: [
|
|
27
|
+
{
|
|
28
|
+
type: "text",
|
|
29
|
+
text: `🔍 搜索: "${params.query}"\n\n未找到匹配的知识点`,
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
const resultText = results
|
|
35
|
+
.map((kp, i) => `${i + 1}. **${kp.title}** (${kp.type})\n ID: ${kp.id}\n ${kp.content.slice(0, 100)}${kp.content.length > 100 ? "..." : ""}\n 标签: ${kp.tags.join(", ") || "无"}`)
|
|
36
|
+
.join("\n\n");
|
|
37
|
+
return {
|
|
38
|
+
content: [
|
|
39
|
+
{
|
|
40
|
+
type: "text",
|
|
41
|
+
text: `🔍 找到 ${results.length} 条知识点\n\n${resultText}`,
|
|
42
|
+
},
|
|
43
|
+
],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
return {
|
|
48
|
+
content: [
|
|
49
|
+
{
|
|
50
|
+
type: "text",
|
|
51
|
+
text: `❌ 搜索失败: ${error instanceof Error ? error.message : String(error)}`,
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* knowledge_tags MCP Tool
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { listTags } from "../core.js";
|
|
6
|
+
export function registerTagsTool(server) {
|
|
7
|
+
server.registerTool("knowledge_tags", {
|
|
8
|
+
title: "列出标签",
|
|
9
|
+
description: "列出知识库中的所有标签",
|
|
10
|
+
inputSchema: z.object({}),
|
|
11
|
+
}, async () => {
|
|
12
|
+
try {
|
|
13
|
+
const tags = await listTags();
|
|
14
|
+
const entries = Object.entries(tags).sort((a, b) => b[1] - a[1]);
|
|
15
|
+
if (entries.length === 0) {
|
|
16
|
+
return {
|
|
17
|
+
content: [
|
|
18
|
+
{
|
|
19
|
+
type: "text",
|
|
20
|
+
text: "🏷️ 知识库标签\n\n暂无标签",
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const tagsText = entries.map(([tag, count]) => `- **${tag}** (${count} 条)`).join("\n");
|
|
26
|
+
return {
|
|
27
|
+
content: [
|
|
28
|
+
{
|
|
29
|
+
type: "text",
|
|
30
|
+
text: `🏷️ 知识库标签 (${entries.length} 个)\n\n${tagsText}`,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
return {
|
|
37
|
+
content: [
|
|
38
|
+
{
|
|
39
|
+
type: "text",
|
|
40
|
+
text: `❌ 获取标签失败: ${error instanceof Error ? error.message : String(error)}`,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=tags.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* knowledge_update MCP Tool
|
|
3
|
+
*/
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { updateKnowledge } from "../core.js";
|
|
6
|
+
export function registerUpdateTool(server) {
|
|
7
|
+
server.registerTool("knowledge_update", {
|
|
8
|
+
title: "更新知识点",
|
|
9
|
+
description: "更新现有知识点的内容或标签",
|
|
10
|
+
inputSchema: z.object({
|
|
11
|
+
id: z.string().describe("知识点 ID"),
|
|
12
|
+
title: z.string().optional().describe("新标题"),
|
|
13
|
+
content: z.string().optional().describe("新内容"),
|
|
14
|
+
tags: z.array(z.string()).optional().describe("新标签列表"),
|
|
15
|
+
appendTags: z.array(z.string()).optional().describe("追加标签"),
|
|
16
|
+
}),
|
|
17
|
+
}, async (params) => {
|
|
18
|
+
try {
|
|
19
|
+
const kp = await updateKnowledge(params.id, {
|
|
20
|
+
title: params.title,
|
|
21
|
+
content: params.content,
|
|
22
|
+
tags: params.tags,
|
|
23
|
+
appendTags: params.appendTags,
|
|
24
|
+
});
|
|
25
|
+
if (!kp) {
|
|
26
|
+
return {
|
|
27
|
+
content: [
|
|
28
|
+
{
|
|
29
|
+
type: "text",
|
|
30
|
+
text: `❌ 未找到知识点: ${params.id}`,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
content: [
|
|
37
|
+
{
|
|
38
|
+
type: "text",
|
|
39
|
+
text: `✅ 知识已更新\n\n📝 **${kp.title}**\nID: ${kp.id}\n标签: ${kp.tags.join(", ") || "无"}`,
|
|
40
|
+
},
|
|
41
|
+
],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
return {
|
|
46
|
+
content: [
|
|
47
|
+
{
|
|
48
|
+
type: "text",
|
|
49
|
+
text: `❌ 更新失败: ${error instanceof Error ? error.message : String(error)}`,
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=update.js.map
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zsc-glitch/knowledge-keeper-mcp",
|
|
3
|
+
"version": "0.1.0-alpha.1",
|
|
4
|
+
"description": "MCP Server for Knowledge Keeper - 智能知识管理的 Model Context Protocol 实现",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"knowledge-keeper-mcp": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc && sed -i '1i#!/usr/bin/env node' dist/index.js",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"start": "node dist/index.js",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"mcp",
|
|
18
|
+
"model-context-protocol",
|
|
19
|
+
"memory",
|
|
20
|
+
"knowledge",
|
|
21
|
+
"ai",
|
|
22
|
+
"claude-code",
|
|
23
|
+
"openclaw"
|
|
24
|
+
],
|
|
25
|
+
"author": "小影 <zsc-glitch>",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/zsc-glitch/knowledge-keeper-mcp"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@cfworker/json-schema": "^4.1.1",
|
|
33
|
+
"@modelcontextprotocol/server": "2.0.0-alpha.2",
|
|
34
|
+
"zod": "^4.0.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^22.19.17",
|
|
38
|
+
"typescript": "^5.8.0"
|
|
39
|
+
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=18.0.0"
|
|
42
|
+
}
|
|
43
|
+
}
|