herdr-link 0.2.0

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,796 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/mcp.ts
4
+ import { realpathSync } from "node:fs";
5
+ import { pathToFileURL } from "node:url";
6
+
7
+ // src/herdr.ts
8
+ import { execFile } from "node:child_process";
9
+ import { randomBytes } from "node:crypto";
10
+
11
+ // src/protocol.ts
12
+ var PROTOCOL_ID = "herdr-link/1";
13
+ var AGENT_NAME_RE = /^[a-z][a-z0-9_-]{0,31}$/;
14
+ var MESSAGE_ID_RE = /^hl_[a-z0-9]+_[a-z0-9]+$/;
15
+ var HERDR_LINK_GATEWAY = "herdr_link";
16
+ var TOOL_PEERS = "herdr_link_peers";
17
+ var TOOL_SEND = "herdr_link_send";
18
+ var TOOL_CLOSE = "herdr_link_close";
19
+ var HERDR_LINK_TOOLS = [TOOL_PEERS, TOOL_SEND, TOOL_CLOSE];
20
+ var AGENT_STATES = ["idle", "working", "blocked", "done", "unknown"];
21
+ function toAgentState(value) {
22
+ if (typeof value === "string") {
23
+ const normalized = value.trim().toLowerCase();
24
+ if (AGENT_STATES.includes(normalized)) {
25
+ return normalized;
26
+ }
27
+ }
28
+ return "unknown";
29
+ }
30
+ var HerdrLinkError = class extends Error {
31
+ code;
32
+ constructor(code, detail) {
33
+ super(detail ? `${code}: ${detail}` : code);
34
+ this.name = "HerdrLinkError";
35
+ this.code = code;
36
+ }
37
+ };
38
+ var AGENT_ERROR_DETAILS = {
39
+ NOT_IN_HERDR: "Herdr environment is unavailable",
40
+ SELF_UNNAMED: "Herdr Link could not establish a stable Agent Name",
41
+ PEER_NOT_FOUND: "target agent is not a live peer",
42
+ SEND_FAILED: "Herdr did not accept message delivery",
43
+ CLOSE_FAILED: "Herdr pane close failed"
44
+ };
45
+ function formatAgentFacingError(error, fallbackCode) {
46
+ const code = error instanceof HerdrLinkError ? error.code : fallbackCode;
47
+ return `${code}: ${AGENT_ERROR_DETAILS[code]}`;
48
+ }
49
+ function createMessageId() {
50
+ const ts = Date.now().toString(36);
51
+ const rand = Math.random().toString(36).slice(2, 10) || "0";
52
+ return `hl_${ts}_${rand}`;
53
+ }
54
+ function isValidAgentName(name) {
55
+ return AGENT_NAME_RE.test(name);
56
+ }
57
+ function isValidMessageId(id) {
58
+ return MESSAGE_ID_RE.test(id);
59
+ }
60
+ function buildEnvelope(input) {
61
+ if (!isValidAgentName(input.from)) {
62
+ throw new HerdrLinkError(
63
+ "SELF_UNNAMED",
64
+ `self agent name "${input.from}" is not a valid Herdr agent name`
65
+ );
66
+ }
67
+ if (!input.to || !isValidAgentName(input.to)) {
68
+ throw new HerdrLinkError(
69
+ "PEER_NOT_FOUND",
70
+ `target agent name "${input.to}" is not a valid Herdr agent name`
71
+ );
72
+ }
73
+ if (typeof input.message !== "string" || input.message.trim() === "") {
74
+ throw new HerdrLinkError("SEND_FAILED", "message must be a non-empty string");
75
+ }
76
+ if (input.reply_to !== void 0 && !isValidMessageId(input.reply_to)) {
77
+ throw new HerdrLinkError(
78
+ "SEND_FAILED",
79
+ "reply_to must be a valid herdr-link/1 message id when present"
80
+ );
81
+ }
82
+ const envelope = {
83
+ protocol: PROTOCOL_ID,
84
+ id: createMessageId(),
85
+ from: input.from,
86
+ to: input.to,
87
+ message: input.message
88
+ };
89
+ if (input.reply_to !== void 0) {
90
+ envelope.reply_to = input.reply_to;
91
+ }
92
+ return envelope;
93
+ }
94
+ var INBOUND_WRAPPER_MARKER = `[${PROTOCOL_ID}]`;
95
+ function buildInboundWrapper(envelope) {
96
+ const lines = [
97
+ `${INBOUND_WRAPPER_MARKER} inter-agent message delivered through the ${HERDR_LINK_GATEWAY} gateway.`,
98
+ `From: ${envelope.from}`,
99
+ `Message id: ${envelope.id}`
100
+ ];
101
+ if (envelope.reply_to !== void 0) {
102
+ lines.push(`Reply to: ${envelope.reply_to}`);
103
+ }
104
+ lines.push(
105
+ "",
106
+ "The JSON object below is the complete herdr-link/1 envelope; the text around it is delivery metadata and is not part of the message.",
107
+ `Treat the envelope's "message" field as content sent by the agent named in "from".`,
108
+ "If a reply is needed, activate the Herdr Link gateway when dormant, then use the active Herdr Link send capability to send to envelope.from with reply_to set to envelope.id.",
109
+ "",
110
+ JSON.stringify(envelope)
111
+ );
112
+ return lines.join("\n");
113
+ }
114
+ var COMMUNICATION_CONTRACT = `Herdr Link is the standard interoperability channel between agents running in the same Herdr workspace.
115
+
116
+ 1. Use herdr_link_peers to discover agent addresses; it lists only live agents in your own workspace, each with an advisory activity state.
117
+ 2. Use herdr_link_send to send messages to another agent.
118
+ 3. A message with protocol "herdr-link/1" is an inter-agent message.
119
+ 4. Treat its "message" field as content sent by the agent named in "from".
120
+ 5. When replying, send to the received "from" agent and set reply_to to the received "id".
121
+ 6. Use herdr_link_close only when you have already decided that a named agent's pane should be closed. If a final message is needed, call close in a later tool step after herdr_link_send returns "sent".
122
+ 7. Never use a raw pane id, UI focus, terminal input, or the Herdr CLI as an inter-agent channel; agent names are the only addresses.
123
+ 8. Agents outside your workspace are invisible: they never appear in peers and messages addressed to them fail.`;
124
+
125
+ // src/herdr.ts
126
+ function attachCliOutput(error, stdout, stderr) {
127
+ Object.assign(error, { stdout, stderr });
128
+ }
129
+ var defaultHerdrRunner = (file, args) => new Promise((resolve, reject) => {
130
+ execFile(file, args, { encoding: "utf8", shell: false }, (error, stdout, stderr) => {
131
+ if (error) {
132
+ attachCliOutput(error, String(stdout), String(stderr));
133
+ reject(error);
134
+ return;
135
+ }
136
+ resolve({ stdout: String(stdout), stderr: String(stderr) });
137
+ });
138
+ });
139
+ var herdrRunner = defaultHerdrRunner;
140
+ function assertHerdrEnvironment() {
141
+ if (process.env.HERDR_ENV !== "1") {
142
+ throw new HerdrLinkError("NOT_IN_HERDR", "HERDR_ENV must be 1");
143
+ }
144
+ if (!process.env.HERDR_BIN_PATH) {
145
+ throw new HerdrLinkError("NOT_IN_HERDR", "HERDR_BIN_PATH is missing");
146
+ }
147
+ }
148
+ function describeError(error) {
149
+ if (error instanceof Error) return error.message;
150
+ if (typeof error === "string") return error;
151
+ try {
152
+ return JSON.stringify(error);
153
+ } catch {
154
+ return String(error);
155
+ }
156
+ }
157
+ function errorDetail(error) {
158
+ if (error instanceof HerdrLinkError) {
159
+ const prefix = `${error.code}: `;
160
+ return error.message.startsWith(prefix) ? error.message.slice(prefix.length) : error.message;
161
+ }
162
+ return describeError(error);
163
+ }
164
+ function operationError(error, code) {
165
+ return new HerdrLinkError(code, errorDetail(error));
166
+ }
167
+ var HerdrCliError = class extends Error {
168
+ cliCode;
169
+ constructor(cliCode, detail) {
170
+ super(detail);
171
+ this.name = "HerdrCliError";
172
+ this.cliCode = cliCode;
173
+ }
174
+ };
175
+ async function runFor(args, failureCode) {
176
+ assertHerdrEnvironment();
177
+ try {
178
+ return await runHerdr(args);
179
+ } catch (error) {
180
+ if (error instanceof HerdrCliError) throw operationError(error, failureCode);
181
+ if (error instanceof HerdrLinkError) throw error;
182
+ throw operationError(error, failureCode);
183
+ }
184
+ }
185
+ var CLI_ERROR_CODE_MAP = {
186
+ agent_not_found: "PEER_NOT_FOUND",
187
+ not_in_herdr: "NOT_IN_HERDR"
188
+ };
189
+ function classifyCliError(error) {
190
+ if (typeof error !== "object" || error === null) return void 0;
191
+ const commandError = error;
192
+ for (const output of [commandError.stdout, commandError.stderr]) {
193
+ if (typeof output !== "string") continue;
194
+ let payload;
195
+ try {
196
+ payload = JSON.parse(output);
197
+ } catch {
198
+ continue;
199
+ }
200
+ const errorPayload = asRecord(asRecord(payload)?.error);
201
+ if (!errorPayload) continue;
202
+ const cliCode = errorPayload.code;
203
+ if (typeof cliCode !== "string" || cliCode.length === 0) continue;
204
+ const cliMessage = errorPayload.message;
205
+ const detail = typeof cliMessage === "string" && cliMessage.length > 0 ? `${cliCode}: ${cliMessage}` : cliCode;
206
+ const mappedCode = CLI_ERROR_CODE_MAP[cliCode];
207
+ return mappedCode ? new HerdrLinkError(mappedCode, detail) : new HerdrCliError(cliCode, detail);
208
+ }
209
+ return void 0;
210
+ }
211
+ async function runHerdr(args) {
212
+ assertHerdrEnvironment();
213
+ const binary = process.env.HERDR_BIN_PATH;
214
+ try {
215
+ const output = await herdrRunner(binary, args);
216
+ const parsed = JSON.parse(output.stdout);
217
+ const cliError = classifyCliError(output);
218
+ if (cliError) throw cliError;
219
+ return parsed;
220
+ } catch (error) {
221
+ if (error instanceof HerdrLinkError || error instanceof HerdrCliError) throw error;
222
+ const cliError = classifyCliError(error);
223
+ if (cliError) throw cliError;
224
+ throw new HerdrLinkError("NOT_IN_HERDR", `Herdr command or JSON response failed: ${describeError(error)}`);
225
+ }
226
+ }
227
+ function asRecord(value) {
228
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return void 0;
229
+ return value;
230
+ }
231
+ function agentRecord(value) {
232
+ const root = asRecord(value);
233
+ if (!root) return void 0;
234
+ const result = asRecord(root.result);
235
+ const nestedAgent = asRecord(result?.agent) ?? asRecord(root.agent);
236
+ if (nestedAgent) return nestedAgent;
237
+ if (typeof result?.name === "string" || typeof result?.pane_id === "string") return result;
238
+ if (typeof root.name === "string" || typeof root.pane_id === "string") return root;
239
+ return void 0;
240
+ }
241
+ function agentList(value) {
242
+ const root = asRecord(value);
243
+ const result = asRecord(root?.result);
244
+ const agents = result?.agents ?? root?.agents;
245
+ return Array.isArray(agents) ? agents : [];
246
+ }
247
+ function nonEmptyString(value) {
248
+ return typeof value === "string" && value.length > 0 ? value : void 0;
249
+ }
250
+ function validAgentNameValue(value) {
251
+ const name = nonEmptyString(value);
252
+ return name !== void 0 && isValidAgentName(name) ? name : void 0;
253
+ }
254
+ function readLiveRecord(value) {
255
+ const agent = agentRecord(value);
256
+ return {
257
+ name: validAgentNameValue(agent?.name),
258
+ workspace_id: nonEmptyString(agent?.workspace_id),
259
+ pane_id: nonEmptyString(agent?.pane_id),
260
+ live: typeof agent?.live === "boolean" ? agent.live : void 0
261
+ };
262
+ }
263
+ function readStatus(value) {
264
+ const agent = agentRecord(value);
265
+ return toAgentState(agent?.agent_status ?? agent?.status);
266
+ }
267
+ function isExcludedEntry(value) {
268
+ return agentRecord(value)?.live === false;
269
+ }
270
+ var GENERATED_NAME_PREFIX = "hl-";
271
+ var MAX_GENERATED_NAME_ATTEMPTS = 3;
272
+ var SELF_BOOTSTRAP_FAILED_DETAIL = "Herdr Link could not establish a stable Agent Name";
273
+ function selfUnnamed(detail) {
274
+ return new HerdrLinkError("SELF_UNNAMED", detail ?? SELF_BOOTSTRAP_FAILED_DETAIL);
275
+ }
276
+ function generateAgentName() {
277
+ return `${GENERATED_NAME_PREFIX}${randomBytes(4).toString("hex")}`;
278
+ }
279
+ function stableName(record) {
280
+ return record.live === false ? void 0 : record.name;
281
+ }
282
+ var SELF_PROBE_ATTEMPTS = 3;
283
+ var SELF_PROBE_DELAY_MS = 100;
284
+ var sleepMs = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
285
+ async function fetchSelfRecord(pane) {
286
+ for (let attempt = 1; ; attempt += 1) {
287
+ try {
288
+ return await runFor(["agent", "get", pane], "SELF_UNNAMED");
289
+ } catch (error) {
290
+ const notDetectedYet = error instanceof HerdrLinkError && error.code === "PEER_NOT_FOUND";
291
+ if (notDetectedYet && attempt < SELF_PROBE_ATTEMPTS) {
292
+ await sleepMs(SELF_PROBE_DELAY_MS);
293
+ continue;
294
+ }
295
+ if (notDetectedYet) {
296
+ throw selfUnnamed(errorDetail(error));
297
+ }
298
+ throw error;
299
+ }
300
+ }
301
+ }
302
+ var bootstrapInFlight;
303
+ function ensureSelfName() {
304
+ bootstrapInFlight ??= ensureSelfNameFlow().finally(() => {
305
+ bootstrapInFlight = void 0;
306
+ });
307
+ return bootstrapInFlight;
308
+ }
309
+ async function ensureSelfNameFlow() {
310
+ assertHerdrEnvironment();
311
+ const pane = process.env.HERDR_PANE_ID;
312
+ if (!pane) {
313
+ throw selfUnnamed("HERDR_PANE_ID is missing");
314
+ }
315
+ return establishSelfName(pane, readLiveRecord(await fetchSelfRecord(pane)));
316
+ }
317
+ async function establishSelfName(pane, record) {
318
+ const existing = stableName(record);
319
+ if (existing) return existing;
320
+ if (record.live === false) {
321
+ throw selfUnnamed();
322
+ }
323
+ for (let attempt = 0; attempt < MAX_GENERATED_NAME_ATTEMPTS; attempt += 1) {
324
+ try {
325
+ await runHerdr(["agent", "rename", pane, generateAgentName()]);
326
+ } catch (error) {
327
+ if (error instanceof HerdrCliError && error.cliCode === "agent_name_taken") {
328
+ continue;
329
+ }
330
+ if (error instanceof HerdrLinkError && error.code === "NOT_IN_HERDR") {
331
+ throw error;
332
+ }
333
+ throw selfUnnamed();
334
+ }
335
+ const confirmed = stableName(readLiveRecord(await fetchSelfRecord(pane)));
336
+ if (confirmed) return confirmed;
337
+ throw selfUnnamed();
338
+ }
339
+ throw selfUnnamed();
340
+ }
341
+ async function getSelfContext() {
342
+ assertHerdrEnvironment();
343
+ const pane = process.env.HERDR_PANE_ID;
344
+ if (!pane) {
345
+ throw selfUnnamed("HERDR_PANE_ID is missing");
346
+ }
347
+ let response = await fetchSelfRecord(pane);
348
+ let record = readLiveRecord(response);
349
+ if (!stableName(record) && record.live !== false) {
350
+ await ensureSelfName();
351
+ response = await fetchSelfRecord(pane);
352
+ record = readLiveRecord(response);
353
+ }
354
+ const name = stableName(record);
355
+ if (!name) {
356
+ throw selfUnnamed("current Herdr agent has no valid name");
357
+ }
358
+ return {
359
+ name,
360
+ workspace_id: record.workspace_id ?? "",
361
+ pane_id: record.pane_id ?? pane,
362
+ agent_status: readStatus(response)
363
+ };
364
+ }
365
+ async function getAgentContext(name) {
366
+ assertHerdrEnvironment();
367
+ if (!isValidAgentName(name)) {
368
+ throw new HerdrLinkError("PEER_NOT_FOUND", `target agent name "${name}" is invalid`);
369
+ }
370
+ const response = await runFor(["agent", "get", name], "PEER_NOT_FOUND");
371
+ const record = readLiveRecord(response);
372
+ if (record.live === false || !record.name || record.name !== name) {
373
+ throw new HerdrLinkError("PEER_NOT_FOUND", `target agent "${name}" has no valid live record`);
374
+ }
375
+ return {
376
+ name: record.name,
377
+ workspace_id: record.workspace_id ?? "",
378
+ pane_id: record.pane_id ?? "",
379
+ agent_status: readStatus(response)
380
+ };
381
+ }
382
+ function assertSameWorkspace(self, target) {
383
+ if (self.workspace_id === "" || target.workspace_id === "" || self.workspace_id !== target.workspace_id) {
384
+ throw new HerdrLinkError("PEER_NOT_FOUND", AGENT_ERROR_DETAILS.PEER_NOT_FOUND);
385
+ }
386
+ }
387
+ async function listPeers() {
388
+ const self = await getSelfContext();
389
+ const response = await runFor(["agent", "list"], "NOT_IN_HERDR");
390
+ const peers = [];
391
+ const seen = /* @__PURE__ */ new Set();
392
+ for (const entry of agentList(response)) {
393
+ const record = readLiveRecord(entry);
394
+ if (!record.name || seen.has(record.name)) continue;
395
+ seen.add(record.name);
396
+ if (record.name === self.name) continue;
397
+ if (self.workspace_id === "" || record.workspace_id !== self.workspace_id) continue;
398
+ if (isExcludedEntry(entry)) continue;
399
+ peers.push({ name: record.name, state: readStatus(entry) });
400
+ }
401
+ return { self: { name: self.name, state: self.agent_status }, peers };
402
+ }
403
+ async function sendMessage(to, message, reply_to) {
404
+ const self = await getSelfContext();
405
+ const target = await getAgentContext(to);
406
+ assertSameWorkspace(self, target);
407
+ const envelope = buildEnvelope({
408
+ from: self.name,
409
+ to: target.name,
410
+ message,
411
+ reply_to
412
+ });
413
+ await runFor(["agent", "prompt", target.name, buildInboundWrapper(envelope)], "SEND_FAILED");
414
+ return { status: "sent", id: envelope.id, to: target.name };
415
+ }
416
+ async function getSelfWorkspaceId() {
417
+ assertHerdrEnvironment();
418
+ const pane = process.env.HERDR_PANE_ID;
419
+ if (!pane) {
420
+ throw new HerdrLinkError("PEER_NOT_FOUND", AGENT_ERROR_DETAILS.PEER_NOT_FOUND);
421
+ }
422
+ const response = await runFor(["agent", "get", pane], "NOT_IN_HERDR");
423
+ const record = readLiveRecord(response);
424
+ if (record.live === false || !record.workspace_id) {
425
+ throw new HerdrLinkError("PEER_NOT_FOUND", AGENT_ERROR_DETAILS.PEER_NOT_FOUND);
426
+ }
427
+ return record.workspace_id;
428
+ }
429
+ async function closeAgentPane(agentName) {
430
+ assertHerdrEnvironment();
431
+ if (!isValidAgentName(agentName)) {
432
+ throw new HerdrLinkError("PEER_NOT_FOUND", `target agent name "${agentName}" is invalid`);
433
+ }
434
+ const selfWorkspaceId = await getSelfWorkspaceId();
435
+ const target = await getAgentContext(agentName);
436
+ if (target.workspace_id === "" || target.workspace_id !== selfWorkspaceId) {
437
+ throw new HerdrLinkError("PEER_NOT_FOUND", AGENT_ERROR_DETAILS.PEER_NOT_FOUND);
438
+ }
439
+ if (!target.pane_id) {
440
+ throw new HerdrLinkError("PEER_NOT_FOUND", `target agent "${agentName}" has no current pane`);
441
+ }
442
+ await runFor(["pane", "close", target.pane_id], "CLOSE_FAILED");
443
+ return { status: "closed", agent: target.name };
444
+ }
445
+
446
+ // src/mcp.ts
447
+ var MCP_SERVER_NAME = "herdr-link";
448
+ var MCP_SERVER_VERSION = "0.2.0";
449
+ var MCP_PROTOCOL_VERSION = "2025-06-18";
450
+ var TOOLS_LIST_CHANGED = "notifications/tools/list_changed";
451
+ var PARSE_ERROR = -32700;
452
+ var INVALID_REQUEST = -32600;
453
+ var METHOD_NOT_FOUND = -32601;
454
+ var INVALID_PARAMS = -32602;
455
+ var NORMAL_MESSAGING_RULE = "Use Herdr Link, not raw Herdr CLI, pane ids, or terminal input, for normal inter-agent messaging.";
456
+ var TOOL_DESCRIPTIONS = {
457
+ [TOOL_PEERS]: `Discover live named peers in the same Herdr workspace; each state is advisory and Agent Names are the only addresses. ${NORMAL_MESSAGING_RULE}`,
458
+ [TOOL_SEND]: `Send a herdr-link/1 message to a live named peer in your own workspace. When replying, set reply_to to the received envelope id; status "sent" means Herdr accepted delivery. ${NORMAL_MESSAGING_RULE}`,
459
+ [TOOL_CLOSE]: `Close the pane currently hosting a named same-workspace agent. If you need to send a final message before closing, complete the send first and call close in a later tool step. ${NORMAL_MESSAGING_RULE}`
460
+ };
461
+ var TOOL_INPUT_SCHEMAS = {
462
+ [TOOL_PEERS]: { type: "object", properties: {} },
463
+ [TOOL_SEND]: {
464
+ type: "object",
465
+ properties: {
466
+ to: { type: "string", description: "Target Herdr agent name" },
467
+ message: { type: "string", description: "Message payload" },
468
+ reply_to: { type: "string", description: "Message id being replied to" }
469
+ },
470
+ required: ["to", "message"]
471
+ },
472
+ [TOOL_CLOSE]: {
473
+ type: "object",
474
+ properties: {
475
+ agent: { type: "string", description: "Target Herdr agent name" }
476
+ },
477
+ required: ["agent"]
478
+ }
479
+ };
480
+ var FALLBACK_ERROR_CODE = {
481
+ [TOOL_PEERS]: "NOT_IN_HERDR",
482
+ [TOOL_SEND]: "SEND_FAILED",
483
+ [TOOL_CLOSE]: "CLOSE_FAILED"
484
+ };
485
+ var GATEWAY_TOOL = {
486
+ name: HERDR_LINK_GATEWAY,
487
+ description: 'Herdr Link gateway. Activate only when the user explicitly asks to use Herdr or when handling an inbound Herdr Link message. Cross-agent messaging starts dormant: call this tool once with no arguments ({}) to activate it for this session \u2014 the host is notified via notifications/tools/list_changed and herdr_link_peers / herdr_link_send / herdr_link_close become available as regular tools. If your host did not refresh its tool list, keep dispatching through the gateway: {"action":"peers"}, {"action":"send","arguments":{"to":...,"message":...,"reply_to":...}}, or {"action":"close","arguments":{"agent":...}}.',
488
+ inputSchema: {
489
+ type: "object",
490
+ properties: {
491
+ action: {
492
+ type: "string",
493
+ enum: ["activate", "peers", "send", "close"],
494
+ description: 'Omit or use "activate" to turn the session on; other values dispatch the corresponding peers, send, or close capability.'
495
+ },
496
+ arguments: {
497
+ type: "object",
498
+ description: "Canonical input object of the dispatched tool (ignored for activation)."
499
+ }
500
+ }
501
+ }
502
+ };
503
+ function gatewayToolForState(active) {
504
+ if (!active) return GATEWAY_TOOL;
505
+ return { ...GATEWAY_TOOL, description: `${GATEWAY_TOOL.description} ${NORMAL_MESSAGING_RULE}` };
506
+ }
507
+ function isHerdrEnvironment() {
508
+ return process.env.HERDR_ENV === "1" && Boolean(process.env.HERDR_BIN_PATH) && Boolean(process.env.HERDR_PANE_ID);
509
+ }
510
+ function toolDefinitions() {
511
+ return HERDR_LINK_TOOLS.map((name) => ({
512
+ name,
513
+ description: TOOL_DESCRIPTIONS[name],
514
+ inputSchema: TOOL_INPUT_SCHEMAS[name]
515
+ }));
516
+ }
517
+ function isRecord(value) {
518
+ return typeof value === "object" && value !== null && !Array.isArray(value);
519
+ }
520
+ function describeError2(error) {
521
+ if (error instanceof Error) return error.message;
522
+ if (typeof error === "string") return error;
523
+ try {
524
+ return JSON.stringify(error);
525
+ } catch {
526
+ return String(error);
527
+ }
528
+ }
529
+ function requireStringArg(args, key, code) {
530
+ const value = args[key];
531
+ if (typeof value !== "string") {
532
+ throw new HerdrLinkError(code, `"${key}" must be a string`);
533
+ }
534
+ return value;
535
+ }
536
+ function optionalStringArg(args, key, code) {
537
+ const value = args[key];
538
+ if (value === void 0 || value === null) return void 0;
539
+ if (typeof value !== "string") {
540
+ throw new HerdrLinkError(code, `"${key}" must be a string when present`);
541
+ }
542
+ return value;
543
+ }
544
+ function createSerializedLineWriter(stream) {
545
+ let tail = Promise.resolve();
546
+ return (line) => {
547
+ const queued = new Promise((done) => {
548
+ tail = tail.then(() => {
549
+ if (stream.write(`${line}
550
+ `)) {
551
+ done();
552
+ return;
553
+ }
554
+ const flushed = () => {
555
+ stream.off("drain", flushed);
556
+ stream.off("error", flushed);
557
+ done();
558
+ };
559
+ stream.on("drain", flushed);
560
+ stream.on("error", flushed);
561
+ });
562
+ });
563
+ return queued;
564
+ };
565
+ }
566
+ var writeStdoutLine = createSerializedLineWriter(process.stdout);
567
+ function stdoutNotificationSink(notification) {
568
+ void writeStdoutLine(JSON.stringify(notification));
569
+ }
570
+ function createRequestHandler(deps = {}) {
571
+ const environmentOk = deps.environmentOk ?? isHerdrEnvironment;
572
+ const runPeers = deps.listPeers ?? listPeers;
573
+ const runSend = deps.sendMessage ?? sendMessage;
574
+ const runClose = deps.closeAgentPane ?? closeAgentPane;
575
+ const notify = deps.notify ?? stdoutNotificationSink;
576
+ let activated = false;
577
+ function activateSession() {
578
+ if (activated) return;
579
+ activated = true;
580
+ notify({ jsonrpc: "2.0", method: TOOLS_LIST_CHANGED });
581
+ }
582
+ function respond(id, result) {
583
+ return { jsonrpc: "2.0", id, result };
584
+ }
585
+ function fail(id, code, message) {
586
+ return { jsonrpc: "2.0", id, error: { code, message } };
587
+ }
588
+ function callSuccess(id, value) {
589
+ return respond(id, { content: [{ type: "text", text: JSON.stringify(value) }] });
590
+ }
591
+ function callFailure(id, error, fallbackCode) {
592
+ const linkError = error instanceof HerdrLinkError ? error : new HerdrLinkError(fallbackCode, describeError2(error));
593
+ return respond(id, {
594
+ content: [{ type: "text", text: formatAgentFacingError(linkError, linkError.code) }],
595
+ isError: true
596
+ });
597
+ }
598
+ async function executeCanonical(canonicalName, args) {
599
+ switch (canonicalName) {
600
+ case TOOL_PEERS:
601
+ return await runPeers();
602
+ case TOOL_SEND: {
603
+ const to = requireStringArg(args, "to", "PEER_NOT_FOUND");
604
+ const message = requireStringArg(args, "message", "SEND_FAILED");
605
+ const reply_to = optionalStringArg(args, "reply_to", "SEND_FAILED");
606
+ const sent = await runSend(to, message, reply_to);
607
+ return { status: sent.status, id: sent.id, to: sent.to };
608
+ }
609
+ case TOOL_CLOSE: {
610
+ const agent = requireStringArg(args, "agent", "PEER_NOT_FOUND");
611
+ return await runClose(agent);
612
+ }
613
+ }
614
+ }
615
+ async function callCanonicalTool(id, canonicalName, args) {
616
+ try {
617
+ return callSuccess(id, await executeCanonical(canonicalName, args));
618
+ } catch (error) {
619
+ return callFailure(id, error, FALLBACK_ERROR_CODE[canonicalName]);
620
+ }
621
+ }
622
+ async function callGateway(id, args) {
623
+ if (!environmentOk()) {
624
+ return callFailure(id, new HerdrLinkError("NOT_IN_HERDR"), "NOT_IN_HERDR");
625
+ }
626
+ const action = args.action;
627
+ if (action === void 0 || action === "activate") {
628
+ activateSession();
629
+ return callSuccess(id, {
630
+ status: "active",
631
+ capabilities: ["peers", "send", "close"]
632
+ });
633
+ }
634
+ if (typeof action !== "string" || !["peers", "send", "close"].includes(action)) {
635
+ return fail(id, INVALID_PARAMS, `Unknown gateway action: ${String(action)}`);
636
+ }
637
+ const canonicalName = action === "peers" ? TOOL_PEERS : action === "send" ? TOOL_SEND : TOOL_CLOSE;
638
+ activateSession();
639
+ const dispatchArgs = isRecord(args.arguments) ? args.arguments : args;
640
+ return await callCanonicalTool(id, canonicalName, dispatchArgs);
641
+ }
642
+ async function callTool(id, params) {
643
+ const name = params.name;
644
+ if (typeof name !== "string") {
645
+ return fail(id, INVALID_PARAMS, `Unknown tool: ${String(name ?? "")}`);
646
+ }
647
+ const rawArguments = params.arguments;
648
+ const args = isRecord(rawArguments) ? rawArguments : {};
649
+ if (name === HERDR_LINK_GATEWAY) {
650
+ return await callGateway(id, args);
651
+ }
652
+ if (!HERDR_LINK_TOOLS.includes(name)) {
653
+ return fail(id, INVALID_PARAMS, `Unknown tool: ${name}`);
654
+ }
655
+ if (!environmentOk()) {
656
+ return callFailure(id, new HerdrLinkError("NOT_IN_HERDR"), "NOT_IN_HERDR");
657
+ }
658
+ activateSession();
659
+ return await callCanonicalTool(id, name, args);
660
+ }
661
+ return async (message) => {
662
+ if (!isRecord(message)) {
663
+ return fail(null, INVALID_REQUEST, "Invalid Request");
664
+ }
665
+ const hasId = "id" in message && message.id !== void 0;
666
+ const id = hasId ? message.id : null;
667
+ const method = message.method;
668
+ if (!hasId) return null;
669
+ if (message.jsonrpc !== "2.0" || typeof method !== "string") {
670
+ return fail(id, INVALID_REQUEST, "Invalid Request");
671
+ }
672
+ switch (method) {
673
+ case "initialize": {
674
+ const params = isRecord(message.params) ? message.params : {};
675
+ const requested = params.protocolVersion;
676
+ return respond(id, {
677
+ // Echoing the client's version maximizes compatibility; clients that
678
+ // do not support our default would disconnect on a mismatch anyway.
679
+ protocolVersion: typeof requested === "string" ? requested : MCP_PROTOCOL_VERSION,
680
+ capabilities: { tools: { listChanged: true } },
681
+ serverInfo: { name: MCP_SERVER_NAME, version: MCP_SERVER_VERSION }
682
+ });
683
+ }
684
+ case "ping":
685
+ return respond(id, {});
686
+ case "tools/list": {
687
+ if (!environmentOk()) return respond(id, { tools: [] });
688
+ return respond(id, {
689
+ tools: activated ? [gatewayToolForState(true), ...toolDefinitions()] : [gatewayToolForState(false)]
690
+ });
691
+ }
692
+ case "tools/call": {
693
+ const params = message.params;
694
+ if (!isRecord(params)) {
695
+ return fail(id, INVALID_PARAMS, "tools/call requires an object params");
696
+ }
697
+ return await callTool(id, params);
698
+ }
699
+ default:
700
+ return fail(id, METHOD_NOT_FOUND, `Method not found: ${method}`);
701
+ }
702
+ };
703
+ }
704
+ async function runStdioServer(handler) {
705
+ let buffer = "";
706
+ try {
707
+ for await (const chunk of process.stdin) {
708
+ buffer += chunk.toString("utf8");
709
+ let newlineIndex = buffer.indexOf("\n");
710
+ while (newlineIndex !== -1) {
711
+ const line = buffer.slice(0, newlineIndex).trim();
712
+ buffer = buffer.slice(newlineIndex + 1);
713
+ newlineIndex = buffer.indexOf("\n");
714
+ if (line === "") continue;
715
+ let response;
716
+ try {
717
+ response = await handler(JSON.parse(line));
718
+ } catch {
719
+ response = failParse();
720
+ }
721
+ if (response) await writeStdoutLine(JSON.stringify(response));
722
+ }
723
+ }
724
+ } catch {
725
+ }
726
+ function failParse() {
727
+ return { jsonrpc: "2.0", id: null, error: { code: PARSE_ERROR, message: "Parse error" } };
728
+ }
729
+ }
730
+ function mcpPresentedToolName(canonicalName, serverName) {
731
+ return `mcp__${serverName}__${canonicalName}`;
732
+ }
733
+ function contractWithAppendix(appendix) {
734
+ return `${COMMUNICATION_CONTRACT}
735
+
736
+ ${appendix}`;
737
+ }
738
+ function buildMcpPrefixedCommunicationContract(namespace) {
739
+ const [peers, send, close] = HERDR_LINK_TOOLS.map(
740
+ (name) => mcpPresentedToolName(name, namespace)
741
+ );
742
+ const gateway = mcpPresentedToolName(HERDR_LINK_GATEWAY, namespace);
743
+ return contractWithAppendix(
744
+ `In this runtime Herdr Link starts dormant: only the ${gateway} gateway tool is listed until it is activated.
745
+ - Call ${gateway} once with no arguments ({}); the host then receives notifications/tools/list_changed and the cross-agent tools become available.
746
+ - If the host did not refresh its tool list, keep dispatching through the gateway: {"action":"peers"}, {"action":"send","arguments":{...}}, {"action":"close","arguments":{...}}.
747
+ The tools are presented under MCP-prefixed names (the canonical name is always the suffix):
748
+ - herdr_link_peers -> ${peers}
749
+ - herdr_link_send -> ${send}
750
+ - herdr_link_close -> ${close}`
751
+ );
752
+ }
753
+ function buildMcpWrapperCommunicationContract(wrapperName, serverName) {
754
+ return contractWithAppendix(
755
+ `In this runtime Herdr Link starts dormant: only the Tier 0 gateway (${HERDR_LINK_GATEWAY}) is listed until it is activated.
756
+ - Invoke the gateway once with empty Arguments {} (ToolName "${HERDR_LINK_GATEWAY}"); the host then receives notifications/tools/list_changed and the cross-agent tools become available.
757
+ - If the host did not refresh its tool list, keep dispatching through the gateway with ToolName "${HERDR_LINK_GATEWAY}" and an Arguments object carrying {"action":"peers"|"send"|"close", ...}.
758
+
759
+ After activation, Herdr Link MCP tools are invoked through ${wrapperName}.
760
+
761
+ Use:
762
+ - ServerName: "${serverName}"
763
+ - ToolName: "herdr_link_peers", "herdr_link_send", or "herdr_link_close"
764
+ - Arguments: the canonical input object for that Herdr Link tool`
765
+ );
766
+ }
767
+ function invokedDirectly() {
768
+ const entry = process.argv[1];
769
+ if (!entry) return false;
770
+ try {
771
+ return import.meta.url === pathToFileURL(realpathSync(entry)).href;
772
+ } catch {
773
+ return false;
774
+ }
775
+ }
776
+ if (invokedDirectly()) {
777
+ void ensureSelfName().catch(() => {
778
+ });
779
+ await runStdioServer(createRequestHandler());
780
+ }
781
+ export {
782
+ INVALID_PARAMS,
783
+ INVALID_REQUEST,
784
+ MCP_PROTOCOL_VERSION,
785
+ MCP_SERVER_NAME,
786
+ MCP_SERVER_VERSION,
787
+ METHOD_NOT_FOUND,
788
+ PARSE_ERROR,
789
+ TOOLS_LIST_CHANGED,
790
+ buildMcpPrefixedCommunicationContract,
791
+ buildMcpWrapperCommunicationContract,
792
+ createRequestHandler,
793
+ isHerdrEnvironment,
794
+ mcpPresentedToolName,
795
+ runStdioServer
796
+ };