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.
Files changed (3) hide show
  1. package/README.md +47 -0
  2. package/index.mjs +251 -0
  3. 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
+ }