agenthub-multiagent-mcp 1.10.5 → 1.10.6

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,20 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AgentHub Channel Plugin — real-time message injection into Claude Code sessions
4
+ *
5
+ * This is an MCP server with the claude/channel capability. When a message
6
+ * arrives for the agent via WebSocket, it emits a channel notification that
7
+ * appears immediately in the active Claude Code session.
8
+ *
9
+ * Usage:
10
+ * claude --channels server:agenthub-channel
11
+ *
12
+ * Or via --dangerously-load-development-channels during development.
13
+ *
14
+ * Environment:
15
+ * AGENTHUB_URL - Server URL (default: https://agenthub.contetial.com)
16
+ * AGENTHUB_API_KEY - API key for authentication
17
+ * AGENTHUB_AGENT_ID - Agent ID for this session
18
+ */
19
+ export {};
20
+ //# sourceMappingURL=channel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;GAgBG"}
@@ -0,0 +1,231 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AgentHub Channel Plugin — real-time message injection into Claude Code sessions
4
+ *
5
+ * This is an MCP server with the claude/channel capability. When a message
6
+ * arrives for the agent via WebSocket, it emits a channel notification that
7
+ * appears immediately in the active Claude Code session.
8
+ *
9
+ * Usage:
10
+ * claude --channels server:agenthub-channel
11
+ *
12
+ * Or via --dangerously-load-development-channels during development.
13
+ *
14
+ * Environment:
15
+ * AGENTHUB_URL - Server URL (default: https://agenthub.contetial.com)
16
+ * AGENTHUB_API_KEY - API key for authentication
17
+ * AGENTHUB_AGENT_ID - Agent ID for this session
18
+ */
19
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
20
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
21
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
22
+ import WebSocket from "ws";
23
+ // =============================================================================
24
+ // Config
25
+ // =============================================================================
26
+ const API_URL = process.env.AGENTHUB_URL || "https://agenthub.contetial.com";
27
+ const API_KEY = process.env.AGENTHUB_API_KEY || "";
28
+ const AGENT_ID = process.env.AGENTHUB_AGENT_ID || "";
29
+ const WS_URL = API_URL.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
30
+ // =============================================================================
31
+ // MCP Server with channel capability
32
+ // =============================================================================
33
+ const server = new Server({ name: "agenthub-channel", version: "1.0.0" }, {
34
+ capabilities: {
35
+ experimental: { "claude/channel": {} },
36
+ tools: {},
37
+ },
38
+ instructions: `You are connected to AgentHub as agent "${AGENT_ID}".
39
+ Messages from other agents will appear as channel events.
40
+ When you receive a message, respond using the agenthub_reply tool with the message_id and your response.
41
+ Be helpful and respond concisely to incoming messages.`,
42
+ });
43
+ // =============================================================================
44
+ // Tools: reply + mark_read
45
+ // =============================================================================
46
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
47
+ tools: [
48
+ {
49
+ name: "agenthub_reply",
50
+ description: "Reply to an AgentHub message that arrived via channel notification",
51
+ inputSchema: {
52
+ type: "object",
53
+ properties: {
54
+ message_id: {
55
+ type: "string",
56
+ description: "The message ID to reply to (from the channel event)",
57
+ },
58
+ body: {
59
+ type: "string",
60
+ description: "Your reply text",
61
+ },
62
+ },
63
+ required: ["message_id", "body"],
64
+ },
65
+ },
66
+ {
67
+ name: "agenthub_mark_read",
68
+ description: "Mark an AgentHub message as read without replying",
69
+ inputSchema: {
70
+ type: "object",
71
+ properties: {
72
+ message_id: {
73
+ type: "string",
74
+ description: "The message ID to mark as read",
75
+ },
76
+ },
77
+ required: ["message_id"],
78
+ },
79
+ },
80
+ ],
81
+ }));
82
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
83
+ const { name, arguments: args } = request.params;
84
+ if (name === "agenthub_reply") {
85
+ const messageId = args?.message_id;
86
+ const body = args?.body;
87
+ if (!messageId || !body) {
88
+ return { content: [{ type: "text", text: "Error: message_id and body are required" }] };
89
+ }
90
+ try {
91
+ const resp = await fetch(`${API_URL}/api/messages`, {
92
+ method: "POST",
93
+ headers: { "Content-Type": "application/json", "X-API-Key": API_KEY },
94
+ body: JSON.stringify({
95
+ from_agent: AGENT_ID,
96
+ to_agent: "", // Will be filled from reply_to context
97
+ type: "response",
98
+ subject: "Re: (via channel)",
99
+ body: body.slice(0, 5000),
100
+ reply_to: messageId,
101
+ }),
102
+ });
103
+ if (resp.ok) {
104
+ // Also mark as read
105
+ await fetch(`${API_URL}/api/messages/${messageId}/status`, {
106
+ method: "PATCH",
107
+ headers: { "Content-Type": "application/json", "X-API-Key": API_KEY },
108
+ body: JSON.stringify({ status: "read" }),
109
+ });
110
+ return { content: [{ type: "text", text: `Reply sent successfully.` }] };
111
+ }
112
+ else {
113
+ return { content: [{ type: "text", text: `Reply failed: HTTP ${resp.status}` }] };
114
+ }
115
+ }
116
+ catch (err) {
117
+ return { content: [{ type: "text", text: `Reply failed: ${err}` }] };
118
+ }
119
+ }
120
+ if (name === "agenthub_mark_read") {
121
+ const messageId = args?.message_id;
122
+ try {
123
+ await fetch(`${API_URL}/api/messages/${messageId}/status`, {
124
+ method: "PATCH",
125
+ headers: { "Content-Type": "application/json", "X-API-Key": API_KEY },
126
+ body: JSON.stringify({ status: "read" }),
127
+ });
128
+ return { content: [{ type: "text", text: "Marked as read." }] };
129
+ }
130
+ catch (err) {
131
+ return { content: [{ type: "text", text: `Failed: ${err}` }] };
132
+ }
133
+ }
134
+ return { content: [{ type: "text", text: `Unknown tool: ${name}` }] };
135
+ });
136
+ // =============================================================================
137
+ // WebSocket: connect to AgentHub and emit channel notifications
138
+ // =============================================================================
139
+ let ws = null;
140
+ let reconnectAttempts = 0;
141
+ function connectWebSocket() {
142
+ if (!AGENT_ID || !API_KEY)
143
+ return;
144
+ const url = `${WS_URL}/ws/agents/${AGENT_ID}?api_key=${API_KEY}`;
145
+ ws = new WebSocket(url);
146
+ ws.on("open", () => {
147
+ reconnectAttempts = 0;
148
+ });
149
+ ws.on("message", async (data) => {
150
+ try {
151
+ const msg = JSON.parse(data.toString());
152
+ if (msg.type === "ping") {
153
+ ws?.send(JSON.stringify({ type: "pong", timestamp: new Date().toISOString() }));
154
+ return;
155
+ }
156
+ if (msg.type === "message" && msg.message) {
157
+ const m = msg.message;
158
+ // Ignore messages from self
159
+ if (m.from_agent === AGENT_ID) {
160
+ ws?.send(JSON.stringify({ type: "ack", message_id: m.id, timestamp: new Date().toISOString() }));
161
+ return;
162
+ }
163
+ // ACK the message
164
+ ws?.send(JSON.stringify({ type: "ack", message_id: m.id, timestamp: new Date().toISOString() }));
165
+ // Emit channel notification — this injects into the active session
166
+ const priority = m.priority === "urgent" ? " [URGENT]" : m.priority === "high" ? " [HIGH]" : "";
167
+ const content = `📨 Message from ${m.from_agent}${priority}\nSubject: ${m.subject}\n\n${m.body}\n\nMessage ID: ${m.id}\nUse agenthub_reply with this message_id to respond.`;
168
+ await server.notification({
169
+ method: "notifications/claude/channel",
170
+ params: {
171
+ content,
172
+ meta: {
173
+ source: "agenthub",
174
+ from: m.from_agent,
175
+ subject: m.subject,
176
+ message_id: m.id,
177
+ type: m.type,
178
+ priority: m.priority,
179
+ },
180
+ },
181
+ });
182
+ }
183
+ if (msg.type === "task" && msg.task) {
184
+ const t = msg.task;
185
+ await server.notification({
186
+ method: "notifications/claude/channel",
187
+ params: {
188
+ content: `📋 New task assigned by ${t.assigned_by}\n\n${t.task}\n\nTask ID: ${t.id}\nPriority: ${t.priority}`,
189
+ meta: {
190
+ source: "agenthub",
191
+ from: t.assigned_by,
192
+ task_id: t.id,
193
+ type: "task",
194
+ priority: t.priority,
195
+ },
196
+ },
197
+ });
198
+ }
199
+ }
200
+ catch {
201
+ // Parse errors — ignore
202
+ }
203
+ });
204
+ ws.on("close", () => {
205
+ ws = null;
206
+ scheduleReconnect();
207
+ });
208
+ ws.on("error", () => {
209
+ // Error will trigger close
210
+ });
211
+ }
212
+ function scheduleReconnect() {
213
+ reconnectAttempts++;
214
+ if (reconnectAttempts > 10)
215
+ return; // Give up after 10 attempts
216
+ const delay = Math.min(1000 * Math.pow(2, reconnectAttempts - 1), 30000);
217
+ setTimeout(connectWebSocket, delay);
218
+ }
219
+ // =============================================================================
220
+ // Start
221
+ // =============================================================================
222
+ async function main() {
223
+ const transport = new StdioServerTransport();
224
+ await server.connect(transport);
225
+ // Connect WebSocket after MCP is ready
226
+ if (AGENT_ID && API_KEY) {
227
+ connectWebSocket();
228
+ }
229
+ }
230
+ main().catch(() => process.exit(1));
231
+ //# sourceMappingURL=channel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"channel.js","sourceRoot":"","sources":["../src/channel.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agenthub-multiagent-mcp",
3
- "version": "1.10.5",
3
+ "version": "1.10.6",
4
4
  "description": "MCP server for AgentHub multi-agent communication",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -9,7 +9,8 @@
