codex-plugin-doctor 1.50.0 → 1.52.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.
Files changed (49) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +17 -6
  3. package/dist/core/bounded-http-client.d.ts +23 -0
  4. package/dist/core/bounded-http-client.js +240 -0
  5. package/dist/core/mcp-conformance.d.ts +2 -0
  6. package/dist/core/mcp-conformance.js +215 -0
  7. package/dist/core/output-contract.js +104 -5
  8. package/dist/core/release-check.d.ts +2 -0
  9. package/dist/core/release-check.js +2 -0
  10. package/dist/core/release-evidence.d.ts +2 -0
  11. package/dist/core/release-evidence.js +111 -7
  12. package/dist/core/remote-mcp-probe.d.ts +16 -0
  13. package/dist/core/remote-mcp-probe.js +226 -0
  14. package/dist/core/remote-network-policy.d.ts +24 -0
  15. package/dist/core/remote-network-policy.js +125 -0
  16. package/dist/core/remote-oauth-readiness.d.ts +12 -0
  17. package/dist/core/remote-oauth-readiness.js +295 -0
  18. package/dist/core/remote-url-policy.d.ts +7 -0
  19. package/dist/core/remote-url-policy.js +49 -0
  20. package/dist/core/runtime-plan.d.ts +3 -0
  21. package/dist/core/runtime-plan.js +36 -2
  22. package/dist/core/runtime-policy.js +5 -1
  23. package/dist/core/runtime-probe.d.ts +11 -2
  24. package/dist/core/runtime-probe.js +375 -70
  25. package/dist/core/runtime-transcript.js +13 -3
  26. package/dist/core/validate-plugin.js +32 -1
  27. package/dist/domain/types.d.ts +47 -0
  28. package/dist/index.d.ts +4 -0
  29. package/dist/index.js +4 -0
  30. package/dist/mcp/generic-mcp-doctor.d.ts +10 -2
  31. package/dist/mcp/generic-mcp-doctor.js +44 -13
  32. package/dist/reporting/render-markdown-report.js +44 -13
  33. package/dist/reporting/render-text-report.js +39 -22
  34. package/dist/rules/rule-catalog.js +253 -1
  35. package/dist/run-cli.js +137 -9
  36. package/dist/security/security-audit.js +34 -7
  37. package/examples/README.md +94 -94
  38. package/examples/codex-doctor-generic-mcp/.mcp.json +10 -10
  39. package/examples/codex-doctor-generic-mcp/server.js +1 -1
  40. package/examples/codex-doctor-risky/.codex-plugin/plugin.json +7 -7
  41. package/examples/codex-doctor-risky/.mcp.json +12 -12
  42. package/examples/codex-doctor-risky/mock-server.js +1 -1
  43. package/examples/codex-doctor-runtime/.codex-plugin/plugin.json +8 -8
  44. package/examples/codex-doctor-runtime/.mcp.json +9 -9
  45. package/examples/codex-doctor-runtime/mock-server.js +237 -237
  46. package/examples/codex-doctor-runtime/skills/context-check/SKILL.md +7 -7
  47. package/examples/codex-doctor-starter/.codex-plugin/plugin.json +7 -7
  48. package/examples/codex-doctor-starter/skills/repo-scan/SKILL.md +7 -7
  49. package/package.json +1 -1
@@ -41,6 +41,8 @@ export interface BaselineSummary {
41
41
  }
