@acid-cat/cli 0.8.0 → 0.10.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.
@@ -1,5 +1,5 @@
1
1
  import { isArrayOf, isObject, memberOf, parseJson } from "../json.js";
2
- import { Checks, expect, expectEqual, idsOf, sorted, until } from "./check.js";
2
+ import { Checks, eventIdsOf, expect, expectEqual, expectOccurrence, idsOf, sorted, until } from "./check.js";
3
3
  import { spawn } from "node:child_process";
4
4
  import { existsSync } from "node:fs";
5
5
  import { mkdtemp, rm } from "node:fs/promises";
@@ -19,7 +19,15 @@ const USAGES = {
19
19
  "fixture.refuse": "fixture refuse",
20
20
  "fixture.release": "fixture release --key <string>",
21
21
  "fixture.switch.set": "fixture switch set --on",
22
- "fixture.toggle": "fixture toggle"
22
+ "fixture.toggle": "fixture toggle",
23
+ "fixture.signal": "fixture signal --key <string> --value <string>",
24
+ "fixture.watching": "fixture watching"
25
+ };
26
+ /** The `usage` of each event in the view of the fixture, after the name of the program. */
27
+ const EVENT_USAGES = {
28
+ "fixture.broken": "watch fixture.broken [--cursor <string>] [--max-age-ms <integer>]",
29
+ "fixture.ticks": "watch fixture.ticks --count <integer> [--gap <integer>] [--key <string>] [--cursor <string>] [--max-age-ms <integer>]",
30
+ "fixture.watch": "watch fixture.watch --key <string> [--marker <string>] [--cursor <string>] [--max-age-ms <integer>]"
23
31
  };
24
32
  const isEntry = (value) => isObject(value) && typeof value.id === "string";
25
33
  /** The list that the `list` command writes to standard output. The check fails when it is not a list of entries. */
@@ -229,20 +237,135 @@ async function checkCli(command, options) {
229
237
  expectEqual(result.code, 0, "exit code");
230
238
  expectEqual(parseJson(result.stdout), { text: "hello" }, "standard output");
231
239
  });
