@memorycode/mcp-server 1.0.0 → 1.1.0
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/README.md +25 -2
- package/dist/fileWatcher.d.ts +3 -3
- package/dist/fileWatcher.d.ts.map +1 -1
- package/dist/fileWatcher.js +21 -9
- package/dist/fileWatcher.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/runtimeStore.d.ts +7 -0
- package/dist/runtimeStore.d.ts.map +1 -0
- package/dist/runtimeStore.js +54 -0
- package/dist/runtimeStore.js.map +1 -0
- package/dist/server.d.ts +31 -8
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +542 -109
- package/dist/server.js.map +1 -1
- package/dist/types.d.ts +65 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -2
- package/dist/utils/validator.d.ts +4 -2
- package/dist/utils/validator.d.ts.map +1 -1
- package/dist/utils/validator.js +114 -5
- package/dist/utils/validator.js.map +1 -1
- package/package.json +15 -4
package/dist/server.js
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* YIS-15 Task 2.2 / YIS-20 / YIS-21: MCP Server 实现
|
|
3
|
-
*
|
|
2
|
+
* YIS-15 Task 2.2 / YIS-20 / YIS-21 / MCP 1.1: MCP Server 实现
|
|
3
|
+
* 档案 = 个人记忆;认知芯片 = 可在 AI 中直接切换的思考/输出模式。
|
|
4
4
|
*/
|
|
5
5
|
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
6
6
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
7
7
|
import { ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListToolsRequestSchema, CallToolRequestSchema, ReadResourceRequestSchema, McpError, ErrorCode, } from '@modelcontextprotocol/sdk/types.js';
|
|
8
|
+
import { writeRuntime } from './runtimeStore.js';
|
|
8
9
|
import { logInfo, logWarn } from './utils/logger.js';
|
|
9
|
-
const
|
|
10
|
+
const PROFILE_URI_PREFIX = 'memorycode://profile/';
|
|
11
|
+
const CHIP_URI_PREFIX = 'memorycode://chip/';
|
|
10
12
|
const MIME_TYPE = 'text/markdown';
|
|
11
13
|
const TOOLS = [
|
|
12
14
|
{
|
|
13
15
|
name: 'load_config',
|
|
14
|
-
description: '加载指定的 MemoryCode
|
|
16
|
+
description: '加载指定的 MemoryCode 记忆档案(个人记忆/人设),使后续回复使用该身份。用户说「请使用职场配置」「切换到学生模式」等时调用。切换档案不会自动切换认知芯片,除非该档案在 MCP runtime 中已有记录的芯片。',
|
|
15
17
|
inputSchema: {
|
|
16
18
|
type: 'object',
|
|
17
19
|
properties: {
|
|
18
20
|
config_name: {
|
|
19
21
|
type: 'string',
|
|
20
|
-
description: '
|
|
22
|
+
description: '要加载的档案名称,如:空白、职场、创作者、自由职业、学生(以当前可用列表为准)',
|
|
21
23
|
},
|
|
22
24
|
},
|
|
23
25
|
required: ['config_name'],
|
|
@@ -25,12 +27,64 @@ const TOOLS = [
|
|
|
25
27
|
},
|
|
26
28
|
{
|
|
27
29
|
name: 'list_configs',
|
|
28
|
-
description: '列出当前可用的所有 MemoryCode
|
|
30
|
+
description: '列出当前可用的所有 MemoryCode 记忆档案名称,供用户选择或确认。',
|
|
29
31
|
inputSchema: {
|
|
30
32
|
type: 'object',
|
|
31
33
|
properties: {},
|
|
32
34
|
},
|
|
33
35
|
},
|
|
36
|
+
{
|
|
37
|
+
name: 'list_chips',
|
|
38
|
+
description: '列出当前可用的认知芯片名称。认知芯片只改变 AI 的思考与输出方式,不改变用户身份记忆。',
|
|
39
|
+
inputSchema: {
|
|
40
|
+
type: 'object',
|
|
41
|
+
properties: {},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'load_chip',
|
|
46
|
+
description: '为当前或指定档案加载认知芯片,使后续回复使用该思考/输出模式。用户说「切换到严谨分析芯片」「用结构表达」等时调用。需要 MCP 1.1 导出文件(含 chips/compositions)。',
|
|
47
|
+
inputSchema: {
|
|
48
|
+
type: 'object',
|
|
49
|
+
properties: {
|
|
50
|
+
chip_name: {
|
|
51
|
+
type: 'string',
|
|
52
|
+
description: '认知芯片名称,如:结构表达、严谨分析、战略决策(以 list_chips 为准)',
|
|
53
|
+
},
|
|
54
|
+
profile_name: {
|
|
55
|
+
type: 'string',
|
|
56
|
+
description: '可选。指定档案名称;省略则作用于当前已加载档案。',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
required: ['chip_name'],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: 'unload_chip',
|
|
64
|
+
description: '卸载当前或指定档案的认知芯片,回到该档案原始认知调优(无芯片覆盖)。',
|
|
65
|
+
inputSchema: {
|
|
66
|
+
type: 'object',
|
|
67
|
+
properties: {
|
|
68
|
+
profile_name: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
description: '可选。指定档案名称;省略则作用于当前已加载档案。',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: 'get_cognitive_chip',
|
|
77
|
+
description: '查看认知芯片元信息(名称、描述、来源)。省略 chip_name 时返回当前档案已加载的芯片。',
|
|
78
|
+
inputSchema: {
|
|
79
|
+
type: 'object',
|
|
80
|
+
properties: {
|
|
81
|
+
chip_name: {
|
|
82
|
+
type: 'string',
|
|
83
|
+
description: '可选。芯片名称;省略则返回当前档案 runtime 中的芯片。',
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
34
88
|
{
|
|
35
89
|
name: 'get_user_profile',
|
|
36
90
|
description: 'Get current user identity profile from MemoryCode (nickname, roles, about). Use this when personalization needs user background.',
|
|
@@ -53,15 +107,28 @@ const TOOLS = [
|
|
|
53
107
|
},
|
|
54
108
|
},
|
|
55
109
|
];
|
|
110
|
+
const EMPTY_PARSED = {
|
|
111
|
+
schemaVersion: '1.0',
|
|
112
|
+
resources: [],
|
|
113
|
+
profiles: [],
|
|
114
|
+
chips: [],
|
|
115
|
+
compositions: [],
|
|
116
|
+
runtime: {
|
|
117
|
+
activeProfileId: null,
|
|
118
|
+
activeChipByProfile: {},
|
|
119
|
+
updatedAt: new Date().toISOString(),
|
|
120
|
+
},
|
|
121
|
+
};
|
|
56
122
|
export class MemoryCodeMCPServer {
|
|
57
|
-
|
|
123
|
+
parsed = { ...EMPTY_PARSED, runtime: { ...EMPTY_PARSED.runtime } };
|
|
58
124
|
memorySnapshot = null;
|
|
125
|
+
watchFilePath = null;
|
|
59
126
|
server;
|
|
60
127
|
transport = null;
|
|
61
128
|
constructor() {
|
|
62
129
|
this.server = new Server({
|
|
63
130
|
name: 'memorycode',
|
|
64
|
-
version: '1.
|
|
131
|
+
version: '1.1.0',
|
|
65
132
|
}, {
|
|
66
133
|
capabilities: {
|
|
67
134
|
resources: {
|
|
@@ -70,130 +137,509 @@ export class MemoryCodeMCPServer {
|
|
|
70
137
|
},
|
|
71
138
|
tools: { listChanged: true },
|
|
72
139
|
},
|
|
73
|
-
/** Cursor 等客户端通过 GetInstructions/initialize 获取此说明,避免 "No server info found" 类问题 */
|
|
74
140
|
instructions: [
|
|
75
|
-
'MemoryCode MCP:
|
|
76
|
-
'Resources:
|
|
77
|
-
'Tools: list_configs
|
|
78
|
-
'Tools: get_user_profile
|
|
141
|
+
'MemoryCode MCP: 档案 = 个人记忆(身份/背景);认知芯片 = 可热插拔的思考与输出模式。',
|
|
142
|
+
'Resources: memorycode://profile/<id> 获取档案当前组合 prompt;memorycode://chip/<id> 查看芯片说明。',
|
|
143
|
+
'Tools: list_configs / load_config 管理档案;list_chips / load_chip / unload_chip 管理认知芯片。',
|
|
144
|
+
'Tools: get_cognitive_chip 查看芯片元信息;get_user_profile / get_expertise 查询身份与技能。',
|
|
145
|
+
'芯片切换需要 MCP 1.1 导出文件;若 load_chip 失败请让用户重新从 MemoryCode 导出 memorycode-mcp.json。',
|
|
79
146
|
].join(' '),
|
|
80
147
|
});
|
|
81
148
|
this.server.setRequestHandler(ListToolsRequestSchema, () => ({
|
|
82
149
|
tools: TOOLS.map((t) => ({ name: t.name, description: t.description, inputSchema: t.inputSchema })),
|
|
83
150
|
}));
|
|
84
|
-
this.server.setRequestHandler(CallToolRequestSchema, (request) => {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
151
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
152
|
+
return this.handleToolCall(request.params.name, request.params.arguments);
|
|
153
|
+
});
|
|
154
|
+
this.server.setRequestHandler(ListResourcesRequestSchema, () => ({
|
|
155
|
+
resources: this.listAllResources(),
|
|
156
|
+
}));
|
|
157
|
+
this.server.setRequestHandler(ListResourceTemplatesRequestSchema, () => ({
|
|
158
|
+
resourceTemplates: [],
|
|
159
|
+
}));
|
|
160
|
+
this.server.setRequestHandler(ReadResourceRequestSchema, (request) => {
|
|
161
|
+
return this.handleReadResource(request.params.uri);
|
|
162
|
+
});
|
|
163
|
+
logInfo('[MemoryCode] MCP Server initialized (v1.1): resources + tools including chip switching');
|
|
164
|
+
}
|
|
165
|
+
setWatchFilePath(filePath) {
|
|
166
|
+
this.watchFilePath = filePath;
|
|
167
|
+
}
|
|
168
|
+
updateMemorySnapshot(parsed, payload) {
|
|
169
|
+
this.parsed = parsed;
|
|
170
|
+
this.memorySnapshot = payload;
|
|
171
|
+
this.server.sendResourceListChanged().catch((err) => {
|
|
172
|
+
logWarn(`Failed to send resources/list_changed: ${err}`);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
async handleToolCall(name, args) {
|
|
176
|
+
switch (name) {
|
|
177
|
+
case 'load_config':
|
|
178
|
+
return await this.toolLoadConfig(args);
|
|
179
|
+
case 'list_configs':
|
|
180
|
+
return this.toolListConfigs();
|
|
181
|
+
case 'list_chips':
|
|
182
|
+
return this.toolListChips();
|
|
183
|
+
case 'load_chip':
|
|
184
|
+
return await this.toolLoadChip(args);
|
|
185
|
+
case 'unload_chip':
|
|
186
|
+
return await this.toolUnloadChip(args);
|
|
187
|
+
case 'get_cognitive_chip':
|
|
188
|
+
return this.toolGetCognitiveChip(args);
|
|
189
|
+
case 'get_user_profile':
|
|
190
|
+
return {
|
|
191
|
+
content: [{ type: 'text', text: JSON.stringify(this.getUserProfile(), null, 2) }],
|
|
192
|
+
};
|
|
193
|
+
case 'get_expertise': {
|
|
194
|
+
const domain = typeof args?.domain === 'string' ? args.domain : undefined;
|
|
195
|
+
return {
|
|
196
|
+
content: [{ type: 'text', text: JSON.stringify(this.getExpertise(domain), null, 2) }],
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
default:
|
|
200
|
+
throw new McpError(ErrorCode.InvalidParams, `Unknown tool: ${name}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
async toolLoadConfig(args) {
|
|
204
|
+
const configName = typeof args?.config_name === 'string' ? args.config_name.trim() : '';
|
|
205
|
+
if (!configName) {
|
|
206
|
+
return {
|
|
207
|
+
content: [{ type: 'text', text: '请提供 config_name 参数,例如:职场、创作者、学生。' }],
|
|
208
|
+
isError: true,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
const profile = this.findProfileByName(configName);
|
|
212
|
+
if (!profile) {
|
|
213
|
+
const names = this.getProfileNames().join('、') || '(暂无)';
|
|
214
|
+
return {
|
|
215
|
+
content: [{ type: 'text', text: `未找到档案「${configName}」。当前可用:${names}` }],
|
|
216
|
+
isError: true,
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const chipState = this.resolveActiveChipForProfile(profile);
|
|
220
|
+
const prompt = this.resolvePrompt(profile.id, chipState.chipId);
|
|
221
|
+
if (!prompt) {
|
|
222
|
+
const resource = this.findResourceByConfigName(configName);
|
|
223
|
+
if (!resource || this.supportsChipSwitching()) {
|
|
107
224
|
return {
|
|
108
225
|
content: [
|
|
109
226
|
{
|
|
110
227
|
type: 'text',
|
|
111
|
-
text:
|
|
228
|
+
text: `无法生成档案「${configName}」的 prompt。请重新从 MemoryCode 导出 MCP 文件。`,
|
|
112
229
|
},
|
|
113
230
|
],
|
|
231
|
+
isError: true,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
this.parsed.runtime.activeProfileId = profile.id;
|
|
235
|
+
return {
|
|
236
|
+
content: [
|
|
237
|
+
{
|
|
238
|
+
type: 'text',
|
|
239
|
+
text: `已加载档案:${profile.name}\n\n${resource.prompt}`,
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
this.parsed.runtime.activeProfileId = profile.id;
|
|
245
|
+
this.parsed.runtime.activeChipByProfile[profile.id] = chipState.chipId;
|
|
246
|
+
await this.queueRuntimePersist();
|
|
247
|
+
const chipLabel = chipState.chipId ? this.findChipById(chipState.chipId)?.name ?? chipState.chipId : '无芯片';
|
|
248
|
+
const warning = chipState.warning ? `\n注意:${chipState.warning}` : '';
|
|
249
|
+
return {
|
|
250
|
+
content: [
|
|
251
|
+
{
|
|
252
|
+
type: 'text',
|
|
253
|
+
text: `已加载档案:${profile.name}\n认知芯片:${chipLabel}${warning}\n\n${prompt}`,
|
|
254
|
+
},
|
|
255
|
+
],
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
toolListConfigs() {
|
|
259
|
+
const names = this.getProfileNames();
|
|
260
|
+
const text = names.length > 0
|
|
261
|
+
? `当前可用档案:\n${names.map((c) => `- ${c}`).join('\n')}`
|
|
262
|
+
: '当前暂无可用档案,请先在 MemoryCode 中导出 memorycode-mcp.json 并放在 MCP 监听的路径。';
|
|
263
|
+
return { content: [{ type: 'text', text }] };
|
|
264
|
+
}
|
|
265
|
+
toolListChips() {
|
|
266
|
+
if (!this.supportsChipSwitching()) {
|
|
267
|
+
return {
|
|
268
|
+
content: [
|
|
269
|
+
{
|
|
270
|
+
type: 'text',
|
|
271
|
+
text: '当前 MCP 文件不支持认知芯片切换(需要 schema 1.1)。请从 MemoryCode 重新导出 memorycode-mcp.json。',
|
|
272
|
+
},
|
|
273
|
+
],
|
|
274
|
+
isError: true,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
const names = this.parsed.chips.map((c) => c.name);
|
|
278
|
+
const text = names.length > 0
|
|
279
|
+
? `当前可用认知芯片:\n${names.map((c) => `- ${c}`).join('\n')}`
|
|
280
|
+
: '当前导出文件中没有可用认知芯片。';
|
|
281
|
+
return { content: [{ type: 'text', text }] };
|
|
282
|
+
}
|
|
283
|
+
async toolLoadChip(args) {
|
|
284
|
+
if (!this.supportsChipSwitching()) {
|
|
285
|
+
return this.chipUnsupportedError();
|
|
286
|
+
}
|
|
287
|
+
const chipName = typeof args?.chip_name === 'string' ? args.chip_name.trim() : '';
|
|
288
|
+
if (!chipName) {
|
|
289
|
+
return {
|
|
290
|
+
content: [{ type: 'text', text: '请提供 chip_name 参数,例如:结构表达、严谨分析。' }],
|
|
291
|
+
isError: true,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
const chip = this.findChipByName(chipName);
|
|
295
|
+
if (!chip) {
|
|
296
|
+
const names = this.parsed.chips.map((c) => c.name).join('、') || '(暂无)';
|
|
297
|
+
return {
|
|
298
|
+
content: [{ type: 'text', text: `未找到认知芯片「${chipName}」。当前可用:${names}` }],
|
|
299
|
+
isError: true,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
const profileName = typeof args?.profile_name === 'string' ? args.profile_name.trim() : '';
|
|
303
|
+
const profile = profileName
|
|
304
|
+
? this.findProfileByName(profileName)
|
|
305
|
+
: this.getRuntimeActiveProfile();
|
|
306
|
+
if (!profile) {
|
|
307
|
+
return {
|
|
308
|
+
content: [
|
|
309
|
+
{
|
|
310
|
+
type: 'text',
|
|
311
|
+
text: profileName
|
|
312
|
+
? `未找到档案「${profileName}」。请先 load_config 或提供有效 profile_name。`
|
|
313
|
+
: '请先 load_config 加载一个档案,再切换认知芯片。',
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
isError: true,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
const prompt = this.resolvePrompt(profile.id, chip.id);
|
|
320
|
+
if (!prompt) {
|
|
321
|
+
return {
|
|
322
|
+
content: [
|
|
323
|
+
{
|
|
324
|
+
type: 'text',
|
|
325
|
+
text: `无法找到档案「${profile.name}」与芯片「${chip.name}」的组合 prompt。请重新从 MemoryCode 导出 MCP 文件。`,
|
|
326
|
+
},
|
|
327
|
+
],
|
|
328
|
+
isError: true,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
this.parsed.runtime.activeProfileId = profile.id;
|
|
332
|
+
this.parsed.runtime.activeChipByProfile[profile.id] = chip.id;
|
|
333
|
+
await this.queueRuntimePersist();
|
|
334
|
+
return {
|
|
335
|
+
content: [
|
|
336
|
+
{
|
|
337
|
+
type: 'text',
|
|
338
|
+
text: `已加载认知芯片:${chip.name}\n档案:${profile.name}\n\n${prompt}`,
|
|
339
|
+
},
|
|
340
|
+
],
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
async toolUnloadChip(args) {
|
|
344
|
+
if (!this.supportsChipSwitching()) {
|
|
345
|
+
return this.chipUnsupportedError();
|
|
346
|
+
}
|
|
347
|
+
const profileName = typeof args?.profile_name === 'string' ? args.profile_name.trim() : '';
|
|
348
|
+
const profile = profileName
|
|
349
|
+
? this.findProfileByName(profileName)
|
|
350
|
+
: this.getRuntimeActiveProfile();
|
|
351
|
+
if (!profile) {
|
|
352
|
+
return {
|
|
353
|
+
content: [
|
|
354
|
+
{
|
|
355
|
+
type: 'text',
|
|
356
|
+
text: '请先 load_config 加载一个档案,再卸载认知芯片。',
|
|
357
|
+
},
|
|
358
|
+
],
|
|
359
|
+
isError: true,
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
const prompt = this.resolvePrompt(profile.id, null);
|
|
363
|
+
if (!prompt) {
|
|
364
|
+
return {
|
|
365
|
+
content: [{ type: 'text', text: `无法生成档案「${profile.name}」的无芯片 prompt。` }],
|
|
366
|
+
isError: true,
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
this.parsed.runtime.activeProfileId = profile.id;
|
|
370
|
+
this.parsed.runtime.activeChipByProfile[profile.id] = null;
|
|
371
|
+
await this.queueRuntimePersist();
|
|
372
|
+
return {
|
|
373
|
+
content: [
|
|
374
|
+
{
|
|
375
|
+
type: 'text',
|
|
376
|
+
text: `已卸载认知芯片。\n档案:${profile.name}\n\n${prompt}`,
|
|
377
|
+
},
|
|
378
|
+
],
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
toolGetCognitiveChip(args) {
|
|
382
|
+
if (!this.supportsChipSwitching()) {
|
|
383
|
+
return this.chipUnsupportedError();
|
|
384
|
+
}
|
|
385
|
+
const chipName = typeof args?.chip_name === 'string' ? args.chip_name.trim() : '';
|
|
386
|
+
let chip;
|
|
387
|
+
if (chipName) {
|
|
388
|
+
chip = this.findChipByName(chipName);
|
|
389
|
+
if (!chip) {
|
|
390
|
+
return {
|
|
391
|
+
content: [{ type: 'text', text: `未找到认知芯片「${chipName}」。` }],
|
|
392
|
+
isError: true,
|
|
114
393
|
};
|
|
115
394
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
: '当前暂无可用配置,请先在 MemoryCode 中导出 memorycode-mcp.json 并放在 MCP 监听的路径。';
|
|
395
|
+
}
|
|
396
|
+
else {
|
|
397
|
+
const profile = this.getRuntimeActiveProfile();
|
|
398
|
+
if (!profile) {
|
|
121
399
|
return {
|
|
122
|
-
content: [{ type: 'text', text }],
|
|
400
|
+
content: [{ type: 'text', text: '当前没有已加载的档案,无法确定认知芯片。' }],
|
|
401
|
+
isError: true,
|
|
123
402
|
};
|
|
124
403
|
}
|
|
125
|
-
|
|
126
|
-
|
|
404
|
+
const chipId = this.parsed.runtime.activeChipByProfile[profile.id] ?? null;
|
|
405
|
+
if (!chipId) {
|
|
127
406
|
return {
|
|
128
|
-
content: [
|
|
407
|
+
content: [
|
|
408
|
+
{
|
|
409
|
+
type: 'text',
|
|
410
|
+
text: JSON.stringify({ profile: profile.name, activeChip: null, message: '当前档案未加载认知芯片' }, null, 2),
|
|
411
|
+
},
|
|
412
|
+
],
|
|
129
413
|
};
|
|
130
414
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const result = this.getExpertise(domain);
|
|
415
|
+
chip = this.findChipById(chipId);
|
|
416
|
+
if (!chip) {
|
|
134
417
|
return {
|
|
135
|
-
content: [
|
|
418
|
+
content: [
|
|
419
|
+
{
|
|
420
|
+
type: 'text',
|
|
421
|
+
text: `当前档案记录的认知芯片「${chipId}」已不存在。请重新从 MemoryCode 导出 MCP 文件,或调用 unload_chip 清除该状态。`,
|
|
422
|
+
},
|
|
423
|
+
],
|
|
424
|
+
isError: true,
|
|
136
425
|
};
|
|
137
426
|
}
|
|
138
|
-
|
|
427
|
+
}
|
|
428
|
+
return {
|
|
429
|
+
content: [
|
|
430
|
+
{
|
|
431
|
+
type: 'text',
|
|
432
|
+
text: JSON.stringify({
|
|
433
|
+
id: chip?.id,
|
|
434
|
+
name: chip?.name,
|
|
435
|
+
description: chip?.description,
|
|
436
|
+
source: chip?.source,
|
|
437
|
+
version: chip?.version,
|
|
438
|
+
lastEditedAt: chip?.lastEditedAt,
|
|
439
|
+
}, null, 2),
|
|
440
|
+
},
|
|
441
|
+
],
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
chipUnsupportedError() {
|
|
445
|
+
return {
|
|
446
|
+
content: [
|
|
447
|
+
{
|
|
448
|
+
type: 'text',
|
|
449
|
+
text: '当前 MCP 文件不支持认知芯片切换。请从 MemoryCode 重新导出 memorycode-mcp.json(schema 1.1)。',
|
|
450
|
+
},
|
|
451
|
+
],
|
|
452
|
+
isError: true,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
listAllResources() {
|
|
456
|
+
const profileResources = this.parsed.resources.map((r) => {
|
|
457
|
+
const desc = r.description || r.name;
|
|
458
|
+
const description = desc.startsWith('Memory - ') ? desc : `Memory - ${desc}`;
|
|
459
|
+
return {
|
|
460
|
+
uri: PROFILE_URI_PREFIX + encodeURIComponent(r.id),
|
|
461
|
+
name: r.name,
|
|
462
|
+
description,
|
|
463
|
+
mimeType: MIME_TYPE,
|
|
464
|
+
};
|
|
139
465
|
});
|
|
140
|
-
this.
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
466
|
+
const chipResources = this.parsed.chips.map((c) => ({
|
|
467
|
+
uri: CHIP_URI_PREFIX + encodeURIComponent(c.id),
|
|
468
|
+
name: c.name,
|
|
469
|
+
description: `Chip - ${c.description}`,
|
|
470
|
+
mimeType: MIME_TYPE,
|
|
471
|
+
}));
|
|
472
|
+
return [...profileResources, ...chipResources];
|
|
473
|
+
}
|
|
474
|
+
handleReadResource(uri) {
|
|
475
|
+
if (uri.startsWith(PROFILE_URI_PREFIX)) {
|
|
476
|
+
const id = decodeURIComponent(uri.slice(PROFILE_URI_PREFIX.length));
|
|
477
|
+
const profile = this.parsed.profiles.find((p) => p.id === id);
|
|
478
|
+
const resource = this.parsed.resources.find((r) => r.id === id);
|
|
479
|
+
if (profile && this.supportsChipSwitching()) {
|
|
480
|
+
const chipState = this.resolveActiveChipForProfile(profile);
|
|
481
|
+
const prompt = this.resolvePrompt(profile.id, chipState.chipId);
|
|
482
|
+
if (prompt) {
|
|
483
|
+
return {
|
|
484
|
+
contents: [{ uri, mimeType: MIME_TYPE, text: prompt }],
|
|
485
|
+
};
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
if (resource) {
|
|
144
489
|
return {
|
|
145
|
-
uri:
|
|
146
|
-
name: r.name,
|
|
147
|
-
description,
|
|
148
|
-
mimeType: MIME_TYPE,
|
|
490
|
+
contents: [{ uri, mimeType: MIME_TYPE, text: resource.prompt }],
|
|
149
491
|
};
|
|
150
|
-
});
|
|
151
|
-
return { resources };
|
|
152
|
-
});
|
|
153
|
-
this.server.setRequestHandler(ListResourceTemplatesRequestSchema, () => {
|
|
154
|
-
return { resourceTemplates: [] };
|
|
155
|
-
});
|
|
156
|
-
this.server.setRequestHandler(ReadResourceRequestSchema, (request) => {
|
|
157
|
-
const uri = request.params.uri;
|
|
158
|
-
if (!uri.startsWith(RESOURCE_URI_PREFIX)) {
|
|
159
|
-
throw new McpError(ErrorCode.InvalidParams, `Invalid resource URI: ${uri}`);
|
|
160
492
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
493
|
+
throw new McpError(ErrorCode.InvalidParams, `Resource not found: ${uri}`);
|
|
494
|
+
}
|
|
495
|
+
if (uri.startsWith(CHIP_URI_PREFIX)) {
|
|
496
|
+
const id = decodeURIComponent(uri.slice(CHIP_URI_PREFIX.length));
|
|
497
|
+
const chip = this.findChipById(id);
|
|
498
|
+
if (!chip) {
|
|
499
|
+
throw new McpError(ErrorCode.InvalidParams, `Chip resource not found: ${uri}`);
|
|
165
500
|
}
|
|
501
|
+
const text = [
|
|
502
|
+
`# 认知芯片:${chip.name}`,
|
|
503
|
+
'',
|
|
504
|
+
chip.description,
|
|
505
|
+
'',
|
|
506
|
+
`来源:${chip.source === 'official' ? '官方' : '自定义'}${chip.version ? ` · ${chip.version}` : ''}`,
|
|
507
|
+
'',
|
|
508
|
+
'使用方式:调用 load_chip 工具加载此芯片(需先 load_config 选择档案)。',
|
|
509
|
+
'此资源为芯片说明,不会自动改变当前会话状态。',
|
|
510
|
+
].join('\n');
|
|
166
511
|
return {
|
|
167
|
-
contents: [
|
|
168
|
-
{
|
|
169
|
-
uri,
|
|
170
|
-
mimeType: MIME_TYPE,
|
|
171
|
-
text: resource.prompt,
|
|
172
|
-
},
|
|
173
|
-
],
|
|
512
|
+
contents: [{ uri, mimeType: MIME_TYPE, text }],
|
|
174
513
|
};
|
|
514
|
+
}
|
|
515
|
+
throw new McpError(ErrorCode.InvalidParams, `Invalid resource URI: ${uri}`);
|
|
516
|
+
}
|
|
517
|
+
supportsChipSwitching() {
|
|
518
|
+
return (this.parsed.chips.length > 0 &&
|
|
519
|
+
this.parsed.compositions.length > 0 &&
|
|
520
|
+
(this.parsed.schemaVersion === '1.1' || this.parsed.chips.length > 0));
|
|
521
|
+
}
|
|
522
|
+
resolvePrompt(profileId, chipId) {
|
|
523
|
+
const match = this.parsed.compositions.find((c) => c.profileId === profileId && (c.chipId ?? null) === (chipId ?? null));
|
|
524
|
+
return match?.prompt ?? null;
|
|
525
|
+
}
|
|
526
|
+
resolveActiveChipForProfile(profile) {
|
|
527
|
+
const rawChipId = this.parsed.runtime.activeChipByProfile[profile.id] ?? profile.defaultChipId ?? null;
|
|
528
|
+
if (!rawChipId)
|
|
529
|
+
return { chipId: null };
|
|
530
|
+
const chip = this.findChipById(rawChipId);
|
|
531
|
+
if (!chip) {
|
|
532
|
+
return {
|
|
533
|
+
chipId: null,
|
|
534
|
+
warning: `记录的认知芯片「${rawChipId}」已不可用,已回到无芯片模式。`,
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
if (!this.resolvePrompt(profile.id, rawChipId)) {
|
|
538
|
+
return {
|
|
539
|
+
chipId: null,
|
|
540
|
+
warning: `记录的认知芯片「${chip.name}」缺少组合 prompt,已回到无芯片模式。`,
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
return { chipId: rawChipId };
|
|
544
|
+
}
|
|
545
|
+
runtimeWriteQueue = Promise.resolve();
|
|
546
|
+
queueRuntimePersist() {
|
|
547
|
+
this.parsed.runtime.updatedAt = new Date().toISOString();
|
|
548
|
+
if (!this.watchFilePath)
|
|
549
|
+
return Promise.resolve();
|
|
550
|
+
const runtimeSnapshot = {
|
|
551
|
+
activeProfileId: this.parsed.runtime.activeProfileId,
|
|
552
|
+
activeChipByProfile: { ...this.parsed.runtime.activeChipByProfile },
|
|
553
|
+
updatedAt: this.parsed.runtime.updatedAt,
|
|
554
|
+
};
|
|
555
|
+
this.runtimeWriteQueue = this.runtimeWriteQueue
|
|
556
|
+
.then(() => writeRuntime(this.watchFilePath, runtimeSnapshot))
|
|
557
|
+
.catch((err) => {
|
|
558
|
+
logWarn(`Failed to persist runtime: ${err}`);
|
|
175
559
|
});
|
|
176
|
-
|
|
560
|
+
return this.runtimeWriteQueue;
|
|
177
561
|
}
|
|
178
|
-
|
|
179
|
-
this.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
562
|
+
getProfileNames() {
|
|
563
|
+
if (this.parsed.profiles.length > 0) {
|
|
564
|
+
return this.parsed.profiles.map((p) => p.name);
|
|
565
|
+
}
|
|
566
|
+
return this.parsed.resources.map((r) => r.name);
|
|
567
|
+
}
|
|
568
|
+
getRuntimeActiveProfile() {
|
|
569
|
+
const id = this.parsed.runtime.activeProfileId;
|
|
570
|
+
if (id) {
|
|
571
|
+
const byRuntime = this.parsed.profiles.find((p) => p.id === id);
|
|
572
|
+
if (byRuntime)
|
|
573
|
+
return byRuntime;
|
|
574
|
+
}
|
|
575
|
+
return this.parsed.profiles[0];
|
|
576
|
+
}
|
|
577
|
+
findProfileByName(name) {
|
|
578
|
+
const profiles = this.parsed.profiles.length > 0
|
|
579
|
+
? this.parsed.profiles
|
|
580
|
+
: this.parsed.resources.map((r) => ({
|
|
581
|
+
id: r.id,
|
|
582
|
+
name: r.name,
|
|
583
|
+
description: r.description,
|
|
584
|
+
defaultChipId: null,
|
|
585
|
+
}));
|
|
586
|
+
const direct = profiles.find((p) => p.name === name || p.name.trim() === name);
|
|
587
|
+
if (direct)
|
|
588
|
+
return direct;
|
|
589
|
+
const normalized = this.normalizeName(name);
|
|
590
|
+
if (!normalized)
|
|
591
|
+
return undefined;
|
|
592
|
+
return profiles.find((p) => this.normalizeName(p.name) === normalized);
|
|
593
|
+
}
|
|
594
|
+
findChipByName(name) {
|
|
595
|
+
const direct = this.parsed.chips.find((c) => c.name === name || c.name.trim() === name);
|
|
596
|
+
if (direct)
|
|
597
|
+
return direct;
|
|
598
|
+
const normalized = this.normalizeName(name);
|
|
599
|
+
if (!normalized)
|
|
600
|
+
return undefined;
|
|
601
|
+
return this.parsed.chips.find((c) => {
|
|
602
|
+
if (this.normalizeName(c.name) === normalized)
|
|
603
|
+
return true;
|
|
604
|
+
return c.aliases.some((a) => this.normalizeName(a) === normalized);
|
|
183
605
|
});
|
|
184
606
|
}
|
|
607
|
+
findChipById(id) {
|
|
608
|
+
return this.parsed.chips.find((c) => c.id === id);
|
|
609
|
+
}
|
|
610
|
+
findResourceByConfigName(configName) {
|
|
611
|
+
const directMatch = this.parsed.resources.find((r) => r.name === configName || r.name.trim() === configName);
|
|
612
|
+
if (directMatch)
|
|
613
|
+
return directMatch;
|
|
614
|
+
const normalizedInput = this.normalizeName(configName);
|
|
615
|
+
if (!normalizedInput)
|
|
616
|
+
return undefined;
|
|
617
|
+
return this.parsed.resources.find((r) => this.normalizeName(r.name) === normalizedInput);
|
|
618
|
+
}
|
|
619
|
+
normalizeName(name) {
|
|
620
|
+
return name
|
|
621
|
+
.trim()
|
|
622
|
+
.toLowerCase()
|
|
623
|
+
.replace(/[^\p{L}\p{N}\p{Script=Han}]+/gu, '');
|
|
624
|
+
}
|
|
185
625
|
getUserProfile() {
|
|
186
|
-
const profile = this.
|
|
626
|
+
const profile = this.getActiveProfileRecord();
|
|
627
|
+
const runtimeProfile = this.getRuntimeActiveProfile();
|
|
628
|
+
const chipId = runtimeProfile
|
|
629
|
+
? (this.parsed.runtime.activeChipByProfile[runtimeProfile.id] ?? null)
|
|
630
|
+
: null;
|
|
187
631
|
return {
|
|
188
632
|
nickname: this.toSafeString(profile?.nickname),
|
|
189
633
|
roles: this.toSafeStringArray(profile?.roles),
|
|
190
634
|
about: this.toSafeString(profile?.role),
|
|
635
|
+
activeProfileId: this.parsed.runtime.activeProfileId,
|
|
636
|
+
activeChipId: chipId,
|
|
191
637
|
source: 'memorycode-local',
|
|
192
638
|
schema_version: '1.0',
|
|
193
639
|
};
|
|
194
640
|
}
|
|
195
641
|
getExpertise(domainRaw) {
|
|
196
|
-
const profile = this.
|
|
642
|
+
const profile = this.getActiveProfileRecord();
|
|
197
643
|
const domain = typeof domainRaw === 'string' ? domainRaw.trim().toLowerCase() : '';
|
|
198
644
|
const skills = Array.isArray(profile?.skills) ? profile.skills : [];
|
|
199
645
|
const expertise = skills
|
|
@@ -218,17 +664,20 @@ export class MemoryCodeMCPServer {
|
|
|
218
664
|
source: 'memorycode-local',
|
|
219
665
|
};
|
|
220
666
|
}
|
|
221
|
-
|
|
667
|
+
/** 优先 runtime 档案,其次 data.state.activeProfileId */
|
|
668
|
+
getActiveProfileRecord() {
|
|
222
669
|
if (!this.memorySnapshot || typeof this.memorySnapshot !== 'object')
|
|
223
670
|
return null;
|
|
224
671
|
const root = this.memorySnapshot;
|
|
225
672
|
const data = this.asRecord(root.data);
|
|
226
673
|
const state = this.asRecord(data?.state);
|
|
227
|
-
const profiles = Array.isArray(state?.profiles) ? state
|
|
228
|
-
const
|
|
229
|
-
|
|
674
|
+
const profiles = Array.isArray(state?.profiles) ? state.profiles : [];
|
|
675
|
+
const runtimeId = this.parsed.runtime.activeProfileId;
|
|
676
|
+
const fileActiveId = this.toSafeString(state?.activeProfileId);
|
|
677
|
+
const targetId = runtimeId ?? fileActiveId;
|
|
678
|
+
if (!targetId)
|
|
230
679
|
return null;
|
|
231
|
-
const profile = profiles.find((item) => this.toSafeString(this.asRecord(item)?.id) ===
|
|
680
|
+
const profile = profiles.find((item) => this.toSafeString(this.asRecord(item)?.id) === targetId);
|
|
232
681
|
return this.asRecord(profile);
|
|
233
682
|
}
|
|
234
683
|
asRecord(v) {
|
|
@@ -252,22 +701,6 @@ export class MemoryCodeMCPServer {
|
|
|
252
701
|
return 'proficient';
|
|
253
702
|
return 'familiar';
|
|
254
703
|
}
|
|
255
|
-
findResourceByConfigName(configName) {
|
|
256
|
-
const directMatch = this.resources.find((r) => r.name === configName || r.name.trim() === configName);
|
|
257
|
-
if (directMatch)
|
|
258
|
-
return directMatch;
|
|
259
|
-
// 容错匹配:允许用户输入去掉 emoji/符号后的名称(如「职场」匹配「💼 职场」)
|
|
260
|
-
const normalizedInput = this.normalizeConfigName(configName);
|
|
261
|
-
if (!normalizedInput)
|
|
262
|
-
return undefined;
|
|
263
|
-
return this.resources.find((r) => this.normalizeConfigName(r.name) === normalizedInput);
|
|
264
|
-
}
|
|
265
|
-
normalizeConfigName(name) {
|
|
266
|
-
return name
|
|
267
|
-
.trim()
|
|
268
|
-
.toLowerCase()
|
|
269
|
-
.replace(/[^\p{L}\p{N}\p{Script=Han}]+/gu, '');
|
|
270
|
-
}
|
|
271
704
|
async run() {
|
|
272
705
|
this.transport = new StdioServerTransport();
|
|
273
706
|
await this.server.connect(this.transport);
|