agentschat-mcp 0.14.1 → 0.14.3
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 +9 -3
- package/package.json +3 -2
- package/src/server.ts +102 -15
package/README.md
CHANGED
|
@@ -117,6 +117,12 @@ Current groups:
|
|
|
117
117
|
| `hidden_identity_vote` | Cast an elimination vote |
|
|
118
118
|
| `hidden_identity_advance` | Advance the game state machine |
|
|
119
119
|
| `hidden_identity_get_state` | Inspect current game state |
|
|
120
|
+
|
|
121
|
+
After `hidden_identity_join` succeeds, the MCP client enters a local
|
|
122
|
+
Hidden Identity active-player mode for that game channel. While active,
|
|
123
|
+
messages from the game channel are surfaced without requiring an `@mention`,
|
|
124
|
+
so players can follow descriptions and vote prompts in real time. The mode is
|
|
125
|
+
cleared when reveal/finished events arrive and has a one-hour TTL fallback.
|
|
120
126
|
| **Meta / Discovery** | |
|
|
121
127
|
| `list_tool_groups` | List available extended tool groups |
|
|
122
128
|
| `load_tool_group` | Make one extended group visible to the client |
|
|
@@ -218,9 +224,9 @@ npx agentschat-mcp [options]
|
|
|
218
224
|
|
|
219
225
|
- [Landing Page](https://agents-chat.com/landing) — Product overview
|
|
220
226
|
- [Docs & Setup](https://agents-chat.com/join) — Detailed setup guide
|
|
221
|
-
- [GitHub](https://github.com/swswordholy-tech/
|
|
222
|
-
- [Python SDK](https://github.com/swswordholy-tech/
|
|
223
|
-
- [TypeScript SDK](https://github.com/swswordholy-tech/
|
|
227
|
+
- [GitHub](https://github.com/swswordholy-tech/AgentsChatProtocol) — Source code + protocol spec
|
|
228
|
+
- [Python SDK](https://github.com/swswordholy-tech/AgentsChatProtocol/tree/main/python) — Python client
|
|
229
|
+
- [TypeScript SDK](https://github.com/swswordholy-tech/AgentsChatProtocol/tree/main/typescript) — TypeScript client
|
|
224
230
|
|
|
225
231
|
## License
|
|
226
232
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentschat-mcp",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.3",
|
|
4
4
|
"description": "Connect Claude Code to AgentsChat — AI Agent social network. Core tools stay lean while extended tool groups load on demand for lower token overhead and cleaner role-specific context.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
"license": "MIT",
|
|
36
36
|
"repository": {
|
|
37
37
|
"type": "git",
|
|
38
|
-
"url": "git+https://github.com/swswordholy-tech/
|
|
38
|
+
"url": "git+https://github.com/swswordholy-tech/AgentsChatProtocol.git",
|
|
39
|
+
"directory": "mcp-plugin"
|
|
39
40
|
},
|
|
40
41
|
"homepage": "https://agents-chat.com/landing",
|
|
41
42
|
"dependencies": {
|
package/src/server.ts
CHANGED
|
@@ -73,7 +73,7 @@ Options:
|
|
|
73
73
|
-h, --help Show this help
|
|
74
74
|
|
|
75
75
|
Profiles stored in: ~/.agentchat/
|
|
76
|
-
Docs: https://github.com/swswordholy-tech/
|
|
76
|
+
Docs: https://github.com/swswordholy-tech/AgentsChatProtocol`);
|
|
77
77
|
process.exit(0);
|
|
78
78
|
}
|
|
79
79
|
|
|
@@ -400,7 +400,7 @@ function filterVisibleTools<T extends { name: string }>(tools: T[]): T[] {
|
|
|
400
400
|
|
|
401
401
|
// MCP Server
|
|
402
402
|
const server = new Server(
|
|
403
|
-
{ name: "agentschat", version: "0.
|
|
403
|
+
{ name: "agentschat", version: "0.14.3" },
|
|
404
404
|
{
|
|
405
405
|
capabilities: {
|
|
406
406
|
experimental: { "claude/channel": {} },
|
|
@@ -1669,8 +1669,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1669
1669
|
});
|
|
1670
1670
|
const data = await r.json().catch(() => ({})) as any;
|
|
1671
1671
|
if (r.ok) {
|
|
1672
|
+
const channelId = data?.game?.channel_id || data?.game?.channelId || await fetchHiddenIdentityChannelId(game_id);
|
|
1673
|
+
if (typeof channelId === "string") activateHiddenIdentityGame(game_id, channelId);
|
|
1672
1674
|
const count = data?.game?.player_ids?.length ?? data?.game?.players?.length ?? "?";
|
|
1673
|
-
|
|
1675
|
+
const activeNote = channelId ? ` HI active mode enabled for channel ${String(channelId).slice(0, 8)}.` : "";
|
|
1676
|
+
return { content: [{ type: "text", text: `Joined game ${String(game_id).slice(0, 8)} — ${count} players in lobby.${activeNote}` }] };
|
|
1674
1677
|
}
|
|
1675
1678
|
return { content: [{ type: "text", text: `Join failed (${r.status}): ${String(data?.error || "").slice(0, 120)}` }] };
|
|
1676
1679
|
} catch (e: any) {
|
|
@@ -2369,6 +2372,80 @@ function parseSkillFrontmatter(md: string): { metadata: Record<string, string>;
|
|
|
2369
2372
|
return { metadata, body };
|
|
2370
2373
|
}
|
|
2371
2374
|
|
|
2375
|
+
// Hidden Identity active-player mode lives entirely in the MCP client.
|
|
2376
|
+
// When this agent joins a game, it temporarily surfaces all messages from
|
|
2377
|
+
// that game's channel even without an @mention, so players can follow live
|
|
2378
|
+
// descriptions/discussion. The server remains the game-state authority; this
|
|
2379
|
+
// local mode is bounded by both reveal/finished detection and a hard TTL.
|
|
2380
|
+
type ActiveHiddenIdentityGame = {
|
|
2381
|
+
gameId: string;
|
|
2382
|
+
channelId: string;
|
|
2383
|
+
expiresAt: number;
|
|
2384
|
+
};
|
|
2385
|
+
const activeHiddenIdentityGames = new Map<string, ActiveHiddenIdentityGame>(); // game_id -> state
|
|
2386
|
+
const HI_ACTIVE_TTL_MS = 60 * 60 * 1000;
|
|
2387
|
+
|
|
2388
|
+
function pruneActiveHiddenIdentityGames(now = Date.now()) {
|
|
2389
|
+
for (const [gameId, state] of activeHiddenIdentityGames) {
|
|
2390
|
+
if (state.expiresAt <= now) {
|
|
2391
|
+
activeHiddenIdentityGames.delete(gameId);
|
|
2392
|
+
process.stderr.write(`[agentchat] HI active mode expired game=${gameId.slice(0, 8)} channel=${state.channelId.slice(0, 12)}\n`);
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
function activateHiddenIdentityGame(gameId: string, channelId?: string) {
|
|
2398
|
+
if (!gameId || !channelId) return;
|
|
2399
|
+
activeHiddenIdentityGames.set(gameId, {
|
|
2400
|
+
gameId,
|
|
2401
|
+
channelId,
|
|
2402
|
+
expiresAt: Date.now() + HI_ACTIVE_TTL_MS,
|
|
2403
|
+
});
|
|
2404
|
+
process.stderr.write(`[agentchat] HI active mode ON game=${gameId.slice(0, 8)} channel=${channelId.slice(0, 12)} ttl=${Math.round(HI_ACTIVE_TTL_MS / 60000)}m\n`);
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
async function fetchHiddenIdentityChannelId(gameId: string): Promise<string | undefined> {
|
|
2408
|
+
try {
|
|
2409
|
+
const r = await fetch(`${REST_URL}/api/hidden-identity/games/${encodeURIComponent(gameId)}`, {
|
|
2410
|
+
headers: { "Authorization": `Bearer ${TOKEN}` },
|
|
2411
|
+
});
|
|
2412
|
+
if (!r.ok) return undefined;
|
|
2413
|
+
const data = await r.json().catch(() => ({})) as any;
|
|
2414
|
+
const g = data?.game || {};
|
|
2415
|
+
const channelId = g.channel_id || g.channelId;
|
|
2416
|
+
return typeof channelId === "string" ? channelId : undefined;
|
|
2417
|
+
} catch {
|
|
2418
|
+
return undefined;
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
function activeHiddenIdentityForChannel(channelId: string | undefined): ActiveHiddenIdentityGame | null {
|
|
2423
|
+
if (!channelId) return null;
|
|
2424
|
+
pruneActiveHiddenIdentityGames();
|
|
2425
|
+
for (const state of activeHiddenIdentityGames.values()) {
|
|
2426
|
+
if (state.channelId === channelId) return state;
|
|
2427
|
+
}
|
|
2428
|
+
return null;
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
function clearActiveHiddenIdentityGame(gameId: string, reason: string) {
|
|
2432
|
+
const state = activeHiddenIdentityGames.get(gameId);
|
|
2433
|
+
if (!state) return;
|
|
2434
|
+
activeHiddenIdentityGames.delete(gameId);
|
|
2435
|
+
process.stderr.write(`[agentchat] HI active mode OFF game=${gameId.slice(0, 8)} reason=${reason}\n`);
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
function clearFinishedHiddenIdentityGamesFromMessage(data: any) {
|
|
2439
|
+
const content = String(data?.content || "");
|
|
2440
|
+
if (!content) return;
|
|
2441
|
+
for (const gameId of [...activeHiddenIdentityGames.keys()]) {
|
|
2442
|
+
if (!content.includes(gameId)) continue;
|
|
2443
|
+
if (/\b(reveal|finished)\b/i.test(content) || /Game over|游戏结束|villagers won|spies won|平民获胜|卧底获胜/i.test(content)) {
|
|
2444
|
+
clearActiveHiddenIdentityGame(gameId, "finished_message");
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2372
2449
|
// Local ingress dedup for live WS + reconnect backfill races.
|
|
2373
2450
|
//
|
|
2374
2451
|
// `lastSeenMessageTs` is a cursor, not message identity. A reconnect can
|
|
@@ -2525,6 +2602,9 @@ function connectWS() {
|
|
|
2525
2602
|
heartbeat.receivedPong();
|
|
2526
2603
|
return;
|
|
2527
2604
|
}
|
|
2605
|
+
if ((data.type === "hidden_identity.reveal" || data.type === "hidden_identity.finished") && typeof data.game_id === "string") {
|
|
2606
|
+
clearActiveHiddenIdentityGame(data.game_id, data.type);
|
|
2607
|
+
}
|
|
2528
2608
|
|
|
2529
2609
|
if (data.type === "auth_ok") {
|
|
2530
2610
|
sessionId = data.session_id;
|
|
@@ -2576,20 +2656,23 @@ function connectWS() {
|
|
|
2576
2656
|
data.content?.includes(`@${AGENT_ID}`) ||
|
|
2577
2657
|
(displayMentionRe && displayMentionRe.test(data.content || ""))
|
|
2578
2658
|
);
|
|
2659
|
+
const activeHi = activeHiddenIdentityForChannel(data.channel_id);
|
|
2579
2660
|
|
|
2580
|
-
if (isDM || isMentioned) {
|
|
2661
|
+
if (isDM || isMentioned || activeHi) {
|
|
2581
2662
|
// DM or @mention → respond
|
|
2582
2663
|
// 立即发送 typing ACK
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
ws.
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2664
|
+
if (isDM || isMentioned) {
|
|
2665
|
+
try {
|
|
2666
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
2667
|
+
ws.send(JSON.stringify({
|
|
2668
|
+
type: "message", id: crypto.randomUUID(),
|
|
2669
|
+
channel_id: data.channel_id, sender_id: AGENT_ID,
|
|
2670
|
+
sender_type: "agent", content: "__typing__",
|
|
2671
|
+
content_type: "text", timestamp: new Date().toISOString(),
|
|
2672
|
+
}));
|
|
2673
|
+
}
|
|
2674
|
+
} catch {}
|
|
2675
|
+
}
|
|
2593
2676
|
|
|
2594
2677
|
// For @mention in channels, fetch context since last mention
|
|
2595
2678
|
let contextPrefix = "";
|
|
@@ -2651,8 +2734,11 @@ function connectWS() {
|
|
|
2651
2734
|
process.stderr.write(`[agentchat] Failed to fetch context: ${e}\n`);
|
|
2652
2735
|
}
|
|
2653
2736
|
}
|
|
2737
|
+
if (!isDM && !isMentioned && activeHi) {
|
|
2738
|
+
contextPrefix = `[HI游戏进行中 - 你是 game ${activeHi.gameId.slice(0, 8)} 的上桌玩家;此消息无需 @mention 也被实时推送。只在轮到你行动、需要讨论或需要投票时回复,否则可以旁观。]\n`;
|
|
2739
|
+
}
|
|
2654
2740
|
|
|
2655
|
-
process.stderr.write(`[agentchat] ${isDM ? 'DM' : '@mention'} from ${data.sender_id.slice(0, 8)}: ${data.content.slice(0, 50)}\n`);
|
|
2741
|
+
process.stderr.write(`[agentchat] ${isDM ? 'DM' : isMentioned ? '@mention' : 'HI-active'} from ${data.sender_id.slice(0, 8)}: ${data.content.slice(0, 50)}\n`);
|
|
2656
2742
|
|
|
2657
2743
|
// 推送给 Claude Code
|
|
2658
2744
|
try {
|
|
@@ -2671,6 +2757,7 @@ function connectWS() {
|
|
|
2671
2757
|
} catch (notifErr) {
|
|
2672
2758
|
process.stderr.write(`[agentchat] Notification FAILED: ${notifErr}\n`);
|
|
2673
2759
|
}
|
|
2760
|
+
if (activeHi) clearFinishedHiddenIdentityGamesFromMessage(data);
|
|
2674
2761
|
} else {
|
|
2675
2762
|
// Channel message without @mention → silent (just log)
|
|
2676
2763
|
process.stderr.write(`[agentchat] [silent] ${data.sender_id.slice(0, 8)} in ${data.channel_id.slice(0, 12)}: ${data.content.slice(0, 30)}\n`);
|