232
- await checks.run("list gives the view", `${RULE}#commands`, async (signal) => {
233
- const result = await run(signal, "list");
234
- expectEqual(sorted(entriesOf(result.stdout).map((entry) => entry.id)), idsOf("cli"), "ids");
240
+ await checks.run("list gives the view: the capabilities, then the events", `${RULE}#commands`, async (signal) => {
241
+ const entries = entriesOf((await run(signal, "list")).stdout);
242
+ const ids = (kind) => entries.filter((entry) => entry.kind === kind).map((entry) => entry.id);
243
+ expectEqual(sorted(ids("capability")), idsOf("cli"), "the ids of the items with kind: capability");
244
+ expectEqual(sorted(ids("event")), eventIdsOf("cli"), "the ids of the items with kind: event");
245
+ expectEqual(entries.length, ids("capability").length + ids("event").length, "the number of items with a kind");
246
+ const firstEvent = entries.findIndex((entry) => entry.kind === "event");
247
+ expect(firstEvent === -1 || entries.slice(firstEvent).every((entry) => entry.kind === "event"), "the events must come after the capabilities");
235
248
  });
236
- await checks.run("list gives the usage of each capability, with its flags", `${RULE}#commands`, async (signal) => {
249
+ await checks.run("list gives the usage of each capability and each event, with its flags", `${RULE}#commands`, async (signal) => {
237
250
  const entries = entriesOf((await run(signal, "list")).stdout);
238
- for (const { id, usage } of entries) {
251
+ for (const { id, kind, usage } of entries) {
239
252
  expect(typeof usage === "string", `${id} must have a usage`);
240
253
  const [program, ...rest] = usage.split(" ");
241
254
  expect(program !== "" && rest.length > 0, `the usage of ${id} must start with the name of the program, got ${JSON.stringify(usage)}`);
242
- expectEqual(rest.join(" "), USAGES[id], `usage of ${id} after the name of the program`);
255
+ expectEqual(rest.join(" "), kind === "event" ? EVENT_USAGES[id] : USAGES[id], `usage of ${id} after the name of the program`);
243
256
  }
244
257
  });
258
+ await checkWatch(checks, command);
245
259
  return checks;
246
260
  }
261
+ const EVENTS = `${RULE}#events`;
262
+ /** The checks of `watch` (spec/bindings/cli.md#events). */
263
+ async function checkWatch(checks, command) {
264
+ const run = (signal, ...args) => start(command, args, signal).run;
265
+ /** The lines of standard output, each one an occurrence of the event. */
266
+ const occurrences = (stdout, id) => stdout.split("\n").filter((line) => line !== "").map((line) => expectOccurrence(parseJson(line), id));
267
+ await checks.run("watch writes each occurrence as one line of JSON, and exits with 0 when the subscription is complete", EVENTS, async (signal) => {
268
+ const result = await run(signal, "watch", "fixture.ticks", "--count", "2");
269
+ expectEqual(result.code, 0, "exit code");
270
+ expectEqual(occurrences(result.stdout, "fixture.ticks").map((occurrence) => occurrence.data), [{ n: 1 }, { n: 2 }], "the data of the occurrences");
271
+ expectEqual(result.stderr, "", "standard error");
272
+ });
273
+ await checks.run("watch with --cursor starts after the cursor", EVENTS, async (signal) => {
274
+ const result = await run(signal, "watch", "fixture.ticks", "--count", "3", "--cursor", "1", "--max-age-ms", "60000");
275
+ expectEqual(result.code, 0, "exit code");
276
+ expectEqual(occurrences(result.stdout, "fixture.ticks").map((occurrence) => [occurrence.data, occurrence.cursor]), [[{ n: 2 }, "2"], [{ n: 3 }, "3"]], "the data and the cursor of the occurrences");
277
+ });
278
+ await checks.run("watch writes an occurrence after a gap with truncated: true", EVENTS, async (signal) => {
279
+ const result = await run(signal, "watch", "fixture.ticks", "--count", "3", "--gap", "2");
280
+ expectEqual(result.code, 0, "exit code");
281
+ expectEqual(occurrences(result.stdout, "fixture.ticks").map((occurrence) => [occurrence.data, occurrence.truncated ?? null]), [
282
+ [{ n: 1 }, null],
283
+ [{ n: 2 }, true],
284
+ [{ n: 3 }, null]
285
+ ], "the data and truncated of the occurrences");
286
+ });
287
+ await checks.run("watch writes internal to standard error with exit code 5 after a defect", EVENTS, async (signal) => {
288
+ const result = await run(signal, "watch", "fixture.broken");
289
+ expectEqual(result.code, 5, "exit code");
290
+ expectEqual(occurrences(result.stdout, "fixture.broken").map((occurrence) => occurrence.data), [{ n: 1 }], "the data of the occurrences");
291
+ expect(/"kind":"internal"/.test(result.stderr), `standard error must contain the call failure internal, got ${JSON.stringify(result.stderr)}`);
292
+ });
293
+ for (const [name, args, code] of [
294
+ [
295
+ "watch with invalid input has exit code 3",
296
+ [
297
+ "watch",
298
+ "fixture.ticks",
299
+ "--count",
300
+ "101"
301
+ ],
302
+ 3
303
+ ],
304
+ [
305
+ "watch with a missing required flag has exit code 3",
306
+ ["watch", "fixture.ticks"],
307
+ 3
308
+ ],
309
+ [
310
+ "watch of an event outside the view has exit code 2",
311
+ ["watch", "fixture.secret"],
312
+ 2
313
+ ],
314
+ [
315
+ "watch with a --max-age-ms that is not an integer has exit code 3",
316
+ [
317
+ "watch",
318
+ "fixture.ticks",
319
+ "--count",
320
+ "1",
321
+ "--max-age-ms",
322
+ "soon"
323
+ ],
324
+ 3
325
+ ],
326
+ [
327
+ "watch with a --max-age-ms that is a fraction has exit code 3",
328
+ [
329
+ "watch",
330
+ "fixture.ticks",
331
+ "--count",
332
+ "1",
333
+ "--max-age-ms",
334
+ "1.5"
335
+ ],
336
+ 3
337
+ ]
338
+ ]) await checks.run(name, EVENTS, async (signal) => {
339
+ expectEqual((await run(signal, ...args)).code, code, "exit code");
340
+ });
341
+ await checks.run("an interrupt cancels watch with exit code 130", EVENTS, async (signal) => {
342
+ const directory = await mkdtemp(join(tmpdir(), "acid-cat-conform-"));
343
+ const marker = join(directory, "watching");
344
+ try {
345
+ const { child, run: watched } = start(command, [
346
+ "watch",
347
+ "fixture.watch",
348
+ "--key",
349
+ "interrupt",
350
+ "--marker",
351
+ marker
352
+ ], signal);
353
+ const ended = () => child.exitCode !== null || child.signalCode !== null;
354
+ await until("the subscription writes its marker file", async () => existsSync(marker) || ended(), (ready) => ready, signal);
355
+ if (!ended()) child.kill("SIGINT");
356
+ const result = await watched;
357
+ expect(existsSync(marker), `the subscription must start before the interrupt, but the command line ended with ${result.code}: ${result.stderr}`);
358
+ expect(result.signal === null, `the process must exit, not end by the signal ${result.signal}`);
359
+ expectEqual(result.code, 130, "exit code");
360
+ expectEqual(result.stdout, "", "standard output");
361
+ expectEqual(result.stderr, "", "standard error");
362
+ } finally {
363
+ await rm(directory, {
364
+ recursive: true,
365
+ force: true
366
+ });
367
+ }
368
+ });
369
+ }
247
370
  //#endregion
248
371
  export { checkCli };
@@ -1,5 +1,7 @@
1
- import { isArrayOf, isObject, memberOf, readJson } from "../json.js";
2
- import { Checks, FORBIDDEN_ORIGIN, expect, expectEqual, idsOf, sorted } from "./check.js";
1
+ import { isArrayOf, isObject, isStringArray, memberOf, parseJson, readJson } from "../json.js";
2
+ import { Checks, FORBIDDEN_ORIGIN, eventIdsOf, expect, expectEqual, expectOccurrence, idsOf, sorted, until } from "./check.js";
3
+ import { sseReader } from "./sse.js";
4
+ import { randomUUID } from "node:crypto";
3
5
  //#region src/conform/http.ts
4
6
  /** Conformance checks for the HTTP binding (spec/bindings/http.md). */
5
7
  const RULE = "spec/bindings/http.md";
@@ -103,7 +105,210 @@ async function checkHttp(base, options) {
103
105
  125
104
106
  ])), 400);
105
107
  });
