bosun 0.41.7 → 0.41.9
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 +23 -1
- package/agent/agent-event-bus.mjs +31 -2
- package/agent/agent-pool.mjs +251 -11
- package/agent/agent-prompts.mjs +5 -1
- package/agent/agent-supervisor.mjs +22 -0
- package/agent/primary-agent.mjs +115 -5
- package/cli.mjs +3 -2
- package/config/config.mjs +4 -1
- package/desktop/main.mjs +350 -25
- package/desktop/preload.cjs +8 -0
- package/desktop/preload.mjs +19 -0
- package/entrypoint.mjs +332 -0
- package/infra/health-status.mjs +72 -0
- package/infra/library-manager.mjs +58 -1
- package/infra/maintenance.mjs +1 -2
- package/infra/monitor.mjs +25 -7
- package/infra/session-tracker.mjs +30 -3
- package/package.json +10 -4
- package/server/bosun-mcp-server.mjs +1004 -0
- package/server/setup-web-server.mjs +287 -258
- package/server/ui-server.mjs +218 -23
- package/shell/claude-shell.mjs +14 -1
- package/shell/codex-model-profiles.mjs +166 -29
- package/shell/codex-shell.mjs +56 -18
- package/shell/opencode-providers.mjs +20 -8
- package/task/task-executor.mjs +28 -0
- package/task/task-store.mjs +13 -4
- package/tools/list-todos.mjs +7 -1
- package/ui/app.js +3 -2
- package/ui/components/agent-selector.js +127 -0
- package/ui/components/session-list.js +2 -0
- package/ui/demo-defaults.js +6 -6
- package/ui/modules/router.js +2 -0
- package/ui/modules/state.js +13 -5
- package/ui/tabs/chat.js +3 -0
- package/ui/tabs/library.js +284 -52
- package/ui/tabs/tasks.js +5 -13
- package/workflow/workflow-engine.mjs +16 -4
- package/workflow/workflow-nodes/definitions.mjs +37 -0
- package/workflow/workflow-nodes.mjs +489 -153
- package/workflow/workflow-templates.mjs +0 -5
- package/workflow-templates/github.mjs +106 -16
- package/workspace/worktree-manager.mjs +1 -1
|
@@ -0,0 +1,1004 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { format } from "node:util";
|
|
5
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
6
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
+
import {
|
|
8
|
+
CallToolRequestSchema,
|
|
9
|
+
ListToolsRequestSchema,
|
|
10
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
11
|
+
|
|
12
|
+
const TAG = "[bosun-mcp]";
|
|
13
|
+
const DEFAULT_DISCOVERY_PORTS = [3080, 4400];
|
|
14
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 10_000;
|
|
15
|
+
const ENV_KEYS_FOR_EMBEDDED = [
|
|
16
|
+
"TELEGRAM_UI_ALLOW_UNSAFE",
|
|
17
|
+
"TELEGRAM_UI_TUNNEL",
|
|
18
|
+
"TELEGRAM_UI_TLS_DISABLE",
|
|
19
|
+
"TELEGRAM_UI_HOST",
|
|
20
|
+
"TELEGRAM_UI_PORT",
|
|
21
|
+
"BOSUN_UI_ALLOW_EPHEMERAL_PORT",
|
|
22
|
+
"BOSUN_UI_SKIP_INSTANCE_LOCK",
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
function isMainModule() {
|
|
26
|
+
const entry = process.argv[1] ? resolve(process.argv[1]) : "";
|
|
27
|
+
return entry === fileURLToPath(import.meta.url);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function redirectConsoleToStderr() {
|
|
31
|
+
const write = (level, args) => {
|
|
32
|
+
const message = format(...args);
|
|
33
|
+
process.stderr.write(message.endsWith("\n") ? message : `${message}\n`);
|
|
34
|
+
};
|
|
35
|
+
console.log = (...args) => write("log", args);
|
|
36
|
+
console.info = (...args) => write("info", args);
|
|
37
|
+
console.warn = (...args) => write("warn", args);
|
|
38
|
+
console.error = (...args) => write("error", args);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function normalizePath(pathname) {
|
|
42
|
+
const raw = String(pathname || "").trim();
|
|
43
|
+
if (!raw) return "/";
|
|
44
|
+
return raw.startsWith("/") ? raw : `/${raw}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function normalizeBaseUrl(value) {
|
|
48
|
+
const raw = String(value || "").trim();
|
|
49
|
+
if (!raw) return "";
|
|
50
|
+
return raw.replace(/\/+$/, "");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parseDiscoveryPorts() {
|
|
54
|
+
const envPorts = String(process.env.BOSUN_MCP_DISCOVERY_PORTS || "")
|
|
55
|
+
.split(",")
|
|
56
|
+
.map((value) => Number.parseInt(String(value || "").trim(), 10))
|
|
57
|
+
.filter((value) => Number.isFinite(value) && value > 0);
|
|
58
|
+
const configured = Number.parseInt(String(process.env.TELEGRAM_UI_PORT || ""), 10);
|
|
59
|
+
const ports = [
|
|
60
|
+
...envPorts,
|
|
61
|
+
...(Number.isFinite(configured) && configured > 0 ? [configured] : []),
|
|
62
|
+
...DEFAULT_DISCOVERY_PORTS,
|
|
63
|
+
];
|
|
64
|
+
return Array.from(new Set(ports));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function encodeQuery(url, query = {}) {
|
|
68
|
+
if (!query || typeof query !== "object") return url;
|
|
69
|
+
for (const [key, value] of Object.entries(query)) {
|
|
70
|
+
if (value == null || value === "") continue;
|
|
71
|
+
if (Array.isArray(value)) {
|
|
72
|
+
for (const item of value) {
|
|
73
|
+
url.searchParams.append(key, String(item));
|
|
74
|
+
}
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
url.searchParams.set(key, String(value));
|
|
78
|
+
}
|
|
79
|
+
return url;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function createToolResult(payload) {
|
|
83
|
+
const text = typeof payload === "string"
|
|
84
|
+
? payload
|
|
85
|
+
: JSON.stringify(payload, null, 2);
|
|
86
|
+
return {
|
|
87
|
+
content: [{ type: "text", text }],
|
|
88
|
+
structuredContent: payload,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function settledValue(result) {
|
|
93
|
+
return result?.status === "fulfilled" ? result.value : null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function fetchWithTimeout(url, options = {}, timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {
|
|
97
|
+
const controller = new AbortController();
|
|
98
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
99
|
+
const headers = options.headers
|
|
100
|
+
? {
|
|
101
|
+
Accept: "application/json",
|
|
102
|
+
...options.headers,
|
|
103
|
+
}
|
|
104
|
+
: { Accept: "application/json" };
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
return await fetch(url, {
|
|
108
|
+
...options,
|
|
109
|
+
signal: controller.signal,
|
|
110
|
+
headers,
|
|
111
|
+
});
|
|
112
|
+
} finally {
|
|
113
|
+
clearTimeout(timer);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function probeBosunBaseUrl(baseUrl, options = {}) {
|
|
118
|
+
const normalized = normalizeBaseUrl(baseUrl);
|
|
119
|
+
if (!normalized) return null;
|
|
120
|
+
const timeoutMs = Number(options.timeoutMs) || DEFAULT_REQUEST_TIMEOUT_MS;
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
const healthResponse = await fetchWithTimeout(
|
|
124
|
+
`${normalized}/api/health`,
|
|
125
|
+
{ method: "GET" },
|
|
126
|
+
timeoutMs,
|
|
127
|
+
);
|
|
128
|
+
if (healthResponse?.ok !== true) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const health = await healthResponse.json().catch(() => null);
|
|
132
|
+
if (health?.ok !== true) return null;
|
|
133
|
+
return {
|
|
134
|
+
type: "daemon",
|
|
135
|
+
baseUrl: normalized,
|
|
136
|
+
health,
|
|
137
|
+
};
|
|
138
|
+
} catch {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function createBosunMcpRuntime(options = {}) {
|
|
144
|
+
let uiServerModule = null;
|
|
145
|
+
let backend = null;
|
|
146
|
+
let savedEnv = null;
|
|
147
|
+
|
|
148
|
+
async function detectRunningBackend() {
|
|
149
|
+
if (String(process.env.BOSUN_MCP_DISABLE_DAEMON_DISCOVERY || "") === "1") {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const explicitCandidates = [
|
|
154
|
+
options.baseUrl,
|
|
155
|
+
process.env.BOSUN_MCP_BASE_URL,
|
|
156
|
+
process.env.TELEGRAM_UI_BASE_URL,
|
|
157
|
+
]
|
|
158
|
+
.map(normalizeBaseUrl)
|
|
159
|
+
.filter(Boolean);
|
|
160
|
+
|
|
161
|
+
for (const candidate of explicitCandidates) {
|
|
162
|
+
const resolved = await probeBosunBaseUrl(candidate, options);
|
|
163
|
+
if (resolved) return resolved;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
for (const port of parseDiscoveryPorts()) {
|
|
167
|
+
const candidate = `http://127.0.0.1:${port}`;
|
|
168
|
+
const resolved = await probeBosunBaseUrl(candidate, options);
|
|
169
|
+
if (resolved) return resolved;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
async function startEmbeddedBackend() {
|
|
176
|
+
if (backend) return backend;
|
|
177
|
+
uiServerModule ||= await import("./ui-server.mjs");
|
|
178
|
+
|
|
179
|
+
if (!savedEnv) {
|
|
180
|
+
savedEnv = Object.fromEntries(
|
|
181
|
+
ENV_KEYS_FOR_EMBEDDED.map((key) => [key, process.env[key]]),
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
process.env.TELEGRAM_UI_ALLOW_UNSAFE = "true";
|
|
186
|
+
process.env.TELEGRAM_UI_TUNNEL = "disabled";
|
|
187
|
+
process.env.TELEGRAM_UI_TLS_DISABLE = "true";
|
|
188
|
+
process.env.TELEGRAM_UI_HOST = "127.0.0.1";
|
|
189
|
+
process.env.TELEGRAM_UI_PORT = "0";
|
|
190
|
+
process.env.BOSUN_UI_ALLOW_EPHEMERAL_PORT = "1";
|
|
191
|
+
process.env.BOSUN_UI_SKIP_INSTANCE_LOCK = "1";
|
|
192
|
+
|
|
193
|
+
await uiServerModule.startTelegramUiServer({
|
|
194
|
+
port: 0,
|
|
195
|
+
host: "127.0.0.1",
|
|
196
|
+
allowEphemeralPort: true,
|
|
197
|
+
skipInstanceLock: true,
|
|
198
|
+
skipAutoOpen: true,
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
const baseUrl = normalizeBaseUrl(uiServerModule.getTelegramUiUrl?.());
|
|
202
|
+
if (!baseUrl) {
|
|
203
|
+
throw new Error("Bosun embedded backend started without a resolvable URL");
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
backend = {
|
|
207
|
+
type: "embedded",
|
|
208
|
+
baseUrl,
|
|
209
|
+
};
|
|
210
|
+
return backend;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function ensureBackend() {
|
|
214
|
+
if (backend) return backend;
|
|
215
|
+
backend = await detectRunningBackend();
|
|
216
|
+
if (backend) return backend;
|
|
217
|
+
return startEmbeddedBackend();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function request(pathname, options = {}) {
|
|
221
|
+
const activeBackend = await ensureBackend();
|
|
222
|
+
const method = String(options.method || "GET").trim().toUpperCase() || "GET";
|
|
223
|
+
const timeoutMs = Number(options.timeoutMs) || DEFAULT_REQUEST_TIMEOUT_MS;
|
|
224
|
+
const target = encodeQuery(
|
|
225
|
+
new URL(normalizePath(pathname), `${activeBackend.baseUrl}/`),
|
|
226
|
+
options.query,
|
|
227
|
+
);
|
|
228
|
+
const body = options.body;
|
|
229
|
+
const hasBody = body !== undefined;
|
|
230
|
+
const headers = options.headers ? { ...options.headers } : {};
|
|
231
|
+
if (hasBody) {
|
|
232
|
+
headers["Content-Type"] = "application/json";
|
|
233
|
+
}
|
|
234
|
+
const fetchOptions = {
|
|
235
|
+
method,
|
|
236
|
+
...(Object.keys(headers).length > 0 ? { headers } : {}),
|
|
237
|
+
};
|
|
238
|
+
if (hasBody) {
|
|
239
|
+
fetchOptions.body = JSON.stringify(body);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const response = await fetchWithTimeout(target, fetchOptions, timeoutMs);
|
|
243
|
+
const rawText = await response.text();
|
|
244
|
+
let data = null;
|
|
245
|
+
try {
|
|
246
|
+
data = rawText ? JSON.parse(rawText) : null;
|
|
247
|
+
} catch {
|
|
248
|
+
data = rawText;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
backend: activeBackend,
|
|
253
|
+
url: target.toString(),
|
|
254
|
+
status: response.status,
|
|
255
|
+
ok: response.ok,
|
|
256
|
+
data,
|
|
257
|
+
rawText,
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function requestJson(pathname, options = {}) {
|
|
262
|
+
const response = await request(pathname, options);
|
|
263
|
+
if (!response.ok) {
|
|
264
|
+
throw new Error(`Bosun request failed (${response.status}) for ${pathname}`);
|
|
265
|
+
}
|
|
266
|
+
if (options.expectBosunOk !== false && response.data && typeof response.data === "object" && response.data.ok === false) {
|
|
267
|
+
throw new Error(String(response.data.error || `Bosun returned ok=false for ${pathname}`));
|
|
268
|
+
}
|
|
269
|
+
return response;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function shutdown() {
|
|
273
|
+
if (backend?.type === "embedded" && uiServerModule?.stopTelegramUiServer) {
|
|
274
|
+
try {
|
|
275
|
+
uiServerModule.stopTelegramUiServer();
|
|
276
|
+
} catch {
|
|
277
|
+
/* best effort */
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
backend = null;
|
|
281
|
+
|
|
282
|
+
if (savedEnv) {
|
|
283
|
+
for (const [key, value] of Object.entries(savedEnv)) {
|
|
284
|
+
if (value == null) {
|
|
285
|
+
delete process.env[key];
|
|
286
|
+
} else {
|
|
287
|
+
process.env[key] = value;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
savedEnv = null;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
return {
|
|
295
|
+
ensureBackend,
|
|
296
|
+
request,
|
|
297
|
+
requestJson,
|
|
298
|
+
shutdown,
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export function listBosunMcpTools() {
|
|
303
|
+
return [
|
|
304
|
+
{
|
|
305
|
+
name: "bosun_status",
|
|
306
|
+
description: "Get Bosun backend status, health, infrastructure, and agent info.",
|
|
307
|
+
inputSchema: {
|
|
308
|
+
type: "object",
|
|
309
|
+
properties: {},
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
name: "bosun_monitor_snapshot",
|
|
314
|
+
description: "Get a compact monitoring snapshot across health, sessions, tasks, workflows, and agents.",
|
|
315
|
+
inputSchema: {
|
|
316
|
+
type: "object",
|
|
317
|
+
properties: {
|
|
318
|
+
taskPageSize: { type: "number", description: "Tasks page size." },
|
|
319
|
+
workflowLimit: { type: "number", description: "Workflow run limit." },
|
|
320
|
+
logLines: { type: "number", description: "System log lines." },
|
|
321
|
+
},
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
name: "bosun_request",
|
|
326
|
+
description: "Generic Bosun JSON request against the local Bosun backend. Use as an escape hatch for endpoints not covered by the higher-level tools.",
|
|
327
|
+
inputSchema: {
|
|
328
|
+
type: "object",
|
|
329
|
+
properties: {
|
|
330
|
+
path: { type: "string", description: "Route path such as /api/tasks or /ping." },
|
|
331
|
+
method: { type: "string", description: "HTTP method." },
|
|
332
|
+
query: { type: "object", additionalProperties: true, description: "Query parameters." },
|
|
333
|
+
body: { type: "object", additionalProperties: true, description: "JSON body for write requests." },
|
|
334
|
+
timeoutMs: { type: "number", description: "Request timeout in milliseconds." },
|
|
335
|
+
},
|
|
336
|
+
required: ["path"],
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
name: "bosun_list_workspaces",
|
|
341
|
+
description: "List Bosun workspaces and the active workspace.",
|
|
342
|
+
inputSchema: {
|
|
343
|
+
type: "object",
|
|
344
|
+
properties: {},
|
|
345
|
+
},
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
name: "bosun_list_sessions",
|
|
349
|
+
description: "List Bosun sessions with optional workspace, type, status, and includeHidden filters.",
|
|
350
|
+
inputSchema: {
|
|
351
|
+
type: "object",
|
|
352
|
+
properties: {
|
|
353
|
+
workspace: { type: "string" },
|
|
354
|
+
type: { type: "string" },
|
|
355
|
+
status: { type: "string" },
|
|
356
|
+
includeHidden: { type: "boolean" },
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
name: "bosun_get_session",
|
|
362
|
+
description: "Get a Bosun session and recent or full message history.",
|
|
363
|
+
inputSchema: {
|
|
364
|
+
type: "object",
|
|
365
|
+
properties: {
|
|
366
|
+
sessionId: { type: "string" },
|
|
367
|
+
workspace: { type: "string" },
|
|
368
|
+
limit: { type: "number" },
|
|
369
|
+
offset: { type: "number" },
|
|
370
|
+
full: { type: "boolean" },
|
|
371
|
+
},
|
|
372
|
+
required: ["sessionId"],
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
name: "bosun_create_session",
|
|
377
|
+
description: "Create a Bosun session for an external agent conversation or monitoring workflow.",
|
|
378
|
+
inputSchema: {
|
|
379
|
+
type: "object",
|
|
380
|
+
properties: {
|
|
381
|
+
type: { type: "string" },
|
|
382
|
+
prompt: { type: "string" },
|
|
383
|
+
agent: { type: "string" },
|
|
384
|
+
mode: { type: "string" },
|
|
385
|
+
model: { type: "string" },
|
|
386
|
+
workspaceId: { type: "string" },
|
|
387
|
+
workspaceDir: { type: "string" },
|
|
388
|
+
agentProfileId: { type: "string" },
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
name: "bosun_send_session_message",
|
|
394
|
+
description: "Send a message into a Bosun session to continue or monitor a run.",
|
|
395
|
+
inputSchema: {
|
|
396
|
+
type: "object",
|
|
397
|
+
properties: {
|
|
398
|
+
sessionId: { type: "string" },
|
|
399
|
+
content: { type: "string" },
|
|
400
|
+
mode: { type: "string" },
|
|
401
|
+
model: { type: "string" },
|
|
402
|
+
agentProfileId: { type: "string" },
|
|
403
|
+
attachmentsAppended: { type: "boolean" },
|
|
404
|
+
attachments: {
|
|
405
|
+
type: "array",
|
|
406
|
+
items: { type: "object", additionalProperties: true },
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
required: ["sessionId", "content"],
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
name: "bosun_manage_session",
|
|
414
|
+
description: "Apply a session action such as stop, archive, resume, delete, rename, or diff.",
|
|
415
|
+
inputSchema: {
|
|
416
|
+
type: "object",
|
|
417
|
+
properties: {
|
|
418
|
+
sessionId: { type: "string" },
|
|
419
|
+
action: { type: "string", description: "stop, archive, resume, delete, rename, diff" },
|
|
420
|
+
title: { type: "string", description: "Required for rename." },
|
|
421
|
+
workspace: { type: "string" },
|
|
422
|
+
},
|
|
423
|
+
required: ["sessionId", "action"],
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
{
|
|
427
|
+
name: "bosun_list_tasks",
|
|
428
|
+
description: "List Bosun tasks with paging and filters.",
|
|
429
|
+
inputSchema: {
|
|
430
|
+
type: "object",
|
|
431
|
+
properties: {
|
|
432
|
+
status: { type: "string" },
|
|
433
|
+
project: { type: "string" },
|
|
434
|
+
workspace: { type: "string" },
|
|
435
|
+
repository: { type: "string" },
|
|
436
|
+
search: { type: "string" },
|
|
437
|
+
page: { type: "number" },
|
|
438
|
+
pageSize: { type: "number" },
|
|
439
|
+
},
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
name: "bosun_get_task_detail",
|
|
444
|
+
description: "Get task detail, diagnostics, DAG, and workflow links for a Bosun task.",
|
|
445
|
+
inputSchema: {
|
|
446
|
+
type: "object",
|
|
447
|
+
properties: {
|
|
448
|
+
taskId: { type: "string" },
|
|
449
|
+
workspace: { type: "string" },
|
|
450
|
+
includeDag: { type: "boolean" },
|
|
451
|
+
includeWorkflowRuns: { type: "boolean" },
|
|
452
|
+
},
|
|
453
|
+
required: ["taskId"],
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
name: "bosun_create_task",
|
|
458
|
+
description: "Create a Bosun task.",
|
|
459
|
+
inputSchema: {
|
|
460
|
+
type: "object",
|
|
461
|
+
properties: {
|
|
462
|
+
title: { type: "string" },
|
|
463
|
+
description: { type: "string" },
|
|
464
|
+
status: { type: "string" },
|
|
465
|
+
priority: { type: "string" },
|
|
466
|
+
assignee: { type: "string" },
|
|
467
|
+
repository: { type: "string" },
|
|
468
|
+
workspace: { type: "string" },
|
|
469
|
+
},
|
|
470
|
+
required: ["title"],
|
|
471
|
+
},
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
name: "bosun_list_workflow_runs",
|
|
475
|
+
description: "List workflow run history for Bosun.",
|
|
476
|
+
inputSchema: {
|
|
477
|
+
type: "object",
|
|
478
|
+
properties: {
|
|
479
|
+
offset: { type: "number" },
|
|
480
|
+
limit: { type: "number" },
|
|
481
|
+
workspace: { type: "string" },
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
name: "bosun_get_workflow_run",
|
|
487
|
+
description: "Get a Bosun workflow run by run ID.",
|
|
488
|
+
inputSchema: {
|
|
489
|
+
type: "object",
|
|
490
|
+
properties: {
|
|
491
|
+
runId: { type: "string" },
|
|
492
|
+
workspace: { type: "string" },
|
|
493
|
+
},
|
|
494
|
+
required: ["runId"],
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
name: "bosun_execute_workflow",
|
|
499
|
+
description: "Execute a Bosun workflow by workflow ID.",
|
|
500
|
+
inputSchema: {
|
|
501
|
+
type: "object",
|
|
502
|
+
properties: {
|
|
503
|
+
workflowId: { type: "string" },
|
|
504
|
+
input: { type: "object", additionalProperties: true },
|
|
505
|
+
waitForCompletion: { type: "boolean" },
|
|
506
|
+
workspace: { type: "string" },
|
|
507
|
+
},
|
|
508
|
+
required: ["workflowId"],
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
name: "bosun_get_logs",
|
|
513
|
+
description: "Get the merged Bosun system log tail.",
|
|
514
|
+
inputSchema: {
|
|
515
|
+
type: "object",
|
|
516
|
+
properties: {
|
|
517
|
+
lines: { type: "number" },
|
|
518
|
+
},
|
|
519
|
+
},
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
name: "bosun_tail_agent_logs",
|
|
523
|
+
description: "Get focused or raw Bosun agent log tail content.",
|
|
524
|
+
inputSchema: {
|
|
525
|
+
type: "object",
|
|
526
|
+
properties: {
|
|
527
|
+
file: { type: "string" },
|
|
528
|
+
query: { type: "string" },
|
|
529
|
+
lines: { type: "number" },
|
|
530
|
+
},
|
|
531
|
+
},
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
name: "bosun_get_agent_log_context",
|
|
535
|
+
description: "Get git context for a worktree or task-relevant agent log query.",
|
|
536
|
+
inputSchema: {
|
|
537
|
+
type: "object",
|
|
538
|
+
properties: {
|
|
539
|
+
query: { type: "string" },
|
|
540
|
+
},
|
|
541
|
+
required: ["query"],
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
name: "bosun_list_agents",
|
|
546
|
+
description: "List running Bosun agents and current agent selection details.",
|
|
547
|
+
inputSchema: {
|
|
548
|
+
type: "object",
|
|
549
|
+
properties: {},
|
|
550
|
+
},
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
name: "bosun_get_agent_events",
|
|
554
|
+
description: "Get Bosun agent event bus entries.",
|
|
555
|
+
inputSchema: {
|
|
556
|
+
type: "object",
|
|
557
|
+
properties: {
|
|
558
|
+
taskId: { type: "string" },
|
|
559
|
+
type: { type: "string" },
|
|
560
|
+
since: { type: "number" },
|
|
561
|
+
limit: { type: "number" },
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
name: "bosun_sdk_command",
|
|
567
|
+
description: "Run a Bosun SDK command through the currently configured agent adapter.",
|
|
568
|
+
inputSchema: {
|
|
569
|
+
type: "object",
|
|
570
|
+
properties: {
|
|
571
|
+
command: { type: "string" },
|
|
572
|
+
args: { type: "string" },
|
|
573
|
+
adapter: { type: "string" },
|
|
574
|
+
sessionId: { type: "string" },
|
|
575
|
+
},
|
|
576
|
+
required: ["command"],
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
{
|
|
580
|
+
name: "bosun_list_agent_tools",
|
|
581
|
+
description: "List Bosun shared agent tools exposed through the agent tool route.",
|
|
582
|
+
inputSchema: {
|
|
583
|
+
type: "object",
|
|
584
|
+
properties: {
|
|
585
|
+
sessionId: { type: "string" },
|
|
586
|
+
executor: { type: "string" },
|
|
587
|
+
mode: { type: "string" },
|
|
588
|
+
model: { type: "string" },
|
|
589
|
+
voiceAgentId: { type: "string" },
|
|
590
|
+
},
|
|
591
|
+
},
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
name: "bosun_run_agent_tool",
|
|
595
|
+
description: "Execute a Bosun shared agent tool by name.",
|
|
596
|
+
inputSchema: {
|
|
597
|
+
type: "object",
|
|
598
|
+
properties: {
|
|
599
|
+
toolName: { type: "string" },
|
|
600
|
+
args: { type: "object", additionalProperties: true },
|
|
601
|
+
sessionId: { type: "string" },
|
|
602
|
+
executor: { type: "string" },
|
|
603
|
+
mode: { type: "string" },
|
|
604
|
+
model: { type: "string" },
|
|
605
|
+
voiceAgentId: { type: "string" },
|
|
606
|
+
},
|
|
607
|
+
required: ["toolName"],
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
];
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const BOSUN_TOOL_HANDLERS = {
|
|
614
|
+
async bosun_status(runtime) {
|
|
615
|
+
const backend = await runtime.ensureBackend();
|
|
616
|
+
const [health, infra, agentInfo] = await Promise.allSettled([
|
|
617
|
+
runtime.requestJson("/api/health"),
|
|
618
|
+
runtime.requestJson("/api/infra", { expectBosunOk: false }),
|
|
619
|
+
runtime.requestJson("/api/agents/info"),
|
|
620
|
+
]);
|
|
621
|
+
return {
|
|
622
|
+
backend,
|
|
623
|
+
health: settledValue(health)?.data || null,
|
|
624
|
+
infra: settledValue(infra)?.data || null,
|
|
625
|
+
agentInfo: settledValue(agentInfo)?.data || null,
|
|
626
|
+
};
|
|
627
|
+
},
|
|
628
|
+
|
|
629
|
+
async bosun_monitor_snapshot(runtime, args) {
|
|
630
|
+
const taskPageSize = Number(args.taskPageSize) > 0 ? Number(args.taskPageSize) : 10;
|
|
631
|
+
const workflowLimit = Number(args.workflowLimit) > 0 ? Number(args.workflowLimit) : 10;
|
|
632
|
+
const logLines = Number(args.logLines) > 0 ? Number(args.logLines) : 40;
|
|
633
|
+
const [status, sessions, tasks, workflows, agents, logs] = await Promise.all([
|
|
634
|
+
BOSUN_TOOL_HANDLERS.bosun_status(runtime, {}),
|
|
635
|
+
runtime.requestJson("/api/sessions"),
|
|
636
|
+
runtime.requestJson("/api/tasks", { query: { pageSize: taskPageSize, page: 0 } }),
|
|
637
|
+
runtime.requestJson("/api/workflows/runs", { query: { limit: workflowLimit, offset: 0 } }),
|
|
638
|
+
runtime.requestJson("/api/agents"),
|
|
639
|
+
runtime.requestJson("/api/logs", { query: { lines: logLines } }),
|
|
640
|
+
]);
|
|
641
|
+
return {
|
|
642
|
+
status,
|
|
643
|
+
sessions: {
|
|
644
|
+
total: Array.isArray(sessions.data?.sessions) ? sessions.data.sessions.length : 0,
|
|
645
|
+
items: sessions.data?.sessions || [],
|
|
646
|
+
},
|
|
647
|
+
tasks: {
|
|
648
|
+
total: Number(tasks.data?.total || 0),
|
|
649
|
+
statusCounts: tasks.data?.statusCounts || {},
|
|
650
|
+
items: tasks.data?.data || [],
|
|
651
|
+
},
|
|
652
|
+
workflows: {
|
|
653
|
+
runs: workflows.data?.runs || [],
|
|
654
|
+
pagination: workflows.data?.pagination || null,
|
|
655
|
+
},
|
|
656
|
+
agents: agents.data?.data || [],
|
|
657
|
+
logs: logs.data?.data || null,
|
|
658
|
+
};
|
|
659
|
+
},
|
|
660
|
+
|
|
661
|
+
async bosun_request(runtime, args) {
|
|
662
|
+
return runtime.request(args.path, {
|
|
663
|
+
method: args.method,
|
|
664
|
+
query: args.query,
|
|
665
|
+
body: args.body,
|
|
666
|
+
timeoutMs: args.timeoutMs,
|
|
667
|
+
});
|
|
668
|
+
},
|
|
669
|
+
|
|
670
|
+
async bosun_list_workspaces(runtime) {
|
|
671
|
+
const [workspaces, active] = await Promise.all([
|
|
672
|
+
runtime.requestJson("/api/workspaces"),
|
|
673
|
+
runtime.requestJson("/api/workspaces/active"),
|
|
674
|
+
]);
|
|
675
|
+
return {
|
|
676
|
+
workspaces: workspaces.data?.data || [],
|
|
677
|
+
activeId: workspaces.data?.activeId || null,
|
|
678
|
+
activeWorkspace: active.data?.data || null,
|
|
679
|
+
};
|
|
680
|
+
},
|
|
681
|
+
|
|
682
|
+
async bosun_list_sessions(runtime, args) {
|
|
683
|
+
const response = await runtime.requestJson("/api/sessions", {
|
|
684
|
+
query: {
|
|
685
|
+
workspace: args.workspace,
|
|
686
|
+
type: args.type,
|
|
687
|
+
status: args.status,
|
|
688
|
+
includeHidden: args.includeHidden ? 1 : undefined,
|
|
689
|
+
},
|
|
690
|
+
});
|
|
691
|
+
return response.data;
|
|
692
|
+
},
|
|
693
|
+
|
|
694
|
+
async bosun_get_session(runtime, args) {
|
|
695
|
+
const response = await runtime.requestJson(`/api/sessions/${encodeURIComponent(String(args.sessionId || "").trim())}`, {
|
|
696
|
+
query: {
|
|
697
|
+
workspace: args.workspace,
|
|
698
|
+
limit: args.limit,
|
|
699
|
+
offset: args.offset,
|
|
700
|
+
full: args.full ? 1 : undefined,
|
|
701
|
+
},
|
|
702
|
+
});
|
|
703
|
+
return response.data;
|
|
704
|
+
},
|
|
705
|
+
|
|
706
|
+
async bosun_create_session(runtime, args) {
|
|
707
|
+
const response = await runtime.requestJson("/api/sessions/create", {
|
|
708
|
+
method: "POST",
|
|
709
|
+
body: {
|
|
710
|
+
type: args.type,
|
|
711
|
+
prompt: args.prompt,
|
|
712
|
+
agent: args.agent,
|
|
713
|
+
mode: args.mode,
|
|
714
|
+
model: args.model,
|
|
715
|
+
workspaceId: args.workspaceId,
|
|
716
|
+
workspaceDir: args.workspaceDir,
|
|
717
|
+
agentProfileId: args.agentProfileId,
|
|
718
|
+
},
|
|
719
|
+
});
|
|
720
|
+
return response.data;
|
|
721
|
+
},
|
|
722
|
+
|
|
723
|
+
async bosun_send_session_message(runtime, args) {
|
|
724
|
+
const response = await runtime.requestJson(`/api/sessions/${encodeURIComponent(String(args.sessionId || "").trim())}/message`, {
|
|
725
|
+
method: "POST",
|
|
726
|
+
body: {
|
|
727
|
+
content: args.content,
|
|
728
|
+
mode: args.mode,
|
|
729
|
+
model: args.model,
|
|
730
|
+
agentProfileId: args.agentProfileId,
|
|
731
|
+
attachmentsAppended: args.attachmentsAppended === true,
|
|
732
|
+
attachments: Array.isArray(args.attachments) ? args.attachments : [],
|
|
733
|
+
},
|
|
734
|
+
});
|
|
735
|
+
return response.data;
|
|
736
|
+
},
|
|
737
|
+
|
|
738
|
+
async bosun_manage_session(runtime, args) {
|
|
739
|
+
const sessionId = encodeURIComponent(String(args.sessionId || "").trim());
|
|
740
|
+
const action = String(args.action || "").trim().toLowerCase();
|
|
741
|
+
if (!action) throw new Error("action is required");
|
|
742
|
+
if (action === "diff") {
|
|
743
|
+
const response = await runtime.requestJson(`/api/sessions/${sessionId}/diff`, {
|
|
744
|
+
query: { workspace: args.workspace },
|
|
745
|
+
});
|
|
746
|
+
return response.data;
|
|
747
|
+
}
|
|
748
|
+
const body = action === "rename" ? { title: args.title } : {};
|
|
749
|
+
const response = await runtime.requestJson(`/api/sessions/${sessionId}/${action}`, {
|
|
750
|
+
method: "POST",
|
|
751
|
+
query: { workspace: args.workspace },
|
|
752
|
+
body,
|
|
753
|
+
});
|
|
754
|
+
return response.data;
|
|
755
|
+
},
|
|
756
|
+
|
|
757
|
+
async bosun_list_tasks(runtime, args) {
|
|
758
|
+
const response = await runtime.requestJson("/api/tasks", {
|
|
759
|
+
query: {
|
|
760
|
+
status: args.status,
|
|
761
|
+
project: args.project,
|
|
762
|
+
workspace: args.workspace,
|
|
763
|
+
repository: args.repository,
|
|
764
|
+
search: args.search,
|
|
765
|
+
page: args.page,
|
|
766
|
+
pageSize: args.pageSize,
|
|
767
|
+
},
|
|
768
|
+
});
|
|
769
|
+
return response.data;
|
|
770
|
+
},
|
|
771
|
+
|
|
772
|
+
async bosun_get_task_detail(runtime, args) {
|
|
773
|
+
const response = await runtime.requestJson("/api/tasks/detail", {
|
|
774
|
+
query: {
|
|
775
|
+
taskId: args.taskId,
|
|
776
|
+
workspace: args.workspace,
|
|
777
|
+
includeDag: args.includeDag === false ? 0 : 1,
|
|
778
|
+
includeWorkflowRuns: args.includeWorkflowRuns === false ? 0 : 1,
|
|
779
|
+
},
|
|
780
|
+
});
|
|
781
|
+
return response.data;
|
|
782
|
+
},
|
|
783
|
+
|
|
784
|
+
async bosun_create_task(runtime, args) {
|
|
785
|
+
const response = await runtime.requestJson("/api/tasks/create", {
|
|
786
|
+
method: "POST",
|
|
787
|
+
body: {
|
|
788
|
+
title: args.title,
|
|
789
|
+
description: args.description,
|
|
790
|
+
status: args.status,
|
|
791
|
+
priority: args.priority,
|
|
792
|
+
assignee: args.assignee,
|
|
793
|
+
repository: args.repository,
|
|
794
|
+
workspace: args.workspace,
|
|
795
|
+
},
|
|
796
|
+
});
|
|
797
|
+
return response.data;
|
|
798
|
+
},
|
|
799
|
+
|
|
800
|
+
async bosun_list_workflow_runs(runtime, args) {
|
|
801
|
+
const response = await runtime.requestJson("/api/workflows/runs", {
|
|
802
|
+
query: {
|
|
803
|
+
offset: args.offset,
|
|
804
|
+
limit: args.limit,
|
|
805
|
+
workspace: args.workspace,
|
|
806
|
+
},
|
|
807
|
+
});
|
|
808
|
+
return response.data;
|
|
809
|
+
},
|
|
810
|
+
|
|
811
|
+
async bosun_get_workflow_run(runtime, args) {
|
|
812
|
+
const response = await runtime.requestJson(`/api/workflows/runs/${encodeURIComponent(String(args.runId || "").trim())}`, {
|
|
813
|
+
query: { workspace: args.workspace },
|
|
814
|
+
});
|
|
815
|
+
return response.data;
|
|
816
|
+
},
|
|
817
|
+
|
|
818
|
+
async bosun_execute_workflow(runtime, args) {
|
|
819
|
+
const workflowId = encodeURIComponent(String(args.workflowId || "").trim());
|
|
820
|
+
const input = args.input && typeof args.input === "object" ? args.input : {};
|
|
821
|
+
const response = await runtime.requestJson(`/api/workflows/${workflowId}/execute`, {
|
|
822
|
+
method: "POST",
|
|
823
|
+
query: { workspace: args.workspace },
|
|
824
|
+
body: {
|
|
825
|
+
...input,
|
|
826
|
+
waitForCompletion: args.waitForCompletion === true,
|
|
827
|
+
},
|
|
828
|
+
});
|
|
829
|
+
return response.data;
|
|
830
|
+
},
|
|
831
|
+
|
|
832
|
+
async bosun_get_logs(runtime, args) {
|
|
833
|
+
const response = await runtime.requestJson("/api/logs", {
|
|
834
|
+
query: { lines: args.lines },
|
|
835
|
+
});
|
|
836
|
+
return response.data;
|
|
837
|
+
},
|
|
838
|
+
|
|
839
|
+
async bosun_tail_agent_logs(runtime, args) {
|
|
840
|
+
const response = await runtime.requestJson("/api/agent-logs/tail", {
|
|
841
|
+
query: {
|
|
842
|
+
file: args.file,
|
|
843
|
+
query: args.query,
|
|
844
|
+
lines: args.lines,
|
|
845
|
+
},
|
|
846
|
+
});
|
|
847
|
+
return response.data;
|
|
848
|
+
},
|
|
849
|
+
|
|
850
|
+
async bosun_get_agent_log_context(runtime, args) {
|
|
851
|
+
const response = await runtime.requestJson("/api/agent-logs/context", {
|
|
852
|
+
query: { query: args.query },
|
|
853
|
+
});
|
|
854
|
+
return response.data;
|
|
855
|
+
},
|
|
856
|
+
|
|
857
|
+
async bosun_list_agents(runtime) {
|
|
858
|
+
const [agents, available, info] = await Promise.all([
|
|
859
|
+
runtime.requestJson("/api/agents"),
|
|
860
|
+
runtime.requestJson("/api/agents/available"),
|
|
861
|
+
runtime.requestJson("/api/agents/info"),
|
|
862
|
+
]);
|
|
863
|
+
return {
|
|
864
|
+
agents: agents.data?.data || [],
|
|
865
|
+
available: available.data || null,
|
|
866
|
+
info: info.data || null,
|
|
867
|
+
};
|
|
868
|
+
},
|
|
869
|
+
|
|
870
|
+
async bosun_get_agent_events(runtime, args) {
|
|
871
|
+
const response = await runtime.requestJson("/api/agents/events", {
|
|
872
|
+
query: {
|
|
873
|
+
taskId: args.taskId,
|
|
874
|
+
type: args.type,
|
|
875
|
+
since: args.since,
|
|
876
|
+
limit: args.limit,
|
|
877
|
+
},
|
|
878
|
+
});
|
|
879
|
+
return response.data;
|
|
880
|
+
},
|
|
881
|
+
|
|
882
|
+
async bosun_sdk_command(runtime, args) {
|
|
883
|
+
const response = await runtime.requestJson("/api/agents/sdk-command", {
|
|
884
|
+
method: "POST",
|
|
885
|
+
body: {
|
|
886
|
+
command: args.command,
|
|
887
|
+
args: args.args,
|
|
888
|
+
adapter: args.adapter,
|
|
889
|
+
sessionId: args.sessionId,
|
|
890
|
+
},
|
|
891
|
+
});
|
|
892
|
+
return response.data;
|
|
893
|
+
},
|
|
894
|
+
|
|
895
|
+
async bosun_list_agent_tools(runtime, args) {
|
|
896
|
+
const response = await runtime.requestJson("/api/agents/tools", {
|
|
897
|
+
query: {
|
|
898
|
+
sessionId: args.sessionId,
|
|
899
|
+
executor: args.executor,
|
|
900
|
+
mode: args.mode,
|
|
901
|
+
model: args.model,
|
|
902
|
+
voiceAgentId: args.voiceAgentId,
|
|
903
|
+
},
|
|
904
|
+
});
|
|
905
|
+
return response.data;
|
|
906
|
+
},
|
|
907
|
+
|
|
908
|
+
async bosun_run_agent_tool(runtime, args) {
|
|
909
|
+
const response = await runtime.requestJson("/api/agents/tool", {
|
|
910
|
+
method: "POST",
|
|
911
|
+
body: {
|
|
912
|
+
toolName: args.toolName,
|
|
913
|
+
args: args.args,
|
|
914
|
+
sessionId: args.sessionId,
|
|
915
|
+
executor: args.executor,
|
|
916
|
+
mode: args.mode,
|
|
917
|
+
model: args.model,
|
|
918
|
+
voiceAgentId: args.voiceAgentId,
|
|
919
|
+
},
|
|
920
|
+
});
|
|
921
|
+
return response.data;
|
|
922
|
+
},
|
|
923
|
+
};
|
|
924
|
+
|
|
925
|
+
async function callBosunTool(runtime, name, rawArgs = {}) {
|
|
926
|
+
const args = rawArgs && typeof rawArgs === "object" ? rawArgs : {};
|
|
927
|
+
const handler = BOSUN_TOOL_HANDLERS[name];
|
|
928
|
+
if (!handler) {
|
|
929
|
+
throw new Error(`Unknown Bosun MCP tool: ${name}`);
|
|
930
|
+
}
|
|
931
|
+
return handler(runtime, args);
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
export function createBosunMcpHandlers(runtime) {
|
|
935
|
+
return {
|
|
936
|
+
listTools() {
|
|
937
|
+
return { tools: listBosunMcpTools() };
|
|
938
|
+
},
|
|
939
|
+
|
|
940
|
+
async callTool(name, args) {
|
|
941
|
+
const payload = await callBosunTool(runtime, name, args || {});
|
|
942
|
+
return createToolResult(payload);
|
|
943
|
+
},
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
export async function startBosunMcpServer(options = {}) {
|
|
948
|
+
const runtime = await createBosunMcpRuntime(options);
|
|
949
|
+
const handlers = createBosunMcpHandlers(runtime);
|
|
950
|
+
const server = new Server(
|
|
951
|
+
{ name: "bosun-mcp-server", version: "1.0.0" },
|
|
952
|
+
{ capabilities: { tools: {} } },
|
|
953
|
+
);
|
|
954
|
+
|
|
955
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => handlers.listTools());
|
|
956
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
957
|
+
const name = String(request.params?.name || "").trim();
|
|
958
|
+
return handlers.callTool(name, request.params?.arguments || {});
|
|
959
|
+
});
|
|
960
|
+
|
|
961
|
+
const transport = new StdioServerTransport();
|
|
962
|
+
await server.connect(transport);
|
|
963
|
+
|
|
964
|
+
return { server, runtime, transport };
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
async function main() {
|
|
968
|
+
redirectConsoleToStderr();
|
|
969
|
+
const { runtime } = await startBosunMcpServer();
|
|
970
|
+
let shuttingDown = false;
|
|
971
|
+
const shutdown = async (code = 0) => {
|
|
972
|
+
if (shuttingDown) return;
|
|
973
|
+
shuttingDown = true;
|
|
974
|
+
try {
|
|
975
|
+
await runtime.shutdown();
|
|
976
|
+
} finally {
|
|
977
|
+
process.exit(code);
|
|
978
|
+
}
|
|
979
|
+
};
|
|
980
|
+
|
|
981
|
+
process.on("SIGINT", () => {
|
|
982
|
+
void shutdown(0);
|
|
983
|
+
});
|
|
984
|
+
process.on("SIGTERM", () => {
|
|
985
|
+
void shutdown(0);
|
|
986
|
+
});
|
|
987
|
+
process.on("uncaughtException", (error) => {
|
|
988
|
+
console.error(`${TAG} uncaught exception: ${error?.stack || error?.message || error}`);
|
|
989
|
+
void shutdown(1);
|
|
990
|
+
});
|
|
991
|
+
process.on("unhandledRejection", (error) => {
|
|
992
|
+
console.error(`${TAG} unhandled rejection: ${error?.stack || error?.message || error}`);
|
|
993
|
+
void shutdown(1);
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
if (isMainModule()) {
|
|
998
|
+
try {
|
|
999
|
+
await main();
|
|
1000
|
+
} catch (error) {
|
|
1001
|
+
console.error(`${TAG} failed to start: ${error?.stack || error?.message || error}`);
|
|
1002
|
+
process.exit(1);
|
|
1003
|
+
}
|
|
1004
|
+
}
|