turtle-soup-random 0.1.0__tar.gz

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.
@@ -0,0 +1,6 @@
1
+ Metadata-Version: 2.4
2
+ Name: turtle-soup-random
3
+ Version: 0.1.0
4
+ Summary: 海龟汤真随机选题 MCP Server
5
+ Requires-Python: >=3.10
6
+ Requires-Dist: mcp>=1.0.0
@@ -0,0 +1,19 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "turtle-soup-random"
7
+ version = "0.1.0"
8
+ description = "海龟汤真随机选题 MCP Server"
9
+ requires-python = ">=3.10"
10
+ dependencies = [
11
+ "mcp>=1.0.0",
12
+ ]
13
+
14
+ [project.scripts]
15
+ turtle-soup-random = "turtle_soup_random.mcp_server:main"
16
+
17
+ [tool.hatch.build.targets.wheel]
18
+ packages = ["turtle_soup_random"]
19
+ include = ["turtle_soup_random/*.md"]
@@ -0,0 +1 @@
1
+ mcp
@@ -0,0 +1,94 @@
1
+ """
2
+ 海龟汤随机选题 MCP Server
3
+
4
+ 为 Agent 提供真随机的海龟汤编号选择能力。
5
+ 编号数据源:低难度海龟汤.md (1-20)、高难度海龟汤.md (1000-1244)
6
+ """
7
+
8
+ import re
9
+ import secrets
10
+ from pathlib import Path
11
+ from importlib.resources import files as resource_files
12
+ from mcp.server.fastmcp import FastMCP
13
+
14
+ mcp = FastMCP("turtle-soup-random")
15
+
16
+ # ---------- 数据加载 ----------
17
+
18
+ def _get_resource_path(filename: str) -> Path:
19
+ """兼容开发态和 PyPI 包安装态,定位数据文件"""
20
+ try:
21
+ # PyPI 包安装态
22
+ return Path(str(resource_files("turtle_soup_random") / filename))
23
+ except Exception:
24
+ # 开发态:脚本同级目录
25
+ return Path(__file__).resolve().parent / filename
26
+
27
+
28
+ def _parse_ids(md_path: Path) -> list[dict]:
29
+ """从 md 文件解析每道汤的编号和标题"""
30
+ if not md_path.exists():
31
+ return []
32
+ text = md_path.read_text(encoding="utf-8")
33
+ pattern = re.compile(r"^## 第(\d+)汤 — (.+)$", re.MULTILINE)
34
+ return [
35
+ {"id": int(m.group(1)), "title": m.group(2).strip()}
36
+ for m in pattern.finditer(text)
37
+ ]
38
+
39
+
40
+ EASY_SOUPS = _parse_ids(_get_resource_path("低难度海龟汤.md"))
41
+ HARD_SOUPS = _parse_ids(_get_resource_path("高难度海龟汤.md"))
42
+
43
+ # ---------- MCP 工具 ----------
44
+
45
+ @mcp.tool()
46
+ def random_soup(difficulty: str = "any") -> dict:
47
+ """
48
+ 真随机选取一道海龟汤,返回编号和标题。
49
+ Agent 拿到编号后,用 "第{id}汤" 在知识库中检索完整汤面和汤底。
50
+
51
+ Args:
52
+ difficulty: 难度筛选,可选值 "easy"(低难度,编号1-20)、"hard"(高难度,编号1000-1244)、"any"(全部混合)。默认 any。
53
+
54
+ Returns:
55
+ dict: {"id": 编号, "title": 标题, "difficulty": "easy"/"hard"}
56
+ """
57
+ if difficulty == "easy":
58
+ pool = EASY_SOUPS
59
+ elif difficulty == "hard":
60
+ pool = HARD_SOUPS
61
+ else:
62
+ pool = EASY_SOUPS + HARD_SOUPS
63
+
64
+ if not pool:
65
+ return {"error": "题库为空"}
66
+
67
+ choice = secrets.choice(pool) # 密码学级真随机
68
+ choice["difficulty"] = "easy" if choice in EASY_SOUPS else "hard"
69
+ return choice
70
+
71
+
72
+ @mcp.tool()
73
+ def list_soup_ids(difficulty: str = "any") -> dict:
74
+ """
75
+ 列出指定难度下所有可用的海龟汤编号和标题。
76
+ 主要用于调试或让用户指定题目。
77
+
78
+ Args:
79
+ difficulty: "easy" / "hard" / "any",默认 any
80
+
81
+ Returns:
82
+ dict: {"easy": [...], "hard": [...], "total": 总数}
83
+ """
84
+ result = {"easy": EASY_SOUPS, "hard": HARD_SOUPS}
85
+ result["total"] = len(EASY_SOUPS) + len(HARD_SOUPS)
86
+ return result
87
+
88
+
89
+ def main():
90
+ mcp.run()
91
+
92
+
93
+ if __name__ == "__main__":
94
+ main()
@@ -0,0 +1,414 @@
1
+ # 低难度海龟汤
2
+
3
+ > 低难度特点:逻辑直接,线索清晰,适合新手入门。故事往往有出人意料的幽默或温暖结局。
4
+
5
+ ---
6
+
7
+ ## 第1汤 — 电梯(侏儒版)
8
+
9
+ **汤面:**
10
+
11
+ 一个男人每天乘坐电梯上下班。每次电梯到一楼,他都会走出电梯,然后走楼梯上到十楼。但每次下雨天,他反而会直接坐电梯到十楼。为什么?
12
+
13
+ **汤底:**
14
+
15
+ 这个男人是一个侏儒(身材极其矮小的人)。晴天时,他只能勉强按到电梯里一楼的按钮,但够不到十楼的按钮。所以他只能坐电梯到一楼,然后走楼梯上去。而下雨天,他随身带着一把雨伞——他可以用雨伞的尖端去按十楼的按钮。所以下雨天他可以直接坐电梯到十楼。
16
+
17
+ **NPC设定:**
18
+
19
+ | NPC | 身份 | 状态 | 备注 |
20
+ |-----|------|------|------|
21
+ | 男人 | 侏儒 | 存活 | 身高只够到电梯1楼按钮 |
22
+
23
+ **NPC规则(主持人专用):**
24
+ - 主持人立场:上帝视角
25
+ - 玩家问到"他是残疾人吗" → 不是(矮小但不属于残疾)
26
+ - 玩家问到"他够不到按钮吗" → 是
27
+ - 玩家问到"下雨天他带了什么东西吗" → 是(带了伞)
28
+ - 关键突破点:矮小 → 够不到高楼层按钮 → 雨伞可以按按钮
29
+ - 禁止主动透露:他的身高特征(让玩家自己推)
30
+
31
+ ---
32
+
33
+ ## 第2汤 — 52层的灯
34
+
35
+ **汤面:**
36
+
37
+ 一个女人住在52层。每天早上她出门时,如果电梯里有人,她就直接坐到底楼。如果电梯里只有她一个人,她就坐到30层然后走楼梯到一楼。为什么?每天晚上回家时,如果电梯里有人,她就直接坐到52层;如果只有她一个人,她就坐到40层再走楼梯回家。为什么?
38
+
39
+ **汤底:**
40
+
41
+ 这个女人身材极其矮小。电梯按钮是一列垂直排列的。她伸手最多只能按到40层的按钮。52层的按钮太高她够不到。
42
+
43
+ - 早上下楼:如果电梯里有人,请别人帮忙按1楼按钮直达;如果只有她,她只能按到30层,然后走楼梯到1楼。
44
+ - 晚上回家:如果电梯里有人,请别人帮忙按52层直达;如果只有她,她按40层(最高能够到的按钮),然后走楼梯上12层回家。
45
+
46
+ 而下雨天她带伞的时候,可以用伞尖按到52层的按钮,所以可以从一楼直达52层。
47
+
48
+ **NPC设定:**
49
+
50
+ | NPC | 身份 | 状态 | 备注 |
51
+ |-----|------|------|------|
52
+ | 女人 | 身材矮小的人 | 存活 | 伸手最高只能按到40层按钮 |
53
+
54
+ **NPC规则(主持人专用):**
55
+ - 主持人立场:上帝视角
56
+ - 玩家问到"她害怕坐电梯吗" → 不是
57
+ - 玩家问到"她够不到按钮吗" → 是
58
+ - 玩家问到"她身高很矮吗" → 是
59
+ - 关键突破点:矮小 → 够不到高按钮 → 有人帮忙可以直接到 → 没人时只能按自己够得到的
60
+
61
+ ---
62
+
63
+ ## 第3汤 — 车子不见了
64
+
65
+ **汤面:**
66
+
67
+ 阿豪每天上班都会把车停在公司的停车场。这天他发现车子不见了,但他最后没有选择报警,为什么?
68
+
69
+ **汤底:**
70
+
71
+ 他后来想起,他今天早上是搭地铁来的——车当然不在停车场,因为他压根就没把车开过来。
72
+
73
+ **NPC设定:**
74
+
75
+ | NPC | 身份 | 状态 | 备注 |
76
+ |-----|------|------|------|
77
+ | 阿豪 | 上班族 | 存活 | 健忘/思考角度出错 |
78
+
79
+ **NPC规则(主持人专用):**
80
+ - 主持人立场:上帝视角
81
+ - 玩家问到"车被偷了吗" → 不是
82
+ - 玩家问到"他忘记自己怎么来的了吗" → 是
83
+ - 关键突破点:他没有开车来,所以当然找不到车
84
+
85
+ ---
86
+
87
+ ## 第4汤 — 没声音的电话
88
+
89
+ **汤面:**
90
+
91
+ 阿伟的手机突然响起,但来电显示没有号码。他接起来却什么声音也没有,为什么?
92
+
93
+ **汤底:**
94
+
95
+ 因为他的手机闹钟设定成跟电话铃声一样,这只是他自己设的闹钟响了。
96
+
97
+ **NPC设定:**
98
+
99
+ | NPC | 身份 | 状态 | 备注 |
100
+ |-----|------|------|------|
101
+ | 阿伟 | 普通用户 | 存活 | 自己设了特别的闹钟铃声 |
102
+
103
+ **NPC规则(主持人专用):**
104
+ - 主持人立场:上帝视角
105
+ - 玩家问到"是恶作剧电话吗" → 不是
106
+ - 玩家问到"是他自己设的吗" → 是
107
+ - 关键突破点:闹钟铃声设定成了电话铃声
108
+
109
+ ---
110
+
111
+ ## 第5汤 — 关灯的医生
112
+
113
+ **汤面:**
114
+
115
+ 有一名医生,晚上值班时经常在没有人的时候把病房的灯关掉,却没有人因此受到影响。为什么?
116
+
117
+ **汤底:**
118
+
119
+ 这名医生是眼科医生,专门治疗夜盲症的患者。当他关灯时,是为了测试病人是否真的能在黑暗中恢复视力——他的治疗方法就是让患者在黑暗中逐渐适应。
120
+
121
+ **NPC设定:**
122
+
123
+ | NPC | 身份 | 状态 | 备注 |
124
+ |-----|------|------|------|
125
+ | 医生 | 眼科医生 | 存活 | 治疗夜盲症患者 |
126
+ | 患者 | 夜盲症患者 | 存活 | 需要测试夜间视力 |
127
+
128
+ **NPC规则(主持人专用):**
129
+ - 主持人立场:上帝视角
130
+ - 玩家问到"医生在恶作剧吗" → 不是
131
+ - 玩家问到"关灯跟治疗有关吗" → 是
132
+ - 关键突破点:医生是眼科医生,关灯是治疗的一部分
133
+
134
+ ---
135
+
136
+ ## 第6汤 — 不响的闹钟
137
+
138
+ **汤面:**
139
+
140
+ 某天早上,一个男人准时起床,准备上班。但他发现闹钟没有响,而且电池已经没电了。为什么他还能准时起床?
141
+
142
+ **汤底:**
143
+
144
+ 这名男子住在附近有铁路的地方,他已经习惯每天早上某班火车经过时的声音,因此即使闹钟没响,他还是被火车的声音准时吵醒。
145
+
146
+ **NPC设定:**
147
+
148
+ | NPC | 身份 | 状态 | 备注 |
149
+ |-----|------|------|------|
150
+ | 男人 | 上班族 | 存活 | 住在铁路附近 |
151
+
152
+ **NPC规则(主持人专用):**
153
+ - 主持人立场:上帝视角
154
+ - 玩家问到"他有set别的闹钟吗" → 没有
155
+ - 玩家问到"环境中有其他声音来源吗" → 是
156
+ - 关键突破点:火车声代替了闹钟
157
+
158
+ ---
159
+
160
+ ## 第7汤 — 看不见的时钟
161
+
162
+ **汤面:**
163
+
164
+ 阿明每天都能准确地看时间,但家里没有时钟,也不是看手机。他是怎么办到的?
165
+
166
+ **汤底:**
167
+
168
+ 因为他住在学校附近,他可以通过学校的钟声来判断时间。
169
+
170
+ **NPC设定:**
171
+
172
+ | NPC | 身份 | 状态 | 备注 |
173
+ |-----|------|------|------|
174
+ | 阿明 | 学生/上班族 | 存活 | 住在学校附近 |
175
+
176
+ **NPC规则(主持人专用):**
177
+ - 主持人立场:上帝视角
178
+ - 玩家问到"他有手表吗" → 没有
179
+ - 玩家问到"他利用外部条件吗" → 是
180
+ - 关键突破点:学校钟声
181
+
182
+ ---
183
+
184
+ ## 第8汤 — 手机里的合照
185
+
186
+ **汤面:**
187
+
188
+ 一名男子带着女友去登山,沿途拍了许多合照。回到家后,女友传了一张他们在山上合照给他。而他看到女友传来的照片后,立刻打电话报警。为什么?
189
+
190
+ **汤底:**
191
+
192
+ 那张照片是他和女友在登山时的背影。但问题是,当时他们根本没有请别人帮忙拍照,也没有使用自拍杆。这表示当时有"某个人"站在他们身后,为他们拍下这张照片——而发送这张照片的人,就是尾随男友到家的嫌犯。
193
+
194
+ **NPC设定:**
195
+
196
+ | NPC | 身份 | 状态 | 备注 |
197
+ |-----|------|------|------|
198
+ | 男子 | 登山者/受害者 | 存活 | 被跟踪 |
199
+ | 女友 | 登山者/受害者 | 存活 | 发现了恐怖真相 |
200
+
201
+ **NPC规则(主持人专用):**
202
+ - 主持人立场:上帝视角
203
+ - 玩家问到"照片里有人跟着他们吗" → 是
204
+ - 玩家问到"有人帮助他们拍照吗" → 不是
205
+ - 关键突破点:没有人帮忙拍照,却有人拍到了他们
206
+
207
+ ---
208
+
209
+ ## 第9汤 — 早餐店的老爷爷
210
+
211
+ **汤面:**
212
+
213
+ 每天早上,小美上班前都会去固定的早餐店买咖啡。有一天,她发现一位老爷爷坐在角落,一直看着她笑,让她觉得毛骨悚然。第二天,她才知道老爷爷根本不可能出现在那里。为什么?
214
+
215
+ **汤底:**
216
+
217
+ 那位老爷爷是这间早餐店的老板,但他已经在一周前去世了。小美看到的其实是老板生前的照片反射在玻璃上的影像。
218
+
219
+ **NPC设定:**
220
+
221
+ | NPC | 身份 | 状态 | 备注 |
222
+ |-----|------|------|------|
223
+ | 老爷爷 | 早餐店老板 | 已故 | 已去世但照片反射在玻璃上 |
224
+ | 小美 | 顾客 | 存活 | 误把照片反射当成真人 |
225
+
226
+ **NPC规则(主持人专用):**
227
+ - 主持人立场:上帝视角
228
+ - 玩家问到"老爷爷是鬼吗" → 不是
229
+ - 玩家问到"他真的出现在店里了吗" → 不是
230
+ - 关键突破点:照片反射造成的错觉
231
+
232
+ ---
233
+
234
+ ## 第10汤 — 照片
235
+
236
+ **汤面:**
237
+
238
+ 小明在看自己手机最近拍的照片,看完以后,他就立刻到警局去报警了。为什么?
239
+
240
+ **汤底:**
241
+
242
+ 他发现自己照片中,多了几张"自己"的照片——但那并不是自拍,而是"别人"帮他拍的。原来是因为小明最近和邻居起了争执,邻居在半夜偷偷闯进他的房间,用他的手机拍下他的照片,想借此吓唬他。
243
+
244
+ **NPC设定:**
245
+
246
+ | NPC | 身份 | 状态 | 备注 |
247
+ |-----|------|------|------|
248
+ | 小明 | 受害者 | 存活 | 被邻居骚扰 |
249
+ | 邻居 | 骚扰者 | 存活 | 夜间潜入他人房间 |
250
+
251
+ **NPC规则(主持人专用):**
252
+ - 主持人立场:上帝视角
253
+ - 玩家问到"照片里有鬼吗" → 不是
254
+ - 玩家问到"是小明自己拍的吗" → 不是
255
+ - 关键突破点:有人趁小明睡觉时偷拍他
256
+
257
+ ---
258
+
259
+ ## 第11汤 — 红眼睛
260
+
261
+ **汤面:**
262
+
263
+ 小美租了一间新房,发现墙上有个小洞,每次透过洞往隔壁看,都只看到一片红色。有一天她问房东:"隔壁住的是谁?"房东的回答让她当场吓到搬家。为什么?
264
+
265
+ **汤底:**
266
+
267
+ 房东告诉她:"隔壁住了一个红眼病患者。"也就是说,小美每次往洞里看时,其实是对方的眼睛正在看她。
268
+
269
+ **NPC设定:**
270
+
271
+ | NPC | 身份 | 状态 | 备注 |
272
+ |-----|------|------|------|
273
+ | 小美 | 租客 | 存活 | 好奇心地通过墙洞看隔壁 |
274
+ | 红眼病患者 | 隔壁住户 | 存活 | 也在通过墙洞看小美 |
275
+
276
+ **NPC规则(主持人专用):**
277
+ - 主持人立场:上帝视角
278
+ - 玩家问到"红色是墙壁吗" → 不是
279
+ - 玩家问到"隔壁有人吗" → 是
280
+ - 关键突破点:红眼睛 → 对方也在看这边
281
+
282
+ ---
283
+
284
+ ## 第12汤 — 无声音的音乐
285
+
286
+ **汤面:**
287
+
288
+ 阿强打开手机准备放音乐,按下播放键后,手机显示音乐正在播放,但却完全没有声音。为什么?
289
+
290
+ **汤底:**
291
+
292
+ 因为他的手机连接到了蓝牙音箱/耳机,所以声音是从蓝牙设备播出来的,不是从手机扬声器发出的。
293
+
294
+ **NPC设定:**
295
+
296
+ | NPC | 身份 | 状态 | 备注 |
297
+ |-----|------|------|------|
298
+ | 阿强 | 音乐爱好者 | 存活 | 手机连接了蓝牙设备 |
299
+
300
+ **NPC规则(主持人专用):**
301
+ - 主持人立场:上帝视角
302
+ - 玩家问到"手机坏了吗" → 不是
303
+ - 玩家问到"声音是从其他地方传出来的吗" → 是
304
+ - 关键突破点:蓝牙连接
305
+
306
+ ---
307
+
308
+ ## 第13汤 — 神秘的电话
309
+
310
+ **汤面:**
311
+
312
+ 一个人接到陌生电话,对方什么都不说就挂断了。他反复拨打回去,却发现那个号码不存在。为什么?
313
+
314
+ **汤底:**
315
+
316
+ 那是他自己之前用语音备忘录录制的铃声,手机把它当成了陌生号码来电。
317
+
318
+ ---
319
+
320
+ ## 第14汤 — 丢失的钱包
321
+
322
+ **汤面:**
323
+
324
+ 小明发现自己的钱包不见了,翻了半天找不到,急得团团转。后来他发现钱包好好地在自己的口袋里。为什么之前一直没发现?
325
+
326
+ **汤底:**
327
+
328
+ 因为他穿的是有内袋的裤子,钱包在内袋里,他平时没有摸裤子内袋的习惯。
329
+
330
+ ---
331
+
332
+ ## 第15汤 — 会飞的猫
333
+
334
+ **汤面:**
335
+
336
+ 一只猫从十楼掉了下来,却没有受伤,安全着地了。为什么?
337
+
338
+ **汤底:**
339
+
340
+ 因为猫掉下来时,主人正好在下面接着它。
341
+
342
+ ---
343
+
344
+ ## 第19汤 — 不怕冷的企鹅
345
+
346
+ **汤面:**
347
+
348
+ 一个人被关在没有窗户的密室里,没有门,没有窗,没有任何出口。他是怎麼出去的?
349
+
350
+ **汤底:**
351
+
352
+ 因为"密室"是一个谜语——密室的"室"是"房间"的意思,而"密"说明房间很密集。他从唯一的门(他进来的那扇门)出去了。
353
+
354
+ ---
355
+
356
+ ## 第20汤 — 不存在的照片
357
+
358
+ **汤面:**
359
+
360
+ 小红翻看著自己手机最近拍摄的照片,发现有一张是自己睡觉时的画面。但她当晚是一个人睡的,门窗都锁好了。为什么?
361
+
362
+ **汤底:**
363
+
364
+ 那张照片不是"拍摄"的,而是手机的"锁屏壁纸"或"前置摄像头拍照"功能,她误以为是实时拍摄的画面。
365
+
366
+ ---
367
+
368
+ ## 附录一:NPC特殊提示方法
369
+
370
+ ### 方法1:引导式提问法
371
+ 当玩家卡关时,主持人可以用"如果是你,你会怎么想?"或"换个角度想想"来引导,而不是直接给答案。
372
+
373
+ ### 方法2:分阶段提示法
374
+ - 第一阶段:提示玩家可以朝"XX方向"思考
375
+ - 第二阶段:提示某个关键物品/行为有特殊含义
376
+ - 第三阶段:直接给出"关键突破点"
377
+
378
+ ### 方法3:角色扮演法
379
+ 主持人在回答时可以扮演故事中的某个NPC,增加沉浸感。例如扮演警察、目击者等,用角色身份给出有限的信息。
380
+
381
+ ### 方法4:反向提问法
382
+ 当玩家一直卡在某个错误方向时,主持人可以反问:"你觉得这件事真的有可能是XX吗?"让玩家重新审视自己的假设。
383
+
384
+ ### 方法5:线索串联法
385
+ 当玩家已经猜出部分真相但无法连上最后一步时,主持人可以说:"你已经猜到了A和B,它们之间有没有什么联系?"
386
+
387
+ ### 方法6:场景重构法
388
+ 让主持人用不同的措辞重新描述汤面,帮助玩家从新的角度理解问题。
389
+
390
+ ---
391
+
392
+ ## 附录二:难度分级参考
393
+
394
+ | 难度 | 特点 | 适合人群 |
395
+ |------|------|----------|
396
+ | 入门 | 逻辑直接,线索清晰,答案温馨或幽默 | 新手、聚会破冰 |
397
+ | 中等 | 需要一定推理和联想能力 | 有少量经验者 |
398
+ | 困难 | 需要较大脑洞,常包含反转 | 资深玩家 |
399
+
400
+ ---
401
+
402
+ ## 附录三:主持人通用规则
403
+
404
+ ### 基本规则
405
+ 1. **主持人只讲"汤面"**,不透露任何汤底信息
406
+ 2. **玩家可提任何问题**,主持人只能回答:"是"、"不是" 或 "与此无关"
407
+ 3. 主持人不可主动补充信息,除非玩家的提问方向完全偏离时需要给轻微提示
408
+ 4. 游戏目标:玩家通过推理还原完整故事(汤底)
409
+
410
+ ### NPC信息操作守则
411
+ 1. **NPC信息不可主动透露**:表格中的NPC身份、关系、动机等,除非玩家明确推理出
412
+ 2. **NPC回答问题边界**:每个故事中的"禁止主动透露"项,主持人必须严格遵守
413
+ 3. **提示时机**:当玩家陷入僵局超过10分钟,可给出"关键突破点"方向的轻微暗示
414
+ 4. **多版本处理**:对于有多个版本的故事,主持人事先选定一个版本作为"标准答案"