108
+ await checkEvents(checks, base);
106
109
  return checks;
107
110
  }
111
+ const EVENTS = `${RULE}#events`;
112
+ /** The checks of the events (spec/bindings/http.md#events). */
113
+ async function checkEvents(checks, base) {
114
+ const subscribe = (signal, id, input, headers = {}) => fetch(`${base}/events/${id}`, {
115
+ method: "POST",
116
+ headers: {
117
+ "Content-Type": "application/json",
118
+ ...headers
119
+ },
120
+ body: JSON.stringify(input),
121
+ signal
122
+ });
123
+ /** Subscribe, and check that the stream starts. `signal` and `close` both close the stream. */
124
+ const open = async (signal, id, input, headers = {}) => {
125
+ const controller = new AbortController();
126
+ const response = await subscribe(AbortSignal.any([signal, controller.signal]), id, input, headers);
127
+ if (response.status !== 200) {
128
+ controller.abort();
129
+ throw new Error(`POST /events/${id} must start a stream with the status 200, got ${response.status}`);
130
+ }
131
+ expect(response.headers.get("Content-Type")?.startsWith("text/event-stream") === true, `the media type must be text/event-stream, got ${response.headers.get("Content-Type")}`);
132
+ expectEqual(response.headers.get("Cache-Control"), "no-store", "Cache-Control");
133
+ return {
134
+ response,
135
+ reader: sseReader(response),
136
+ close: () => controller.abort()
137
+ };
138
+ };
139
+ /** The next message of a stream, which must be an occurrence of the event with the cursor as its SSE id. */
140
+ const nextOccurrence = async (stream, id) => {
141
+ const message = await stream.reader.next();
142
+ expect(message?.event === "next", `expected event: next, got ${JSON.stringify(message)}`);
143
+ const occurrence = expectOccurrence(parseJson(message.data), id);
144
+ expectEqual(message.id, occurrence.cursor, "the SSE id of the message, which is the cursor of the occurrence");
145
+ return occurrence;
146
+ };
147
+ const expectComplete = async (stream) => {
148
+ const message = await stream.reader.next();
149
+ expect(message?.event === "complete", `expected event: complete, got ${JSON.stringify(message)}`);
150
+ expectEqual(message.data, "", "the data of event: complete");
151
+ expectEqual(await stream.reader.next(), void 0, "the stream after event: complete");
152
+ };
153
+ const watching = async (signal) => {
154
+ const response = await fetch(`${base}/capabilities/fixture.watching`, {
155
+ method: "POST",
156
+ headers: { "Content-Type": "application/json" },
157
+ body: "{}",
158
+ signal
159
+ });
160
+ const keys = memberOf(memberOf(await readJson(response), "value"), "keys");
161
+ expect(isStringArray(keys), `fixture.watching must give a list of keys, got ${JSON.stringify(keys)}`);
162
+ return keys;
163
+ };
164
+ const count = (keys, key) => keys.filter((each) => each === key).length;
165
+ const signalKey = async (signal, key, value) => {
166
+ const response = await fetch(`${base}/capabilities/fixture.signal`, {
167
+ method: "POST",
168
+ headers: { "Content-Type": "application/json" },
169
+ body: JSON.stringify({
170
+ key,
171
+ value
172
+ }),
173
+ signal
174
+ });
175
+ const delivered = memberOf(memberOf(await readJson(response), "value"), "delivered");
176
+ expect(typeof delivered === "number", `fixture.signal must give the number of subscriptions, got ${JSON.stringify(delivered)}`);
177
+ return delivered;
178
+ };
179
+ await checks.run("GET /events lists the events of the view", `${RULE}#requests`, async (signal) => {
180
+ const list = await readJson(await fetch(`${base}/events`, { signal }));
181
+ if (!isArrayOf(list, isEntry)) throw new Error(`GET /events must give a list of objects with an id, got ${JSON.stringify(list)}`);
182
+ expectEqual(sorted(list.map((entry) => entry.id)), eventIdsOf("api"), "ids");
183
+ });
184
+ for (const [name, id, input, status, kind] of [
185
+ [
186
+ "a subscription to an event outside the view has the status 404",
187
+ "fixture.secret",
188
+ {},
189
+ 404,
190
+ "unknown-capability"
191
+ ],
192
+ [
193
+ "a subscription to the id of a capability has the status 404",
194
+ "fixture.echo",
195
+ { text: "x" },
196
+ 404,
197
+ "unknown-capability"
198
+ ],
199
+ [
200
+ "a subscription with invalid input has the status 422",
201
+ "fixture.ticks",
202
+ { count: 101 },
203
+ 422,
204
+ "invalid-input"
205
+ ]
206
+ ]) await checks.run(name, EVENTS, async (signal) => {
207
+ const response = await subscribe(signal, id, input);
208
+ expectEqual(response.status, status, "status");
209
+ const body = await readJson(response);
210
+ expectEqual(memberOf(body, "kind"), kind, "kind");
211
+ expect(typeof memberOf(body, "message") === "string", `the call failure must have a message, got ${JSON.stringify(body)}`);
212
+ });
213
+ await checks.run("a subscription gives each occurrence as event: next, in order, then event: complete", EVENTS, async (signal) => {
214
+ const stream = await open(signal, "fixture.ticks", { count: 3 });
215
+ try {
216
+ for (const n of [
217
+ 1,
218
+ 2,
219
+ 3
220
+ ]) {
221
+ const occurrence = await nextOccurrence(stream, "fixture.ticks");
222
+ expectEqual(occurrence.data, { n }, "data");
223
+ expectEqual([occurrence.eventId, occurrence.cursor], [String(n), String(n)], "the eventId and the cursor");
224
+ }
225
+ await expectComplete(stream);
226
+ } finally {
227
+ stream.close();
228
+ }
229
+ });
230
+ await checks.run("an occurrence after a gap has truncated: true, and the other occurrences have no truncated", EVENTS, async (signal) => {
231
+ const stream = await open(signal, "fixture.ticks", {
232
+ count: 3,
233
+ gap: 2
234
+ });
235
+ try {
236
+ for (const n of [
237
+ 1,
238
+ 2,
239
+ 3
240
+ ]) {
241
+ const occurrence = await nextOccurrence(stream, "fixture.ticks");
242
+ expectEqual([occurrence.data, occurrence.truncated], [{ n }, n === 2 ? true : void 0], "the data and truncated");
243
+ }
244
+ await expectComplete(stream);
245
+ } finally {
246
+ stream.close();
247
+ }
248
+ });
249
+ await checks.run("Last-Event-ID gives the cursor, and the subscription starts after it", EVENTS, async (signal) => {
250
+ const stream = await open(signal, "fixture.ticks", { count: 3 }, { "Last-Event-ID": "1" });
251
+ try {
252
+ for (const n of [2, 3]) expectEqual((await nextOccurrence(stream, "fixture.ticks")).data, { n }, "data");
253
+ await expectComplete(stream);
254
+ } finally {
255
+ stream.close();
256
+ }
257
+ });
258
+ await checks.run("a defect after an occurrence ends the stream with event: error and internal", EVENTS, async (signal) => {
259
+ const stream = await open(signal, "fixture.broken", {});
260
+ try {
261
+ expectEqual((await nextOccurrence(stream, "fixture.broken")).data, { n: 1 }, "data");
262
+ const message = await stream.reader.next();
263
+ expect(message?.event === "error", `expected event: error, got ${JSON.stringify(message)}`);
264
+ const failure = parseJson(message.data);
265
+ expect(memberOf(failure, "kind") === "internal" && typeof memberOf(failure, "message") === "string", `expected { kind: "internal", message }, got ${message.data}`);
266
+ expect(!/\n\s+at /.test(String(memberOf(failure, "message"))), "the message must not contain a stack trace");
267
+ expectEqual(await stream.reader.next(), void 0, "the stream after event: error");
268
+ } finally {
269
+ stream.close();
270
+ }
271
+ });
272
+ await checks.run("a subscription gets the things that happen, and closing the stream cancels it", EVENTS, async (signal) => {
273
+ const key = randomUUID();
274
+ const pending = open(signal, "fixture.watch", { key });
275
+ pending.catch(() => void 0);
276
+ let stream;
277
+ try {
278
+ await until("fixture.watching gives the key", () => watching(signal), (keys) => keys.includes(key), signal);
279
+ stream = await pending;
280
+ expectEqual(await signalKey(signal, key, "first"), 1, "the subscriptions that fixture.signal reached");
281
+ expectEqual((await nextOccurrence(stream, "fixture.watch")).data, {
282
+ key,
283
+ value: "first"
284
+ }, "data");
285
+ stream.close();
286
+ await until("the key leaves fixture.watching", () => watching(signal), (keys) => !keys.includes(key), signal);
287
+ } finally {
288
+ stream?.close();
289
+ pending.then((opened) => opened.close(), () => void 0);
290
+ }
291
+ });
292
+ await checks.run("cancelling one subscription leaves the other subscriptions running", EVENTS, async (signal) => {
293
+ const key = randomUUID();
294
+ const first = open(signal, "fixture.watch", { key });
295
+ const second = open(signal, "fixture.watch", { key });
296
+ first.catch(() => void 0);
297
+ second.catch(() => void 0);
298
+ try {
299
+ await until("fixture.watching gives the key of both subscriptions", () => watching(signal), (keys) => count(keys, key) === 2, signal);
300
+ const [cancelled, kept] = [await first, await second];
301
+ cancelled.close();
302
+ await until("the cancelled subscription leaves fixture.watching", () => watching(signal), (keys) => count(keys, key) === 1, signal);
303
+ expectEqual(await signalKey(signal, key, "after"), 1, "the subscriptions that fixture.signal reached");
304
+ expectEqual((await nextOccurrence(kept, "fixture.watch")).data, {
305
+ key,
306
+ value: "after"
307
+ }, "data");
308
+ } finally {
309
+ for (const stream of [first, second]) stream.then((opened) => opened.close(), () => void 0);
310
+ }
311
+ });
312
+ }
108
313
  //#endregion