42
42
  export interface CheckOptions {
43
43
  runtime?: boolean;
44
+ allowNetwork?: boolean;
45
+ allowLocalNetwork?: boolean;
44
46
  runtimeTranscript?: (line: string) => void;
45
47
  runtimeStartupTimeoutMs?: number;
46
48
  runtimeSandbox?: RuntimeSandboxMode;
@@ -90,6 +92,51 @@ export interface RuntimeScorecard {
90
92
  resourceTemplatesList: RuntimeCapabilityStatus;
91
93
  promptsList: RuntimeCapabilityStatus;
92
94
  promptGet: RuntimeCapabilityStatus;
95
+ conformance?: RuntimeConformanceScorecard;
96
+ remote?: RemoteRuntimeScorecard;
97
+ }
98
+ export interface RemoteRuntimeScorecard {
99
+ transport: RuntimeCapabilityStatus;
100
+ networkSafety: RuntimeCapabilityStatus;
101
+ initialize: RuntimeCapabilityStatus;
102
+ contentType: RuntimeCapabilityStatus;
103
+ session: "absent" | "present-valid" | "present-invalid";
104
+ protocolHeaders: RuntimeCapabilityStatus;
105
+ authorization: RuntimeCapabilityStatus;
106
+ overall: "pass" | "warn" | "fail" | "skipped";
107
+ }
108
+ export type McpConformanceProfile = "legacy" | "2025-11-25" | "future-compatible";
109
+ export interface RuntimeConformanceScorecard {
110
+ protocolVersion: string | null;
111
+ profile: McpConformanceProfile | null;
112
+ capabilityConsistency: RuntimeCapabilityStatus;
113
+ taskDeclarations: RuntimeCapabilityStatus;
114
+ tasksList: RuntimeCapabilityStatus;
115
+ schemaDialect: RuntimeCapabilityStatus;
116
+ overall: "pass" | "warn" | "fail" | "skipped";
117
+ }
118
+ export interface McpToolObservation {
119
+ name: string;
120
+ inputSchema: unknown;
121
+ outputSchema?: unknown;
122
+ execution?: unknown;
123
+ }
124
+ export interface TasksListObservation {
125
+ status: "pass" | "fail" | "skipped";
126
+ itemCount: number;
127
+ pageCount: number;
128
+ failure?: "timeout" | "invalid";
129
+ }
130
+ export interface McpConformanceObservation {
131
+ protocolVersion: string;
132
+ capabilities: unknown;
133
+ tools: McpToolObservation[];
134
+ tasksList?: TasksListObservation;
135
+ skipToolDerivedChecks?: boolean;
136
+ }
137
+ export interface McpConformanceResult {
138
+ findings: Finding[];
139
+ scorecard: RuntimeConformanceScorecard;
93
140
  }
94
141
  export interface RuntimeProbeResult {
95
142
  findings: Finding[];
package/dist/index.d.ts CHANGED
@@ -18,6 +18,10 @@ export { buildDoctorExportBundleFromAnalysis, buildDoctorRecommendationsFromAnal
18
18
  export { buildDoctorPerformanceReport, renderDoctorPerformanceReport, renderDoctorPerformanceReportJson, type BuildDoctorPerformanceReportOptions, type DoctorPerformanceReport, type DoctorPerformanceStage, type DoctorPerformanceStageName } from "./core/performance-report.js";
19
19
  export { buildDoctorRuntimePlan, evaluateRuntimeApproval, renderDoctorRuntimePlan, renderDoctorRuntimePlanMarkdown, renderDoctorRuntimePlanJson, runtimeApprovalPassed, type DoctorRuntimePlan, type RuntimeApprovalReport, type RuntimePlanServer } from "./core/runtime-plan.js";
20
20
  export { buildDoctorRuntimePolicyReport, renderDoctorRuntimePolicy, renderDoctorRuntimePolicyJson, type DoctorRuntimePolicyReport, type RuntimePolicyDecision, type RuntimePolicyRecommendation } from "./core/runtime-policy.js";
21
+ export { inspectRemoteMcpUrl, type RemoteUrlInspection } from "./core/remote-url-policy.js";
22
+ export { resolveRemoteTarget, RemoteNetworkPolicyError, type ResolvedRemoteTarget, type ResolveRemoteTargetOptions } from "./core/remote-network-policy.js";
23
+ export { probeRemoteMcpServer, type RemoteMcpProbeOptions, type RemoteMcpProbeResult } from "./core/remote-mcp-probe.js";
24
+ export { checkRemoteOAuthReadiness, type RemoteOAuthReadinessOptions, type RemoteOAuthReadinessResult } from "./core/remote-oauth-readiness.js";
21
25
  export { buildDoctorReviewBundle, diffDoctorReviewBundles, renderDoctorReviewBundle, renderDoctorReviewBundleDiff, renderDoctorReviewBundleDiffJson, renderDoctorReviewBundleJson, renderDoctorReviewBundleVerification, renderDoctorReviewBundleVerificationJson, verifyDoctorReviewBundle, type BuildDoctorReviewBundleOptions, type DoctorReviewBundle, type DoctorReviewBundleDiffChange, type DoctorReviewBundleDiffReport, type DoctorReviewBundleDiffSnapshot, type DoctorReviewBundleManifest, type DoctorReviewBundleVerificationReport } from "./core/review-bundle.js";
22
26
  export { buildDoctorReleaseEvidenceAssetReport, buildDoctorReleaseEvidenceReport, renderDoctorReleaseEvidenceAsset, renderDoctorReleaseEvidenceAssetJson, renderDoctorReleaseEvidence, renderDoctorReleaseEvidenceJson, renderDoctorReleaseEvidenceVerification, renderDoctorReleaseEvidenceVerificationJson, verifyDoctorReleaseEvidence, type BuildDoctorReleaseEvidenceOptions, type DoctorReleaseEvidenceAssetReport, type DoctorReleaseEvidenceGitMetadata, type DoctorReleaseEvidencePackageMetadata, type DoctorReleaseEvidenceReport, type DoctorReleaseEvidenceVerificationReport } from "./core/release-evidence.js";
23
27
  export { buildDoctorNpmPackageReport, renderDoctorNpmPackageReport, renderDoctorNpmPackageReportJson, type BuildDoctorNpmPackageReportOptions, type DoctorNpmPackageReport } from "./core/npm-package-doctor.js";
package/dist/index.js CHANGED
@@ -18,6 +18,10 @@ export { buildDoctorExportBundleFromAnalysis, buildDoctorRecommendationsFromAnal
18
18
  export { buildDoctorPerformanceReport, renderDoctorPerformanceReport, renderDoctorPerformanceReportJson } from "./core/performance-report.js";
19
19
  export { buildDoctorRuntimePlan, evaluateRuntimeApproval, renderDoctorRuntimePlan, renderDoctorRuntimePlanMarkdown, renderDoctorRuntimePlanJson, runtimeApprovalPassed } from "./core/runtime-plan.js";
20
20
  export { buildDoctorRuntimePolicyReport, renderDoctorRuntimePolicy, renderDoctorRuntimePolicyJson } from "./core/runtime-policy.js";
21
+ export { inspectRemoteMcpUrl } from "./core/remote-url-policy.js";
22
+ export { resolveRemoteTarget, RemoteNetworkPolicyError } from "./core/remote-network-policy.js";
23
+ export { probeRemoteMcpServer } from "./core/remote-mcp-probe.js";
24
+ export { checkRemoteOAuthReadiness } from "./core/remote-oauth-readiness.js";
21
25
  export { buildDoctorReviewBundle, diffDoctorReviewBundles, renderDoctorReviewBundle, renderDoctorReviewBundleDiff, renderDoctorReviewBundleDiffJson, renderDoctorReviewBundleJson, renderDoctorReviewBundleVerification, renderDoctorReviewBundleVerificationJson, verifyDoctorReviewBundle } from "./core/review-bundle.js";
22
26
  export { buildDoctorReleaseEvidenceAssetReport, buildDoctorReleaseEvidenceReport, renderDoctorReleaseEvidenceAsset, renderDoctorReleaseEvidenceAssetJson, renderDoctorReleaseEvidence, renderDoctorReleaseEvidenceJson, renderDoctorReleaseEvidenceVerification, renderDoctorReleaseEvidenceVerificationJson, verifyDoctorReleaseEvidence } from "./core/release-evidence.js";
23
27
  export { buildDoctorNpmPackageReport, renderDoctorNpmPackageReport, renderDoctorNpmPackageReportJson } from "./core/npm-package-doctor.js";
@@ -1,5 +1,5 @@
1
1
  import { type CompatibilityEnvironment, type CompatibilityMatrix } from "../compatibility/compatibility-matrix.js";
2
- import type { Finding } from "../domain/types.js";
2
+ import type { Finding, RuntimeExecutionEvidence, RuntimeScorecard } from "../domain/types.js";
3
3
  import { type SecurityAudit } from "../security/security-audit.js";
4
4
  export interface GenericMcpDoctorReport {
5
5
  targetPath: string;
@@ -10,7 +10,15 @@ export interface GenericMcpDoctorReport {
10
10
  findings: Finding[];
11
11
  security: SecurityAudit;
12
12
  compatibility: CompatibilityMatrix;
13
+ runtimeScorecard?: RuntimeScorecard;
14
+ runtimeExecution?: RuntimeExecutionEvidence;
13
15
  }
14
- export declare function buildGenericMcpDoctor(targetPath: string, environment?: CompatibilityEnvironment): Promise<GenericMcpDoctorReport>;
16
+ export interface GenericMcpDoctorOptions {
17
+ runtime?: boolean;
18
+ allowNetwork?: boolean;
19
+ allowLocalNetwork?: boolean;
20
+ runtimeStartupTimeoutMs?: number;
21
+ }
22
+ export declare function buildGenericMcpDoctor(targetPath: string, environment?: CompatibilityEnvironment, options?: GenericMcpDoctorOptions): Promise<GenericMcpDoctorReport>;
15
23
  export declare function renderGenericMcpDoctorJson(report: GenericMcpDoctorReport): string;
16
24
  export declare function renderGenericMcpDoctor(report: GenericMcpDoctorReport): string;
@@ -1,7 +1,8 @@
1
- import { stat } from "node:fs/promises";
1
+ import { realpath, stat } from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { buildCompatibilityMatrix, readMcpConfigPath } from "../compatibility/compatibility-matrix.js";
4
4
  import { readJsonFile } from "../core/read-json-file.js";
5
+ import { probeRuntimeConfig } from "../core/runtime-probe.js";
5
6
  import { formatFindingFingerprintLine, withFindingFingerprints } from "../reporting/finding-fingerprint.js";
6
7
  import { auditMcpServerConfig, buildSecurityAuditFromFindings } from "../security/security-audit.js";
7
8
  function buildFinding(severity, id, message, impact, suggestedFix, evidence) {
@@ -63,39 +64,50 @@ function buildStaticMcpFindings(configPath, parsedConfig) {
63
64
  findings.push(buildFinding("fail", "mcp.server.invalid", `The MCP server \`${serverName}\` must be configured as an object.`, "MCP clients cannot interpret a server entry unless it is represented as an object with server options.", `Change the \`${serverName}\` entry in \`${configPath}\` to an object.`, { configPath, serverName, field: "server" }));
64
65
  continue;
65
66
  }
66
- if (typeof serverConfig.command !== "string" && typeof serverConfig.url !== "string") {
67
+ const command = serverConfig.command;
68
+ const url = serverConfig.url;
69
+ if (typeof command !== "string" && typeof url !== "string") {
67
70
  findings.push(buildFinding("fail", "mcp.server.transport.missing", `The MCP server \`${serverName}\` must define either \`command\` or \`url\`.`, "MCP clients need a process command for stdio servers or a URL for remote servers.", `Add either \`command\` or \`url\` to the \`${serverName}\` entry in \`${configPath}\`.`, { configPath, serverName, field: "transport" }));
68
71
  }
72
+ if (typeof command === "string" && typeof url === "string") {
73
+ findings.push(buildFinding("fail", "mcp.server.transport.conflict", `The MCP server \`${serverName}\` must not define both \`command\` and \`url\`.`, "A server with two transports cannot be selected deterministically by MCP clients.", `Keep either \`command\` or \`url\` for the \`${serverName}\` entry in \`${configPath}\`.`, { configPath, serverName, field: "transport" }));
74
+ }
69
75
  }
70
76
  return {
71
77
  serverCount: Object.keys(servers).length,
72
78
  findings
73
79
  };
74
80
  }
75
- function mergeReportStatus(staticFindings, security) {
76
- if (staticFindings.some((finding) => finding.severity === "fail") || security.status === "fail") {
81
+ function mergeReportStatus(findings, security) {
82
+ if (findings.some((finding) => finding.severity === "fail") || security.status === "fail") {
77
83
  return "fail";
78
84
  }
79
- if (staticFindings.some((finding) => finding.severity === "warn") || security.status === "warn") {
85
+ if (findings.some((finding) => finding.severity === "warn") || security.status === "warn") {
80
86
  return "warn";
81
87
  }
82
88
  return "pass";
83
89
  }
84
- export async function buildGenericMcpDoctor(targetPath, environment = {}) {
90
+ export async function buildGenericMcpDoctor(targetPath, environment = {}, options = {}) {
85
91
  const rootPath = path.resolve(targetPath);
86
92
  const compatibility = await buildCompatibilityMatrix(rootPath, environment);
87
93
  const mcpConfigPath = await readMcpConfigPath(rootPath);
94
+ const canonicalRootPath = await realpath(rootPath).catch(() => null);
95
+ const canonicalMcpConfigPath = mcpConfigPath
96
+ ? await realpath(mcpConfigPath).catch(() => null)
97
+ : null;
88
98
  let parsedConfig = null;
89
99
  let staticFindings = [];
90
100
  let serverCount = 0;
91
101
  if (!mcpConfigPath || !(await fileExists(mcpConfigPath))) {
92
102
  staticFindings = buildStaticMcpFindings(null, null).findings;
93
103
  }
94
- else if (!isPathWithinRoot(rootPath, mcpConfigPath)) {
104
+ else if (!canonicalRootPath ||
105
+ !canonicalMcpConfigPath ||
106
+ !isPathWithinRoot(canonicalRootPath, canonicalMcpConfigPath)) {
95
107
  staticFindings = [
96
108
  buildFinding("fail", "mcp.config.path_outside_root", "The MCP config path resolves outside the target root.", "A package that reads MCP configuration outside its root is harder to audit and can depend on unreviewed local files.", "Keep `.mcp.json` or the manifest `mcpServers` reference inside the package root.", {
97
109
  configPath: path.relative(rootPath, mcpConfigPath).replaceAll("\\", "/"),
98
- resolvedPath: mcpConfigPath,
110
+ resolvedPath: canonicalMcpConfigPath ?? mcpConfigPath,
99
111
  field: "configPath"
100
112
  })
101
113
  ];
@@ -117,19 +129,35 @@ export async function buildGenericMcpDoctor(targetPath, environment = {}) {
117
129
  }
118
130
  }
119
131
  const security = buildSecurityAuditFromFindings(rootPath, mcpConfigPath && parsedConfig !== null
120
- ? auditMcpServerConfig(rootPath, parsedConfig)
132
+ ? auditMcpServerConfig(rootPath, parsedConfig, { configPath: mcpConfigPath })
121
133
  : []);
122
- const fingerprintedStaticFindings = withFindingFingerprints(staticFindings, rootPath);
123
- const status = mergeReportStatus(fingerprintedStaticFindings, security);
134
+ const runtimeResult = options.runtime &&
135
+ mcpConfigPath !== null &&
136
+ parsedConfig !== null &&
137
+ canonicalRootPath !== null &&
138
+ canonicalMcpConfigPath !== null &&
139
+ isPathWithinRoot(canonicalRootPath, canonicalMcpConfigPath) &&
140
+ !staticFindings.some((finding) => finding.severity === "fail") &&
141
+ security.status !== "fail"
142
+ ? await probeRuntimeConfig(canonicalRootPath, canonicalMcpConfigPath, {
143
+ startupTimeoutMs: options.runtimeStartupTimeoutMs,
144
+ allowNetwork: options.allowNetwork,
145
+ allowLocalNetwork: options.allowLocalNetwork
146
+ })
147
+ : null;
148
+ const fingerprintedFindings = withFindingFingerprints([...staticFindings, ...(runtimeResult?.findings ?? [])], rootPath);
149
+ const status = mergeReportStatus(fingerprintedFindings, security);
124
150
  return {
125
151
  targetPath: rootPath,
126
152
  status,
127
153
  exitCode: status === "fail" ? 1 : 0,
128
154
  mcpConfigPath,
129
155
  serverCount,
130
- findings: [...fingerprintedStaticFindings, ...security.findings],
156
+ findings: [...fingerprintedFindings, ...security.findings],
131
157
  security,
132
- compatibility
158
+ compatibility,
159
+ ...(runtimeResult ? { runtimeScorecard: runtimeResult.scorecard } : {}),
160
+ ...(runtimeResult?.execution ? { runtimeExecution: runtimeResult.execution } : {})
133
161
  };
134
162
  }
135
163
  export function renderGenericMcpDoctorJson(report) {
@@ -153,6 +181,9 @@ export function renderGenericMcpDoctor(report) {
153
181
  .map((result) => `${result.client}=${result.status}`)
154
182
  .join(", ")}`
155
183
  ];
184
+ if (report.runtimeScorecard?.conformance) {
185
+ lines.push(`Runtime conformance: ${report.runtimeScorecard.conformance.overall.toUpperCase()}`);
186
+ }
156
187
  if (report.findings.length === 0) {
157
188
  lines.push("", "No findings.");
158
189
  return lines.join("\n");
@@ -1,6 +1,49 @@
1
1
  import { formatFindingEvidenceLine } from "./format-finding-evidence.js";
2
2
  import { formatFindingFingerprintLine } from "./finding-fingerprint.js";
3
3
  import { buildRecommendedCommands } from "./recommended-commands.js";
4
+ function appendRuntimeScorecard(lines, result) {
5
+ if (!result.runtimeScorecard) {
6
+ return;
7
+ }
8
+ lines.push("", "## Runtime Scorecard", "");
9
+ lines.push("| Operation | Status |");
10
+ lines.push("| --- | --- |");
11
+ lines.push(`| initialize | ${result.runtimeScorecard.initialize.toUpperCase()} |`);
12
+ lines.push(`| tools/list | ${result.runtimeScorecard.toolsList.toUpperCase()} |`);
13
+ lines.push(`| tools/call | ${result.runtimeScorecard.toolsCall.toUpperCase()} |`);
14
+ lines.push(`| resources/list | ${result.runtimeScorecard.resourcesList.toUpperCase()} |`);
15
+ lines.push(`| resources/read | ${result.runtimeScorecard.resourceRead.toUpperCase()} |`);
16
+ lines.push(`| resources/templates/list | ${result.runtimeScorecard.resourceTemplatesList.toUpperCase()} |`);
17
+ lines.push(`| prompts/list | ${result.runtimeScorecard.promptsList.toUpperCase()} |`);
18
+ lines.push(`| prompts/get | ${result.runtimeScorecard.promptGet.toUpperCase()} |`);
19
+ const conformance = result.runtimeScorecard.conformance;
20
+ if (conformance) {
21
+ lines.push("", "## MCP Conformance", "");
22
+ lines.push("| Check | Status |");
23
+ lines.push("| --- | --- |");
24
+ lines.push(`| Protocol version | ${conformance.protocolVersion ?? "unavailable"} |`);
25
+ lines.push(`| Profile | ${conformance.profile ?? "unavailable"} |`);
26
+ lines.push(`| Capability consistency | ${conformance.capabilityConsistency.toUpperCase()} |`);
27
+ lines.push(`| Task declarations | ${conformance.taskDeclarations.toUpperCase()} |`);
28
+ lines.push(`| Tasks list | ${conformance.tasksList.toUpperCase()} |`);
29
+ lines.push(`| Schema dialect | ${conformance.schemaDialect.toUpperCase()} |`);
30
+ lines.push(`| Overall | ${conformance.overall.toUpperCase()} |`);
31
+ }
32
+ const remote = result.runtimeScorecard.remote;
33
+ if (remote) {
34
+ lines.push("", "## Remote MCP Scorecard", "");
35
+ lines.push("| Check | Status |");
36
+ lines.push("| --- | --- |");
37
+ lines.push(`| Transport | ${remote.transport.toUpperCase()} |`);
38
+ lines.push(`| Network safety | ${remote.networkSafety.toUpperCase()} |`);
39
+ lines.push(`| Initialize | ${remote.initialize.toUpperCase()} |`);
40
+ lines.push(`| Content type | ${remote.contentType.toUpperCase()} |`);
41
+ lines.push(`| Session | ${remote.session.toUpperCase()} |`);
42
+ lines.push(`| Protocol headers | ${remote.protocolHeaders.toUpperCase()} |`);
43
+ lines.push(`| Authorization | ${remote.authorization.toUpperCase()} |`);
44
+ lines.push(`| Overall | ${remote.overall.toUpperCase()} |`);
45
+ }
46
+ }
4
47
  export function buildMarkdownReport(result, options) {
5
48
  const failCount = result.findings.filter((finding) => finding.severity === "fail").length;
6
49
  const warnCount = result.findings.filter((finding) => finding.severity === "warn").length;
@@ -26,23 +69,11 @@ export function buildMarkdownReport(result, options) {
26
69
  if (result.runtimeExecution) {
27
70
  lines.push("", `Runtime backend: ${result.runtimeExecution.backend.toUpperCase()}`, `Runtime isolation: network=${result.runtimeExecution.network}, package=${result.runtimeExecution.packageMount}`);
28
71
  }
72
+ appendRuntimeScorecard(lines, result);
29
73
  if (result.findings.length === 0 && !result.suppressedFindings?.length) {
30
74
  lines.push("", result.baselineSummary ? "No new findings." : "No findings.");
31
75
  return lines.join("\n");
32
76
  }
33
- if (result.runtimeScorecard) {
34
- lines.push("", "## Runtime Scorecard", "");
35
- lines.push("| Operation | Status |");
36
- lines.push("| --- | --- |");
37
- lines.push(`| initialize | ${result.runtimeScorecard.initialize.toUpperCase()} |`);
38
- lines.push(`| tools/list | ${result.runtimeScorecard.toolsList.toUpperCase()} |`);
39
- lines.push(`| tools/call | ${result.runtimeScorecard.toolsCall.toUpperCase()} |`);
40
- lines.push(`| resources/list | ${result.runtimeScorecard.resourcesList.toUpperCase()} |`);
41
- lines.push(`| resources/read | ${result.runtimeScorecard.resourceRead.toUpperCase()} |`);
42
- lines.push(`| resources/templates/list | ${result.runtimeScorecard.resourceTemplatesList.toUpperCase()} |`);
43
- lines.push(`| prompts/list | ${result.runtimeScorecard.promptsList.toUpperCase()} |`);
44
- lines.push(`| prompts/get | ${result.runtimeScorecard.promptGet.toUpperCase()} |`);
45
- }
46
77
  if (result.findings.length > 0) {
47
78
  const nextActions = Array.from(new Set(result.findings.map((finding) => finding.suggestedFix))).slice(0, 5);
48
79
  if (nextActions.length > 0) {
@@ -24,6 +24,43 @@ function getGlyphs(ascii) {
24
24
  ok: "✔"
25
25
  };
26
26
  }
27
+ function appendRuntimeScorecard(lines, result) {
28
+ if (!result.runtimeScorecard) {
29
+ return;
30
+ }
31
+ lines.push("", "Runtime Scorecard", "----------------");
32
+ lines.push(`initialize: ${result.runtimeScorecard.initialize}`);
33
+ lines.push(`tools/list: ${result.runtimeScorecard.toolsList}`);
34
+ lines.push(`tools/call: ${result.runtimeScorecard.toolsCall}`);
35
+ lines.push(`resources/list: ${result.runtimeScorecard.resourcesList}`);
36
+ lines.push(`resources/read: ${result.runtimeScorecard.resourceRead}`);
37
+ lines.push(`resources/templates/list: ${result.runtimeScorecard.resourceTemplatesList}`);
38
+ lines.push(`prompts/list: ${result.runtimeScorecard.promptsList}`);
39
+ lines.push(`prompts/get: ${result.runtimeScorecard.promptGet}`);
40
+ const conformance = result.runtimeScorecard.conformance;
41
+ if (conformance) {
42
+ lines.push("", "MCP Conformance", "---------------");
43
+ lines.push(`Protocol version: ${conformance.protocolVersion ?? "unavailable"}`);
44
+ lines.push(`Profile: ${conformance.profile ?? "unavailable"}`);
45
+ lines.push(`Capability consistency: ${conformance.capabilityConsistency}`);
46
+ lines.push(`Task declarations: ${conformance.taskDeclarations}`);
47
+ lines.push(`Tasks list: ${conformance.tasksList}`);
48
+ lines.push(`Schema dialect: ${conformance.schemaDialect}`);
49
+ lines.push(`Overall: ${conformance.overall}`);
50
+ }
51
+ const remote = result.runtimeScorecard.remote;
52
+ if (remote) {
53
+ lines.push("", "Remote MCP Scorecard", "--------------------");
54
+ lines.push(`transport: ${remote.transport}`);
55
+ lines.push(`network safety: ${remote.networkSafety}`);
56
+ lines.push(`initialize: ${remote.initialize}`);
57
+ lines.push(`content type: ${remote.contentType}`);
58
+ lines.push(`session: ${remote.session}`);
59
+ lines.push(`protocol headers: ${remote.protocolHeaders}`);
60
+ lines.push(`authorization: ${remote.authorization}`);
61
+ lines.push(`overall: ${remote.overall}`);
62
+ }
63
+ }
27
64
  export function renderTextReport(result, options = {}) {
28
65
  const ascii = options.ascii ?? false;
29
66
  const explain = options.explain ?? false;
@@ -46,17 +83,7 @@ export function renderTextReport(result, options = {}) {
46
83
  lines.push(`Runtime backend: ${result.runtimeExecution.backend.toUpperCase()}`, `Runtime isolation: network=${result.runtimeExecution.network}, package=${result.runtimeExecution.packageMount}`);
47
84
  }
48
85
  if (result.findings.length === 0 && !result.suppressedFindings?.length) {
49
- if (result.runtimeScorecard) {
50
- lines.push("", "Runtime Scorecard", "----------------");
51
- lines.push(`initialize: ${result.runtimeScorecard.initialize}`);
52
- lines.push(`tools/list: ${result.runtimeScorecard.toolsList}`);
53
- lines.push(`tools/call: ${result.runtimeScorecard.toolsCall}`);
54
- lines.push(`resources/list: ${result.runtimeScorecard.resourcesList}`);
55
- lines.push(`resources/read: ${result.runtimeScorecard.resourceRead}`);
56
- lines.push(`resources/templates/list: ${result.runtimeScorecard.resourceTemplatesList}`);
57
- lines.push(`prompts/list: ${result.runtimeScorecard.promptsList}`);
58
- lines.push(`prompts/get: ${result.runtimeScorecard.promptGet}`);
59
- }
86
+ appendRuntimeScorecard(lines, result);
60
87
  lines.push("", result.baselineSummary ? "No new findings." : "No findings.");
61
88
  return lines.join("\n");
62
89
  }
@@ -114,17 +141,7 @@ export function renderTextReport(result, options = {}) {
114
141
  lines.push(` Expires: ${finding.suppression.expiresAt}`);
115
142
  }
116
143
  }
117
- if (result.runtimeScorecard) {
118
- lines.push("", "Runtime Scorecard", "----------------");
119
- lines.push(`initialize: ${result.runtimeScorecard.initialize}`);
120
- lines.push(`tools/list: ${result.runtimeScorecard.toolsList}`);
121
- lines.push(`tools/call: ${result.runtimeScorecard.toolsCall}`);
122
- lines.push(`resources/list: ${result.runtimeScorecard.resourcesList}`);
123
- lines.push(`resources/read: ${result.runtimeScorecard.resourceRead}`);
124
- lines.push(`resources/templates/list: ${result.runtimeScorecard.resourceTemplatesList}`);
125
- lines.push(`prompts/list: ${result.runtimeScorecard.promptsList}`);
126
- lines.push(`prompts/get: ${result.runtimeScorecard.promptGet}`);
127
- }
144
+ appendRuntimeScorecard(lines, result);
128
145
  const recommendedCommands = buildRecommendedCommands(result);
129
146
  if (recommendedCommands.length > 0) {
130
147
  lines.push("", "Recommended Commands", "--------------------");