@yawlabs/mcp-compliance 0.13.3 → 0.13.5

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.
@@ -1447,6 +1447,7 @@ async function runComplianceSuite(target, options = {}) {
1447
1447
  }
1448
1448
  const nextId = createIdCounter(1e3);
1449
1449
  const timeout = options.timeout || 15e3;
1450
+ const startupTimeout = options.startupTimeout ?? Math.max(timeout, 6e4);
1450
1451
  const retries = options.retries || 0;
1451
1452
  let sessionId = null;
1452
1453
  let negotiatedProtocolVersion = null;
@@ -1702,16 +1703,24 @@ async function runComplianceSuite(target, options = {}) {
1702
1703
  }
1703
1704
  );
1704
1705
  let initRes = null;
1706
+ const initStart = Date.now();
1705
1707
  try {
1706
- initRes = await rpc("initialize", {
1707
- protocolVersion: SPEC_VERSION,
1708
- capabilities: {
1709
- sampling: {},
1710
- roots: { listChanged: true },
1711
- elicitation: {}
1708
+ initRes = await mcpRequest(
1709
+ backendUrl,
1710
+ "initialize",
1711
+ {
1712
+ protocolVersion: SPEC_VERSION,
1713
+ capabilities: {
1714
+ sampling: {},
1715
+ roots: { listChanged: true },
1716
+ elicitation: {}
1717
+ },
1718
+ clientInfo: { name: "mcp-compliance", version: TOOL_VERSION }
1712
1719
  },
1713
- clientInfo: { name: "mcp-compliance", version: TOOL_VERSION }
1714
- });
1720
+ nextId,
1721
+ buildHeaders2(),
1722
+ startupTimeout
1723
+ );
1715
1724
  const result = initRes?.body?.result;
1716
1725
  if (result) {
1717
1726
  serverInfo.protocolVersion = result.protocolVersion || null;
@@ -1730,8 +1739,14 @@ async function runComplianceSuite(target, options = {}) {
1730
1739
  }
1731
1740
  } catch {
1732
1741
  }
1742
+ const initElapsed = Date.now() - initStart;
1743
+ if (initRes && initElapsed > timeout) {
1744
+ warnings.push(
1745
+ `Initialize handshake took ${initElapsed}ms \u2014 longer than --timeout ${timeout}ms. Per-test requests use --timeout, so slow servers may flake. Consider raising --timeout.`
1746
+ );
1747
+ }
1733
1748
  try {
1734
- await mcpNotification(backendUrl, "notifications/initialized", void 0, buildHeaders2(), timeout);
1749
+ await mcpNotification(backendUrl, "notifications/initialized", void 0, buildHeaders2(), startupTimeout);
1735
1750
  } catch {
1736
1751
  }
1737
1752
  const hasTools = !!serverInfo.capabilities.tools;
package/dist/index.js CHANGED
@@ -559,6 +559,7 @@ function validate(raw, source) {
559
559
  const allowed = /* @__PURE__ */ new Set([
560
560
  "target",
561
561
  "timeout",
562
+ "startupTimeout",
562
563
  "preflightTimeout",
563
564
  "retries",
564
565
  "only",
@@ -1803,6 +1804,7 @@ async function runComplianceSuite(target, options = {}) {
1803
1804
  }
1804
1805
  const nextId = createIdCounter(1e3);
1805
1806
  const timeout = options.timeout || 15e3;
1807
+ const startupTimeout = options.startupTimeout ?? Math.max(timeout, 6e4);
1806
1808
  const retries = options.retries || 0;
1807
1809
  let sessionId = null;
1808
1810
  let negotiatedProtocolVersion = null;
@@ -2058,16 +2060,24 @@ async function runComplianceSuite(target, options = {}) {
2058
2060
  }
2059
2061
  );
2060
2062
  let initRes = null;
2063
+ const initStart = Date.now();
2061
2064
  try {
2062
- initRes = await rpc("initialize", {
2063
- protocolVersion: SPEC_VERSION,
2064
- capabilities: {
2065
- sampling: {},
2066
- roots: { listChanged: true },
2067
- elicitation: {}
2065
+ initRes = await mcpRequest(
2066
+ backendUrl,
2067
+ "initialize",
2068
+ {
2069
+ protocolVersion: SPEC_VERSION,
2070
+ capabilities: {
2071
+ sampling: {},
2072
+ roots: { listChanged: true },
2073
+ elicitation: {}
2074
+ },
2075
+ clientInfo: { name: "mcp-compliance", version: TOOL_VERSION }
2068
2076
  },
2069
- clientInfo: { name: "mcp-compliance", version: TOOL_VERSION }
2070
- });
2077
+ nextId,
2078
+ buildHeaders2(),
2079
+ startupTimeout
2080
+ );
2071
2081
  const result = initRes?.body?.result;
2072
2082
  if (result) {
2073
2083
  serverInfo.protocolVersion = result.protocolVersion || null;
@@ -2086,8 +2096,14 @@ async function runComplianceSuite(target, options = {}) {
2086
2096
  }
2087
2097
  } catch {
2088
2098
  }
2099
+ const initElapsed = Date.now() - initStart;
2100
+ if (initRes && initElapsed > timeout) {
2101
+ warnings.push(
2102
+ `Initialize handshake took ${initElapsed}ms \u2014 longer than --timeout ${timeout}ms. Per-test requests use --timeout, so slow servers may flake. Consider raising --timeout.`
2103
+ );
2104
+ }
2089
2105
  try {
2090
- await mcpNotification(backendUrl, "notifications/initialized", void 0, buildHeaders2(), timeout);
2106
+ await mcpNotification(backendUrl, "notifications/initialized", void 0, buildHeaders2(), startupTimeout);
2091
2107
  } catch {
2092
2108
  }
2093
2109
  const hasTools = !!serverInfo.capabilities.tools;
@@ -4756,18 +4772,25 @@ var CATEGORY_LABELS = {
4756
4772
  security: "Security"
4757
4773
  };
4758
4774
  var CATEGORY_ORDER = ["transport", "lifecycle", "tools", "resources", "prompts", "errors", "schema", "security"];
4775
+ var GRADE_ART = {
4776
+ A: [" \u2588\u2588\u2588\u2588\u2588\u2557 ", "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557", "\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551", "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551", "\u2588\u2588\u2551 \u2588\u2588\u2551", "\u255A\u2550\u255D \u255A\u2550\u255D"],
4777
+ B: ["\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ", "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557", "\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D", "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557", "\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D", "\u255A\u2550\u2550\u2550\u2550\u2550\u255D "],
4778
+ C: [" \u2588\u2588\u2588\u2588\u2588\u2588\u2557", "\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D", "\u2588\u2588\u2551 ", "\u2588\u2588\u2551 ", "\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557", " \u255A\u2550\u2550\u2550\u2550\u2550\u255D"],
4779
+ D: ["\u2588\u2588\u2588\u2588\u2588\u2588\u2557 ", "\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557", "\u2588\u2588\u2551 \u2588\u2588\u2551", "\u2588\u2588\u2551 \u2588\u2588\u2551", "\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D", "\u255A\u2550\u2550\u2550\u2550\u2550\u255D "],
4780
+ F: ["\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557", "\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D", "\u2588\u2588\u2588\u2588\u2588\u2557 ", "\u2588\u2588\u2554\u2550\u2550\u255D ", "\u2588\u2588\u2551 ", "\u255A\u2550\u255D "]
4781
+ };
4759
4782
  function gradeColor(grade) {
4760
4783
  switch (grade) {
4761
4784
  case "A":
4762
- return chalk.green.bold(grade);
4785
+ return (s) => chalk.green.bold(s);
4763
4786
  case "B":
4764
- return chalk.greenBright.bold(grade);
4787
+ return (s) => chalk.greenBright.bold(s);
4765
4788
  case "C":
4766
- return chalk.yellow.bold(grade);
4789
+ return (s) => chalk.yellow.bold(s);
4767
4790
  case "D":
4768
- return chalk.rgb(255, 165, 0).bold(grade);
4791
+ return (s) => chalk.rgb(255, 165, 0).bold(s);
4769
4792
  case "F":
4770
- return chalk.red.bold(grade);
4793
+ return (s) => chalk.red.bold(s);
4771
4794
  }
4772
4795
  }
4773
4796
  function overallColor(overall) {
@@ -4782,104 +4805,136 @@ function overallColor(overall) {
4782
4805
  return overall;
4783
4806
  }
4784
4807
  }
4785
- function testLine(t) {
4786
- const icon = t.passed ? chalk.green(" PASS") : chalk.red(" FAIL");
4787
- const req = t.required ? chalk.dim(" (required)") : "";
4788
- const dur = chalk.dim(` ${t.durationMs}ms`);
4789
- let line = `${icon} ${t.name}${req}${dur}
4790
- ${chalk.dim(` ${t.details}`)}`;
4791
- if (!t.passed) {
4792
- const def = TEST_DEFINITIONS.find((d) => d.id === t.id);
4793
- if (def?.recommendation) {
4794
- line += `
4795
- ${chalk.cyan(` Fix: ${def.recommendation}`)}`;
4796
- }
4797
- }
4798
- return line;
4808
+ function makeBar(passed, total, width = 24) {
4809
+ if (total === 0) return { filled: "", rest: "\u2500".repeat(width) };
4810
+ const n = Math.max(0, Math.min(width, Math.round(passed / total * width)));
4811
+ return { filled: "\u2588".repeat(n), rest: "\u2591".repeat(width - n) };
4812
+ }
4813
+ function barColor(passed, total) {
4814
+ if (total === 0) return (s) => chalk.dim(s);
4815
+ const pct2 = passed / total;
4816
+ if (pct2 >= 1) return (s) => chalk.green(s);
4817
+ if (pct2 >= 0.85) return (s) => chalk.greenBright(s);
4818
+ if (pct2 >= 0.6) return (s) => chalk.yellow(s);
4819
+ return (s) => chalk.red(s);
4820
+ }
4821
+ function padRight(s, n) {
4822
+ return s.length >= n ? s : s + " ".repeat(n - s.length);
4799
4823
  }
4824
+ function padLeft(s, n) {
4825
+ return s.length >= n ? s : " ".repeat(n - s.length) + s;
4826
+ }
4827
+ var RULE = "\u2500".repeat(62);
4828
+ var HEAVY_RULE = "\u2550".repeat(62);
4800
4829
  function formatTerminal(report) {
4801
- const lines = [];
4802
- lines.push("");
4803
- lines.push(chalk.bold("MCP Compliance Report"));
4804
- lines.push(chalk.dim(`Spec: ${report.specVersion} | Tool: v${report.toolVersion} | ${report.timestamp}`));
4805
- lines.push(chalk.dim(`URL: ${report.url}`));
4830
+ const out = [];
4831
+ const color = gradeColor(report.grade);
4832
+ const art = GRADE_ART[report.grade];
4833
+ out.push("");
4834
+ out.push(chalk.bold(" MCP COMPLIANCE REPORT"));
4835
+ out.push(chalk.dim(` ${HEAVY_RULE}`));
4806
4836
  if (report.serverInfo.name) {
4807
- lines.push(
4808
- chalk.dim(
4809
- `Server: ${report.serverInfo.name} v${report.serverInfo.version || "?"} (protocol ${report.serverInfo.protocolVersion || "?"})`
4810
- )
4811
- );
4837
+ const v = report.serverInfo.version ? ` v${report.serverInfo.version}` : "";
4838
+ const proto = report.serverInfo.protocolVersion ? ` (protocol ${report.serverInfo.protocolVersion})` : "";
4839
+ out.push(chalk.dim(` Server: ${report.serverInfo.name}${v}${proto}`));
4812
4840
  }
4813
- lines.push("");
4814
- lines.push(
4815
- ` Grade: ${gradeColor(report.grade)} Score: ${chalk.bold(String(report.score))}% Overall: ${overallColor(report.overall)}`
4816
- );
4817
- lines.push(
4818
- ` Tests: ${chalk.green(String(report.summary.passed))} passed / ${chalk.red(String(report.summary.failed))} failed / ${report.summary.total} total`
4819
- );
4820
- lines.push(` Required: ${report.summary.requiredPassed}/${report.summary.required} passed`);
4821
- const grouped = {};
4822
- for (const t of report.tests) {
4823
- if (!grouped[t.category]) grouped[t.category] = [];
4824
- grouped[t.category].push(t);
4841
+ out.push(chalk.dim(` Target: ${report.url}`));
4842
+ out.push(chalk.dim(` Spec: ${report.specVersion} \xB7 Tool v${report.toolVersion} \xB7 ${report.timestamp}`));
4843
+ out.push("");
4844
+ const reqOk = report.summary.requiredPassed === report.summary.required;
4845
+ const infoRows = [
4846
+ "",
4847
+ "",
4848
+ `${chalk.bold("GRADE")} ${color(report.grade)} ${chalk.bold(`${report.score}%`)}`,
4849
+ `${chalk.dim("Overall ")}${overallColor(report.overall)}`,
4850
+ `${chalk.dim("Tests ")}${chalk.green(String(report.summary.passed))}${chalk.dim("/")}${report.summary.total}${report.summary.failed > 0 ? chalk.dim(" (") + chalk.red(`${report.summary.failed} failed`) + chalk.dim(")") : ""}`,
4851
+ `${chalk.dim("Required ")}${reqOk ? chalk.green(`${report.summary.requiredPassed}/${report.summary.required} \u2713`) : chalk.red(`${report.summary.requiredPassed}/${report.summary.required}`)}`
4852
+ ];
4853
+ for (let i = 0; i < 6; i++) {
4854
+ out.push(` ${color(art[i])} ${infoRows[i] || ""}`);
4825
4855
  }
4826
- for (const cat of CATEGORY_ORDER) {
4827
- const catTests = grouped[cat];
4828
- if (!catTests || catTests.length === 0) continue;
4829
- const catStats = report.categories[cat];
4856
+ out.push("");
4857
+ out.push(chalk.bold(" CATEGORY BREAKDOWN"));
4858
+ out.push(chalk.dim(` ${RULE}`));
4859
+ const cats = CATEGORY_ORDER.filter((c) => report.categories[c] && report.categories[c].total > 0);
4860
+ const maxLabel = Math.max(...cats.map((c) => (CATEGORY_LABELS[c] || c).length));
4861
+ for (const cat of cats) {
4862
+ const stats = report.categories[cat];
4830
4863
  const label = CATEGORY_LABELS[cat] || cat;
4831
- const catColor = catStats && catStats.passed === catStats.total ? chalk.green : chalk.yellow;
4832
- lines.push("");
4833
- lines.push(catColor(` ${label} (${catStats?.passed || 0}/${catStats?.total || 0})`));
4834
- for (const t of catTests) {
4835
- lines.push(testLine(t));
4836
- }
4837
- }
4838
- const caps = report.serverInfo.capabilities;
4839
- const declared = Object.keys(caps).filter((k) => caps[k] !== void 0);
4840
- if (declared.length > 0) {
4841
- lines.push("");
4842
- lines.push(chalk.dim(` Capabilities: ${declared.join(", ")}`));
4843
- }
4844
- if (report.toolCount > 0) {
4845
- lines.push(
4846
- chalk.dim(
4847
- ` Tools (${report.toolCount}): ${report.toolNames.slice(0, 10).join(", ")}${report.toolCount > 10 ? "..." : ""}`
4848
- )
4849
- );
4850
- }
4851
- if (report.resourceCount > 0) {
4852
- lines.push(
4853
- chalk.dim(
4854
- ` Resources (${report.resourceCount}): ${report.resourceNames.slice(0, 10).join(", ")}${report.resourceCount > 10 ? "..." : ""}`
4855
- )
4864
+ const { filled, rest } = makeBar(stats.passed, stats.total, 24);
4865
+ const colorFn = barColor(stats.passed, stats.total);
4866
+ const pct2 = stats.total === 0 ? 0 : Math.round(stats.passed / stats.total * 100);
4867
+ const ratio = `${stats.passed}/${stats.total}`;
4868
+ out.push(
4869
+ ` ${padRight(label, maxLabel)} ${colorFn(filled)}${chalk.dim(rest)} ${padLeft(ratio, 7)} ${padLeft(`${pct2}%`, 4)}`
4856
4870
  );
4857
4871
  }
4858
- if (report.promptCount > 0) {
4859
- lines.push(
4860
- chalk.dim(
4861
- ` Prompts (${report.promptCount}): ${report.promptNames.slice(0, 10).join(", ")}${report.promptCount > 10 ? "..." : ""}`
4862
- )
4863
- );
4872
+ out.push("");
4873
+ const failed = report.tests.filter((t) => !t.passed);
4874
+ if (failed.length > 0) {
4875
+ out.push(chalk.bold.red(` FAILED TESTS (${failed.length})`));
4876
+ out.push(chalk.dim(` ${RULE}`));
4877
+ for (const t of failed) {
4878
+ const req = t.required ? chalk.red("required") : chalk.dim("optional");
4879
+ out.push(
4880
+ ` ${chalk.red("\u2717")} ${chalk.bold(t.name)} ${chalk.dim(`[${t.id}]`)} ${req} ${chalk.dim(`${t.durationMs}ms`)}`
4881
+ );
4882
+ out.push(` ${t.details}`);
4883
+ const def = TEST_DEFINITIONS.find((d) => d.id === t.id);
4884
+ if (def?.recommendation) {
4885
+ out.push(` ${chalk.cyan(`\u2192 ${def.recommendation}`)}`);
4886
+ }
4887
+ if (t.specRef) {
4888
+ out.push(chalk.dim(` spec: ${t.specRef}`));
4889
+ }
4890
+ out.push("");
4891
+ }
4892
+ } else {
4893
+ out.push(` ${chalk.green.bold("\u2713 All tests passed")}`);
4894
+ out.push("");
4864
4895
  }
4865
4896
  if (report.warnings.length > 0) {
4866
- lines.push("");
4867
- lines.push(chalk.yellow(` Warnings (${report.warnings.length}):`));
4897
+ out.push(chalk.bold.yellow(` WARNINGS (${report.warnings.length})`));
4898
+ out.push(chalk.dim(` ${RULE}`));
4868
4899
  for (const w of report.warnings) {
4869
- lines.push(chalk.yellow(` - ${w}`));
4900
+ out.push(` ${chalk.yellow("!")} ${w}`);
4870
4901
  }
4902
+ out.push("");
4903
+ }
4904
+ const caps = report.serverInfo.capabilities;
4905
+ const declared = Object.keys(caps).filter((k) => caps[k] !== void 0);
4906
+ const hasContext = declared.length > 0 || report.toolCount > 0 || report.resourceCount > 0 || report.promptCount > 0;
4907
+ if (hasContext) {
4908
+ out.push(chalk.bold(" SERVER CONTEXT"));
4909
+ out.push(chalk.dim(` ${RULE}`));
4910
+ if (declared.length > 0) {
4911
+ out.push(chalk.dim(` Capabilities: ${declared.join(", ")}`));
4912
+ }
4913
+ if (report.toolCount > 0) {
4914
+ const more = report.toolCount > 10 ? ", ..." : "";
4915
+ out.push(chalk.dim(` Tools (${report.toolCount}): ${report.toolNames.slice(0, 10).join(", ")}${more}`));
4916
+ }
4917
+ if (report.resourceCount > 0) {
4918
+ const more = report.resourceCount > 10 ? ", ..." : "";
4919
+ out.push(
4920
+ chalk.dim(` Resources (${report.resourceCount}): ${report.resourceNames.slice(0, 10).join(", ")}${more}`)
4921
+ );
4922
+ }
4923
+ if (report.promptCount > 0) {
4924
+ const more = report.promptCount > 10 ? ", ..." : "";
4925
+ out.push(chalk.dim(` Prompts (${report.promptCount}): ${report.promptNames.slice(0, 10).join(", ")}${more}`));
4926
+ }
4927
+ out.push("");
4871
4928
  }
4872
- lines.push("");
4873
4929
  if (report.url.startsWith("stdio:")) {
4874
- lines.push(
4875
- chalk.dim(" Badge: stdio servers can't be published. Use `--output badge.svg` for a local badge image.")
4930
+ out.push(
4931
+ chalk.dim(" Badge: stdio targets aren't published. Run with --output badge.svg for a local badge image.")
4876
4932
  );
4877
4933
  } else {
4878
- lines.push(chalk.dim(" Badge markdown:"));
4879
- lines.push(` ${report.badge.markdown}`);
4934
+ out.push(chalk.dim(` Badge: ${report.badge.markdown}`));
4880
4935
  }
4881
- lines.push("");
4882
- return lines.join("\n");
4936
+ out.push("");
4937
+ return out.join("\n");
4883
4938
  }
4884
4939
  function formatJson(report) {
4885
4940
  return JSON.stringify(report, null, 2);
@@ -5255,6 +5310,16 @@ function parsePositiveInt(value, name, min = 0) {
5255
5310
  function parseList(value) {
5256
5311
  return value.split(",").map((s) => s.trim()).filter(Boolean);
5257
5312
  }
5313
+ function ttyHint(format) {
5314
+ if (!process.stdout.isTTY) return;
5315
+ process.stderr.write(
5316
+ chalk2.dim(
5317
+ ` [tip] --format=${format} is machine-readable. Redirect with \`> report.${format === "sarif" ? "sarif" : format}\` or drop --format for the human report.
5318
+
5319
+ `
5320
+ )
5321
+ );
5322
+ }
5258
5323
  function parseEnvVar(value, prev) {
5259
5324
  const idx = value.indexOf("=");
5260
5325
  if (idx === -1) throw new Error(`Invalid env var: "${value}" (expected "KEY=VALUE")`);
@@ -5404,8 +5469,11 @@ program.command("test").description("Run the full compliance test suite against
5404
5469
  {}
5405
5470
  ).option("--auth <token>", 'Shorthand for -H "Authorization: <token>" (HTTP only)').option("-E, --env <var>", 'Set env var for stdio command ("KEY=VALUE", repeatable)', parseEnvVar, {}).option("--env-file <path>", "Load env vars from file (KEY=VALUE per line, stdio only)").option("--cwd <dir>", "Working directory for stdio command").option(
5406
5471
  "--timeout <ms>",
5407
- "Request timeout in milliseconds (bump to 30000+ for stdio servers with slow startup)",
5472
+ "Per-request timeout in milliseconds (applies to every test request after the initial handshake)",
5408
5473
  "15000"
5474
+ ).option(
5475
+ "--startup-timeout <ms>",
5476
+ "Deadline for the initial initialize handshake (default: max(--timeout, 60000); covers cold `npx` cache fetches before a stdio server starts)"
5409
5477
  ).option("--no-color", "Disable colored output (also honors NO_COLOR env var)").option("--watch", "Re-run tests when files in the cwd change (stdio targets only)").option(
5410
5478
  "--concurrency <n>",
5411
5479
  "Max parallel-safe tests in flight (default 1; see docs/PERFORMANCE.md before raising)",
@@ -5467,6 +5535,7 @@ Testing ${describeTarget(transportTarget)}...
5467
5535
  }
5468
5536
  const report2 = await runComplianceSuite(transportTarget, {
5469
5537
  timeout: parsePositiveInt(opts.timeout, "--timeout", 1),
5538
+ startupTimeout: opts.startupTimeout ? parsePositiveInt(opts.startupTimeout, "--startup-timeout", 1) : config?.startupTimeout,
5470
5539
  preflightTimeout: opts.preflightTimeout ? parsePositiveInt(opts.preflightTimeout, "--preflight-timeout", 1) : config?.preflightTimeout,
5471
5540
  retries: parsePositiveInt(opts.retries, "--retries"),
5472
5541
  concurrency: parsePositiveInt(opts.concurrency, "--concurrency", 1),
@@ -5483,14 +5552,17 @@ Testing ${describeTarget(transportTarget)}...
5483
5552
  console.log("");
5484
5553
  }
5485
5554
  if (opts.format === "json") {
5555
+ ttyHint("json");
5486
5556
  console.log(formatJson(report2));
5487
5557
  } else if (opts.format === "sarif") {
5558
+ ttyHint("sarif");
5488
5559
  console.log(formatSarif(report2));
5489
5560
  } else if (opts.format === "github") {
5490
5561
  console.log(formatGithub(report2));
5491
5562
  } else if (opts.format === "markdown") {
5492
5563
  console.log(formatMarkdown(report2));
5493
5564
  } else if (opts.format === "html") {
5565
+ ttyHint("html");
5494
5566
  console.log(formatHtml(report2));
5495
5567
  } else {
5496
5568
  console.log(formatTerminal(report2));
@@ -2,7 +2,7 @@ import {
2
2
  SPEC_BASE,
3
3
  TEST_DEFINITIONS,
4
4
  runComplianceSuite
5
- } from "../chunk-X5CVUDPW.js";
5
+ } from "../chunk-6PF56RRO.js";
6
6
 
7
7
  // src/mcp/server.ts
8
8
  import { existsSync, readFileSync, realpathSync } from "fs";
package/dist/runner.d.ts CHANGED
@@ -180,6 +180,18 @@ interface RunOptions {
180
180
  headers?: Record<string, string>;
181
181
  /** Request timeout in milliseconds (default: 15000) */
182
182
  timeout?: number;
183
+ /**
184
+ * Deadline for the initial `initialize` handshake + `initialized`
185
+ * notification, in milliseconds. Kept separate from `timeout` because
186
+ * cold-started stdio servers — especially `npx @pkg ...` targets where
187
+ * npm has to resolve and fetch the package before the MCP server
188
+ * starts — can take tens of seconds to produce their first response,
189
+ * while steady-state requests complete in milliseconds. Default is
190
+ * `max(timeout, 60000)` so users who bump `--timeout` keep that value
191
+ * and users on defaults get 60s for startup. Does not apply to any
192
+ * per-test requests past the handshake.
193
+ */
194
+ startupTimeout?: number;
183
195
  /** Number of retries for failed tests (default: 0) */
184
196
  retries?: number;
185
197
  /** Only run tests matching these category names or test IDs */
package/dist/runner.js CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  previewTests,
11
11
  runComplianceSuite,
12
12
  urlHash
13
- } from "./chunk-X5CVUDPW.js";
13
+ } from "./chunk-6PF56RRO.js";
14
14
  export {
15
15
  SPEC_BASE,
16
16
  SPEC_VERSION,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yawlabs/mcp-compliance",
3
- "version": "0.13.3",
3
+ "version": "0.13.5",
4
4
  "description": "CLI tool and MCP server that tests MCP servers for spec compliance",
5
5
  "license": "MIT",
6
6
  "author": "Yaw Labs <contact@yaw.sh> (https://yaw.sh)",