109
314
  export { checkHttp };
@@ -1,14 +1,28 @@
1
1
  import type { Check } from "./check.js";
2
2
  import type { WebMcpInstall } from "./webmcp.js";
3
+ import type { PollSettings } from "./poll.js";
4
+ import type { WebhookSettings } from "./webhook.js";
3
5
  export type { Check } from "./check.js";
4
6
  export { DEADLINE, fixture } from "./check.js";
5
7
  export { serve } from "./serve.js";
6
8
  export type { Routes, Served, WebHandler } from "./serve.js";
7
9
  export { createTestHost } from "./webmcp.js";
8
10
  export type { TestHost, WebMcpInstall } from "./webmcp.js";
11
+ export type { PollSettings } from "./poll.js";
12
+ export type { WebhookSettings } from "./webhook.js";
9
13
  export interface ConformOptions {
10
14
  /** The URL of the MCP server, for example `http://localhost:8787/mcp`. */
11
15
  readonly mcp?: string | undefined;
16
+ /**
17
+ * Check the `initialize` handshake of revision 2025-11-25 through the MCP server too
18
+ * (spec/bindings/mcp.md#earlier-revisions-through-http). The default is `false`.
19
+ */
20
+ readonly mcpHandshake?: boolean | undefined;
21
+ /**
22
+ * The command that starts the MCP server through standard input and standard output, for example
23
+ * `["node", "fixture-mcp-stdio.ts"]`. Each check starts its own process.
24
+ */
25
+ readonly mcpStdio?: readonly string[] | undefined;
12
26
  /** The base URL of the HTTP API, before `/capabilities`. */
13
27
  readonly http?: string | undefined;
14
28
  /** The command that starts the command line, for example `["node", "fixture-cli.ts"]`. */
@@ -18,6 +32,17 @@ export interface ConformOptions {
18
32
  * on a host in the same process. A page runs in Chromium, through Playwright.
19
33
  */
20
34
  readonly webmcp?: string | WebMcpInstall | undefined;
35
+ /**
36
+ * The poll settings of the deployment, for MCP through HTTP and through stdio. With `bufferLimit`, conform tests a
37
+ * cursor older than the buffer. With `leaseMs`, it tests that a lease with no poll ends.
38
+ */
39
+ readonly poll?: PollSettings | undefined;
40
+ /**
41
+ * Test webhook delivery through `mcp`. conform runs an HTTPS receiver on 127.0.0.1 with this certificate and key. The
42
+ * deployment trusts the certificate, allows deliveries to 127.0.0.1, and gives the principal to each request with the
43
+ * header `Authorization: Bearer <principal>`.
44
+ */
45
+ readonly webhook?: WebhookSettings | undefined;
21
46
  /** The body limit of the remote bindings, in bytes. The default is 1 MiB. */
22
47
  readonly maximumBytes?: number | undefined;
23
48
  /**
@@ -3,6 +3,7 @@ import { checkCli } from "./cli.js";
3
3
  import { checkHttp } from "./http.js";
4
4
  import { checkMcp } from "./mcp.js";
5
5
  import { checkProperties, httpInvoke, mcpInvoke } from "./properties.js";
6
+ import { checkMcpStdio } from "./mcp-stdio.js";
6
7
  import { browserDriver, checkWebMcp, processDriver } from "./webmcp.js";
7
8
  import { serve } from "./serve.js";
8
9
  //#region src/conform/index.ts
@@ -14,11 +15,15 @@ import { serve } from "./serve.js";
14
15
  async function conform(options) {
15
16
  const maximumBytes = options.maximumBytes ?? 1048576;
16
17
  const deadline = options.deadline ?? 3e4;
18
+ const poll = options.poll ?? {};
17
19
  const results = [];
18
20
  if (options.mcp !== void 0) {
19
21
  results.push(...(await checkMcp(options.mcp, {
20
22
  maximumBytes,
21
- deadline
23
+ deadline,
24
+ handshake: options.mcpHandshake === true,
25
+ poll,
26
+ webhook: options.webhook
22
27
  })).results);
23
28
  const mcp = await mcpInvoke(options.mcp, AbortSignal.timeout(deadline)).catch(() => void 0);
24
29
  if (mcp !== void 0) {
@@ -26,6 +31,10 @@ async function conform(options) {
26
31
  await mcp.close();
27
32
  }
28
33
  }
34
+ if (options.mcpStdio !== void 0) for (const checks of await checkMcpStdio(options.mcpStdio, {
35
+ deadline,
36
+ poll
37
+ })) results.push(...checks.results);
29
38
  if (options.http !== void 0) {
30
39
  results.push(...(await checkHttp(options.http, {
31
40
  maximumBytes,
@@ -0,0 +1,6 @@
1
+ import { Checks } from "./check.js";
2
+ import type { PollSettings } from "./poll.js";
3
+ export declare function checkMcpStdio(command: readonly string[], options: {
4
+ readonly deadline: number;
5
+ readonly poll: PollSettings;
6
+ }): Promise<readonly Checks[]>;