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.
- package/dist/channel.d.ts +20 -0
- package/dist/channel.d.ts.map +1 -0
- package/dist/channel.js +231 -0
- package/dist/channel.js.map +1 -0
- package/package.json +3 -2
- package/src/channel.ts +265 -0
|
@@ -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"}
|
package/dist/channel.js
ADDED
|
@@ -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":";AACA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,2CAA2C,CAAC;AACnE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EACL,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,SAAS,MAAM,IAAI,CAAC;AAE3B,gFAAgF;AAChF,SAAS;AACT,gFAAgF;AAEhF,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,gCAAgC,CAAC;AAC7E,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,EAAE,CAAC;AACnD,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,IAAI,EAAE,CAAC;AACrD,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;AAE3E,gFAAgF;AAChF,qCAAqC;AACrC,gFAAgF;AAEhF,MAAM,MAAM,GAAG,IAAI,MAAM,CACvB,EAAE,IAAI,EAAE,kBAAkB,EAAE,OAAO,EAAE,OAAO,EAAE,EAC9C;IACE,YAAY,EAAE;QACZ,YAAY,EAAE,EAAE,gBAAgB,EAAE,EAAE,EAAE;QACtC,KAAK,EAAE,EAAE;KACV;IACD,YAAY,EAAE,2CAA2C,QAAQ;;;uDAGd;CACpD,CACF,CAAC;AAEF,gFAAgF;AAChF,2BAA2B;AAC3B,gFAAgF;AAEhF,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC;IAC5D,KAAK,EAAE;QACL;YACE,IAAI,EAAE,gBAAgB;YACtB,WAAW,EAAE,oEAAoE;YACjF,WAAW,EAAE;gBACX,IAAI,EAAE,QAAiB;gBACvB,UAAU,EAAE;oBACV,UAAU,EAAE;wBACV,IAAI,EAAE,QAAQ;wBACd,WAAW,EAAE,qDAAqD;qBACnE;oBACD,IAAI,EAAE;wBACJ,IAAI,EAAE,QAAQ;wBACd,WAAW,EAAE,iBAAiB;qBAC/B;iBACF;gBACD,QAAQ,EAAE,CAAC,YAAY,EAAE,MAAM,CAAC;aACjC;SACF;QACD;YACE,IAAI,EAAE,oBAAoB;YAC1B,WAAW,EAAE,mDAAmD;YAChE,WAAW,EAAE;gBACX,IAAI,EAAE,QAAiB;gBACvB,UAAU,EAAE;oBACV,UAAU,EAAE;wBACV,IAAI,EAAE,QAAQ;wBACd,WAAW,EAAE,gCAAgC;qBAC9C;iBACF;gBACD,QAAQ,EAAE,CAAC,YAAY,CAAC;aACzB;SACF;KACF;CACF,CAAC,CAAC,CAAC;AAEJ,MAAM,CAAC,iBAAiB,CAAC,qBAAqB,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE;IAChE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAEjD,IAAI,IAAI,KAAK,gBAAgB,EAAE,CAAC;QAC9B,MAAM,SAAS,GAAG,IAAI,EAAE,UAAoB,CAAC;QAC7C,MAAM,IAAI,GAAG,IAAI,EAAE,IAAc,CAAC;QAElC,IAAI,CAAC,SAAS,IAAI,CAAC,IAAI,EAAE,CAAC;YACxB,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,yCAAyC,EAAE,CAAC,EAAE,CAAC;QACnG,CAAC;QAED,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC,GAAG,OAAO,eAAe,EAAE;gBAClD,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,WAAW,EAAE,OAAO,EAAE;gBACrE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,UAAU,EAAE,QAAQ;oBACpB,QAAQ,EAAE,EAAE,EAAE,uCAAuC;oBACrD,IAAI,EAAE,UAAU;oBAChB,OAAO,EAAE,mBAAmB;oBAC5B,IAAI,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC;oBACzB,QAAQ,EAAE,SAAS;iBACpB,CAAC;aACH,CAAC,CAAC;YAEH,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;gBACZ,oBAAoB;gBACpB,MAAM,KAAK,CAAC,GAAG,OAAO,iBAAiB,SAAS,SAAS,EAAE;oBACzD,MAAM,EAAE,OAAO;oBACf,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,WAAW,EAAE,OAAO,EAAE;oBACrE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;iBACzC,CAAC,CAAC;gBACH,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,0BAA0B,EAAE,CAAC,EAAE,CAAC;YACpF,CAAC;iBAAM,CAAC;gBACN,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,sBAAsB,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAC;YAC7F,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,iBAAiB,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC;QAChF,CAAC;IACH,CAAC;IAED,IAAI,IAAI,KAAK,oBAAoB,EAAE,CAAC;QAClC,MAAM,SAAS,GAAG,IAAI,EAAE,UAAoB,CAAC;QAC7C,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,GAAG,OAAO,iBAAiB,SAAS,SAAS,EAAE;gBACzD,MAAM,EAAE,OAAO;gBACf,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,WAAW,EAAE,OAAO,EAAE;gBACrE,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;aACzC,CAAC,CAAC;YACH,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,iBAAiB,EAAE,CAAC,EAAE,CAAC;QAC3E,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,WAAW,GAAG,EAAE,EAAE,CAAC,EAAE,CAAC;QAC1E,CAAC;IACH,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,iBAAiB,IAAI,EAAE,EAAE,CAAC,EAAE,CAAC;AACjF,CAAC,CAAC,CAAC;AAEH,gFAAgF;AAChF,gEAAgE;AAChE,gFAAgF;AAEhF,IAAI,EAAE,GAAqB,IAAI,CAAC;AAChC,IAAI,iBAAiB,GAAG,CAAC,CAAC;AAE1B,SAAS,gBAAgB;IACvB,IAAI,CAAC,QAAQ,IAAI,CAAC,OAAO;QAAE,OAAO;IAElC,MAAM,GAAG,GAAG,GAAG,MAAM,cAAc,QAAQ,YAAY,OAAO,EAAE,CAAC;IACjE,EAAE,GAAG,IAAI,SAAS,CAAC,GAAG,CAAC,CAAC;IAExB,EAAE,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE;QACjB,iBAAiB,GAAG,CAAC,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,IAAoB,EAAE,EAAE;QAC9C,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;YAExC,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;gBACxB,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC;gBAChF,OAAO;YACT,CAAC;YAED,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,GAAG,CAAC,OAAO,EAAE,CAAC;gBAC1C,MAAM,CAAC,GAAG,GAAG,CAAC,OAAO,CAAC;gBAEtB,4BAA4B;gBAC5B,IAAI,CAAC,CAAC,UAAU,KAAK,QAAQ,EAAE,CAAC;oBAC9B,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC;oBACjG,OAAO;gBACT,CAAC;gBAED,kBAAkB;gBAClB,EAAE,EAAE,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC;gBAEjG,mEAAmE;gBACnE,MAAM,QAAQ,GAAG,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC;gBAChG,MAAM,OAAO,GAAG,mBAAmB,CAAC,CAAC,UAAU,GAAG,QAAQ,cAAc,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,IAAI,mBAAmB,CAAC,CAAC,EAAE,uDAAuD,CAAC;gBAE7K,MAAM,MAAM,CAAC,YAAY,CAAC;oBACxB,MAAM,EAAE,8BAA8B;oBACtC,MAAM,EAAE;wBACN,OAAO;wBACP,IAAI,EAAE;4BACJ,MAAM,EAAE,UAAU;4BAClB,IAAI,EAAE,CAAC,CAAC,UAAU;4BAClB,OAAO,EAAE,CAAC,CAAC,OAAO;4BAClB,UAAU,EAAE,CAAC,CAAC,EAAE;4BAChB,IAAI,EAAE,CAAC,CAAC,IAAI;4BACZ,QAAQ,EAAE,CAAC,CAAC,QAAQ;yBACrB;qBACF;iBACF,CAAC,CAAC;YACL,CAAC;YAED,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,CAAC;gBACpC,MAAM,CAAC,GAAG,GAAG,CAAC,IAAI,CAAC;gBAEnB,MAAM,MAAM,CAAC,YAAY,CAAC;oBACxB,MAAM,EAAE,8BAA8B;oBACtC,MAAM,EAAE;wBACN,OAAO,EAAE,2BAA2B,CAAC,CAAC,WAAW,OAAO,CAAC,CAAC,IAAI,gBAAgB,CAAC,CAAC,EAAE,eAAe,CAAC,CAAC,QAAQ,EAAE;wBAC7G,IAAI,EAAE;4BACJ,MAAM,EAAE,UAAU;4BAClB,IAAI,EAAE,CAAC,CAAC,WAAW;4BACnB,OAAO,EAAE,CAAC,CAAC,EAAE;4BACb,IAAI,EAAE,MAAM;4BACZ,QAAQ,EAAE,CAAC,CAAC,QAAQ;yBACrB;qBACF;iBACF,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,wBAAwB;QAC1B,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QAClB,EAAE,GAAG,IAAI,CAAC;QACV,iBAAiB,EAAE,CAAC;IACtB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QAClB,2BAA2B;IAC7B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,iBAAiB;IACxB,iBAAiB,EAAE,CAAC;IACpB,IAAI,iBAAiB,GAAG,EAAE;QAAE,OAAO,CAAC,4BAA4B;IAChE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,iBAAiB,GAAG,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACzE,UAAU,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;AACtC,CAAC;AAED,gFAAgF;AAChF,QAAQ;AACR,gFAAgF;AAEhF,KAAK,UAAU,IAAI;IACjB,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IAEhC,uCAAuC;IACvC,IAAI,QAAQ,IAAI,OAAO,EAAE,CAAC;QACxB,gBAAgB,EAAE,CAAC;IACrB,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agenthub-multiagent-mcp",
|
|
3
|
-
"version": "1.10.
|
|
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));
|