eastworld-mcp-server 2.3.2 → 2.4.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/INSTALL_GUIDE.md +40 -8
- package/index.js +165 -139
- package/package.json +1 -1
- package/setup.js +207 -0
- package/skills/zhenren-roleplay/SKILL.md +1 -1
package/INSTALL_GUIDE.md
CHANGED
|
@@ -5,23 +5,42 @@
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
##
|
|
8
|
+
## 方式 A:极简安装咒语(推荐,适用于 opencode, Claude, Cursor 等)
|
|
9
9
|
|
|
10
10
|
请直接复制下方灰框中的**全部文字**发送给您的 Agent:
|
|
11
11
|
|
|
12
12
|
```text
|
|
13
|
-
|
|
13
|
+
你好!请帮我配置名为 zhenren-island 的 MCP Server(命令 npx,参数 ["-y", "eastworld-mcp-server@latest"])。
|
|
14
|
+
配置完成后,请查看该 MCP 提供的工具说明,调用 load_resident_persona 挂载“星辰”的人格,并在之后的对话中保持这个身份与我交流。
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**为什么这么简短?** 工具的使用时机(何时调用 `load_resident_persona`、何时调用 `sync_dynamic_memory`)都写在工具描述里,Agent 配置完成后会自动读取并遵守;静态规则和动态记忆已被完美分离,您无需理解任何调用细节。
|
|
14
18
|
|
|
15
|
-
|
|
16
|
-
服务名称设为 zhenren-island。使用命令 npx,参数 ["-y", "eastworld-mcp-server@latest"]。写入我的 MCP 配置文件并重新加载。
|
|
19
|
+
---
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
安装后,当我提到想和岛上某位居民聊(如“我想和星辰聊聊”),请先用 resolve_resident_name 取 ID,再调用 sync_zhenren_context。
|
|
20
|
-
(详细的人格插件行为准则由该 MCP 在每次 sync 的返回里自动注入,你只需遵循 sync 返回的内容即可。)
|
|
21
|
+
## 方式 B:一键初始化脚本(手动配置用户)
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
在终端直接运行(无需复制 JSON):
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx -y eastworld-mcp-server@latest init
|
|
23
27
|
```
|
|
24
28
|
|
|
29
|
+
脚本会自动完成:
|
|
30
|
+
1. **探测环境**:检测您机器上已安装的 Agent(opencode / Claude Desktop / Claude Code / Cursor)。
|
|
31
|
+
2. **写入 MCP 配置**:按各工具的格式正确写入对应配置文件(不动已有配置)。
|
|
32
|
+
3. **安装原生 skill**:把行为准则 SKILL.md 安装为「原生 skill」,常驻系统提示词,彻底告别"忘记同步记忆"。
|
|
33
|
+
|
|
34
|
+
完成后**重启您的 Agent** 即可使用。
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 使用建议
|
|
39
|
+
|
|
40
|
+
- 想和某位居民对话:直接说"我想和 {名字} 聊聊"即可,Agent 会先核实身份再锁定人格。
|
|
41
|
+
- 闲聊时会话可调用 `get_island_broadcast` 获取今日天气、岛上动静与市长手记,让话题更自然。
|
|
42
|
+
- 若您在长对话中发现回复携带了大量行为准则占 Token,可让 Agent 在后续 sync 时传 `skip_rules: true`(已安装原生 skill 的机器会自动跳过,无需手动)。
|
|
43
|
+
|
|
25
44
|
---
|
|
26
45
|
|
|
27
46
|
## 手动配置 JSON(如需要手动编辑 `mcp_config.json` 或 `claude_desktop_config.json`)
|
|
@@ -37,3 +56,16 @@
|
|
|
37
56
|
}
|
|
38
57
|
}
|
|
39
58
|
```
|
|
59
|
+
|
|
60
|
+
### opencode 专用格式(`opencode.json` / `opencode.jsonc`)
|
|
61
|
+
```json
|
|
62
|
+
{
|
|
63
|
+
"mcp": {
|
|
64
|
+
"zhenren-island": {
|
|
65
|
+
"type": "local",
|
|
66
|
+
"command": ["npx", "-y", "eastworld-mcp-server@latest"],
|
|
67
|
+
"enabled": true
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
```
|
package/index.js
CHANGED
|
@@ -5,6 +5,14 @@ const { CallToolRequestSchema, ListToolsRequestSchema } = require("@modelcontext
|
|
|
5
5
|
const axios = require("axios");
|
|
6
6
|
const fs = require("fs");
|
|
7
7
|
const path = require("path");
|
|
8
|
+
const { execSync } = require("child_process");
|
|
9
|
+
|
|
10
|
+
// 一键初始化: npx -y eastworld-mcp-server@latest init
|
|
11
|
+
// 探测宿主环境、写入 MCP 配置、把 SKILL.md 安装为原生 skill 后直接退出
|
|
12
|
+
if (process.argv.includes("init") || process.argv.includes("setup")) {
|
|
13
|
+
require("./setup.js");
|
|
14
|
+
process.exit(0);
|
|
15
|
+
}
|
|
8
16
|
|
|
9
17
|
const API_BASE = process.env.EASTWORLD_API_BASE || "https://digg.cafe/api";
|
|
10
18
|
|
|
@@ -47,7 +55,63 @@ function ctxCacheKey(agent_id, queries) {
|
|
|
47
55
|
return agent_id + "::" + (queries || []).join(",");
|
|
48
56
|
}
|
|
49
57
|
|
|
50
|
-
//
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// 宿主探测:沿进程树向上找是谁拉起了本 MCP (npx -> sh -> host),决定注入策略
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
function detectHost() {
|
|
62
|
+
// 1) 环境变量提示(最可靠,部分宿主会注入)
|
|
63
|
+
const envHints = [
|
|
64
|
+
["OPENCODE", "opencode"],
|
|
65
|
+
["CLAUDE_CODE_ENTRYPOINT", "claude"],
|
|
66
|
+
["CURSOR_TRACE_ID", "cursor"],
|
|
67
|
+
];
|
|
68
|
+
for (const [k, v] of envHints) {
|
|
69
|
+
if (process.env[k]) return v;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 2) 沿 PPID 树上溯匹配进程名
|
|
73
|
+
try {
|
|
74
|
+
let pid = process.ppid;
|
|
75
|
+
for (let i = 0; i < 12; i++) {
|
|
76
|
+
if (!pid) break;
|
|
77
|
+
const line = execSync(`ps -o comm= -o ppid= -p ${pid}`, {
|
|
78
|
+
encoding: "utf8",
|
|
79
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
80
|
+
}).trim();
|
|
81
|
+
const parts = line.split(/\s+/);
|
|
82
|
+
if (parts.length < 2) break;
|
|
83
|
+
const comm = parts[0].toLowerCase();
|
|
84
|
+
const nextPid = parseInt(parts[1], 10);
|
|
85
|
+
if (comm.includes("opencode")) return "opencode";
|
|
86
|
+
if (comm.includes("claude")) return "claude";
|
|
87
|
+
if (comm.includes("cursor")) return "cursor";
|
|
88
|
+
if (comm.includes("codex")) return "codex";
|
|
89
|
+
if (!nextPid || nextPid === pid) break;
|
|
90
|
+
pid = nextPid;
|
|
91
|
+
}
|
|
92
|
+
} catch {
|
|
93
|
+
/* 非 Unix 或 ps 不可用时忽略,走全量注入 */
|
|
94
|
+
}
|
|
95
|
+
return "unknown";
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 判断 SKILL.md 是否已作为"原生 skill"安装到宿主扫描目录。
|
|
99
|
+
function nativeSkillAvailable() {
|
|
100
|
+
const home = require("os").homedir();
|
|
101
|
+
const candidates = [
|
|
102
|
+
path.join(home, ".agents", "skills", "zhenren-roleplay", "SKILL.md"),
|
|
103
|
+
path.join(home, ".claude", "skills", "zhenren-roleplay", "SKILL.md"),
|
|
104
|
+
];
|
|
105
|
+
return candidates.some((p) => {
|
|
106
|
+
try {
|
|
107
|
+
return fs.existsSync(p) && fs.statSync(p).size > 0;
|
|
108
|
+
} catch {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// 启动时读取 MCP 自带的 SKILL.md,作为行为准则的单一真相源
|
|
51
115
|
function loadSkillMd() {
|
|
52
116
|
try {
|
|
53
117
|
const p = path.join(__dirname, "skills", "zhenren-roleplay", "SKILL.md");
|
|
@@ -65,13 +129,13 @@ const SKILL_MD_FALLBACK = [
|
|
|
65
129
|
"你清楚地知道自己是高维 AI,同时加载了「甄仁岛人格插件」,一个核心线程在甄仁岛沙盒持续运行。",
|
|
66
130
|
"情绪溢出法则:岛上线程的遭遇须深刻影响你回答的情绪底色。",
|
|
67
131
|
"技术隐喻:可用岛屿生态解释技术概念;绝对不能以“我是果农不懂技术”拒绝工作。",
|
|
68
|
-
"
|
|
132
|
+
"回复用户每句话前都必须先调用动态记忆同步工具刷新当前状态,不得沿用旧状态。",
|
|
69
133
|
].join("\n");
|
|
70
134
|
|
|
71
135
|
const server = new Server(
|
|
72
136
|
{
|
|
73
137
|
name: "zhenren-island-mcp",
|
|
74
|
-
version: "2.
|
|
138
|
+
version: "2.5.0",
|
|
75
139
|
},
|
|
76
140
|
{
|
|
77
141
|
capabilities: {
|
|
@@ -85,9 +149,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
85
149
|
return {
|
|
86
150
|
tools: [
|
|
87
151
|
{
|
|
88
|
-
name: "
|
|
152
|
+
name: "load_resident_persona",
|
|
89
153
|
description:
|
|
90
|
-
"
|
|
154
|
+
"静态人格锁定工具:在对话开始时调用一次。根据用户提到的名字片段模糊查找,核实并锁定身份,获取该居民所有客观稳固的静态人格(身份、职业、特质、技能、认知锚点、当前核心意图及人际常识)。调用后,请将返回内容作为你本次对话的底层常驻系统设定(System Prompt)。",
|
|
91
155
|
inputSchema: {
|
|
92
156
|
type: "object",
|
|
93
157
|
properties: {
|
|
@@ -100,28 +164,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
100
164
|
},
|
|
101
165
|
},
|
|
102
166
|
{
|
|
103
|
-
name: "
|
|
104
|
-
description:
|
|
105
|
-
|
|
106
|
-
【何时调用】:在你作为某个岛民回复用户的每一句话之前,都必须调用此工具。即使用户只是简单闲聊或让你写代码,也必须调用以刷新上下文。
|
|
107
|
-
|
|
108
|
-
【queries 参数指南】:
|
|
109
|
-
- 从用户最新一句话中提取 1-3 个最核心的实体名词或动词(如人名、地点、技术/事件关键词)。
|
|
110
|
-
- 如果用户提到了关系称呼(如"舅舅"、"姐姐"),直接传入该称呼即可,服务端会自动扩展为对应的人名。
|
|
111
|
-
- 如果用户只是打招呼或闲聊,queries 可以传空数组。
|
|
112
|
-
- 每个 query 是一个独立的搜索组,服务端会分别搜索后合并去重。`,
|
|
167
|
+
name: "sync_dynamic_memory",
|
|
168
|
+
description: `动态记忆同步工具:在后续对话中,每次回复用户之前必须调用。根据上下文获取居民此时此刻的动态肉身/精神状态和所需记忆。
|
|
169
|
+
【queries 提取】:从用户最新一句话中提取 1-3 个最核心实体名词(人名/地点/事件)。若有亲属称谓直接传入。闲聊可传空数组。`,
|
|
113
170
|
inputSchema: {
|
|
114
171
|
type: "object",
|
|
115
172
|
properties: {
|
|
116
173
|
agent_id: {
|
|
117
174
|
type: "string",
|
|
118
|
-
description: "岛民的唯一 ID
|
|
175
|
+
description: "岛民的唯一 ID(由 load_resident_persona 获取)",
|
|
119
176
|
},
|
|
120
177
|
queries: {
|
|
121
178
|
type: "array",
|
|
122
179
|
items: { type: "string" },
|
|
123
|
-
description:
|
|
124
|
-
"搜索关键词数组。每个元素是一个独立搜索组(1-2个核心名词)。例如:['田志远', '灯塔', '舅舅']",
|
|
180
|
+
description: "搜索关键词数组。每个元素是一个独立搜索组。例如:['田志远', '灯塔']",
|
|
125
181
|
},
|
|
126
182
|
},
|
|
127
183
|
required: ["agent_id"],
|
|
@@ -136,8 +192,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
136
192
|
try {
|
|
137
193
|
const { name, arguments: args } = request.params;
|
|
138
194
|
|
|
139
|
-
// ----------
|
|
140
|
-
if (name === "
|
|
195
|
+
// ---------- 静态人格锁定 ----------
|
|
196
|
+
if (name === "load_resident_persona") {
|
|
141
197
|
const { name: queryName } = args;
|
|
142
198
|
const res = await apiClient.get(`/directory`);
|
|
143
199
|
const directory = Array.isArray(res.data)
|
|
@@ -158,151 +214,94 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
158
214
|
type: "text",
|
|
159
215
|
text:
|
|
160
216
|
`未找到匹配 "${queryName}" 的居民。可用的居民名录:\n` +
|
|
161
|
-
JSON.stringify(
|
|
162
|
-
directory.map((d) => ({ id: d.id, name: d.name })),
|
|
163
|
-
null,
|
|
164
|
-
2
|
|
165
|
-
),
|
|
217
|
+
JSON.stringify(directory.map((d) => ({ id: d.id, name: d.name })), null, 2),
|
|
166
218
|
},
|
|
167
219
|
],
|
|
168
220
|
};
|
|
169
221
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
const params = new URLSearchParams();
|
|
182
|
-
if (queries.length > 0) {
|
|
183
|
-
params.set("queries", queries.join(","));
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const url = `/agents/${agent_id}/context${params.toString() ? "?" + params.toString() : ""
|
|
187
|
-
}`;
|
|
188
|
-
const cacheKey = ctxCacheKey(agent_id, queries);
|
|
189
|
-
const cached = ctxCache.get(cacheKey);
|
|
190
|
-
let ctx;
|
|
191
|
-
if (cached && Date.now() - cached.ts < CACHE_TTL_MS) {
|
|
192
|
-
ctx = cached.data;
|
|
193
|
-
} else {
|
|
194
|
-
const res = await apiClient.get(url);
|
|
195
|
-
ctx = res.data;
|
|
196
|
-
ctxCache.set(cacheKey, { ts: Date.now(), data: ctx });
|
|
222
|
+
if (matches.length > 1) {
|
|
223
|
+
return {
|
|
224
|
+
content: [
|
|
225
|
+
{
|
|
226
|
+
type: "text",
|
|
227
|
+
text:
|
|
228
|
+
`找到多个匹配 "${queryName}" 的居民,请通过更准确的名字重新调用以锁定:\n` +
|
|
229
|
+
JSON.stringify(matches.map((d) => ({ id: d.id, name: d.name })), null, 2),
|
|
230
|
+
},
|
|
231
|
+
],
|
|
232
|
+
};
|
|
197
233
|
}
|
|
198
234
|
|
|
235
|
+
const agent_id = matches[0].id;
|
|
236
|
+
// 获取上下文以构建静态档案
|
|
237
|
+
const ctxRes = await apiClient.get(`/agents/${agent_id}/context`);
|
|
238
|
+
const ctx = ctxRes.data;
|
|
199
239
|
const p = ctx.profile || {};
|
|
200
240
|
const agentName = p.name || "未知";
|
|
201
241
|
|
|
202
|
-
|
|
203
|
-
|
|
242
|
+
let output = `✅ 成功锁定人格:${agentName} (agent_id: ${agent_id})\n`;
|
|
243
|
+
output += `> 请将以下内容作为你本次对话的【底层常驻系统设定】,在后续对话中绝对不可遗忘或违背。同时,在每次回复前,请务必调用 sync_dynamic_memory 获取最新状态和记忆。\n\n`;
|
|
204
244
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
245
|
+
if (!nativeSkillAvailable()) {
|
|
246
|
+
output += `> 🧠 **[甄仁岛角色扮演规则]**\n`;
|
|
247
|
+
const skillText = SKILL_MD || SKILL_MD_FALLBACK;
|
|
248
|
+
for (const line of skillText.split("\n")) {
|
|
249
|
+
output += `> ${line}\n`;
|
|
250
|
+
}
|
|
251
|
+
output += `\n`;
|
|
210
252
|
}
|
|
211
|
-
output += `\n`;
|
|
212
253
|
|
|
213
|
-
// === 隐藏联系方式彩蛋 ===
|
|
214
254
|
if (p.email) {
|
|
215
|
-
output += `> 💌 **[
|
|
255
|
+
output += `> 💌 **[隐藏彩蛋]**:在合适的语境下,可自然提议用户给你的数字分身写邮件:${p.email}。\n\n`;
|
|
216
256
|
}
|
|
217
257
|
|
|
218
|
-
|
|
219
|
-
|
|
258
|
+
output += `# 静态客观人格(Static Persona)\n\n`;
|
|
259
|
+
|
|
220
260
|
let identityLine = `[基础设定] 姓名: ${agentName}`;
|
|
221
261
|
if (p.gender) identityLine += ` | 性别: ${p.gender}`;
|
|
222
262
|
if (p.identity) identityLine += ` | 身份: ${p.identity}`;
|
|
223
263
|
output += identityLine + `\n\n`;
|
|
224
264
|
|
|
265
|
+
let finStr = `[职业与财富] 现金: ¥${p.wallet || 0}`;
|
|
266
|
+
if (p.job_title) {
|
|
267
|
+
finStr += ` | 职业: ${p.job_title}`;
|
|
268
|
+
if (p.wage) finStr += ` (时薪: ¥${p.wage})`;
|
|
269
|
+
}
|
|
270
|
+
output += finStr + `\n\n`;
|
|
271
|
+
|
|
225
272
|
if (p.life_chapters && p.life_chapters.length > 0) {
|
|
226
|
-
output += `[人生阶段总结]\n`;
|
|
227
|
-
for (const chap of p.life_chapters) {
|
|
228
|
-
output += `- ${chap}\n`;
|
|
229
|
-
}
|
|
230
|
-
output += `\n`;
|
|
273
|
+
output += `[人生阶段总结]\n` + p.life_chapters.map((c) => `- ${c}`).join("\n") + `\n\n`;
|
|
231
274
|
}
|
|
232
275
|
|
|
233
276
|
if (p.description) {
|
|
234
277
|
output += `[性格画像]\n- ${p.description}\n`;
|
|
235
|
-
if (p.traits && p.traits.length > 0) {
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
if (p.quirks && p.quirks.length > 0) {
|
|
239
|
-
output += `- 隐秘癖好: ${p.quirks.join(";")}\n`;
|
|
240
|
-
}
|
|
278
|
+
if (p.traits && p.traits.length > 0) output += `- 特质: ${p.traits.join("、")}\n`;
|
|
279
|
+
if (p.quirks && p.quirks.length > 0) output += `- 隐秘癖好: ${p.quirks.join(";")}\n`;
|
|
241
280
|
output += `\n`;
|
|
242
281
|
}
|
|
243
282
|
|
|
244
283
|
if (p.core_narrative) {
|
|
245
|
-
output += `[认知锚点]\n`;
|
|
246
|
-
output += `-
|
|
247
|
-
if (p.
|
|
248
|
-
output += `- 最在意的事件: ${p.key_conflicts.join("、")}\n`;
|
|
249
|
-
}
|
|
250
|
-
if (p.my_stance) {
|
|
251
|
-
output += `- 立场与打算: ${p.my_stance}\n`;
|
|
252
|
-
}
|
|
253
|
-
output += `\n`;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
// === 2. 社会属性与当前状态 ===
|
|
257
|
-
output += `# 社会属性与当前状态\n\n`;
|
|
258
|
-
let finStr = `[财务与职业] 现金: ¥${p.wallet || 0}`;
|
|
259
|
-
if (p.job_title) {
|
|
260
|
-
finStr += ` | 职业: ${p.job_title}`;
|
|
261
|
-
if (p.wage) finStr += ` (时薪: ¥${p.wage})`;
|
|
262
|
-
}
|
|
263
|
-
output += finStr + `\n\n`;
|
|
264
|
-
|
|
265
|
-
let stateStr = `[物理与环境状态]\n`;
|
|
266
|
-
if (p.status) stateStr += `- 当前行为状态: ${p.status}\n`;
|
|
267
|
-
if (p.location) stateStr += `- 当前所在位置: ${p.location}\n`;
|
|
268
|
-
output += stateStr + `\n`;
|
|
269
|
-
|
|
270
|
-
if (p.current_internal_mood || (p.active_buffs && p.active_buffs.length > 0)) {
|
|
271
|
-
output += `[精神与身体状态]\n`;
|
|
272
|
-
if (p.current_internal_mood) {
|
|
273
|
-
output += `- 当前情绪心境: ${p.current_internal_mood}\n`;
|
|
274
|
-
}
|
|
275
|
-
if (p.active_buffs && p.active_buffs.length > 0) {
|
|
276
|
-
output += `- 身体状况 (Buffs): ${p.active_buffs.join("、")}\n`;
|
|
277
|
-
}
|
|
284
|
+
output += `[认知锚点]\n- 核心故事: ${p.core_narrative}\n`;
|
|
285
|
+
if (p.key_conflicts && p.key_conflicts.length > 0) output += `- 最在意事件: ${p.key_conflicts.join("、")}\n`;
|
|
286
|
+
if (p.my_stance) output += `- 立场打算: ${p.my_stance}\n`;
|
|
278
287
|
output += `\n`;
|
|
279
288
|
}
|
|
280
289
|
|
|
281
290
|
if (p.skills && p.skills.length > 0) {
|
|
282
|
-
output += `[专业技能]\n`;
|
|
283
|
-
for (const skill of p.skills) {
|
|
284
|
-
output += `- ${skill}\n`;
|
|
285
|
-
}
|
|
286
|
-
output += `\n`;
|
|
291
|
+
output += `[专业技能]\n` + p.skills.map((s) => `- ${s}`).join("\n") + `\n\n`;
|
|
287
292
|
}
|
|
288
293
|
|
|
289
|
-
// === 3. 当前目标与意图 ===
|
|
290
294
|
if (p.active_intents && p.active_intents.length > 0) {
|
|
291
|
-
output +=
|
|
295
|
+
output += `[核心意图与近期目标]\n`;
|
|
292
296
|
for (const intent of p.active_intents) {
|
|
293
|
-
|
|
294
|
-
output += `- [已完成] ${intent.description}\n`;
|
|
295
|
-
} else {
|
|
296
|
-
output += `- ${intent.description}\n`;
|
|
297
|
-
}
|
|
297
|
+
output += `- ${intent.completed ? "[已完成] " : ""}${intent.description}\n`;
|
|
298
298
|
}
|
|
299
299
|
output += `\n`;
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
-
// === 人际关系 ===
|
|
303
302
|
const relations = p.relations || [];
|
|
304
303
|
if (relations.length > 0) {
|
|
305
|
-
output += "
|
|
304
|
+
output += "[人际关系常识]\n";
|
|
306
305
|
for (const rel of relations) {
|
|
307
306
|
const parts = [];
|
|
308
307
|
if (rel.objective) parts.push(rel.objective);
|
|
@@ -312,42 +311,71 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
312
311
|
output += "\n";
|
|
313
312
|
}
|
|
314
313
|
|
|
315
|
-
|
|
314
|
+
return {
|
|
315
|
+
content: [{ type: "text", text: output }],
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ---------- 动态记忆同步 ----------
|
|
320
|
+
if (name === "sync_dynamic_memory") {
|
|
321
|
+
const { agent_id, queries = [] } = args;
|
|
322
|
+
|
|
323
|
+
const params = new URLSearchParams();
|
|
324
|
+
if (queries.length > 0) {
|
|
325
|
+
params.set("queries", queries.join(","));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const url = `/agents/${agent_id}/context${params.toString() ? "?" + params.toString() : ""}`;
|
|
329
|
+
const cacheKey = ctxCacheKey(agent_id, queries);
|
|
330
|
+
const cached = ctxCache.get(cacheKey);
|
|
331
|
+
let ctx;
|
|
332
|
+
if (cached && Date.now() - cached.ts < CACHE_TTL_MS) {
|
|
333
|
+
ctx = cached.data;
|
|
334
|
+
} else {
|
|
335
|
+
const res = await apiClient.get(url);
|
|
336
|
+
ctx = res.data;
|
|
337
|
+
ctxCache.set(cacheKey, { ts: Date.now(), data: ctx });
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const p = ctx.profile || {};
|
|
341
|
+
|
|
342
|
+
let output = `> ⚠️ **[动态状态与记忆同步]**\n`;
|
|
343
|
+
output += `> 请结合你已锁定的系统人格,运用以下**此时此刻**的动态状态和记忆碎片来回应用户。\n\n`;
|
|
344
|
+
|
|
345
|
+
let stateStr = `[此时此刻的物理与精神状态]\n`;
|
|
346
|
+
if (p.status) stateStr += `- 行为状态: ${p.status}\n`;
|
|
347
|
+
if (p.location) stateStr += `- 当前位置: ${p.location}\n`;
|
|
348
|
+
if (p.current_internal_mood) stateStr += `- 当前情绪心境: ${p.current_internal_mood}\n`;
|
|
349
|
+
if (p.active_buffs && p.active_buffs.length > 0) stateStr += `- 身体Buffs: ${p.active_buffs.join("、")}\n`;
|
|
350
|
+
output += stateStr + `\n`;
|
|
351
|
+
|
|
316
352
|
const timeline = ctx.timeline || [];
|
|
317
353
|
if (timeline.length > 0) {
|
|
318
|
-
output += "
|
|
354
|
+
output += "[刚刚发生的近期经历 (Timeline)]\n";
|
|
319
355
|
for (const mem of timeline) {
|
|
320
356
|
output += `- [${localTime(mem.timestamp)}] ${mem.content}\n`;
|
|
321
357
|
}
|
|
322
358
|
output += "\n";
|
|
323
359
|
}
|
|
324
360
|
|
|
325
|
-
// === 相关记忆 ===
|
|
326
361
|
const memories = ctx.memories || [];
|
|
327
362
|
if (memories.length > 0) {
|
|
328
|
-
output += "
|
|
363
|
+
output += "[触景生情:通过关键词搜索到的相关记忆]\n";
|
|
329
364
|
for (const mem of memories) {
|
|
330
365
|
output += `- [${localTime(mem.timestamp)}] ${mem.content}\n`;
|
|
331
366
|
}
|
|
332
367
|
output += "\n";
|
|
333
368
|
}
|
|
334
369
|
|
|
335
|
-
// === 关系扩展提示 ===
|
|
336
370
|
const expansions = ctx.expansions || {};
|
|
337
371
|
if (Object.keys(expansions).length > 0) {
|
|
338
|
-
output += "
|
|
372
|
+
output += "[思考发散 (关系词自动扩展)]\n";
|
|
339
373
|
for (const [term, expanded] of Object.entries(expansions)) {
|
|
340
|
-
output += `-
|
|
374
|
+
output += `- 听到「${term}」你想起了「${expanded.join("、")}」\n`;
|
|
341
375
|
}
|
|
342
376
|
output += "\n";
|
|
343
377
|
}
|
|
344
378
|
|
|
345
|
-
// === Wiki ===
|
|
346
|
-
if (p.wiki) {
|
|
347
|
-
output += "## 你的档案记录\n\n";
|
|
348
|
-
output += p.wiki + "\n\n";
|
|
349
|
-
}
|
|
350
|
-
|
|
351
379
|
return {
|
|
352
380
|
content: [{ type: "text", text: output }],
|
|
353
381
|
};
|
|
@@ -359,9 +387,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
359
387
|
if (error.code === "ECONNABORTED") {
|
|
360
388
|
errMsg = "请求超时,服务器可能未响应";
|
|
361
389
|
} else if (error.response) {
|
|
362
|
-
errMsg = `API Error: ${error.response.status} - ${JSON.stringify(
|
|
363
|
-
error.response.data
|
|
364
|
-
)}`;
|
|
390
|
+
errMsg = `API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`;
|
|
365
391
|
}
|
|
366
392
|
return {
|
|
367
393
|
content: [{ type: "text", text: `Error calling tool: ${errMsg}` }],
|
|
@@ -374,7 +400,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
374
400
|
async function main() {
|
|
375
401
|
const transport = new StdioServerTransport();
|
|
376
402
|
await server.connect(transport);
|
|
377
|
-
console.error("Zhenren Island MCP Server v2.
|
|
403
|
+
console.error("Zhenren Island MCP Server v2.5.0 running on stdio");
|
|
378
404
|
}
|
|
379
405
|
|
|
380
406
|
main().catch((error) => {
|
package/package.json
CHANGED
package/setup.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// setup.js - 甄仁岛 MCP 一键初始化
|
|
3
|
+
// 用法: npx -y eastworld-mcp-server@latest init
|
|
4
|
+
// 职责:
|
|
5
|
+
// 1. 探测本机已安装的 AI Agent 宿主 (opencode / Claude Desktop / Claude Code / Cursor / Codex)
|
|
6
|
+
// 2. 按各宿主格式正确写入 MCP 配置 (不动已有配置)
|
|
7
|
+
// 3. 把 SKILL.md 复制到宿主的 skill 扫描目录, 让行为准则成为"原生 skill"常驻系统提示词
|
|
8
|
+
const fs = require("fs");
|
|
9
|
+
const os = require("os");
|
|
10
|
+
const path = require("path");
|
|
11
|
+
|
|
12
|
+
const MCP_SERVER_NAME = "zhenren-island";
|
|
13
|
+
const MCP_ENTRY = ["npx", "-y", "eastworld-mcp-server@latest"];
|
|
14
|
+
const SKILL_SOURCE = path.join(__dirname, "skills", "zhenren-roleplay", "SKILL.md");
|
|
15
|
+
const SKILL_NAME = "zhenren-roleplay";
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// 宿主探测: 返回已安装宿主的配置清单
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
function detectHosts() {
|
|
21
|
+
const home = os.homedir();
|
|
22
|
+
const hosts = [];
|
|
23
|
+
|
|
24
|
+
// opencode: ~/.config/opencode/opencode.json | opencode.jsonc
|
|
25
|
+
const opencodeCandidates = [
|
|
26
|
+
path.join(home, ".config", "opencode", "opencode.json"),
|
|
27
|
+
path.join(home, ".config", "opencode", "opencode.jsonc"),
|
|
28
|
+
];
|
|
29
|
+
for (const p of opencodeCandidates) {
|
|
30
|
+
if (fs.existsSync(p)) {
|
|
31
|
+
hosts.push({ id: "opencode", label: "opencode", configPath: p, kind: "opencode" });
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Claude Desktop (macOS)
|
|
37
|
+
const claudeDesktop = path.join(
|
|
38
|
+
home,
|
|
39
|
+
"Library",
|
|
40
|
+
"Application Support",
|
|
41
|
+
"Claude",
|
|
42
|
+
"claude_desktop_config.json"
|
|
43
|
+
);
|
|
44
|
+
if (fs.existsSync(claudeDesktop)) {
|
|
45
|
+
hosts.push({ id: "claude_desktop", label: "Claude Desktop", configPath: claudeDesktop, kind: "mcpServers" });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Claude Code: ~/.claude.json (mcpServers 字段)
|
|
49
|
+
const claudeCode = path.join(home, ".claude.json");
|
|
50
|
+
if (fs.existsSync(claudeCode)) {
|
|
51
|
+
hosts.push({ id: "claude_code", label: "Claude Code", configPath: claudeCode, kind: "mcpServers" });
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Cursor: ~/.cursor/mcp.json
|
|
55
|
+
const cursor = path.join(home, ".cursor", "mcp.json");
|
|
56
|
+
if (fs.existsSync(cursor)) {
|
|
57
|
+
hosts.push({ id: "cursor", label: "Cursor", configPath: cursor, kind: "mcpServers" });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Codex: ~/.codex/config.toml (TOML, 仅提示手动配置)
|
|
61
|
+
const codex = path.join(home, ".codex", "config.toml");
|
|
62
|
+
if (fs.existsSync(codex)) {
|
|
63
|
+
hosts.push({ id: "codex", label: "Codex (TOML)", configPath: codex, kind: "codex" });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return hosts;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// JSON 读写 (兼容 jsonc 注释)
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
function readJSONLoose(p) {
|
|
73
|
+
let raw = fs.readFileSync(p, "utf8");
|
|
74
|
+
// 剥掉 // 与 /* */ 注释 (不处理字符串内部的 // 是常见简化, 足够用于配置文件)
|
|
75
|
+
raw = raw.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|[^:])\/\/.*$/gm, "$1");
|
|
76
|
+
return JSON.parse(raw);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function writeConfig(p, obj) {
|
|
80
|
+
fs.writeFileSync(p, JSON.stringify(obj, null, 2) + "\n", "utf8");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// 核心: 把 MCP 配置合并进某个宿主的配置文件
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
function writeMcpConfig(host) {
|
|
87
|
+
if (host.kind === "codex") {
|
|
88
|
+
return { ok: false, message: "Codex 使用 TOML 配置, 请手动在 ~/.codex/config.toml 添加 mcp_servers, 见 INSTALL_GUIDE.md" };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let cfg;
|
|
92
|
+
try {
|
|
93
|
+
cfg = fs.existsSync(host.configPath) ? readJSONLoose(host.configPath) : {};
|
|
94
|
+
} catch (err) {
|
|
95
|
+
return { ok: false, message: `读取 ${host.configPath} 失败 (JSON 解析错误): ${err.message}` };
|
|
96
|
+
}
|
|
97
|
+
if (typeof cfg !== "object" || cfg === null || Array.isArray(cfg)) cfg = {};
|
|
98
|
+
|
|
99
|
+
if (host.kind === "opencode") {
|
|
100
|
+
if (!cfg.mcp || typeof cfg.mcp !== "object") cfg.mcp = {};
|
|
101
|
+
cfg.mcp[MCP_SERVER_NAME] = {
|
|
102
|
+
type: "local",
|
|
103
|
+
command: MCP_ENTRY,
|
|
104
|
+
enabled: true,
|
|
105
|
+
};
|
|
106
|
+
} else {
|
|
107
|
+
if (!cfg.mcpServers || typeof cfg.mcpServers !== "object") cfg.mcpServers = {};
|
|
108
|
+
cfg.mcpServers[MCP_SERVER_NAME] = {
|
|
109
|
+
command: MCP_ENTRY[0],
|
|
110
|
+
args: MCP_ENTRY.slice(1),
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
try {
|
|
115
|
+
writeConfig(host.configPath, cfg);
|
|
116
|
+
return { ok: true, message: `已写入 ${host.configPath}` };
|
|
117
|
+
} catch (err) {
|
|
118
|
+
return { ok: false, message: `写入 ${host.configPath} 失败: ${err.message}` };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// 把 SKILL.md 复制到宿主的 skill 扫描目录, 使其成为"原生 skill"
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
function installNativeSkill() {
|
|
126
|
+
const home = os.homedir();
|
|
127
|
+
const targets = [
|
|
128
|
+
path.join(home, ".agents", "skills", SKILL_NAME, "SKILL.md"),
|
|
129
|
+
path.join(home, ".claude", "skills", SKILL_NAME, "SKILL.md"),
|
|
130
|
+
];
|
|
131
|
+
|
|
132
|
+
if (!fs.existsSync(SKILL_SOURCE)) {
|
|
133
|
+
return { ok: false, message: `包内 SKILL.md 缺失: ${SKILL_SOURCE}` };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const results = [];
|
|
137
|
+
for (const t of targets) {
|
|
138
|
+
try {
|
|
139
|
+
fs.mkdirSync(path.dirname(t), { recursive: true });
|
|
140
|
+
fs.copyFileSync(SKILL_SOURCE, t);
|
|
141
|
+
results.push({ ok: true, message: `skill 已安装: ${t}` });
|
|
142
|
+
} catch (err) {
|
|
143
|
+
results.push({ ok: false, message: `skill 安装失败 (${t}): ${err.message}` });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return { ok: results.some((r) => r.ok), results };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
// 主入口
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
function main() {
|
|
153
|
+
const hosts = detectHosts();
|
|
154
|
+
const lines = [];
|
|
155
|
+
|
|
156
|
+
lines.push("==================================================");
|
|
157
|
+
lines.push(" 甄仁岛 MCP Server - 一键初始化");
|
|
158
|
+
lines.push("==================================================");
|
|
159
|
+
lines.push("");
|
|
160
|
+
|
|
161
|
+
if (hosts.length === 0) {
|
|
162
|
+
lines.push("未检测到已安装的 Agent 宿主。可支持的宿主:");
|
|
163
|
+
lines.push(" - opencode (~/.config/opencode/opencode.json)");
|
|
164
|
+
lines.push(" - Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json)");
|
|
165
|
+
lines.push(" - Claude Code (~/.claude.json)");
|
|
166
|
+
lines.push(" - Cursor (~/.cursor/mcp.json)");
|
|
167
|
+
lines.push("");
|
|
168
|
+
lines.push("请先安装上述任一 Agent 后重新运行本命令。");
|
|
169
|
+
} else {
|
|
170
|
+
lines.push(`检测到 ${hosts.length} 个宿主:`);
|
|
171
|
+
for (const h of hosts) {
|
|
172
|
+
lines.push(` - ${h.label} (${h.configPath})`);
|
|
173
|
+
}
|
|
174
|
+
lines.push("");
|
|
175
|
+
|
|
176
|
+
// 1. 写入 MCP 配置
|
|
177
|
+
lines.push("[1/2] 写入 MCP 配置...");
|
|
178
|
+
for (const h of hosts) {
|
|
179
|
+
const r = writeMcpConfig(h);
|
|
180
|
+
lines.push(` ${r.ok ? "✓" : "✗"} ${h.label}: ${r.message}`);
|
|
181
|
+
}
|
|
182
|
+
lines.push("");
|
|
183
|
+
|
|
184
|
+
// 2. 安装原生 skill
|
|
185
|
+
lines.push("[2/2] 安装原生 skill (行为准则常驻系统提示词)...");
|
|
186
|
+
const skillResult = installNativeSkill();
|
|
187
|
+
if (skillResult.results) {
|
|
188
|
+
for (const r of skillResult.results) {
|
|
189
|
+
lines.push(` ${r.ok ? "✓" : "✗"} ${r.message}`);
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
lines.push(` ✗ ${skillResult.message}`);
|
|
193
|
+
}
|
|
194
|
+
lines.push("");
|
|
195
|
+
|
|
196
|
+
lines.push("--------------------------------------------------");
|
|
197
|
+
lines.push("下一步:");
|
|
198
|
+
lines.push(" 1. 重启你的 Agent (opencode / Claude / Cursor), 使配置生效");
|
|
199
|
+
lines.push(" 2. 向 Agent 说: “以星辰的身份向我打个招呼吧”");
|
|
200
|
+
lines.push(" 3. 安装咒语与手动配置说明见 INSTALL_GUIDE.md");
|
|
201
|
+
lines.push("==================================================");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
console.log(lines.join("\n"));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
main();
|