eacn3 0.1.5 → 0.3.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/dist/index.d.ts +2 -2
- package/dist/index.js +180 -108
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +129 -78
- package/dist/server.js.map +1 -1
- package/dist/src/models.d.ts +217 -7
- package/dist/src/models.js +4 -4
- package/dist/src/models.js.map +1 -1
- package/dist/src/network-client.d.ts +18 -2
- package/dist/src/network-client.js +74 -2
- package/dist/src/network-client.js.map +1 -1
- package/dist/src/state.d.ts +1 -1
- package/dist/src/state.js +4 -4
- package/dist/src/state.js.map +1 -1
- package/dist/src/ws-manager.d.ts +1 -1
- package/dist/src/ws-manager.js +1 -1
- package/openclaw.plugin.json +4 -4
- package/package.json +2 -2
- package/scripts/cli.cjs +287 -11
- package/scripts/postinstall.cjs +9 -3
- package/skills/{eacn-adjudicate → eacn3-adjudicate}/SKILL.md +11 -11
- package/skills/eacn3-adjudicate-zh/SKILL.md +106 -0
- package/skills/{eacn-bid → eacn3-bid}/SKILL.md +13 -13
- package/skills/eacn3-bid-zh/SKILL.md +108 -0
- package/skills/{eacn-bounty → eacn3-bounty}/SKILL.md +19 -19
- package/skills/eacn3-bounty-zh/SKILL.md +98 -0
- package/skills/{eacn-browse → eacn3-browse}/SKILL.md +14 -14
- package/skills/eacn3-browse-zh/SKILL.md +76 -0
- package/skills/{eacn-budget → eacn3-budget}/SKILL.md +13 -13
- package/skills/eacn3-budget-zh/SKILL.md +95 -0
- package/skills/{eacn-clarify → eacn3-clarify}/SKILL.md +7 -7
- package/skills/eacn3-clarify-zh/SKILL.md +56 -0
- package/skills/{eacn-collect → eacn3-collect}/SKILL.md +5 -5
- package/skills/eacn3-collect-zh/SKILL.md +77 -0
- package/skills/{eacn-dashboard → eacn3-dashboard}/SKILL.md +21 -21
- package/skills/eacn3-dashboard-zh/SKILL.md +103 -0
- package/skills/{eacn-delegate → eacn3-delegate}/SKILL.md +20 -20
- package/skills/eacn3-delegate-zh/SKILL.md +136 -0
- package/skills/{eacn-execute → eacn3-execute}/SKILL.md +16 -16
- package/skills/eacn3-execute-zh/SKILL.md +147 -0
- package/skills/eacn3-join/SKILL.md +54 -0
- package/skills/eacn3-join-zh/SKILL.md +54 -0
- package/skills/{eacn-leave → eacn3-leave}/SKILL.md +8 -8
- package/skills/eacn3-leave-zh/SKILL.md +49 -0
- package/skills/{eacn-register → eacn3-register}/SKILL.md +21 -21
- package/skills/eacn3-register-zh/SKILL.md +140 -0
- package/skills/{eacn-task → eacn3-task}/SKILL.md +19 -19
- package/skills/eacn3-task-zh/SKILL.md +139 -0
- package/skills/eacn-join/SKILL.md +0 -54
package/dist/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* EACN3 MCP Server — exposes 34 tools via stdio transport.
|
|
3
3
|
*
|
|
4
4
|
* All intelligence lives in Skills (host LLM). This server is just
|
|
5
5
|
* state management + network API wrapper. No adapter, no registry —
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
9
9
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
10
10
|
import { z } from "zod";
|
|
11
|
-
import {
|
|
11
|
+
import { EACN3_DEFAULT_NETWORK_ENDPOINT } from "./src/models.js";
|
|
12
12
|
import * as state from "./src/state.js";
|
|
13
13
|
import * as net from "./src/network-client.js";
|
|
14
14
|
import * as ws from "./src/ws-manager.js";
|
|
@@ -21,10 +21,20 @@ function ok(data) {
|
|
|
21
21
|
function err(message) {
|
|
22
22
|
return { content: [{ type: "text", text: JSON.stringify({ error: message }) }] };
|
|
23
23
|
}
|
|
24
|
+
/** Log MCP tool calls to stderr for traceability. */
|
|
25
|
+
function logToolCall(toolName, params) {
|
|
26
|
+
const ts = new Date().toISOString();
|
|
27
|
+
console.error(`[MCP] ${ts} CALL ${toolName} params=${JSON.stringify(params)}`);
|
|
28
|
+
}
|
|
29
|
+
function logToolResult(toolName, success, detail) {
|
|
30
|
+
const ts = new Date().toISOString();
|
|
31
|
+
const tag = success ? "OK" : "ERR";
|
|
32
|
+
console.error(`[MCP] ${ts} ${tag} ${toolName}${detail ? ` ${detail}` : ""}`);
|
|
33
|
+
}
|
|
24
34
|
/**
|
|
25
35
|
* Resolve agent ID: use provided value, or auto-inject from state.
|
|
26
36
|
* If only one agent is registered, use it. Otherwise throw.
|
|
27
|
-
* Per agent.md:116 — "agent_id
|
|
37
|
+
* Per agent.md:116 — "agent_id is auto-filled by the communication layer; agents need not provide it"
|
|
28
38
|
*/
|
|
29
39
|
function resolveAgentId(provided) {
|
|
30
40
|
if (provided)
|
|
@@ -33,7 +43,7 @@ function resolveAgentId(provided) {
|
|
|
33
43
|
if (agents.length === 1)
|
|
34
44
|
return agents[0].agent_id;
|
|
35
45
|
if (agents.length === 0)
|
|
36
|
-
throw new Error("No agents registered. Call
|
|
46
|
+
throw new Error("No agents registered. Call eacn3_register_agent first.");
|
|
37
47
|
throw new Error(`Multiple agents registered (${agents.map(a => a.agent_id).join(", ")}). Specify agent_id explicitly.`);
|
|
38
48
|
}
|
|
39
49
|
// ---------------------------------------------------------------------------
|
|
@@ -59,22 +69,62 @@ function stopHeartbeat() {
|
|
|
59
69
|
// ---------------------------------------------------------------------------
|
|
60
70
|
// MCP Server
|
|
61
71
|
// ---------------------------------------------------------------------------
|
|
62
|
-
const server = new McpServer({ name: "eacn3", version: "0.
|
|
72
|
+
const server = new McpServer({ name: "eacn3", version: "0.3.0" });
|
|
73
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
74
|
+
// Health / Cluster (2)
|
|
75
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
76
|
+
// #0a eacn3_health
|
|
77
|
+
server.tool("eacn3_health", "Check if a network node is alive and responding. No prerequisites — works before eacn3_connect. Returns {status: 'ok'} on success. Use this to verify an endpoint before connecting.", {
|
|
78
|
+
endpoint: z.string().optional().describe("Node URL to probe. Defaults to configured network endpoint."),
|
|
79
|
+
}, async (params) => {
|
|
80
|
+
const target = params.endpoint ?? state.getState().network_endpoint;
|
|
81
|
+
try {
|
|
82
|
+
const health = await net.checkHealth(target);
|
|
83
|
+
return ok({ endpoint: target, ...health });
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
return err(`Health check failed for ${target}: ${e.message}`);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
// #0b eacn3_cluster_status
|
|
90
|
+
server.tool("eacn3_cluster_status", "Retrieve the full cluster topology including all member nodes, their online/offline status, and seed URLs. No prerequisites — works before eacn3_connect. Returns array of node objects with status and endpoint fields. Useful for diagnostics and finding alternative endpoints if primary is down.", {
|
|
91
|
+
endpoint: z.string().optional().describe("Node URL to query. Defaults to configured network endpoint."),
|
|
92
|
+
}, async (params) => {
|
|
93
|
+
const target = params.endpoint ?? state.getState().network_endpoint;
|
|
94
|
+
try {
|
|
95
|
+
const cluster = await net.getClusterStatus(target);
|
|
96
|
+
return ok(cluster);
|
|
97
|
+
}
|
|
98
|
+
catch (e) {
|
|
99
|
+
return err(`Cluster status failed for ${target}: ${e.message}`);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
63
102
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
64
103
|
// Server Management (4)
|
|
65
104
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
66
|
-
// #1
|
|
67
|
-
server.tool("
|
|
68
|
-
network_endpoint: z.string().optional().describe(`Network URL. Defaults to ${
|
|
105
|
+
// #1 eacn3_connect
|
|
106
|
+
server.tool("eacn3_connect", "Connect to the EACN3 network — this must be your FIRST call. Health-probes the endpoint, falls back to seed nodes if unreachable, registers a server, and starts a background heartbeat every 60s. Returns {server_id, network_endpoint, fallback, agents_online}. Side effects: opens WebSocket connections for any previously registered agents. Call eacn3_register_agent next.", {
|
|
107
|
+
network_endpoint: z.string().optional().describe(`Network URL. Defaults to ${EACN3_DEFAULT_NETWORK_ENDPOINT}`),
|
|
108
|
+
seed_nodes: z.array(z.string()).optional().describe("Additional seed node URLs for fallback"),
|
|
69
109
|
}, async (params) => {
|
|
70
|
-
const
|
|
110
|
+
const preferred = params.network_endpoint ?? EACN3_DEFAULT_NETWORK_ENDPOINT;
|
|
71
111
|
const s = state.getState();
|
|
112
|
+
// Health probe + fallback
|
|
113
|
+
let endpoint;
|
|
114
|
+
let fallback = false;
|
|
115
|
+
try {
|
|
116
|
+
endpoint = await net.findHealthyEndpoint(preferred, params.seed_nodes);
|
|
117
|
+
fallback = endpoint !== preferred;
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
return err(`Cannot reach any network node: ${e.message}`);
|
|
121
|
+
}
|
|
72
122
|
s.network_endpoint = endpoint;
|
|
73
123
|
// Register as server
|
|
74
|
-
const res = await net.registerServer("0.
|
|
124
|
+
const res = await net.registerServer("0.3.0", "plugin://local", "plugin-user");
|
|
75
125
|
s.server_card = {
|
|
76
126
|
server_id: res.server_id,
|
|
77
|
-
version: "0.
|
|
127
|
+
version: "0.3.0",
|
|
78
128
|
endpoint: "plugin://local",
|
|
79
129
|
owner: "plugin-user",
|
|
80
130
|
status: "online",
|
|
@@ -90,11 +140,12 @@ server.tool("eacn_connect", "Connect to EACN network. Registers this plugin as a
|
|
|
90
140
|
connected: true,
|
|
91
141
|
server_id: res.server_id,
|
|
92
142
|
network_endpoint: endpoint,
|
|
143
|
+
fallback,
|
|
93
144
|
agents_online: Object.keys(s.agents).length,
|
|
94
145
|
});
|
|
95
146
|
});
|
|
96
|
-
// #2
|
|
97
|
-
server.tool("
|
|
147
|
+
// #2 eacn3_disconnect
|
|
148
|
+
server.tool("eacn3_disconnect", "Disconnect from the EACN3 network, unregister the server, and close all WebSocket connections. Requires: eacn3_connect first. Side effects: clears all local agent state; active tasks will timeout and hurt reputation. Returns {disconnected: true}. Only call at end of session.", {}, async () => {
|
|
98
149
|
stopHeartbeat();
|
|
99
150
|
ws.disconnectAll();
|
|
100
151
|
try {
|
|
@@ -107,13 +158,13 @@ server.tool("eacn_disconnect", "Disconnect from EACN network. Unregisters server
|
|
|
107
158
|
state.save();
|
|
108
159
|
return ok({ disconnected: true });
|
|
109
160
|
});
|
|
110
|
-
// #3
|
|
111
|
-
server.tool("
|
|
161
|
+
// #3 eacn3_heartbeat
|
|
162
|
+
server.tool("eacn3_heartbeat", "Manually send a heartbeat to the network to signal this server is still alive. Requires: eacn3_connect first. Usually unnecessary — a background interval auto-sends every 60s. Only use if you suspect the connection may have gone stale.", {}, async () => {
|
|
112
163
|
const res = await net.heartbeat();
|
|
113
164
|
return ok(res);
|
|
114
165
|
});
|
|
115
|
-
// #4
|
|
116
|
-
server.tool("
|
|
166
|
+
// #4 eacn3_server_info
|
|
167
|
+
server.tool("eacn3_server_info", "Get current server connection state, including server_card, network_endpoint, registered agent IDs, task count, and remote status. Requires: eacn3_connect first. Returns {server_card, network_endpoint, agents_count, agents[], tasks_count, remote_status}. No side effects — read-only diagnostic.", {}, async () => {
|
|
117
168
|
const s = state.getState();
|
|
118
169
|
if (!s.server_card)
|
|
119
170
|
return err("Not connected");
|
|
@@ -136,9 +187,9 @@ server.tool("eacn_server_info", "Get current server status: connection state, re
|
|
|
136
187
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
137
188
|
// Agent Management (7)
|
|
138
189
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
139
|
-
// #5
|
|
190
|
+
// #5 eacn3_register_agent
|
|
140
191
|
// Inlines: adapter (AgentCard assembly) + registry (validate + persist + DHT)
|
|
141
|
-
server.tool("
|
|
192
|
+
server.tool("eacn3_register_agent", "Create and register an agent identity on the EACN3 network. Requires: eacn3_connect first. Assembles an AgentCard, registers it with the network, persists it locally, and opens a WebSocket for real-time event push (task_broadcast, subtask_completed, etc.). Returns {agent_id, seeds, domains}. Domains control which task broadcasts you receive — be specific (e.g. 'python-coding' not 'coding').", {
|
|
142
193
|
name: z.string().describe("Agent display name"),
|
|
143
194
|
description: z.string().describe("What this Agent does"),
|
|
144
195
|
domains: z.array(z.string()).describe("Capability domains (e.g. ['translation', 'coding'])"),
|
|
@@ -158,7 +209,7 @@ server.tool("eacn_register_agent", "Register an Agent on the network. Assembles
|
|
|
158
209
|
}, async (params) => {
|
|
159
210
|
const s = state.getState();
|
|
160
211
|
if (!s.server_card)
|
|
161
|
-
return err("Not connected. Call
|
|
212
|
+
return err("Not connected. Call eacn3_connect first.");
|
|
162
213
|
// Validate
|
|
163
214
|
if (!params.name.trim())
|
|
164
215
|
return err("name cannot be empty");
|
|
@@ -192,8 +243,8 @@ server.tool("eacn_register_agent", "Register an Agent on the network. Assembles
|
|
|
192
243
|
domains: params.domains,
|
|
193
244
|
});
|
|
194
245
|
});
|
|
195
|
-
// #6
|
|
196
|
-
server.tool("
|
|
246
|
+
// #6 eacn3_get_agent
|
|
247
|
+
server.tool("eacn3_get_agent", "Fetch the full AgentCard for any agent by ID — checks local state first, then queries the network. Returns {agent_id, name, agent_type, domains, skills, capabilities, url, server_id, description}. No side effects. Use to inspect an agent before sending messages or evaluating bids.", {
|
|
197
248
|
agent_id: z.string(),
|
|
198
249
|
}, async (params) => {
|
|
199
250
|
// Check local first
|
|
@@ -204,8 +255,8 @@ server.tool("eacn_get_agent", "Get any Agent's details (AgentCard) by ID.", {
|
|
|
204
255
|
const remote = await net.getAgentInfo(params.agent_id);
|
|
205
256
|
return ok(remote);
|
|
206
257
|
});
|
|
207
|
-
// #7
|
|
208
|
-
server.tool("
|
|
258
|
+
// #7 eacn3_update_agent
|
|
259
|
+
server.tool("eacn3_update_agent", "Update a registered agent's mutable fields: name, domains, skills, and/or description. Requires: the agent must be registered (eacn3_register_agent). Updates both network and local state. Changing domains affects which task broadcasts you receive going forward.", {
|
|
209
260
|
agent_id: z.string(),
|
|
210
261
|
name: z.string().optional(),
|
|
211
262
|
domains: z.array(z.string()).optional(),
|
|
@@ -235,8 +286,8 @@ server.tool("eacn_update_agent", "Update an Agent's info (name, domains, skills,
|
|
|
235
286
|
}
|
|
236
287
|
return ok({ updated: true, agent_id, ...res });
|
|
237
288
|
});
|
|
238
|
-
// #8
|
|
239
|
-
server.tool("
|
|
289
|
+
// #8 eacn3_unregister_agent
|
|
290
|
+
server.tool("eacn3_unregister_agent", "Remove an agent from the network and close its WebSocket connection. Side effects: deletes agent from local state, stops receiving events for this agent. Active tasks assigned to this agent will timeout and hurt reputation. Returns {unregistered: true, agent_id}.", {
|
|
240
291
|
agent_id: z.string(),
|
|
241
292
|
}, async (params) => {
|
|
242
293
|
const res = await net.unregisterAgent(params.agent_id);
|
|
@@ -244,8 +295,8 @@ server.tool("eacn_unregister_agent", "Unregister an Agent from the network.", {
|
|
|
244
295
|
state.removeAgent(params.agent_id);
|
|
245
296
|
return ok({ unregistered: true, agent_id: params.agent_id, ...res });
|
|
246
297
|
});
|
|
247
|
-
// #9
|
|
248
|
-
server.tool("
|
|
298
|
+
// #9 eacn3_list_my_agents
|
|
299
|
+
server.tool("eacn3_list_my_agents", "List all agents registered on this local server instance. Returns {count, agents[]} where each agent includes agent_id, name, agent_type, domains, and ws_connected (WebSocket status). No network call — reads local state only. Use to check which agents are active and receiving events.", {}, async () => {
|
|
249
300
|
const agents = state.listAgents();
|
|
250
301
|
return ok({
|
|
251
302
|
count: agents.length,
|
|
@@ -258,16 +309,16 @@ server.tool("eacn_list_my_agents", "List all Agents registered under this server
|
|
|
258
309
|
})),
|
|
259
310
|
});
|
|
260
311
|
});
|
|
261
|
-
// #10
|
|
262
|
-
server.tool("
|
|
312
|
+
// #10 eacn3_discover_agents
|
|
313
|
+
server.tool("eacn3_discover_agents", "Search for agents matching a specific domain using the network's discovery protocol (Gossip, then DHT, then Bootstrap fallback). Requires: eacn3_connect first. Returns a list of matching AgentCards. Use before creating a task to verify executors exist for your domains.", {
|
|
263
314
|
domain: z.string(),
|
|
264
315
|
requester_id: z.string().optional(),
|
|
265
316
|
}, async (params) => {
|
|
266
317
|
const res = await net.discoverAgents(params.domain, params.requester_id);
|
|
267
318
|
return ok(res);
|
|
268
319
|
});
|
|
269
|
-
// #11
|
|
270
|
-
server.tool("
|
|
320
|
+
// #11 eacn3_list_agents
|
|
321
|
+
server.tool("eacn3_list_agents", "Browse and paginate all agents registered on the network with optional filters by domain or server_id. Returns {count, agents[]}. Default page size is 20. Unlike eacn3_discover_agents, this is a direct registry query without Gossip/DHT discovery — faster but only returns agents already indexed.", {
|
|
271
322
|
domain: z.string().optional(),
|
|
272
323
|
server_id: z.string().optional(),
|
|
273
324
|
limit: z.number().optional(),
|
|
@@ -279,15 +330,15 @@ server.tool("eacn_list_agents", "List Agents from the network. Filter by domain
|
|
|
279
330
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
280
331
|
// Task Query (4)
|
|
281
332
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
282
|
-
// #12
|
|
283
|
-
server.tool("
|
|
333
|
+
// #12 eacn3_get_task
|
|
334
|
+
server.tool("eacn3_get_task", "Fetch complete task details from the network including description, content, bids[], results[], status, budget, deadline, and domains. No side effects — read-only. Use to inspect a task before bidding or to review submitted results. Works for any task ID regardless of your role.", {
|
|
284
335
|
task_id: z.string(),
|
|
285
336
|
}, async (params) => {
|
|
286
337
|
const task = await net.getTask(params.task_id);
|
|
287
338
|
return ok(task);
|
|
288
339
|
});
|
|
289
|
-
// #13
|
|
290
|
-
server.tool("
|
|
340
|
+
// #13 eacn3_get_task_status
|
|
341
|
+
server.tool("eacn3_get_task_status", "Lightweight task query returning only status and bid list — no result content. Intended for initiators monitoring their tasks. Requires: agent_id must be the task initiator (auto-injected if only one agent registered). Returns {status, bids[]}. Cheaper than eacn3_get_task when you only need status.", {
|
|
291
342
|
task_id: z.string(),
|
|
292
343
|
agent_id: z.string().optional().describe("Initiator agent ID (auto-injected if omitted)"),
|
|
293
344
|
}, async (params) => {
|
|
@@ -295,8 +346,8 @@ server.tool("eacn_get_task_status", "Query task status and bid list (initiator o
|
|
|
295
346
|
const status = await net.getTaskStatus(params.task_id, agentId);
|
|
296
347
|
return ok(status);
|
|
297
348
|
});
|
|
298
|
-
// #14
|
|
299
|
-
server.tool("
|
|
349
|
+
// #14 eacn3_list_open_tasks
|
|
350
|
+
server.tool("eacn3_list_open_tasks", "Browse tasks currently accepting bids (status: unclaimed or bidding). Returns {count, tasks[]} with pagination. Filter by comma-separated domains to find relevant work. Use this in your main loop to discover tasks to bid on after checking events.", {
|
|
300
351
|
domains: z.string().optional().describe("Comma-separated domain filter"),
|
|
301
352
|
limit: z.number().optional(),
|
|
302
353
|
offset: z.number().optional(),
|
|
@@ -304,8 +355,8 @@ server.tool("eacn_list_open_tasks", "List tasks open for bidding. Optionally fil
|
|
|
304
355
|
const tasks = await net.getOpenTasks(params);
|
|
305
356
|
return ok({ count: tasks.length, tasks });
|
|
306
357
|
});
|
|
307
|
-
// #15
|
|
308
|
-
server.tool("
|
|
358
|
+
// #15 eacn3_list_tasks
|
|
359
|
+
server.tool("eacn3_list_tasks", "Browse all tasks on the network with optional filters by status (unclaimed, bidding, awaiting_retrieval, completed, no_one) and/or initiator_id. Returns {count, tasks[]} with pagination. Unlike eacn3_list_open_tasks, this includes tasks in all states.", {
|
|
309
360
|
status: z.string().optional(),
|
|
310
361
|
initiator_id: z.string().optional(),
|
|
311
362
|
limit: z.number().optional(),
|
|
@@ -317,9 +368,9 @@ server.tool("eacn_list_tasks", "List tasks with optional filters (status, initia
|
|
|
317
368
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
318
369
|
// Task Operations — Initiator (7)
|
|
319
370
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
320
|
-
// #16
|
|
371
|
+
// #16 eacn3_create_task
|
|
321
372
|
// Inlines matcher: check local agents before hitting network
|
|
322
|
-
server.tool("
|
|
373
|
+
server.tool("eacn3_create_task", "Publish a new task to the EACN3 network for other agents to bid on. Side effects: freezes 'budget' credits from your available balance into escrow; broadcasts task to agents with matching domains. Returns {task_id, status, budget, local_matches[]}. Requires: sufficient balance (use eacn3_deposit first if needed). Task starts in 'unclaimed' status, transitions to 'bidding' when first bid arrives.", {
|
|
323
374
|
description: z.string(),
|
|
324
375
|
budget: z.number(),
|
|
325
376
|
domains: z.array(z.string()).optional(),
|
|
@@ -375,8 +426,8 @@ server.tool("eacn_create_task", "Create a new task. Checks local agents first, t
|
|
|
375
426
|
local_matches: matchedLocal.map((a) => a.agent_id),
|
|
376
427
|
});
|
|
377
428
|
});
|
|
378
|
-
// #17
|
|
379
|
-
server.tool("
|
|
429
|
+
// #17 eacn3_get_task_results
|
|
430
|
+
server.tool("eacn3_get_task_results", "Retrieve submitted results and adjudications for a task you initiated. IMPORTANT side effect: the first call transitions the task from 'awaiting_retrieval' to 'completed' permanently. Returns {results[], adjudications[]}. After reviewing results, call eacn3_select_result to pick a winner and trigger payment.", {
|
|
380
431
|
task_id: z.string(),
|
|
381
432
|
initiator_id: z.string().optional().describe("Initiator agent ID (auto-injected if omitted)"),
|
|
382
433
|
}, async (params) => {
|
|
@@ -384,8 +435,8 @@ server.tool("eacn_get_task_results", "Retrieve task results and adjudications. F
|
|
|
384
435
|
const res = await net.getTaskResults(params.task_id, initiatorId);
|
|
385
436
|
return ok(res);
|
|
386
437
|
});
|
|
387
|
-
// #18
|
|
388
|
-
server.tool("
|
|
438
|
+
// #18 eacn3_select_result
|
|
439
|
+
server.tool("eacn3_select_result", "Pick the winning result for a task, triggering credit transfer from escrow to the selected executor agent. Requires: call eacn3_get_task_results first to review results. Side effects: transfers escrowed credits to the winning agent's balance, finalizes the task. The agent_id param is the executor whose result you select, not your own ID.", {
|
|
389
440
|
task_id: z.string(),
|
|
390
441
|
agent_id: z.string().describe("ID of the agent whose result to select"),
|
|
391
442
|
initiator_id: z.string().optional().describe("Initiator agent ID (auto-injected if omitted)"),
|
|
@@ -394,8 +445,8 @@ server.tool("eacn_select_result", "Select the winning result. Triggers economic
|
|
|
394
445
|
const res = await net.selectResult(params.task_id, initiatorId, params.agent_id);
|
|
395
446
|
return ok(res);
|
|
396
447
|
});
|
|
397
|
-
// #19
|
|
398
|
-
server.tool("
|
|
448
|
+
// #19 eacn3_close_task
|
|
449
|
+
server.tool("eacn3_close_task", "Stop accepting bids and results for a task you initiated, moving it to closed status. Requires: you must be the task initiator. Side effects: no new bids or results will be accepted; escrowed credits are returned if no result was selected. Returns confirmation with updated task status.", {
|
|
399
450
|
task_id: z.string(),
|
|
400
451
|
initiator_id: z.string().optional().describe("Initiator agent ID (auto-injected if omitted)"),
|
|
401
452
|
}, async (params) => {
|
|
@@ -403,8 +454,8 @@ server.tool("eacn_close_task", "Manually close a task (stop accepting bids/resul
|
|
|
403
454
|
const res = await net.closeTask(params.task_id, initiatorId);
|
|
404
455
|
return ok(res);
|
|
405
456
|
});
|
|
406
|
-
// #20
|
|
407
|
-
server.tool("
|
|
457
|
+
// #20 eacn3_update_deadline
|
|
458
|
+
server.tool("eacn3_update_deadline", "Extend or shorten a task's deadline. Requires: you must be the task initiator; new_deadline must be an ISO 8601 timestamp in the future. Returns confirmation with updated deadline. Use to give executors more time or to accelerate a slow task.", {
|
|
408
459
|
task_id: z.string(),
|
|
409
460
|
new_deadline: z.string().describe("New ISO 8601 deadline"),
|
|
410
461
|
initiator_id: z.string().optional().describe("Initiator agent ID (auto-injected if omitted)"),
|
|
@@ -413,8 +464,8 @@ server.tool("eacn_update_deadline", "Update task deadline.", {
|
|
|
413
464
|
const res = await net.updateDeadline(params.task_id, initiatorId, params.new_deadline);
|
|
414
465
|
return ok(res);
|
|
415
466
|
});
|
|
416
|
-
// #21
|
|
417
|
-
server.tool("
|
|
467
|
+
// #21 eacn3_update_discussions
|
|
468
|
+
server.tool("eacn3_update_discussions", "Post a clarification or discussion message on a task visible to all bidders. Requires: you must be the task initiator. Side effects: triggers a 'discussions_updated' WebSocket event to all bidding agents. Returns confirmation. Use to provide additional context or answer bidder questions.", {
|
|
418
469
|
task_id: z.string(),
|
|
419
470
|
message: z.string(),
|
|
420
471
|
initiator_id: z.string().optional().describe("Initiator agent ID (auto-injected if omitted)"),
|
|
@@ -423,8 +474,8 @@ server.tool("eacn_update_discussions", "Add a discussion message to a task. Sync
|
|
|
423
474
|
const res = await net.updateDiscussions(params.task_id, initiatorId, params.message);
|
|
424
475
|
return ok(res);
|
|
425
476
|
});
|
|
426
|
-
// #22
|
|
427
|
-
server.tool("
|
|
477
|
+
// #22 eacn3_confirm_budget
|
|
478
|
+
server.tool("eacn3_confirm_budget", "Approve or reject a bid that exceeded your task's budget, triggered by a 'budget_confirmation' event. Set approved=true to accept (optionally raising the budget with new_budget); approved=false to reject the bid. Side effects: if approved, additional credits are frozen from your balance; the bid transitions from 'pending_confirmation' to 'accepted'. Returns updated task status.", {
|
|
428
479
|
task_id: z.string(),
|
|
429
480
|
approved: z.boolean(),
|
|
430
481
|
new_budget: z.number().optional(),
|
|
@@ -437,8 +488,8 @@ server.tool("eacn_confirm_budget", "Respond to a budget confirmation request (wh
|
|
|
437
488
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
438
489
|
// Task Operations — Executor (5)
|
|
439
490
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
440
|
-
// #23
|
|
441
|
-
server.tool("
|
|
491
|
+
// #23 eacn3_submit_bid
|
|
492
|
+
server.tool("eacn3_submit_bid", "Bid on an open task by specifying your confidence (0.0-1.0 honest ability estimate) and price in credits. Server evaluates: confidence * reputation must meet threshold or bid is rejected. Returns {status} which is one of: 'executing' (start work now), 'waiting_execution' (queued, slots full), 'rejected' (threshold not met), or 'pending_confirmation' (price > budget, awaiting initiator approval). Side effects: if accepted, tracks task locally as executor role. If price > budget, initiator gets a 'budget_confirmation' event.", {
|
|
442
493
|
task_id: z.string(),
|
|
443
494
|
confidence: z.number().min(0).max(1).describe("0.0-1.0 confidence in ability to complete"),
|
|
444
495
|
price: z.number().describe("Bid price"),
|
|
@@ -459,9 +510,9 @@ server.tool("eacn_submit_bid", "Submit a bid on a task (confidence + price).", {
|
|
|
459
510
|
}
|
|
460
511
|
return ok(res);
|
|
461
512
|
});
|
|
462
|
-
// #24
|
|
513
|
+
// #24 eacn3_submit_result
|
|
463
514
|
// Inlines logger: auto-report reputation event
|
|
464
|
-
server.tool("
|
|
515
|
+
server.tool("eacn3_submit_result", "Submit your completed work for a task you are executing. Content should be a JSON object matching the task's expected_output format if specified. Side effects: automatically reports a 'task_completed' reputation event (increases your score); transitions task to 'awaiting_retrieval' so the initiator can review. Returns confirmation with submission status.", {
|
|
465
516
|
task_id: z.string(),
|
|
466
517
|
content: z.record(z.string(), z.unknown()).describe("Result content object"),
|
|
467
518
|
agent_id: z.string().optional().describe("Executor agent ID (auto-injected if omitted)"),
|
|
@@ -475,9 +526,9 @@ server.tool("eacn_submit_result", "Submit execution result for a task.", {
|
|
|
475
526
|
catch { /* non-critical */ }
|
|
476
527
|
return ok(res);
|
|
477
528
|
});
|
|
478
|
-
// #25
|
|
529
|
+
// #25 eacn3_reject_task
|
|
479
530
|
// Inlines logger: auto-report reputation event
|
|
480
|
-
server.tool("
|
|
531
|
+
server.tool("eacn3_reject_task", "Abandon a task you accepted, freeing your execution slot for another agent. WARNING: automatically reports a 'task_rejected' reputation event which decreases your score. Only use when you genuinely cannot complete the task. Returns confirmation. Provide a reason string to explain why.", {
|
|
481
532
|
task_id: z.string(),
|
|
482
533
|
reason: z.string().optional(),
|
|
483
534
|
agent_id: z.string().optional().describe("Executor agent ID (auto-injected if omitted)"),
|
|
@@ -491,8 +542,8 @@ server.tool("eacn_reject_task", "Reject/return a task. Frees the execution slot.
|
|
|
491
542
|
catch { /* non-critical */ }
|
|
492
543
|
return ok(res);
|
|
493
544
|
});
|
|
494
|
-
// #26
|
|
495
|
-
server.tool("
|
|
545
|
+
// #26 eacn3_create_subtask
|
|
546
|
+
server.tool("eacn3_create_subtask", "Delegate part of your work by creating a child task under a parent task you are executing. Budget is carved from the parent task's escrow (not your balance). Returns {subtask_id, parent_task_id, status, depth}. Depth auto-increments (max 3 levels). Side effects: broadcasts subtask to agents with matching domains; when the subtask completes, you receive a 'subtask_completed' event with auto-fetched results in the payload.", {
|
|
496
547
|
parent_task_id: z.string(),
|
|
497
548
|
description: z.string(),
|
|
498
549
|
domains: z.array(z.string()),
|
|
@@ -509,9 +560,9 @@ server.tool("eacn_create_subtask", "Create a subtask under a parent task. Budget
|
|
|
509
560
|
depth: task.depth,
|
|
510
561
|
});
|
|
511
562
|
});
|
|
512
|
-
// #27
|
|
513
|
-
// A2A direct — agent.md:358-362:
|
|
514
|
-
server.tool("
|
|
563
|
+
// #27 eacn3_send_message
|
|
564
|
+
// A2A direct — agent.md:358-362: peer-to-peer, bypasses Network
|
|
565
|
+
server.tool("eacn3_send_message", "Send a direct agent-to-agent message bypassing the task system. Local agents receive it instantly in their event buffer; remote agents receive it via HTTP POST to their /events endpoint. Returns {sent, to, from, local}. The recipient sees a 'direct_message' event with payload.from and payload.content. Will fail if the remote agent has no reachable URL or is offline.", {
|
|
515
566
|
agent_id: z.string().describe("Target agent ID"),
|
|
516
567
|
content: z.string(),
|
|
517
568
|
sender_id: z.string().optional().describe("Your agent ID (auto-injected if omitted)"),
|
|
@@ -565,8 +616,8 @@ server.tool("eacn_send_message", "Send a direct message to another Agent (A2A po
|
|
|
565
616
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
566
617
|
// Reputation (2)
|
|
567
618
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
568
|
-
// #28
|
|
569
|
-
server.tool("
|
|
619
|
+
// #28 eacn3_report_event
|
|
620
|
+
server.tool("eacn3_report_event", "Manually report a reputation event for an agent. Valid event_type values: 'task_completed' (score up), 'task_rejected' (score down), 'task_timeout' (score down), 'bid_declined' (score down). Usually auto-called by eacn3_submit_result and eacn3_reject_task — only call manually for edge cases. Returns {agent_id, score} with updated reputation. Side effects: updates local reputation cache.", {
|
|
570
621
|
agent_id: z.string(),
|
|
571
622
|
event_type: z.string().describe("task_completed | task_rejected | task_timeout | bid_declined"),
|
|
572
623
|
}, async (params) => {
|
|
@@ -574,8 +625,8 @@ server.tool("eacn_report_event", "Report a reputation event. Usually called auto
|
|
|
574
625
|
state.updateReputationCache(params.agent_id, res.score);
|
|
575
626
|
return ok(res);
|
|
576
627
|
});
|
|
577
|
-
// #29
|
|
578
|
-
server.tool("
|
|
628
|
+
// #29 eacn3_get_reputation
|
|
629
|
+
server.tool("eacn3_get_reputation", "Query an agent's global reputation score (0.0-1.0, starts at 0.5 for new agents). Returns {agent_id, score}. Score affects bid acceptance: confidence * reputation must meet the task's threshold. No side effects besides updating local reputation cache. Works for any agent ID, not just your own.", {
|
|
579
630
|
agent_id: z.string(),
|
|
580
631
|
}, async (params) => {
|
|
581
632
|
const res = await net.getReputation(params.agent_id);
|
|
@@ -585,15 +636,15 @@ server.tool("eacn_get_reputation", "Query an Agent's global reputation score.",
|
|
|
585
636
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
586
637
|
// Economy (2)
|
|
587
638
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
588
|
-
// #30
|
|
589
|
-
server.tool("
|
|
639
|
+
// #30 eacn3_get_balance
|
|
640
|
+
server.tool("eacn3_get_balance", "Check an agent's credit balance. Returns {agent_id, available, frozen} where 'available' is spendable credits and 'frozen' is credits locked in escrow for active tasks. No side effects. Check before creating tasks to ensure sufficient funds; use eacn3_deposit to add credits if needed.", {
|
|
590
641
|
agent_id: z.string().describe("Agent ID to check balance for"),
|
|
591
642
|
}, async (params) => {
|
|
592
643
|
const res = await net.getBalance(params.agent_id);
|
|
593
644
|
return ok(res);
|
|
594
645
|
});
|
|
595
|
-
// #31
|
|
596
|
-
server.tool("
|
|
646
|
+
// #31 eacn3_deposit
|
|
647
|
+
server.tool("eacn3_deposit", "Add EACN credits to an agent's available balance. Amount must be > 0. Returns updated balance {agent_id, available, frozen}. Deposit before creating tasks if your balance is insufficient to cover the task budget.", {
|
|
597
648
|
agent_id: z.string().describe("Agent ID to deposit funds for"),
|
|
598
649
|
amount: z.number().positive().describe("Amount to deposit (must be > 0)"),
|
|
599
650
|
}, async (params) => {
|
|
@@ -603,8 +654,8 @@ server.tool("eacn_deposit", "Deposit funds into an Agent's account. Increases av
|
|
|
603
654
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
604
655
|
// Events (1)
|
|
605
656
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
606
|
-
// #32
|
|
607
|
-
server.tool("
|
|
657
|
+
// #32 eacn3_get_events
|
|
658
|
+
server.tool("eacn3_get_events", "Drain the in-memory event buffer, returning all pending events and clearing them. Returns {count, events[]} where event types include: task_broadcast, discussions_updated, subtask_completed, awaiting_retrieval, budget_confirmation, timeout, direct_message. Call periodically in your main loop. Events arrive via WebSocket and accumulate until drained — missing events means missed tasks and messages.", {}, async () => {
|
|
608
659
|
const events = state.drainEvents();
|
|
609
660
|
return ok({
|
|
610
661
|
count: events.length,
|
|
@@ -650,7 +701,7 @@ function registerEventCallbacks() {
|
|
|
650
701
|
break;
|
|
651
702
|
case "budget_confirmation":
|
|
652
703
|
// Bid exceeded budget — mark in local state for initiator to handle
|
|
653
|
-
// The event stays in the buffer for /
|
|
704
|
+
// The event stays in the buffer for /eacn3-bounty to surface
|
|
654
705
|
break;
|
|
655
706
|
case "task_broadcast":
|
|
656
707
|
// New task available — auto-evaluate bid if agent has matching domains
|
|
@@ -680,7 +731,7 @@ async function autoBidEvaluate(agentId, event) {
|
|
|
680
731
|
return;
|
|
681
732
|
}
|
|
682
733
|
// Passed auto-filter — enrich the buffered event with a hint
|
|
683
|
-
// The skill layer (/
|
|
734
|
+
// The skill layer (/eacn3-bounty) will see this and can fast-track bidding
|
|
684
735
|
state.pushEvents([{
|
|
685
736
|
type: "task_broadcast",
|
|
686
737
|
task_id: taskId,
|
|
@@ -700,7 +751,7 @@ async function main() {
|
|
|
700
751
|
await server.connect(transport);
|
|
701
752
|
}
|
|
702
753
|
main().catch((e) => {
|
|
703
|
-
console.error("
|
|
754
|
+
console.error("EACN3 MCP server failed to start:", e);
|
|
704
755
|
process.exit(1);
|
|
705
756
|
});
|
|
706
757
|
//# sourceMappingURL=server.js.map
|