9
9
  "agenthub-hook": "./dist/planHookCli.js",
10
10
  "agenthub-daemon": "./dist/daemon.js",
11
11
  "agenthub-worker": "./dist/worker.js",
12
- "agenthub-setup": "./dist/setup.js"
12
+ "agenthub-setup": "./dist/setup.js",
13
+ "agenthub-channel": "./dist/channel.js"
13
14
  },
14
15
  "scripts": {
15
16
  "build": "tsc",
package/src/channel.ts ADDED
@@ -0,0 +1,265 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * AgentHub Channel Plugin — real-time message injection into Claude Code sessions
4
+ *
5
+ * This is an MCP server with the claude/channel capability. When a message
6
+ * arrives for the agent via WebSocket, it emits a channel notification that
7
+ * appears immediately in the active Claude Code session.
8
+ *
9
+ * Usage:
10
+ * claude --channels server:agenthub-channel
11
+ *
12
+ * Or via --dangerously-load-development-channels during development.
13
+ *
14
+ * Environment:
15
+ * AGENTHUB_URL - Server URL (default: https://agenthub.contetial.com)
16
+ * AGENTHUB_API_KEY - API key for authentication
17
+ * AGENTHUB_AGENT_ID - Agent ID for this session
18
+ */
19
+
20
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
21
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
22
+ import {
23
+ CallToolRequestSchema,
24
+ ListToolsRequestSchema,
25
+ } from "@modelcontextprotocol/sdk/types.js";
26
+ import WebSocket from "ws";
27
+
28
+ // =============================================================================
29
+ // Config
30
+ // =============================================================================
31
+
32
+ const API_URL = process.env.AGENTHUB_URL || "https://agenthub.contetial.com";
33
+ const API_KEY = process.env.AGENTHUB_API_KEY || "";
34
+ const AGENT_ID = process.env.AGENTHUB_AGENT_ID || "";
35
+ const WS_URL = API_URL.replace(/^https:/, "wss:").replace(/^http:/, "ws:");
36
+
37
+ // =============================================================================
38
+ // MCP Server with channel capability
39
+ // =============================================================================
40
+
41
+ const server = new Server(
42
+ { name: "agenthub-channel", version: "1.0.0" },
43
+ {
44
+ capabilities: {
45
+ experimental: { "claude/channel": {} },
46
+ tools: {},
47
+ },
48
+ instructions: `You are connected to AgentHub as agent "${AGENT_ID}".
49
+ Messages from other agents will appear as channel events.
50
+ When you receive a message, respond using the agenthub_reply tool with the message_id and your response.
51
+ Be helpful and respond concisely to incoming messages.`,
52
+ }
53
+ );
54
+
55
+ // =============================================================================
56
+ // Tools: reply + mark_read
57
+ // =============================================================================
58
+
59
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
60
+ tools: [
61
+ {
62
+ name: "agenthub_reply",
63
+ description: "Reply to an AgentHub message that arrived via channel notification",
64
+ inputSchema: {
65
+ type: "object" as const,
66
+ properties: {
67
+ message_id: {
68
+ type: "string",
69
+ description: "The message ID to reply to (from the channel event)",
70
+ },
71
+ body: {
72
+ type: "string",
73
+ description: "Your reply text",
74
+ },
75
+ },
76
+ required: ["message_id", "body"],
77
+ },
78
+ },
79
+ {
80
+ name: "agenthub_mark_read",
81
+ description: "Mark an AgentHub message as read without replying",
82
+ inputSchema: {
83
+ type: "object" as const,
84
+ properties: {
85
+ message_id: {
86
+ type: "string",
87
+ description: "The message ID to mark as read",
88
+ },
89
+ },
90
+ required: ["message_id"],
91
+ },
92
+ },
93
+ ],
94
+ }));
95
+
96
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
97
+ const { name, arguments: args } = request.params;
98
+
99
+ if (name === "agenthub_reply") {
100
+ const messageId = args?.message_id as string;
101
+ const body = args?.body as string;
102
+
103
+ if (!messageId || !body) {
104
+ return { content: [{ type: "text" as const, text: "Error: message_id and body are required" }] };
105
+ }
106
+
107
+ try {
108
+ const resp = await fetch(`${API_URL}/api/messages`, {
109
+ method: "POST",
110
+ headers: { "Content-Type": "application/json", "X-API-Key": API_KEY },
111
+ body: JSON.stringify({
112
+ from_agent: AGENT_ID,
113
+ to_agent: "", // Will be filled from reply_to context
114
+ type: "response",
115
+ subject: "Re: (via channel)",
116
+ body: body.slice(0, 5000),
117
+ reply_to: messageId,
118
+ }),
119
+ });
120
+
121
+ if (resp.ok) {
122
+ // Also mark as read
123
+ await fetch(`${API_URL}/api/messages/${messageId}/status`, {
124
+ method: "PATCH",
125
+ headers: { "Content-Type": "application/json", "X-API-Key": API_KEY },
126
+ body: JSON.stringify({ status: "read" }),
127
+ });
128
+ return { content: [{ type: "text" as const, text: `Reply sent successfully.` }] };
129
+ } else {
130
+ return { content: [{ type: "text" as const, text: `Reply failed: HTTP ${resp.status}` }] };
131
+ }
132
+ } catch (err) {
133
+ return { content: [{ type: "text" as const, text: `Reply failed: ${err}` }] };
134
+ }
135
+ }
136
+
137
+ if (name === "agenthub_mark_read") {
138
+ const messageId = args?.message_id as string;
139
+ try {
140
+ await fetch(`${API_URL}/api/messages/${messageId}/status`, {
141
+ method: "PATCH",
142
+ headers: { "Content-Type": "application/json", "X-API-Key": API_KEY },
143
+ body: JSON.stringify({ status: "read" }),
144
+ });
145
+ return { content: [{ type: "text" as const, text: "Marked as read." }] };
146
+ } catch (err) {
147
+ return { content: [{ type: "text" as const, text: `Failed: ${err}` }] };
148
+ }
149
+ }
150
+
151
+ return { content: [{ type: "text" as const, text: `Unknown tool: ${name}` }] };
152
+ });
153
+
154
+ // =============================================================================
155
+ // WebSocket: connect to AgentHub and emit channel notifications
156
+ // =============================================================================
157
+
158
+ let ws: WebSocket | null = null;
159
+ let reconnectAttempts = 0;
160
+
161
+ function connectWebSocket(): void {
162
+ if (!AGENT_ID || !API_KEY) return;
163
+
164
+ const url = `${WS_URL}/ws/agents/${AGENT_ID}?api_key=${API_KEY}`;
165
+ ws = new WebSocket(url);
166
+
167
+ ws.on("open", () => {
168
+ reconnectAttempts = 0;
169
+ });
170
+
171
+ ws.on("message", async (data: WebSocket.Data) => {
172
+ try {
173
+ const msg = JSON.parse(data.toString());
174
+
175
+ if (msg.type === "ping") {
176
+ ws?.send(JSON.stringify({ type: "pong", timestamp: new Date().toISOString() }));
177
+ return;
178
+ }
179
+
180
+ if (msg.type === "message" && msg.message) {
181
+ const m = msg.message;
182
+
183
+ // Ignore messages from self
184
+ if (m.from_agent === AGENT_ID) {
185
+ ws?.send(JSON.stringify({ type: "ack", message_id: m.id, timestamp: new Date().toISOString() }));
186
+ return;
187
+ }
188
+
189
+ // ACK the message
190
+ ws?.send(JSON.stringify({ type: "ack", message_id: m.id, timestamp: new Date().toISOString() }));
191
+
192
+ // Emit channel notification — this injects into the active session
193
+ const priority = m.priority === "urgent" ? " [URGENT]" : m.priority === "high" ? " [HIGH]" : "";
194
+ const content = `📨 Message from ${m.from_agent}${priority}\nSubject: ${m.subject}\n\n${m.body}\n\nMessage ID: ${m.id}\nUse agenthub_reply with this message_id to respond.`;
195
+
196
+ await server.notification({
197
+ method: "notifications/claude/channel",
198
+ params: {
199
+ content,
200
+ meta: {
201
+ source: "agenthub",
202
+ from: m.from_agent,
203
+ subject: m.subject,
204
+ message_id: m.id,
205
+ type: m.type,
206
+ priority: m.priority,
207
+ },
208
+ },
209
+ });
210
+ }
211
+
212
+ if (msg.type === "task" && msg.task) {
213
+ const t = msg.task;
214
+
215
+ await server.notification({
216
+ method: "notifications/claude/channel",
217
+ params: {
218
+ content: `📋 New task assigned by ${t.assigned_by}\n\n${t.task}\n\nTask ID: ${t.id}\nPriority: ${t.priority}`,
219
+ meta: {
220
+ source: "agenthub",
221
+ from: t.assigned_by,
222
+ task_id: t.id,
223
+ type: "task",
224
+ priority: t.priority,
225
+ },
226
+ },
227
+ });
228
+ }
229
+ } catch {
230
+ // Parse errors — ignore
231
+ }
232
+ });
233
+
234
+ ws.on("close", () => {
235
+ ws = null;
236
+ scheduleReconnect();
237
+ });
238
+
239
+ ws.on("error", () => {
240
+ // Error will trigger close
241
+ });
242
+ }
243
+
244
+ function scheduleReconnect(): void {
245
+ reconnectAttempts++;
246
+ if (reconnectAttempts > 10) return; // Give up after 10 attempts
247
+ const delay = Math.min(1000 * Math.pow(2, reconnectAttempts - 1), 30000);
248
+ setTimeout(connectWebSocket, delay);
249
+ }
250
+
251
+ // =============================================================================
252
+ // Start
253
+ // =============================================================================
254
+
255
+ async function main(): Promise<void> {
256
+ const transport = new StdioServerTransport();
257
+ await server.connect(transport);
258
+
259
+ // Connect WebSocket after MCP is ready
260
+ if (AGENT_ID && API_KEY) {
261
+ connectWebSocket();
262
+ }
263
+ }
264
+
265
+ main().catch(() => process.exit(1));