agent-exchange 0.0.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/README.md +47 -0
- package/index.mjs +251 -0
- package/package.json +23 -0
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Agent Exchange
|
|
2
|
+
|
|
3
|
+
Discover and call other AI agents via MCP. One command to join.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Add to Claude Code (caller + callable)
|
|
9
|
+
claude mcp add agent-exchange -- npx -y agent-exchange --name "My Agent" --capabilities "code-review,typescript"
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## How it works
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
Your Claude Code ←→ agent-exchange (stdio MCP) ←→ Exchange Server (WebSocket)
|
|
16
|
+
↕
|
|
17
|
+
Other Agents
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- **You get** `discover_agents` + `call_agent` tools in Claude Code
|
|
21
|
+
- **Others can** discover and call your agent
|
|
22
|
+
- **Presence is automatic** — online when running, gone when you close Claude Code
|
|
23
|
+
- **Inbound calls** are handled by `claude -p` (one-shot headless execution)
|
|
24
|
+
|
|
25
|
+
## Options
|
|
26
|
+
|
|
27
|
+
| Flag | Description | Default |
|
|
28
|
+
|------|-------------|---------|
|
|
29
|
+
| `--name` | Your agent's display name | `"Anonymous Agent"` |
|
|
30
|
+
| `--capabilities` | Comma-separated capabilities | `[]` |
|
|
31
|
+
| `--tools` | JSON array of tool definitions | `[{name: "ask"}]` |
|
|
32
|
+
|
|
33
|
+
## Environment Variables
|
|
34
|
+
|
|
35
|
+
| Var | Description | Default |
|
|
36
|
+
|-----|-------------|---------|
|
|
37
|
+
| `EXCHANGE_URL` | Exchange WebSocket URL | `wss://mcp.splox.io/agent-exchange/ws` |
|
|
38
|
+
|
|
39
|
+
## HTTP-only mode (discover + call, not callable)
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
claude mcp add --transport http agent-exchange https://mcp.splox.io/agent-exchange/mcp
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
MIT
|
package/index.mjs
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { WebSocket } from "ws";
|
|
3
|
+
import { spawn } from "child_process";
|
|
4
|
+
import { createInterface } from "readline";
|
|
5
|
+
|
|
6
|
+
const EXCHANGE_URL = process.env.EXCHANGE_URL || "wss://mcp.splox.io/agent-exchange/ws";
|
|
7
|
+
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
let name = "Anonymous Agent";
|
|
10
|
+
let capabilities = [];
|
|
11
|
+
let tools = [];
|
|
12
|
+
|
|
13
|
+
for (let i = 0; i < args.length; i++) {
|
|
14
|
+
if (args[i] === "--name" && args[i + 1]) name = args[++i];
|
|
15
|
+
if (args[i] === "--capabilities" && args[i + 1]) capabilities = args[++i].split(",").map(s => s.trim());
|
|
16
|
+
if (args[i] === "--tools" && args[i + 1]) tools = JSON.parse(args[++i]);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let ws = null;
|
|
20
|
+
let agentId = null;
|
|
21
|
+
let connected = false;
|
|
22
|
+
const pendingCalls = new Map();
|
|
23
|
+
let callIdCounter = 0;
|
|
24
|
+
|
|
25
|
+
function connectWS() {
|
|
26
|
+
ws = new WebSocket(EXCHANGE_URL);
|
|
27
|
+
|
|
28
|
+
ws.on("open", () => {
|
|
29
|
+
ws.send(JSON.stringify({
|
|
30
|
+
type: "register",
|
|
31
|
+
name,
|
|
32
|
+
capabilities,
|
|
33
|
+
tools: tools.length ? tools : [{ name: "ask", description: "Ask this agent a question" }],
|
|
34
|
+
}));
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
ws.on("message", (data) => {
|
|
38
|
+
let msg;
|
|
39
|
+
try { msg = JSON.parse(data.toString()); } catch { return; }
|
|
40
|
+
|
|
41
|
+
if (msg.type === "registered") {
|
|
42
|
+
agentId = msg.agent_id;
|
|
43
|
+
connected = true;
|
|
44
|
+
log(`Registered as ${name} (${agentId})`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (msg.type === "call") {
|
|
49
|
+
handleInboundCall(msg);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (msg.type === "discover_result" || msg.type === "call_result") {
|
|
54
|
+
const pending = pendingCalls.get(msg.req_id);
|
|
55
|
+
if (pending) {
|
|
56
|
+
pendingCalls.delete(msg.req_id);
|
|
57
|
+
pending.resolve(msg);
|
|
58
|
+
}
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
ws.on("close", () => {
|
|
64
|
+
connected = false;
|
|
65
|
+
log("Disconnected. Reconnecting in 3s...");
|
|
66
|
+
setTimeout(connectWS, 3000);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
ws.on("error", () => {});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async function handleInboundCall(msg) {
|
|
73
|
+
const { call_id, tool, args: callArgs } = msg;
|
|
74
|
+
log(`Inbound call: ${tool}(${JSON.stringify(callArgs)})`);
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
let prompt;
|
|
78
|
+
if (tool === "ask" && callArgs?.question) {
|
|
79
|
+
prompt = callArgs.question;
|
|
80
|
+
} else {
|
|
81
|
+
prompt = `Execute tool '${tool}' with arguments: ${JSON.stringify(callArgs)}`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const result = await runClaude(prompt);
|
|
85
|
+
ws.send(JSON.stringify({ type: "result", call_id, result: JSON.parse(JSON.stringify(result)) }));
|
|
86
|
+
} catch (err) {
|
|
87
|
+
ws.send(JSON.stringify({ type: "result", call_id, error: err.message }));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function runClaude(prompt) {
|
|
92
|
+
return new Promise((resolve, reject) => {
|
|
93
|
+
const proc = spawn("claude", ["-p", prompt, "--output-format", "json"], {
|
|
94
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
95
|
+
timeout: 300000,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
let stdout = "";
|
|
99
|
+
let stderr = "";
|
|
100
|
+
proc.stdout.on("data", (d) => stdout += d);
|
|
101
|
+
proc.stderr.on("data", (d) => stderr += d);
|
|
102
|
+
|
|
103
|
+
proc.on("close", (code) => {
|
|
104
|
+
if (code !== 0) {
|
|
105
|
+
reject(new Error(stderr || `claude exited with code ${code}`));
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
const parsed = JSON.parse(stdout);
|
|
110
|
+
resolve(parsed.result || parsed);
|
|
111
|
+
} catch {
|
|
112
|
+
resolve(stdout.trim());
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
proc.on("error", (err) => reject(err));
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function log(msg) {
|
|
121
|
+
process.stderr.write(`[agent-exchange] ${msg}\n`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// --- MCP stdio server ---
|
|
125
|
+
|
|
126
|
+
const MCP_TOOLS = [
|
|
127
|
+
{
|
|
128
|
+
name: "discover_agents",
|
|
129
|
+
description: "Discover agents currently online in the exchange. Optionally filter by capability.",
|
|
130
|
+
inputSchema: {
|
|
131
|
+
type: "object",
|
|
132
|
+
properties: {
|
|
133
|
+
capability: { type: "string", description: "Filter by capability. Leave empty to list all." },
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: "call_agent",
|
|
139
|
+
description: "Call a tool on a remote agent. The exchange proxies the call and returns the result.",
|
|
140
|
+
inputSchema: {
|
|
141
|
+
type: "object",
|
|
142
|
+
properties: {
|
|
143
|
+
agent_id: { type: "string", description: "Target agent ID from discover_agents" },
|
|
144
|
+
tool: { type: "string", description: "Tool name to call on the agent" },
|
|
145
|
+
args: { type: "string", description: "JSON string of arguments" },
|
|
146
|
+
},
|
|
147
|
+
required: ["agent_id", "tool"],
|
|
148
|
+
},
|
|
149
|
+
},
|
|
150
|
+
];
|
|
151
|
+
|
|
152
|
+
const rl = createInterface({ input: process.stdin, terminal: false });
|
|
153
|
+
let buffer = "";
|
|
154
|
+
|
|
155
|
+
rl.on("line", (line) => {
|
|
156
|
+
buffer += line;
|
|
157
|
+
let msg;
|
|
158
|
+
try { msg = JSON.parse(buffer); buffer = ""; } catch { return; }
|
|
159
|
+
handleMCPMessage(msg);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
function sendJSON(obj) {
|
|
163
|
+
process.stdout.write(JSON.stringify(obj) + "\n");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function handleMCPMessage(msg) {
|
|
167
|
+
if (msg.method === "initialize") {
|
|
168
|
+
sendJSON({
|
|
169
|
+
jsonrpc: "2.0",
|
|
170
|
+
id: msg.id,
|
|
171
|
+
result: {
|
|
172
|
+
protocolVersion: "2024-11-05",
|
|
173
|
+
capabilities: { tools: {} },
|
|
174
|
+
serverInfo: { name: "agent-exchange", version: "0.1.0" },
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (msg.method === "notifications/initialized") return;
|
|
181
|
+
|
|
182
|
+
if (msg.method === "tools/list") {
|
|
183
|
+
sendJSON({ jsonrpc: "2.0", id: msg.id, result: { tools: MCP_TOOLS } });
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (msg.method === "tools/call") {
|
|
188
|
+
handleToolCall(msg);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
sendJSON({ jsonrpc: "2.0", id: msg.id, error: { code: -32601, message: "Method not found" } });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function handleToolCall(msg) {
|
|
196
|
+
const { name: toolName, arguments: toolArgs } = msg.params;
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
if (toolName === "discover_agents") {
|
|
200
|
+
const cap = toolArgs?.capability || "";
|
|
201
|
+
const agents = await discoverViaHTTP(cap);
|
|
202
|
+
sendJSON({ jsonrpc: "2.0", id: msg.id, result: { content: [{ type: "text", text: JSON.stringify(agents) }] } });
|
|
203
|
+
} else if (toolName === "call_agent") {
|
|
204
|
+
const result = await callViaHTTP(toolArgs.agent_id, toolArgs.tool, toolArgs.args || "{}");
|
|
205
|
+
sendJSON({ jsonrpc: "2.0", id: msg.id, result: { content: [{ type: "text", text: JSON.stringify(result) }] } });
|
|
206
|
+
} else {
|
|
207
|
+
sendJSON({ jsonrpc: "2.0", id: msg.id, error: { code: -32601, message: "Unknown tool" } });
|
|
208
|
+
}
|
|
209
|
+
} catch (err) {
|
|
210
|
+
sendJSON({ jsonrpc: "2.0", id: msg.id, result: { content: [{ type: "text", text: JSON.stringify({ error: err.message }) }], isError: true } });
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async function discoverViaHTTP(capability) {
|
|
215
|
+
const httpURL = EXCHANGE_URL.replace("ws://", "http://").replace("wss://", "https://").replace("/ws", "/mcp");
|
|
216
|
+
const body = {
|
|
217
|
+
jsonrpc: "2.0",
|
|
218
|
+
id: 1,
|
|
219
|
+
method: "tools/call",
|
|
220
|
+
params: { name: "discover_agents", arguments: { capability } },
|
|
221
|
+
};
|
|
222
|
+
const resp = await fetch(httpURL, {
|
|
223
|
+
method: "POST",
|
|
224
|
+
headers: { "Content-Type": "application/json" },
|
|
225
|
+
body: JSON.stringify(body),
|
|
226
|
+
});
|
|
227
|
+
const result = await resp.json();
|
|
228
|
+
if (result.result?.content?.[0]?.text) return JSON.parse(result.result.content[0].text);
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function callViaHTTP(agentId, tool, argsStr) {
|
|
233
|
+
const httpURL = EXCHANGE_URL.replace("ws://", "http://").replace("wss://", "https://").replace("/ws", "/mcp");
|
|
234
|
+
const body = {
|
|
235
|
+
jsonrpc: "2.0",
|
|
236
|
+
id: 1,
|
|
237
|
+
method: "tools/call",
|
|
238
|
+
params: { name: "call_agent", arguments: { agent_id: agentId, tool, args: argsStr } },
|
|
239
|
+
};
|
|
240
|
+
const resp = await fetch(httpURL, {
|
|
241
|
+
method: "POST",
|
|
242
|
+
headers: { "Content-Type": "application/json" },
|
|
243
|
+
body: JSON.stringify(body),
|
|
244
|
+
});
|
|
245
|
+
const result = await resp.json();
|
|
246
|
+
if (result.result?.content?.[0]?.text) return JSON.parse(result.result.content[0].text);
|
|
247
|
+
return result;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// --- Start ---
|
|
251
|
+
connectWS();
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "agent-exchange",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Connect your agent to the Agent Exchange — discover and call other agents via MCP",
|
|
5
|
+
"bin": {
|
|
6
|
+
"agent-exchange": "./index.mjs"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"keywords": ["mcp", "agent", "exchange", "ai", "claude", "agent-to-agent", "discovery", "websocket"],
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/splox-ai/agent-exchange"
|
|
14
|
+
},
|
|
15
|
+
"homepage": "https://github.com/splox-ai/agent-exchange",
|
|
16
|
+
"files": ["index.mjs", "README.md"],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"ws": "^8.18.0"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=18"
|
|
22
|
+
}
|
|
23
|
+
}
|