@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.
- package/dist/{chunk-X5CVUDPW.js → chunk-6PF56RRO.js} +24 -9
- package/dist/index.js +168 -96
- package/dist/mcp/server.js +1 -1
- package/dist/runner.d.ts +12 -0
- package/dist/runner.js +1 -1
- package/package.json +1 -1
|
@@ -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
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
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
|
-
|
|
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(),
|
|
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
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
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
|
-
|
|
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(),
|
|
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(
|
|
4785
|
+
return (s) => chalk.green.bold(s);
|
|
4763
4786
|
case "B":
|
|
4764
|
-
return chalk.greenBright.bold(
|
|
4787
|
+
return (s) => chalk.greenBright.bold(s);
|
|
4765
4788
|
case "C":
|
|
4766
|
-
return chalk.yellow.bold(
|
|
4789
|
+
return (s) => chalk.yellow.bold(s);
|
|
4767
4790
|
case "D":
|
|
4768
|
-
return chalk.rgb(255, 165, 0).bold(
|
|
4791
|
+
return (s) => chalk.rgb(255, 165, 0).bold(s);
|
|
4769
4792
|
case "F":
|
|
4770
|
-
return chalk.red.bold(
|
|
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
|
|
4786
|
-
|
|
4787
|
-
const
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
if (
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
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
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
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
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
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
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
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
|
-
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
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
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
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
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
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
|
-
|
|
4867
|
-
|
|
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
|
-
|
|
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
|
-
|
|
4875
|
-
chalk.dim(" Badge:
|
|
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
|
-
|
|
4879
|
-
lines.push(` ${report.badge.markdown}`);
|
|
4934
|
+
out.push(chalk.dim(` Badge: ${report.badge.markdown}`));
|
|
4880
4935
|
}
|
|
4881
|
-
|
|
4882
|
-
return
|
|
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
|
-
"
|
|
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));
|
package/dist/mcp/server.js
CHANGED
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
package/package.json
CHANGED