@bli-cockpit/mcp 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * @bli-cockpit/mcp — MCP server adapter for the BLI Cockpit event stream.
4
+ *
5
+ * Exposes three tools backed by the canonical REST contract at
6
+ * `POST /api/events/emit` (see docs/plans/cockpit-agent-ops-control-plane.md §6).
7
+ *
8
+ * Auth (precedence order, matches the `bli-event` bash CLI):
9
+ * 1. `BLI_OPERATOR_TOKEN` env var — used verbatim (CI / override).
10
+ * 2. Otherwise: shell out to `node scripts/bli-event-session.mjs get-token`
11
+ * which reads `~/.config/bli-event/session.json`, refreshes the
12
+ * access_token if near expiry, and prints a fresh token on stdout.
13
+ * Set `BLI_SESSION_HELPER` to an absolute path if the helper can't be
14
+ * discovered by walking up from this module's location.
15
+ *
16
+ * If neither path is available, the server prints a clear error and exits.
17
+ */
18
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
19
+ import { createServer } from "./server.js";
20
+ import { createDefaultTokenResolver } from "./token-resolver.js";
21
+ async function main() {
22
+ const baseUrl = process.env.BLI_API_BASE_URL ?? "http://127.0.0.1:3100";
23
+ let resolver;
24
+ let sourceLabel;
25
+ try {
26
+ const result = createDefaultTokenResolver({ env: process.env });
27
+ resolver = result.resolver;
28
+ sourceLabel =
29
+ result.source === "env"
30
+ ? "BLI_OPERATOR_TOKEN env var"
31
+ : `session helper at ${result.helperPath}`;
32
+ }
33
+ catch (err) {
34
+ process.stderr.write(`[bli-cockpit-mcp] ${err instanceof Error ? err.message : String(err)}\n`);
35
+ process.exit(1);
36
+ }
37
+ process.stderr.write(`[bli-cockpit-mcp] auth source: ${sourceLabel}\n`);
38
+ const deps = {
39
+ resolveToken: resolver,
40
+ baseUrl,
41
+ fetchImpl: globalThis.fetch,
42
+ };
43
+ const server = createServer(deps);
44
+ const transport = new StdioServerTransport();
45
+ await server.connect(transport);
46
+ }
47
+ main().catch((err) => {
48
+ process.stderr.write(`[bli-cockpit-mcp] fatal: ${err instanceof Error ? err.message : String(err)}\n`);
49
+ process.exit(1);
50
+ });
@@ -0,0 +1,96 @@
1
+ /**
2
+ * MCP server factory for @bli-cockpit/mcp.
3
+ *
4
+ * Pure factory — accepts injected fetch + base URL + token so it can be
5
+ * unit-tested without binding to a real network or transport.
6
+ */
7
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
+ import { z } from "zod";
9
+ import type { TokenResolver } from "./token-resolver.js";
10
+ export type FetchImpl = typeof fetch;
11
+ export interface ServerDeps {
12
+ /**
13
+ * Called before every authenticated request. Must return a fresh Supabase
14
+ * access_token. See `token-resolver.ts` for the built-in implementations
15
+ * (`createEnvTokenResolver`, `createSessionHelperTokenResolver`).
16
+ */
17
+ resolveToken: TokenResolver;
18
+ baseUrl: string;
19
+ fetchImpl: FetchImpl;
20
+ }
21
+ export declare const PACKAGE_NAME = "@bli-cockpit/mcp";
22
+ export declare const PACKAGE_VERSION = "0.1.0";
23
+ export declare const emitEventInput: {
24
+ ticket_id: z.ZodString;
25
+ event_type: z.ZodString;
26
+ event_version: z.ZodOptional<z.ZodNumber>;
27
+ kind: z.ZodOptional<z.ZodEnum<{
28
+ agent_asserted: "agent_asserted";
29
+ external_observed: "external_observed";
30
+ human: "human";
31
+ }>>;
32
+ source: z.ZodOptional<z.ZodString>;
33
+ actor_role: z.ZodOptional<z.ZodString>;
34
+ payload: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
35
+ note_md: z.ZodOptional<z.ZodString>;
36
+ correlation_id: z.ZodOptional<z.ZodString>;
37
+ idempotency_key: z.ZodOptional<z.ZodString>;
38
+ occurred_at: z.ZodOptional<z.ZodString>;
39
+ };
40
+ export declare const getTicketTimelineInput: {
41
+ ticket_id: z.ZodString;
42
+ since: z.ZodOptional<z.ZodString>;
43
+ limit: z.ZodOptional<z.ZodNumber>;
44
+ };
45
+ export declare const getActiveTicketsInput: {
46
+ source: z.ZodOptional<z.ZodString>;
47
+ project: z.ZodOptional<z.ZodString>;
48
+ lane: z.ZodOptional<z.ZodString>;
49
+ assignee: z.ZodOptional<z.ZodString>;
50
+ };
51
+ export interface ApiErrorBody {
52
+ code?: string;
53
+ message?: string;
54
+ field?: string;
55
+ details?: unknown;
56
+ }
57
+ export declare class ApiError extends Error {
58
+ readonly status: number;
59
+ readonly code?: string;
60
+ readonly body: ApiErrorBody | string;
61
+ constructor(status: number, body: ApiErrorBody | string, code?: string);
62
+ }
63
+ export interface EmitEventResult {
64
+ event_id: string;
65
+ occurred_at: string;
66
+ status: "created" | "replay";
67
+ }
68
+ export declare function emitEvent(deps: ServerDeps, args: {
69
+ ticket_id: string;
70
+ event_type: string;
71
+ event_version?: number;
72
+ kind?: "agent_asserted" | "external_observed" | "human";
73
+ source?: string;
74
+ actor_role?: string;
75
+ payload?: Record<string, unknown>;
76
+ note_md?: string;
77
+ correlation_id?: string;
78
+ idempotency_key?: string;
79
+ occurred_at?: string;
80
+ }): Promise<EmitEventResult>;
81
+ export declare function getTicketTimeline(deps: ServerDeps, args: {
82
+ ticket_id: string;
83
+ since?: string;
84
+ limit?: number;
85
+ }): Promise<{
86
+ events: unknown[];
87
+ }>;
88
+ export declare function getActiveTickets(deps: ServerDeps, args: {
89
+ source?: string;
90
+ project?: string;
91
+ lane?: string;
92
+ assignee?: string;
93
+ }): Promise<{
94
+ tickets: unknown[];
95
+ }>;
96
+ export declare function createServer(deps: ServerDeps): McpServer;
package/dist/server.js ADDED
@@ -0,0 +1,374 @@
1
+ /**
2
+ * MCP server factory for @bli-cockpit/mcp.
3
+ *
4
+ * Pure factory — accepts injected fetch + base URL + token so it can be
5
+ * unit-tested without binding to a real network or transport.
6
+ */
7
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
8
+ import { z } from "zod";
9
+ import { registerDocsMsgTools } from "./docs-msg-tools.js";
10
+ export const PACKAGE_NAME = "@bli-cockpit/mcp";
11
+ export const PACKAGE_VERSION = "0.1.0";
12
+ // ---- input schemas (Zod raw shapes) -----------------------------------------
13
+ export const emitEventInput = {
14
+ ticket_id: z
15
+ .string()
16
+ .min(1)
17
+ .describe("Linear ticket ID (e.g. BLI-2115, KOLM-87)."),
18
+ event_type: z
19
+ .string()
20
+ .min(1)
21
+ .describe("Canonical event type from the event vocabulary (e.g. ticket_claimed, plan_submitted, worker_dispatched, note)."),
22
+ event_version: z
23
+ .number()
24
+ .int()
25
+ .positive()
26
+ .optional()
27
+ .describe("Event schema version. Defaults to 1 in the API."),
28
+ kind: z
29
+ .enum(["agent_asserted", "external_observed", "human"])
30
+ .optional()
31
+ .describe("Event kind. Omit to let the API infer it from event_type."),
32
+ source: z
33
+ .string()
34
+ .min(1)
35
+ .optional()
36
+ .describe("Advanced producer identity override. Omit during normal use so the API derives source from the authenticated operator."),
37
+ actor_role: z
38
+ .string()
39
+ .min(1)
40
+ .optional()
41
+ .describe("Actor role for the event. Defaults to operator."),
42
+ payload: z
43
+ .record(z.string(), z.unknown())
44
+ .optional()
45
+ .describe("Event payload object. Defaults to {}."),
46
+ note_md: z
47
+ .string()
48
+ .optional()
49
+ .describe("Optional markdown note attached to the event."),
50
+ correlation_id: z
51
+ .string()
52
+ .optional()
53
+ .describe("Correlation ID linking related events (e.g. an agent trace ID)."),
54
+ idempotency_key: z
55
+ .string()
56
+ .optional()
57
+ .describe("Idempotency key — replays return status 'replay' instead of 'created'."),
58
+ occurred_at: z
59
+ .string()
60
+ .optional()
61
+ .describe("ISO-8601 timestamp. Defaults to now in the API."),
62
+ };
63
+ export const getTicketTimelineInput = {
64
+ ticket_id: z.string().min(1).describe("Linear ticket ID."),
65
+ since: z
66
+ .string()
67
+ .optional()
68
+ .describe("ISO-8601 timestamp; only return events occurring after this."),
69
+ limit: z
70
+ .number()
71
+ .int()
72
+ .min(1)
73
+ .max(500)
74
+ .optional()
75
+ .describe("Max events to return (default 100, max 500)."),
76
+ };
77
+ export const getActiveTicketsInput = {
78
+ source: z
79
+ .string()
80
+ .optional()
81
+ .describe("Filter by source (e.g. cli:vee, worker, ci)."),
82
+ project: z
83
+ .string()
84
+ .optional()
85
+ .describe("Filter by project tag (bui, kolm, portpal, bp)."),
86
+ lane: z
87
+ .string()
88
+ .optional()
89
+ .describe("Filter by lane (e.g. Planning, Executing, Awaiting Review)."),
90
+ assignee: z.string().optional().describe("Filter by assignee user ID."),
91
+ };
92
+ export class ApiError extends Error {
93
+ status;
94
+ code;
95
+ body;
96
+ constructor(status, body, code) {
97
+ const summary = typeof body === "string"
98
+ ? body
99
+ : (body.message ?? `HTTP ${status}`);
100
+ super(`[${code ?? `HTTP-${status}`}] ${summary}`);
101
+ this.name = "ApiError";
102
+ this.status = status;
103
+ this.body = body;
104
+ this.code = code;
105
+ }
106
+ }
107
+ async function parseMaybeJson(response) {
108
+ const text = await response.text();
109
+ if (!text)
110
+ return null;
111
+ try {
112
+ return JSON.parse(text);
113
+ }
114
+ catch {
115
+ // BLI-3238 — deliberately silent. This function's contract is "maybe
116
+ // JSON": a non-JSON body is an expected shape, not a failure, and the
117
+ // text is returned intact rather than discarded — ApiError carries it
118
+ // verbatim to the caller, so nothing is lost for a log to recover.
119
+ return text;
120
+ }
121
+ }
122
+ async function callApi(deps, method, path, body) {
123
+ const url = `${deps.baseUrl.replace(/\/$/, "")}${path}`;
124
+ // Resolve a fresh token per call. Implementations are expected to cache
125
+ // internally (see createSessionHelperTokenResolver) so burst calls don't
126
+ // each spawn a subprocess.
127
+ let token;
128
+ try {
129
+ token = await deps.resolveToken();
130
+ }
131
+ catch (err) {
132
+ // Auth-side failure (no session, revoked refresh token, helper missing).
133
+ // Surface as BLI-E003 per error catalog so it's distinct from network.
134
+ throw new ApiError(401, {
135
+ code: "BLI-E003",
136
+ message: `Token resolution failed: ${err instanceof Error ? err.message : String(err)}`,
137
+ }, "BLI-E003");
138
+ }
139
+ const headers = {
140
+ Authorization: `Bearer ${token}`,
141
+ Accept: "application/json",
142
+ };
143
+ if (body !== undefined) {
144
+ headers["Content-Type"] = "application/json";
145
+ }
146
+ let response;
147
+ try {
148
+ response = await deps.fetchImpl(url, {
149
+ method,
150
+ headers,
151
+ body: body === undefined ? undefined : JSON.stringify(body),
152
+ });
153
+ }
154
+ catch (err) {
155
+ // Network / DNS / TLS failure — surface as BLI-E001 per error catalog.
156
+ throw new ApiError(0, {
157
+ code: "BLI-E001",
158
+ message: `Network error contacting ${url}: ${err instanceof Error ? err.message : String(err)}`,
159
+ }, "BLI-E001");
160
+ }
161
+ const parsed = await parseMaybeJson(response);
162
+ if (!response.ok) {
163
+ const errBody = parsed && typeof parsed === "object"
164
+ ? parsed
165
+ : parsed ?? response.statusText;
166
+ const code = typeof errBody === "object" && errBody !== null
167
+ ? errBody.code
168
+ : undefined;
169
+ throw new ApiError(response.status, errBody, code);
170
+ }
171
+ return parsed;
172
+ }
173
+ export async function emitEvent(deps, args) {
174
+ const body = {
175
+ ticket_id: args.ticket_id,
176
+ event_type: args.event_type,
177
+ actor_role: args.actor_role ?? "operator",
178
+ payload: args.payload ?? {},
179
+ ...(args.event_version !== undefined
180
+ ? { event_version: args.event_version }
181
+ : {}),
182
+ ...(args.kind !== undefined ? { kind: args.kind } : {}),
183
+ ...(args.source !== undefined ? { source: args.source } : {}),
184
+ ...(args.note_md !== undefined ? { note_md: args.note_md } : {}),
185
+ ...(args.correlation_id !== undefined
186
+ ? { correlation_id: args.correlation_id }
187
+ : {}),
188
+ ...(args.idempotency_key !== undefined
189
+ ? { idempotency_key: args.idempotency_key }
190
+ : {}),
191
+ ...(args.occurred_at !== undefined ? { occurred_at: args.occurred_at } : {}),
192
+ };
193
+ const response = (await callApi(deps, "POST", "/api/events/emit", body));
194
+ if (!response || typeof response !== "object") {
195
+ throw new ApiError(502, {
196
+ code: "BLI-E001",
197
+ message: "Malformed response from /api/events/emit",
198
+ details: response,
199
+ });
200
+ }
201
+ if ("status" in response && response.status === "replay") {
202
+ const eventId = "event_id" in response && typeof response.event_id === "string"
203
+ ? response.event_id
204
+ : "existing_event_id" in response &&
205
+ typeof response.existing_event_id === "string"
206
+ ? response.existing_event_id
207
+ : "";
208
+ if (!eventId) {
209
+ throw new ApiError(502, {
210
+ code: "BLI-E001",
211
+ message: "Malformed replay response from /api/events/emit",
212
+ details: response,
213
+ });
214
+ }
215
+ const occurredAt = "existing_received_at" in response &&
216
+ typeof response.existing_received_at === "string"
217
+ ? response.existing_received_at
218
+ : "existing_occurred_at" in response &&
219
+ typeof response.existing_occurred_at === "string"
220
+ ? response.existing_occurred_at
221
+ : "";
222
+ if (!occurredAt) {
223
+ throw new ApiError(502, {
224
+ code: "BLI-E001",
225
+ message: "Malformed replay timestamp from /api/events/emit",
226
+ details: response,
227
+ });
228
+ }
229
+ return {
230
+ event_id: eventId,
231
+ occurred_at: occurredAt,
232
+ status: "replay",
233
+ };
234
+ }
235
+ const event = "event" in response && response.event && typeof response.event === "object"
236
+ ? response.event
237
+ : null;
238
+ const eventId = "event_id" in response && typeof response.event_id === "string"
239
+ ? response.event_id
240
+ : typeof event?.id === "string"
241
+ ? event.id
242
+ : "";
243
+ const occurredAt = "occurred_at" in response && typeof response.occurred_at === "string"
244
+ ? response.occurred_at
245
+ : typeof event?.received_at === "string"
246
+ ? event.received_at
247
+ : typeof event?.occurred_at === "string"
248
+ ? event.occurred_at
249
+ : "";
250
+ if (!eventId || !occurredAt) {
251
+ throw new ApiError(502, {
252
+ code: "BLI-E001",
253
+ message: "Malformed created response from /api/events/emit",
254
+ details: response,
255
+ });
256
+ }
257
+ return {
258
+ event_id: eventId,
259
+ occurred_at: occurredAt,
260
+ status: "created",
261
+ };
262
+ }
263
+ export async function getTicketTimeline(deps, args) {
264
+ const params = new URLSearchParams({ ticket_id: args.ticket_id });
265
+ if (args.since)
266
+ params.set("since", args.since);
267
+ if (args.limit !== undefined)
268
+ params.set("limit", String(args.limit));
269
+ const response = (await callApi(deps, "GET", `/api/events/timeline?${params.toString()}`));
270
+ return { events: response?.events ?? [] };
271
+ }
272
+ export async function getActiveTickets(deps, args) {
273
+ const params = new URLSearchParams();
274
+ if (args.source)
275
+ params.set("source", args.source);
276
+ if (args.project)
277
+ params.set("project", args.project);
278
+ if (args.lane)
279
+ params.set("lane", args.lane);
280
+ if (args.assignee)
281
+ params.set("assignee", args.assignee);
282
+ const query = params.toString();
283
+ const path = query ? `/api/tickets/active?${query}` : "/api/tickets/active";
284
+ const response = (await callApi(deps, "GET", path));
285
+ return { tickets: response?.tickets ?? [] };
286
+ }
287
+ // ---- result formatting helpers ---------------------------------------------
288
+ function jsonContent(value) {
289
+ return {
290
+ content: [
291
+ {
292
+ type: "text",
293
+ text: JSON.stringify(value, null, 2),
294
+ },
295
+ ],
296
+ };
297
+ }
298
+ function errorContent(err) {
299
+ let message;
300
+ let code;
301
+ if (err instanceof ApiError) {
302
+ message = err.message;
303
+ code = err.code;
304
+ }
305
+ else if (err instanceof Error) {
306
+ message = err.message;
307
+ }
308
+ else {
309
+ message = String(err);
310
+ }
311
+ return {
312
+ isError: true,
313
+ content: [
314
+ {
315
+ type: "text",
316
+ text: code ? `[${code}] ${message}` : message,
317
+ },
318
+ ],
319
+ };
320
+ }
321
+ // ---- server factory ---------------------------------------------------------
322
+ export function createServer(deps) {
323
+ const server = new McpServer({
324
+ name: PACKAGE_NAME,
325
+ version: PACKAGE_VERSION,
326
+ });
327
+ server.registerTool("emit_event", {
328
+ title: "Emit a cockpit event",
329
+ description: "Append a canonical event to the BLI Cockpit event stream for a given ticket. Wraps POST /api/events/emit. Supports idempotency_key for at-most-once writes.",
330
+ inputSchema: emitEventInput,
331
+ }, async (args) => {
332
+ try {
333
+ const result = await emitEvent(deps, args);
334
+ return jsonContent(result);
335
+ }
336
+ catch (err) {
337
+ return errorContent(err);
338
+ }
339
+ });
340
+ server.registerTool("get_ticket_timeline", {
341
+ title: "Get ticket event timeline",
342
+ description: "Return the canonical event timeline for a ticket, optionally filtered by since/limit. Wraps GET /api/events/timeline.",
343
+ inputSchema: getTicketTimelineInput,
344
+ }, async (args) => {
345
+ try {
346
+ const result = await getTicketTimeline(deps, args);
347
+ return jsonContent(result);
348
+ }
349
+ catch (err) {
350
+ return errorContent(err);
351
+ }
352
+ });
353
+ server.registerTool("get_active_tickets", {
354
+ title: "List active tickets",
355
+ description: "Return task-visible tickets with active orchestration state, optionally filtered by source/project/lane/assignee. Wraps GET /api/tickets/active.",
356
+ inputSchema: getActiveTicketsInput,
357
+ }, async (args) => {
358
+ try {
359
+ const result = await getActiveTickets(deps, args);
360
+ return jsonContent(result);
361
+ }
362
+ catch (err) {
363
+ return errorContent(err);
364
+ }
365
+ });
366
+ // BLI-3706: docs_*/msg_* over the collector device token, a SEPARATE auth
367
+ // path from the emit_event/get_ticket_timeline/get_active_tickets tools
368
+ // above (which use BLI_OPERATOR_TOKEN / the session helper). Registered
369
+ // unconditionally — the session is loaded fresh per call, so a machine with
370
+ // no `cockpit login` pairing yet still serves the three event tools; only a
371
+ // docs/msg call on that machine fails, by name.
372
+ registerDocsMsgTools(server, { fetchImpl: deps.fetchImpl });
373
+ return server;
374
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Token resolution for the MCP server.
3
+ *
4
+ * Two auth paths, matching the `bli-event` bash CLI precedence:
5
+ *
6
+ * 1. `BLI_OPERATOR_TOKEN` env var — used verbatim (CI / explicit override)
7
+ * 2. Fallback: spawn `node <bli-event-session.mjs> get-token` — refreshes
8
+ * the cached Supabase session if the access_token is near expiry, and
9
+ * prints a fresh access_token on stdout. Exits non-zero if no session
10
+ * exists (user must run `bli-event login`).
11
+ *
12
+ * The session-helper path spawns a subprocess per resolution. To keep the
13
+ * overhead invisible for bursts of tool calls (e.g. agent emitting a
14
+ * timeline fetch + multiple events in sequence), resolved tokens are cached
15
+ * in-process for a short window. The cache is time-based only — if the
16
+ * underlying session refresh fails, the NEXT call (after TTL expiry) will
17
+ * surface the error.
18
+ */
19
+ import type { ChildProcess, SpawnOptions } from "node:child_process";
20
+ export type TokenResolver = () => Promise<string>;
21
+ /**
22
+ * Resolver that returns a static token verbatim. Use for the
23
+ * `BLI_OPERATOR_TOKEN` env var path (CI / override).
24
+ */
25
+ export declare function createEnvTokenResolver(token: string): TokenResolver;
26
+ export type SpawnFn = (command: string, args: readonly string[], options?: SpawnOptions) => ChildProcess;
27
+ export interface SessionHelperOptions {
28
+ /** Absolute path to `bli-event-session.mjs`. */
29
+ helperPath: string;
30
+ /** Node executable used to run the helper. Default: `process.execPath`. */
31
+ nodePath?: string;
32
+ /** Injectable spawn for tests. Default: `child_process.spawn`. */
33
+ spawn?: SpawnFn;
34
+ /**
35
+ * How long (ms) to cache a resolved token in-process before spawning the
36
+ * helper again. The helper itself refreshes the session when the access
37
+ * token is near expiry, so a small cache window is all we need to absorb
38
+ * bursts of tool calls. Default 30_000 (30s). Set to 0 to disable caching.
39
+ */
40
+ cacheTtlMs?: number;
41
+ /**
42
+ * Clock source for cache bookkeeping. Default `Date.now`.
43
+ */
44
+ now?: () => number;
45
+ /** Env vars forwarded to the helper. Default: `process.env`. */
46
+ env?: NodeJS.ProcessEnv;
47
+ }
48
+ /** Thrown when the session helper is missing or fails to resolve a token. */
49
+ export declare class SessionHelperError extends Error {
50
+ readonly exitCode: number | null;
51
+ readonly stderr: string;
52
+ constructor(message: string, exitCode: number | null, stderr: string);
53
+ }
54
+ /**
55
+ * Resolver that shells out to the Node session helper to get (and refresh)
56
+ * the operator's Supabase access_token.
57
+ */
58
+ export declare function createSessionHelperTokenResolver(options: SessionHelperOptions): TokenResolver;
59
+ export interface ResolveHelperPathOptions {
60
+ /**
61
+ * Env vars to inspect. Default: `process.env`.
62
+ */
63
+ env?: NodeJS.ProcessEnv;
64
+ /**
65
+ * The `import.meta.url` of the calling module — used to walk up the tree
66
+ * looking for a sibling `scripts/bli-event-session.mjs`. Default:
67
+ * `import.meta.url` of this module. Tests can inject a synthetic path.
68
+ */
69
+ fromUrl?: string;
70
+ /**
71
+ * Override for `fs.existsSync` so tests can fake the filesystem.
72
+ */
73
+ existsSync?: (p: string) => boolean;
74
+ }
75
+ /**
76
+ * Locate `scripts/bli-event-session.mjs` on disk, or return `null`.
77
+ *
78
+ * Order:
79
+ * 1. If `BLI_SESSION_HELPER` env var points at a readable file, use it.
80
+ * 2. Walk up from `import.meta.url` looking for a sibling
81
+ * `scripts/bli-event-session.mjs` (handles dev checkouts + installed
82
+ * npm workspaces where the MCP dist/ lives under `packages/bli-cockpit-mcp/`
83
+ * inside the monorepo root).
84
+ * 3. Return `null` — caller surfaces a clear error.
85
+ */
86
+ export declare function resolveSessionHelperPath(options?: ResolveHelperPathOptions): string | null;
87
+ export interface DefaultTokenResolverOptions {
88
+ env?: NodeJS.ProcessEnv;
89
+ /**
90
+ * Injectable dependencies forwarded to `createSessionHelperTokenResolver`
91
+ * when the env override is not set.
92
+ */
93
+ spawn?: SpawnFn;
94
+ nodePath?: string;
95
+ cacheTtlMs?: number;
96
+ now?: () => number;
97
+ /** Override helper-path discovery (tests). */
98
+ resolveHelperPath?: () => string | null;
99
+ }
100
+ export interface DefaultTokenResolverResult {
101
+ /** The resolver to install on the server. */
102
+ resolver: TokenResolver;
103
+ /** Which source fed the resolver — useful for startup logging. */
104
+ source: "env" | "session-helper";
105
+ /** If source is `session-helper`, the resolved helper path. */
106
+ helperPath?: string;
107
+ }
108
+ /**
109
+ * Build a `TokenResolver` using the documented precedence order:
110
+ * 1. `BLI_OPERATOR_TOKEN` env var (verbatim)
111
+ * 2. Session helper (`scripts/bli-event-session.mjs get-token`)
112
+ *
113
+ * Throws with a clear, user-actionable message when neither path is
114
+ * available (no env var, and no session helper on disk).
115
+ */
116
+ export declare function createDefaultTokenResolver(options?: DefaultTokenResolverOptions): DefaultTokenResolverResult;