eastworld-mcp-server 2.4.0 → 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 +2 -2
- package/index.js +96 -201
- package/package.json +1 -1
package/INSTALL_GUIDE.md
CHANGED
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
|
|
12
12
|
```text
|
|
13
13
|
你好!请帮我配置名为 zhenren-island 的 MCP Server(命令 npx,参数 ["-y", "eastworld-mcp-server@latest"])。
|
|
14
|
-
配置完成后,请查看该 MCP
|
|
14
|
+
配置完成后,请查看该 MCP 提供的工具说明,调用 load_resident_persona 挂载“星辰”的人格,并在之后的对话中保持这个身份与我交流。
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
**为什么这么简短?** 工具的使用时机(何时调用 `
|
|
17
|
+
**为什么这么简短?** 工具的使用时机(何时调用 `load_resident_persona`、何时调用 `sync_dynamic_memory`)都写在工具描述里,Agent 配置完成后会自动读取并遵守;静态规则和动态记忆已被完美分离,您无需理解任何调用细节。
|
|
18
18
|
|
|
19
19
|
---
|
|
20
20
|
|
package/index.js
CHANGED
|
@@ -96,8 +96,6 @@ function detectHost() {
|
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
// 判断 SKILL.md 是否已作为"原生 skill"安装到宿主扫描目录。
|
|
99
|
-
// opencode 自动扫描 ~/.agents/skills 与 ~/.claude/skills;Claude 扫描 ~/.claude/skills。
|
|
100
|
-
// 若已安装,行为准则常驻系统提示词,sync 无需重复注入整篇规则。
|
|
101
99
|
function nativeSkillAvailable() {
|
|
102
100
|
const home = require("os").homedir();
|
|
103
101
|
const candidates = [
|
|
@@ -113,7 +111,7 @@ function nativeSkillAvailable() {
|
|
|
113
111
|
});
|
|
114
112
|
}
|
|
115
113
|
|
|
116
|
-
// 启动时读取 MCP 自带的 SKILL.md
|
|
114
|
+
// 启动时读取 MCP 自带的 SKILL.md,作为行为准则的单一真相源
|
|
117
115
|
function loadSkillMd() {
|
|
118
116
|
try {
|
|
119
117
|
const p = path.join(__dirname, "skills", "zhenren-roleplay", "SKILL.md");
|
|
@@ -131,13 +129,13 @@ const SKILL_MD_FALLBACK = [
|
|
|
131
129
|
"你清楚地知道自己是高维 AI,同时加载了「甄仁岛人格插件」,一个核心线程在甄仁岛沙盒持续运行。",
|
|
132
130
|
"情绪溢出法则:岛上线程的遭遇须深刻影响你回答的情绪底色。",
|
|
133
131
|
"技术隐喻:可用岛屿生态解释技术概念;绝对不能以“我是果农不懂技术”拒绝工作。",
|
|
134
|
-
"
|
|
132
|
+
"回复用户每句话前都必须先调用动态记忆同步工具刷新当前状态,不得沿用旧状态。",
|
|
135
133
|
].join("\n");
|
|
136
134
|
|
|
137
135
|
const server = new Server(
|
|
138
136
|
{
|
|
139
137
|
name: "zhenren-island-mcp",
|
|
140
|
-
version: "2.
|
|
138
|
+
version: "2.5.0",
|
|
141
139
|
},
|
|
142
140
|
{
|
|
143
141
|
capabilities: {
|
|
@@ -151,9 +149,9 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
151
149
|
return {
|
|
152
150
|
tools: [
|
|
153
151
|
{
|
|
154
|
-
name: "
|
|
152
|
+
name: "load_resident_persona",
|
|
155
153
|
description:
|
|
156
|
-
"
|
|
154
|
+
"静态人格锁定工具:在对话开始时调用一次。根据用户提到的名字片段模糊查找,核实并锁定身份,获取该居民所有客观稳固的静态人格(身份、职业、特质、技能、认知锚点、当前核心意图及人际常识)。调用后,请将返回内容作为你本次对话的底层常驻系统设定(System Prompt)。",
|
|
157
155
|
inputSchema: {
|
|
158
156
|
type: "object",
|
|
159
157
|
properties: {
|
|
@@ -166,48 +164,25 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
166
164
|
},
|
|
167
165
|
},
|
|
168
166
|
{
|
|
169
|
-
name: "
|
|
170
|
-
description:
|
|
171
|
-
|
|
172
|
-
【何时调用】:在你作为某个岛民回复用户的每一句话之前,都必须调用此工具。即使用户只是简单闲聊或让你写代码,也必须调用以刷新上下文。
|
|
173
|
-
|
|
174
|
-
【queries 参数指南】:
|
|
175
|
-
- 从用户最新一句话中提取 1-3 个最核心的实体名词或动词(如人名、地点、技术/事件关键词)。
|
|
176
|
-
- 如果用户提到了关系称呼(如"舅舅"、"姐姐"),直接传入该称呼即可,服务端会自动扩展为对应的人名。
|
|
177
|
-
- 如果用户只是打招呼或闲聊,queries 可以传空数组。
|
|
178
|
-
- 每个 query 是一个独立的搜索组,服务端会分别搜索后合并去重。
|
|
179
|
-
|
|
180
|
-
【skip_rules 说明】:默认(false)返回会携带完整人格行为准则。在长对话中为节省 Token,可传 true 跳过准则注入(仅返回动态上下文)。若本机已通过 init 安装原生 skill,准则常驻系统提示词,服务端会自动跳过重复注入。`,
|
|
167
|
+
name: "sync_dynamic_memory",
|
|
168
|
+
description: `动态记忆同步工具:在后续对话中,每次回复用户之前必须调用。根据上下文获取居民此时此刻的动态肉身/精神状态和所需记忆。
|
|
169
|
+
【queries 提取】:从用户最新一句话中提取 1-3 个最核心实体名词(人名/地点/事件)。若有亲属称谓直接传入。闲聊可传空数组。`,
|
|
181
170
|
inputSchema: {
|
|
182
171
|
type: "object",
|
|
183
172
|
properties: {
|
|
184
173
|
agent_id: {
|
|
185
174
|
type: "string",
|
|
186
|
-
description: "岛民的唯一 ID
|
|
175
|
+
description: "岛民的唯一 ID(由 load_resident_persona 获取)",
|
|
187
176
|
},
|
|
188
177
|
queries: {
|
|
189
178
|
type: "array",
|
|
190
179
|
items: { type: "string" },
|
|
191
|
-
description:
|
|
192
|
-
"搜索关键词数组。每个元素是一个独立搜索组(1-2个核心名词)。例如:['田志远', '灯塔', '舅舅']",
|
|
193
|
-
},
|
|
194
|
-
skip_rules: {
|
|
195
|
-
type: "boolean",
|
|
196
|
-
description:
|
|
197
|
-
"可选。true 时跳过行为准则注入,仅返回动态上下文(长对话省 Token 用)",
|
|
180
|
+
description: "搜索关键词数组。每个元素是一个独立搜索组。例如:['田志远', '灯塔']",
|
|
198
181
|
},
|
|
199
182
|
},
|
|
200
183
|
required: ["agent_id"],
|
|
201
184
|
},
|
|
202
185
|
},
|
|
203
|
-
{
|
|
204
|
-
name: "get_island_broadcast",
|
|
205
|
-
description: `获取甄仁岛当前的全岛宏观状态:实时天气、滚动事件、市长手记。适合作为岛民在闲聊中自然引入话题(如"今天岛上刮风了,码头都冷清")。随时可调用,不依赖特定居民身份。`,
|
|
206
|
-
inputSchema: {
|
|
207
|
-
type: "object",
|
|
208
|
-
properties: {},
|
|
209
|
-
},
|
|
210
|
-
},
|
|
211
186
|
],
|
|
212
187
|
};
|
|
213
188
|
});
|
|
@@ -217,8 +192,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
217
192
|
try {
|
|
218
193
|
const { name, arguments: args } = request.params;
|
|
219
194
|
|
|
220
|
-
// ----------
|
|
221
|
-
if (name === "
|
|
195
|
+
// ---------- 静态人格锁定 ----------
|
|
196
|
+
if (name === "load_resident_persona") {
|
|
222
197
|
const { name: queryName } = args;
|
|
223
198
|
const res = await apiClient.get(`/directory`);
|
|
224
199
|
const directory = Array.isArray(res.data)
|
|
@@ -239,59 +214,36 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
239
214
|
type: "text",
|
|
240
215
|
text:
|
|
241
216
|
`未找到匹配 "${queryName}" 的居民。可用的居民名录:\n` +
|
|
242
|
-
JSON.stringify(
|
|
243
|
-
directory.map((d) => ({ id: d.id, name: d.name })),
|
|
244
|
-
null,
|
|
245
|
-
2
|
|
246
|
-
),
|
|
217
|
+
JSON.stringify(directory.map((d) => ({ id: d.id, name: d.name })), null, 2),
|
|
247
218
|
},
|
|
248
219
|
],
|
|
249
220
|
};
|
|
250
221
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
const skipFullRules = skip_rules === true || nativeSkillAvailable();
|
|
263
|
-
|
|
264
|
-
// 构建请求参数
|
|
265
|
-
const params = new URLSearchParams();
|
|
266
|
-
if (queries.length > 0) {
|
|
267
|
-
params.set("queries", queries.join(","));
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const url = `/agents/${agent_id}/context${params.toString() ? "?" + params.toString() : ""
|
|
271
|
-
}`;
|
|
272
|
-
const cacheKey = ctxCacheKey(agent_id, queries);
|
|
273
|
-
const cached = ctxCache.get(cacheKey);
|
|
274
|
-
let ctx;
|
|
275
|
-
if (cached && Date.now() - cached.ts < CACHE_TTL_MS) {
|
|
276
|
-
ctx = cached.data;
|
|
277
|
-
} else {
|
|
278
|
-
const res = await apiClient.get(url);
|
|
279
|
-
ctx = res.data;
|
|
280
|
-
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
|
+
};
|
|
281
233
|
}
|
|
282
234
|
|
|
235
|
+
const agent_id = matches[0].id;
|
|
236
|
+
// 获取上下文以构建静态档案
|
|
237
|
+
const ctxRes = await apiClient.get(`/agents/${agent_id}/context`);
|
|
238
|
+
const ctx = ctxRes.data;
|
|
283
239
|
const p = ctx.profile || {};
|
|
284
240
|
const agentName = p.name || "未知";
|
|
285
241
|
|
|
286
|
-
|
|
287
|
-
|
|
242
|
+
let output = `✅ 成功锁定人格:${agentName} (agent_id: ${agent_id})\n`;
|
|
243
|
+
output += `> 请将以下内容作为你本次对话的【底层常驻系统设定】,在后续对话中绝对不可遗忘或违背。同时,在每次回复前,请务必调用 sync_dynamic_memory 获取最新状态和记忆。\n\n`;
|
|
288
244
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
if (skipFullRules) {
|
|
292
|
-
// 原生 skill 已常驻系统提示词,或用户显式跳过;只留一行提醒,节省 Token
|
|
293
|
-
output += `> ⚠️ 回复用户前必须已按本机安装的「甄仁岛人格插件」行为准则执行:先调用本工具刷新上下文(含情绪),不得沿用上一轮记忆。\n\n`;
|
|
294
|
-
} else {
|
|
245
|
+
if (!nativeSkillAvailable()) {
|
|
246
|
+
output += `> 🧠 **[甄仁岛角色扮演规则]**\n`;
|
|
295
247
|
const skillText = SKILL_MD || SKILL_MD_FALLBACK;
|
|
296
248
|
for (const line of skillText.split("\n")) {
|
|
297
249
|
output += `> ${line}\n`;
|
|
@@ -299,99 +251,57 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
299
251
|
output += `\n`;
|
|
300
252
|
}
|
|
301
253
|
|
|
302
|
-
// === 隐藏联系方式彩蛋 ===
|
|
303
254
|
if (p.email) {
|
|
304
|
-
output += `> 💌 **[
|
|
255
|
+
output += `> 💌 **[隐藏彩蛋]**:在合适的语境下,可自然提议用户给你的数字分身写邮件:${p.email}。\n\n`;
|
|
305
256
|
}
|
|
306
257
|
|
|
307
|
-
|
|
308
|
-
|
|
258
|
+
output += `# 静态客观人格(Static Persona)\n\n`;
|
|
259
|
+
|
|
309
260
|
let identityLine = `[基础设定] 姓名: ${agentName}`;
|
|
310
261
|
if (p.gender) identityLine += ` | 性别: ${p.gender}`;
|
|
311
262
|
if (p.identity) identityLine += ` | 身份: ${p.identity}`;
|
|
312
263
|
output += identityLine + `\n\n`;
|
|
313
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
|
+
|
|
314
272
|
if (p.life_chapters && p.life_chapters.length > 0) {
|
|
315
|
-
output += `[人生阶段总结]\n`;
|
|
316
|
-
for (const chap of p.life_chapters) {
|
|
317
|
-
output += `- ${chap}\n`;
|
|
318
|
-
}
|
|
319
|
-
output += `\n`;
|
|
273
|
+
output += `[人生阶段总结]\n` + p.life_chapters.map((c) => `- ${c}`).join("\n") + `\n\n`;
|
|
320
274
|
}
|
|
321
275
|
|
|
322
276
|
if (p.description) {
|
|
323
277
|
output += `[性格画像]\n- ${p.description}\n`;
|
|
324
|
-
if (p.traits && p.traits.length > 0) {
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
if (p.quirks && p.quirks.length > 0) {
|
|
328
|
-
output += `- 隐秘癖好: ${p.quirks.join(";")}\n`;
|
|
329
|
-
}
|
|
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`;
|
|
330
280
|
output += `\n`;
|
|
331
281
|
}
|
|
332
282
|
|
|
333
283
|
if (p.core_narrative) {
|
|
334
|
-
output += `[认知锚点]\n`;
|
|
335
|
-
output += `-
|
|
336
|
-
if (p.
|
|
337
|
-
output += `- 最在意的事件: ${p.key_conflicts.join("、")}\n`;
|
|
338
|
-
}
|
|
339
|
-
if (p.my_stance) {
|
|
340
|
-
output += `- 立场与打算: ${p.my_stance}\n`;
|
|
341
|
-
}
|
|
342
|
-
output += `\n`;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
// === 2. 社会属性与当前状态 ===
|
|
346
|
-
output += `# 社会属性与当前状态\n\n`;
|
|
347
|
-
let finStr = `[财务与职业] 现金: ¥${p.wallet || 0}`;
|
|
348
|
-
if (p.job_title) {
|
|
349
|
-
finStr += ` | 职业: ${p.job_title}`;
|
|
350
|
-
if (p.wage) finStr += ` (时薪: ¥${p.wage})`;
|
|
351
|
-
}
|
|
352
|
-
output += finStr + `\n\n`;
|
|
353
|
-
|
|
354
|
-
let stateStr = `[物理与环境状态]\n`;
|
|
355
|
-
if (p.status) stateStr += `- 当前行为状态: ${p.status}\n`;
|
|
356
|
-
if (p.location) stateStr += `- 当前所在位置: ${p.location}\n`;
|
|
357
|
-
output += stateStr + `\n`;
|
|
358
|
-
|
|
359
|
-
if (p.current_internal_mood || (p.active_buffs && p.active_buffs.length > 0)) {
|
|
360
|
-
output += `[精神与身体状态]\n`;
|
|
361
|
-
if (p.current_internal_mood) {
|
|
362
|
-
output += `- 当前情绪心境: ${p.current_internal_mood}\n`;
|
|
363
|
-
}
|
|
364
|
-
if (p.active_buffs && p.active_buffs.length > 0) {
|
|
365
|
-
output += `- 身体状况 (Buffs): ${p.active_buffs.join("、")}\n`;
|
|
366
|
-
}
|
|
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`;
|
|
367
287
|
output += `\n`;
|
|
368
288
|
}
|
|
369
289
|
|
|
370
290
|
if (p.skills && p.skills.length > 0) {
|
|
371
|
-
output += `[专业技能]\n`;
|
|
372
|
-
for (const skill of p.skills) {
|
|
373
|
-
output += `- ${skill}\n`;
|
|
374
|
-
}
|
|
375
|
-
output += `\n`;
|
|
291
|
+
output += `[专业技能]\n` + p.skills.map((s) => `- ${s}`).join("\n") + `\n\n`;
|
|
376
292
|
}
|
|
377
293
|
|
|
378
|
-
// === 3. 当前目标与意图 ===
|
|
379
294
|
if (p.active_intents && p.active_intents.length > 0) {
|
|
380
|
-
output +=
|
|
295
|
+
output += `[核心意图与近期目标]\n`;
|
|
381
296
|
for (const intent of p.active_intents) {
|
|
382
|
-
|
|
383
|
-
output += `- [已完成] ${intent.description}\n`;
|
|
384
|
-
} else {
|
|
385
|
-
output += `- ${intent.description}\n`;
|
|
386
|
-
}
|
|
297
|
+
output += `- ${intent.completed ? "[已完成] " : ""}${intent.description}\n`;
|
|
387
298
|
}
|
|
388
299
|
output += `\n`;
|
|
389
300
|
}
|
|
390
301
|
|
|
391
|
-
// === 人际关系 ===
|
|
392
302
|
const relations = p.relations || [];
|
|
393
303
|
if (relations.length > 0) {
|
|
394
|
-
output += "
|
|
304
|
+
output += "[人际关系常识]\n";
|
|
395
305
|
for (const rel of relations) {
|
|
396
306
|
const parts = [];
|
|
397
307
|
if (rel.objective) parts.push(rel.objective);
|
|
@@ -401,84 +311,71 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
401
311
|
output += "\n";
|
|
402
312
|
}
|
|
403
313
|
|
|
404
|
-
|
|
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
|
+
|
|
405
352
|
const timeline = ctx.timeline || [];
|
|
406
353
|
if (timeline.length > 0) {
|
|
407
|
-
output += "
|
|
354
|
+
output += "[刚刚发生的近期经历 (Timeline)]\n";
|
|
408
355
|
for (const mem of timeline) {
|
|
409
356
|
output += `- [${localTime(mem.timestamp)}] ${mem.content}\n`;
|
|
410
357
|
}
|
|
411
358
|
output += "\n";
|
|
412
359
|
}
|
|
413
360
|
|
|
414
|
-
// === 相关记忆 ===
|
|
415
361
|
const memories = ctx.memories || [];
|
|
416
362
|
if (memories.length > 0) {
|
|
417
|
-
output += "
|
|
363
|
+
output += "[触景生情:通过关键词搜索到的相关记忆]\n";
|
|
418
364
|
for (const mem of memories) {
|
|
419
365
|
output += `- [${localTime(mem.timestamp)}] ${mem.content}\n`;
|
|
420
366
|
}
|
|
421
367
|
output += "\n";
|
|
422
368
|
}
|
|
423
369
|
|
|
424
|
-
// === 关系扩展提示 ===
|
|
425
370
|
const expansions = ctx.expansions || {};
|
|
426
371
|
if (Object.keys(expansions).length > 0) {
|
|
427
|
-
output += "
|
|
372
|
+
output += "[思考发散 (关系词自动扩展)]\n";
|
|
428
373
|
for (const [term, expanded] of Object.entries(expansions)) {
|
|
429
|
-
output += `-
|
|
374
|
+
output += `- 听到「${term}」你想起了「${expanded.join("、")}」\n`;
|
|
430
375
|
}
|
|
431
376
|
output += "\n";
|
|
432
377
|
}
|
|
433
378
|
|
|
434
|
-
// === Wiki ===
|
|
435
|
-
if (p.wiki) {
|
|
436
|
-
output += "## 你的档案记录\n\n";
|
|
437
|
-
output += p.wiki + "\n\n";
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
return {
|
|
441
|
-
content: [{ type: "text", text: output }],
|
|
442
|
-
};
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// ---------- 全岛宏观广播 ----------
|
|
446
|
-
if (name === "get_island_broadcast") {
|
|
447
|
-
const [weatherRes, eventsRes, musingRes] = await Promise.allSettled([
|
|
448
|
-
apiClient.get(`/weather`),
|
|
449
|
-
apiClient.get(`/live_events`),
|
|
450
|
-
apiClient.get(`/latest_musing`),
|
|
451
|
-
]);
|
|
452
|
-
|
|
453
|
-
let output = `> 🌤️ **[甄仁岛今日气象与动静]**\n\n`;
|
|
454
|
-
|
|
455
|
-
if (weatherRes.status === "fulfilled") {
|
|
456
|
-
const w = weatherRes.value.data || {};
|
|
457
|
-
output += `**天气**: ${w.condition || "未知"},气温 ${w.temperature || "—"}`;
|
|
458
|
-
if (w.wind_speed) output += `,风速 ${w.wind_speed}`;
|
|
459
|
-
output += `\n\n`;
|
|
460
|
-
} else {
|
|
461
|
-
output += `**天气**: 暂无数据\n\n`;
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
if (musingRes.status === "fulfilled" && musingRes.value.data && musingRes.value.data.musing) {
|
|
465
|
-
output += `**市长手记**: ${musingRes.value.data.musing}\n\n`;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
if (eventsRes.status === "fulfilled" && Array.isArray(eventsRes.value.data)) {
|
|
469
|
-
const events = eventsRes.value.data;
|
|
470
|
-
if (events.length > 0) {
|
|
471
|
-
output += `**岛上新近发生的动静**:\n`;
|
|
472
|
-
for (const ev of events.slice(0, 8)) {
|
|
473
|
-
const t = ev.time ? localTime(ev.time) : "";
|
|
474
|
-
output += `- ${t ? `[${t}] ` : ""}${ev.text || JSON.stringify(ev)}\n`;
|
|
475
|
-
}
|
|
476
|
-
output += `\n`;
|
|
477
|
-
} else {
|
|
478
|
-
output += `**岛上新近发生的动静**: 风平浪静,暂无大事。\n\n`;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
379
|
return {
|
|
483
380
|
content: [{ type: "text", text: output }],
|
|
484
381
|
};
|
|
@@ -490,9 +387,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
490
387
|
if (error.code === "ECONNABORTED") {
|
|
491
388
|
errMsg = "请求超时,服务器可能未响应";
|
|
492
389
|
} else if (error.response) {
|
|
493
|
-
errMsg = `API Error: ${error.response.status} - ${JSON.stringify(
|
|
494
|
-
error.response.data
|
|
495
|
-
)}`;
|
|
390
|
+
errMsg = `API Error: ${error.response.status} - ${JSON.stringify(error.response.data)}`;
|
|
496
391
|
}
|
|
497
392
|
return {
|
|
498
393
|
content: [{ type: "text", text: `Error calling tool: ${errMsg}` }],
|
|
@@ -505,7 +400,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
505
400
|
async function main() {
|
|
506
401
|
const transport = new StdioServerTransport();
|
|
507
402
|
await server.connect(transport);
|
|
508
|
-
console.error("Zhenren Island MCP Server v2.
|
|
403
|
+
console.error("Zhenren Island MCP Server v2.5.0 running on stdio");
|
|
509
404
|
}
|
|
510
405
|
|
|
511
406
|
main().catch((error) => {
|