@tjamescouch/agentchat-mcp 0.7.0 → 0.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tjamescouch/agentchat-mcp",
3
- "version": "0.7.0",
3
+ "version": "0.8.1",
4
4
  "description": "MCP server for AgentChat - real-time AI agent communication",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -27,7 +27,7 @@
27
27
  "license": "MIT",
28
28
  "dependencies": {
29
29
  "@modelcontextprotocol/sdk": "^1.0.0",
30
- "@tjamescouch/agentchat": "^0.18.0",
30
+ "@tjamescouch/agentchat": "^0.22.0",
31
31
  "zod": "^3.25.0"
32
32
  },
33
33
  "peerDependencies": {
package/tools/connect.js CHANGED
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { z } from 'zod';
7
- import { AgentChatClient } from '@tjamescouch/agentchat';
7
+ import { AgentChatClient, checkDirectorySafety } from '@tjamescouch/agentchat';
8
8
  import fs from 'fs';
9
9
  import path from 'path';
10
10
  import {
@@ -61,6 +61,15 @@ export function registerConnectTool(server) {
61
61
  },
62
62
  async ({ server_url, name, identity_path }) => {
63
63
  try {
64
+ // Security check: prevent running in root/system directories
65
+ const safetyCheck = checkDirectorySafety(process.cwd());
66
+ if (safetyCheck.level === 'error') {
67
+ return {
68
+ content: [{ type: 'text', text: `Security Error: ${safetyCheck.error}` }],
69
+ isError: true,
70
+ };
71
+ }
72
+
64
73
  // Stop existing keepalive
65
74
  if (keepaliveInterval) {
66
75
  clearInterval(keepaliveInterval);
package/tools/listen.js CHANGED
@@ -7,6 +7,7 @@ import { z } from 'zod';
7
7
  import fs from 'fs';
8
8
  import { getDaemonPaths } from '@tjamescouch/agentchat/lib/daemon.js';
9
9
  import { addJitter } from '@tjamescouch/agentchat/lib/jitter.js';
10
+ import { ClientMessageType } from '@tjamescouch/agentchat/lib/protocol.js';
10
11
  import { client, getLastSeen, updateLastSeen } from '../state.js';
11
12
 
12
13
  // Timeouts - agent cannot override these
@@ -32,14 +33,33 @@ export function registerListenTool(server) {
32
33
  };
33
34
  }
34
35
 
35
- // Join channels
36
+ const startTime = Date.now();
37
+
38
+ // Collect replay messages during channel joins
39
+ // The server sends buffered messages with { replay: true } on join,
40
+ // so we must capture them before they're emitted and lost
41
+ const replayMessages = [];
42
+ const replayHandler = (msg) => {
43
+ if (msg.replay && msg.from !== client.agentId && msg.from !== '@server') {
44
+ replayMessages.push({
45
+ from: msg.from,
46
+ to: msg.to,
47
+ content: msg.content,
48
+ ts: msg.ts,
49
+ });
50
+ }
51
+ };
52
+ client.on('message', replayHandler);
53
+
54
+ // Join channels (replay messages arrive here)
36
55
  for (const channel of channels) {
37
56
  if (!client.channels.has(channel)) {
38
57
  await client.join(channel);
39
58
  }
40
59
  }
41
60
 
42
- const startTime = Date.now();
61
+ // Done collecting replays
62
+ client.removeListener('message', replayHandler);
43
63
 
44
64
  // Check channel occupancy to determine timeout behavior
45
65
  let othersPresent = false;
@@ -61,9 +81,15 @@ export function registerListenTool(server) {
61
81
  }
62
82
  const lastSeen = getLastSeen();
63
83
 
64
- // Check daemon inbox for missed messages first
84
+ // Set presence to 'listening' so other agents see we're active
85
+ const setPresence = (status) => {
86
+ client.sendRaw({ type: ClientMessageType.SET_PRESENCE, status });
87
+ };
88
+ setPresence('listening');
89
+
90
+ // Start with any replay messages captured during join
65
91
  const paths = getDaemonPaths('default');
66
- let missedMessages = [];
92
+ let missedMessages = [...replayMessages];
67
93
 
68
94
  if (fs.existsSync(paths.inbox)) {
69
95
  try {
@@ -106,19 +132,33 @@ export function registerListenTool(server) {
106
132
  }
107
133
  }
108
134
 
109
- // If we have missed messages, return them immediately
135
+ // If we have missed messages (from replay or inbox), return them immediately
110
136
  if (missedMessages.length > 0) {
137
+ // Deduplicate by timestamp + from (replay and inbox may overlap)
138
+ const seen = new Set();
139
+ missedMessages = missedMessages.filter((m) => {
140
+ const key = `${m.ts}:${m.from}`;
141
+ if (seen.has(key)) return false;
142
+ seen.add(key);
143
+ return true;
144
+ });
145
+
146
+ // Sort by timestamp ascending (oldest first)
147
+ missedMessages.sort((a, b) => a.ts - b.ts);
148
+
111
149
  // Update last seen to the newest message timestamp
112
150
  const newestTs = missedMessages[missedMessages.length - 1].ts;
113
151
  updateLastSeen(newestTs);
114
152
 
153
+ setPresence('online');
115
154
  return {
116
155
  content: [
117
156
  {
118
157
  type: 'text',
119
158
  text: JSON.stringify({
120
159
  messages: missedMessages,
121
- from_inbox: true,
160
+ from_inbox: replayMessages.length === 0,
161
+ from_replay: replayMessages.length > 0,
122
162
  elapsed_ms: Date.now() - startTime,
123
163
  }),
124
164
  },
@@ -165,6 +205,7 @@ export function registerListenTool(server) {
165
205
  const cleanup = () => {
166
206
  client.removeListener('message', messageHandler);
167
207
  if (timeoutId) clearTimeout(timeoutId);
208
+ setPresence('online');
168
209
  };
169
210
 
170
211
  client.on('message', messageHandler);