codex-plugin-doctor 1.49.0 → 1.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +3 -3
- package/dist/core/mcp-conformance.d.ts +2 -0
- package/dist/core/mcp-conformance.js +215 -0
- package/dist/core/npm-package-doctor.d.ts +11 -0
- package/dist/core/npm-package-doctor.js +12 -2
- package/dist/core/output-contract.js +82 -5
- package/dist/core/runtime-plan.js +2 -0
- package/dist/core/runtime-probe.d.ts +4 -2
- package/dist/core/runtime-probe.js +326 -64
- package/dist/core/runtime-transcript.js +13 -3
- package/dist/domain/types.d.ts +34 -0
- package/dist/mcp/generic-mcp-doctor.d.ts +8 -2
- package/dist/mcp/generic-mcp-doctor.js +35 -11
- package/dist/reporting/render-markdown-report.js +31 -13
- package/dist/reporting/render-text-report.js +28 -22
- package/dist/rules/rule-catalog.js +72 -0
- package/dist/run-cli.js +43 -8
- package/examples/README.md +94 -94
- package/examples/codex-doctor-generic-mcp/.mcp.json +10 -10
- package/examples/codex-doctor-generic-mcp/server.js +1 -1
- package/examples/codex-doctor-risky/.codex-plugin/plugin.json +7 -7
- package/examples/codex-doctor-risky/.mcp.json +12 -12
- package/examples/codex-doctor-risky/mock-server.js +1 -1
- package/examples/codex-doctor-runtime/.codex-plugin/plugin.json +8 -8
- package/examples/codex-doctor-runtime/.mcp.json +9 -9
- package/examples/codex-doctor-runtime/mock-server.js +237 -237
- package/examples/codex-doctor-runtime/skills/context-check/SKILL.md +7 -7
- package/examples/codex-doctor-starter/.codex-plugin/plugin.json +7 -7
- package/examples/codex-doctor-starter/skills/repo-scan/SKILL.md +7 -7
- package/package.json +1 -1
|
@@ -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) {
|
|
@@ -72,30 +73,36 @@ function buildStaticMcpFindings(configPath, parsedConfig) {
|
|
|
72
73
|
findings
|
|
73
74
|
};
|
|
74
75
|
}
|
|
75
|
-
function mergeReportStatus(
|
|
76
|
-
if (
|
|
76
|
+
function mergeReportStatus(findings, security) {
|
|
77
|
+
if (findings.some((finding) => finding.severity === "fail") || security.status === "fail") {
|
|
77
78
|
return "fail";
|
|
78
79
|
}
|
|
79
|
-
if (
|
|
80
|
+
if (findings.some((finding) => finding.severity === "warn") || security.status === "warn") {
|
|
80
81
|
return "warn";
|
|
81
82
|
}
|
|
82
83
|
return "pass";
|
|
83
84
|
}
|
|
84
|
-
export async function buildGenericMcpDoctor(targetPath, environment = {}) {
|
|
85
|
+
export async function buildGenericMcpDoctor(targetPath, environment = {}, options = {}) {
|
|
85
86
|
const rootPath = path.resolve(targetPath);
|
|
86
87
|
const compatibility = await buildCompatibilityMatrix(rootPath, environment);
|
|
87
88
|
const mcpConfigPath = await readMcpConfigPath(rootPath);
|
|
89
|
+
const canonicalRootPath = await realpath(rootPath).catch(() => null);
|
|
90
|
+
const canonicalMcpConfigPath = mcpConfigPath
|
|
91
|
+
? await realpath(mcpConfigPath).catch(() => null)
|
|
92
|
+
: null;
|
|
88
93
|
let parsedConfig = null;
|
|
89
94
|
let staticFindings = [];
|
|
90
95
|
let serverCount = 0;
|
|
91
96
|
if (!mcpConfigPath || !(await fileExists(mcpConfigPath))) {
|
|
92
97
|
staticFindings = buildStaticMcpFindings(null, null).findings;
|
|
93
98
|
}
|
|
94
|
-
else if (!
|
|
99
|
+
else if (!canonicalRootPath ||
|
|
100
|
+
!canonicalMcpConfigPath ||
|
|
101
|
+
!isPathWithinRoot(canonicalRootPath, canonicalMcpConfigPath)) {
|
|
95
102
|
staticFindings = [
|
|
96
103
|
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
104
|
configPath: path.relative(rootPath, mcpConfigPath).replaceAll("\\", "/"),
|
|
98
|
-
resolvedPath: mcpConfigPath,
|
|
105
|
+
resolvedPath: canonicalMcpConfigPath ?? mcpConfigPath,
|
|
99
106
|
field: "configPath"
|
|
100
107
|
})
|
|
101
108
|
];
|
|
@@ -119,17 +126,31 @@ export async function buildGenericMcpDoctor(targetPath, environment = {}) {
|
|
|
119
126
|
const security = buildSecurityAuditFromFindings(rootPath, mcpConfigPath && parsedConfig !== null
|
|
120
127
|
? auditMcpServerConfig(rootPath, parsedConfig)
|
|
121
128
|
: []);
|
|
122
|
-
const
|
|
123
|
-
|
|
129
|
+
const runtimeResult = options.runtime &&
|
|
130
|
+
mcpConfigPath !== null &&
|
|
131
|
+
parsedConfig !== null &&
|
|
132
|
+
canonicalRootPath !== null &&
|
|
133
|
+
canonicalMcpConfigPath !== null &&
|
|
134
|
+
isPathWithinRoot(canonicalRootPath, canonicalMcpConfigPath) &&
|
|
135
|
+
!staticFindings.some((finding) => finding.severity === "fail") &&
|
|
136
|
+
security.status !== "fail"
|
|
137
|
+
? await probeRuntimeConfig(canonicalRootPath, canonicalMcpConfigPath, {
|
|
138
|
+
startupTimeoutMs: options.runtimeStartupTimeoutMs
|
|
139
|
+
})
|
|
140
|
+
: null;
|
|
141
|
+
const fingerprintedFindings = withFindingFingerprints([...staticFindings, ...(runtimeResult?.findings ?? [])], rootPath);
|
|
142
|
+
const status = mergeReportStatus(fingerprintedFindings, security);
|
|
124
143
|
return {
|
|
125
144
|
targetPath: rootPath,
|
|
126
145
|
status,
|
|
127
146
|
exitCode: status === "fail" ? 1 : 0,
|
|
128
147
|
mcpConfigPath,
|
|
129
148
|
serverCount,
|
|
130
|
-
findings: [...
|
|
149
|
+
findings: [...fingerprintedFindings, ...security.findings],
|
|
131
150
|
security,
|
|
132
|
-
compatibility
|
|
151
|
+
compatibility,
|
|
152
|
+
...(runtimeResult ? { runtimeScorecard: runtimeResult.scorecard } : {}),
|
|
153
|
+
...(runtimeResult?.execution ? { runtimeExecution: runtimeResult.execution } : {})
|
|
133
154
|
};
|
|
134
155
|
}
|
|
135
156
|
export function renderGenericMcpDoctorJson(report) {
|
|
@@ -153,6 +174,9 @@ export function renderGenericMcpDoctor(report) {
|
|
|
153
174
|
.map((result) => `${result.client}=${result.status}`)
|
|
154
175
|
.join(", ")}`
|
|
155
176
|
];
|
|
177
|
+
if (report.runtimeScorecard?.conformance) {
|
|
178
|
+
lines.push(`Runtime conformance: ${report.runtimeScorecard.conformance.overall.toUpperCase()}`);
|
|
179
|
+
}
|
|
156
180
|
if (report.findings.length === 0) {
|
|
157
181
|
lines.push("", "No findings.");
|
|
158
182
|
return lines.join("\n");
|
|
@@ -1,6 +1,36 @@
|
|
|
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
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
lines.push("", "## MCP Conformance", "");
|
|
24
|
+
lines.push("| Check | Status |");
|
|
25
|
+
lines.push("| --- | --- |");
|
|
26
|
+
lines.push(`| Protocol version | ${conformance.protocolVersion ?? "unavailable"} |`);
|
|
27
|
+
lines.push(`| Profile | ${conformance.profile ?? "unavailable"} |`);
|
|
28
|
+
lines.push(`| Capability consistency | ${conformance.capabilityConsistency.toUpperCase()} |`);
|
|
29
|
+
lines.push(`| Task declarations | ${conformance.taskDeclarations.toUpperCase()} |`);
|
|
30
|
+
lines.push(`| Tasks list | ${conformance.tasksList.toUpperCase()} |`);
|
|
31
|
+
lines.push(`| Schema dialect | ${conformance.schemaDialect.toUpperCase()} |`);
|
|
32
|
+
lines.push(`| Overall | ${conformance.overall.toUpperCase()} |`);
|
|
33
|
+
}
|
|
4
34
|
export function buildMarkdownReport(result, options) {
|
|
5
35
|
const failCount = result.findings.filter((finding) => finding.severity === "fail").length;
|
|
6
36
|
const warnCount = result.findings.filter((finding) => finding.severity === "warn").length;
|
|
@@ -26,23 +56,11 @@ export function buildMarkdownReport(result, options) {
|
|
|
26
56
|
if (result.runtimeExecution) {
|
|
27
57
|
lines.push("", `Runtime backend: ${result.runtimeExecution.backend.toUpperCase()}`, `Runtime isolation: network=${result.runtimeExecution.network}, package=${result.runtimeExecution.packageMount}`);
|
|
28
58
|
}
|
|
59
|
+
appendRuntimeScorecard(lines, result);
|
|
29
60
|
if (result.findings.length === 0 && !result.suppressedFindings?.length) {
|
|
30
61
|
lines.push("", result.baselineSummary ? "No new findings." : "No findings.");
|
|
31
62
|
return lines.join("\n");
|
|
32
63
|
}
|
|
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
64
|
if (result.findings.length > 0) {
|
|
47
65
|
const nextActions = Array.from(new Set(result.findings.map((finding) => finding.suggestedFix))).slice(0, 5);
|
|
48
66
|
if (nextActions.length > 0) {
|
|
@@ -24,6 +24,32 @@ 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
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
lines.push("", "MCP Conformance", "---------------");
|
|
45
|
+
lines.push(`Protocol version: ${conformance.protocolVersion ?? "unavailable"}`);
|
|
46
|
+
lines.push(`Profile: ${conformance.profile ?? "unavailable"}`);
|
|
47
|
+
lines.push(`Capability consistency: ${conformance.capabilityConsistency}`);
|
|
48
|
+
lines.push(`Task declarations: ${conformance.taskDeclarations}`);
|
|
49
|
+
lines.push(`Tasks list: ${conformance.tasksList}`);
|
|
50
|
+
lines.push(`Schema dialect: ${conformance.schemaDialect}`);
|
|
51
|
+
lines.push(`Overall: ${conformance.overall}`);
|
|
52
|
+
}
|
|
27
53
|
export function renderTextReport(result, options = {}) {
|
|
28
54
|
const ascii = options.ascii ?? false;
|
|
29
55
|
const explain = options.explain ?? false;
|
|
@@ -46,17 +72,7 @@ export function renderTextReport(result, options = {}) {
|
|
|
46
72
|
lines.push(`Runtime backend: ${result.runtimeExecution.backend.toUpperCase()}`, `Runtime isolation: network=${result.runtimeExecution.network}, package=${result.runtimeExecution.packageMount}`);
|
|
47
73
|
}
|
|
48
74
|
if (result.findings.length === 0 && !result.suppressedFindings?.length) {
|
|
49
|
-
|
|
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
|
-
}
|
|
75
|
+
appendRuntimeScorecard(lines, result);
|
|
60
76
|
lines.push("", result.baselineSummary ? "No new findings." : "No findings.");
|
|
61
77
|
return lines.join("\n");
|
|
62
78
|
}
|
|
@@ -114,17 +130,7 @@ export function renderTextReport(result, options = {}) {
|
|
|
114
130
|
lines.push(` Expires: ${finding.suppression.expiresAt}`);
|
|
115
131
|
}
|
|
116
132
|
}
|
|
117
|
-
|
|
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
|
-
}
|
|
133
|
+
appendRuntimeScorecard(lines, result);
|
|
128
134
|
const recommendedCommands = buildRecommendedCommands(result);
|
|
129
135
|
if (recommendedCommands.length > 0) {
|
|
130
136
|
lines.push("", "Recommended Commands", "--------------------");
|
|
@@ -278,6 +278,78 @@ export const ruleCatalog = [
|
|
|
278
278
|
fix: "Remove hidden override or exfiltration instructions and keep descriptions scoped to legitimate behavior.",
|
|
279
279
|
example: "Keep SKILL.md, prompt, resource, and tool descriptions direct and user-facing."
|
|
280
280
|
},
|
|
281
|
+
{
|
|
282
|
+
id: "mcp.conformance.protocol.unknown_newer",
|
|
283
|
+
category: "mcp",
|
|
284
|
+
defaultSeverity: "warn",
|
|
285
|
+
summary: "The server advertises a newer MCP protocol version.",
|
|
286
|
+
why: "The validator applies the 2025-11-25 structural baseline, which may not cover newer protocol requirements.",
|
|
287
|
+
fix: "Confirm the server's newer protocol changes remain compatible with the 2025-11-25 MCP contract.",
|
|
288
|
+
example: '{ "protocolVersion": "2026-01-01" }'
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
id: "mcp.conformance.tasks.capability_invalid",
|
|
292
|
+
category: "mcp",
|
|
293
|
+
defaultSeverity: "fail",
|
|
294
|
+
summary: "A Tasks capability declaration is malformed.",
|
|
295
|
+
why: "Codex cannot safely determine whether the server supports task-aware tool calls.",
|
|
296
|
+
fix: "Return an object for the affected Tasks capability field, or omit the capability until it is implemented.",
|
|
297
|
+
example: '{ "capabilities": { "tasks": { "requests": { "tools": { "call": {} } } } } }'
|
|
298
|
+
},
|
|
299
|
+
{
|
|
300
|
+
id: "mcp.conformance.tasks.task_support_invalid",
|
|
301
|
+
category: "mcp",
|
|
302
|
+
defaultSeverity: "fail",
|
|
303
|
+
summary: "A tool declares invalid task support.",
|
|
304
|
+
why: "Codex cannot safely interpret the tool's task support contract.",
|
|
305
|
+
fix: "Set execution.taskSupport to required, optional, or forbidden.",
|
|
306
|
+
example: '{ "execution": { "taskSupport": "optional" } }'
|
|
307
|
+
},
|
|
308
|
+
{
|
|
309
|
+
id: "mcp.conformance.tasks.capability_mismatch",
|
|
310
|
+
category: "mcp",
|
|
311
|
+
defaultSeverity: "fail",
|
|
312
|
+
summary: "A tool's task support does not match server capability.",
|
|
313
|
+
why: "Codex may attempt task-aware tool calls that the server did not advertise as supported.",
|
|
314
|
+
fix: "Advertise capabilities.tasks.requests.tools.call as an object, or set execution.taskSupport to forbidden.",
|
|
315
|
+
example: '{ "capabilities": { "tasks": { "requests": { "tools": { "call": {} } } } }, "execution": { "taskSupport": "required" } }'
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
id: "mcp.conformance.tasks_list.timeout",
|
|
319
|
+
category: "mcp",
|
|
320
|
+
defaultSeverity: "fail",
|
|
321
|
+
summary: "The server did not complete tasks/list in time.",
|
|
322
|
+
why: "Codex cannot rely on task discovery when tasks/list cannot complete with a valid response.",
|
|
323
|
+
fix: "Reduce tasks/list latency and verify pagination completes.",
|
|
324
|
+
example: "Return a valid tasks/list response before the configured runtime timeout."
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
id: "mcp.conformance.tasks_list.invalid",
|
|
328
|
+
category: "mcp",
|
|
329
|
+
defaultSeverity: "fail",
|
|
330
|
+
summary: "The server returned an invalid tasks/list response.",
|
|
331
|
+
why: "Codex cannot rely on task discovery when tasks/list cannot complete with a valid response.",
|
|
332
|
+
fix: "Return a valid tasks/list response with well-formed task items and pagination.",
|
|
333
|
+
example: '{ "tasks": [] }'
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
id: "mcp.conformance.schema.dialect_invalid",
|
|
337
|
+
category: "mcp",
|
|
338
|
+
defaultSeverity: "fail",
|
|
339
|
+
summary: "A tool schema declares an invalid dialect.",
|
|
340
|
+
why: "Codex cannot reliably select a schema dialect for this tool.",
|
|
341
|
+
fix: "Omit $schema or provide an absolute URI in the affected tool schema.",
|
|
342
|
+
example: '{ "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema" } }'
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
id: "mcp.conformance.schema.dialect_unsupported",
|
|
346
|
+
category: "mcp",
|
|
347
|
+
defaultSeverity: "warn",
|
|
348
|
+
summary: "A tool schema uses a non-canonical dialect.",
|
|
349
|
+
why: "The schema may be valid, but its dialect is outside the validator's latest compatibility baseline.",
|
|
350
|
+
fix: "Use https://json-schema.org/draft/2020-12/schema or omit $schema.",
|
|
351
|
+
example: '{ "inputSchema": { "$schema": "https://json-schema.org/draft/2020-12/schema" } }'
|
|
352
|
+
},
|
|
281
353
|
{
|
|
282
354
|
id: "plugin.runtime.exited_early",
|
|
283
355
|
category: "runtime",
|
package/dist/run-cli.js
CHANGED
|
@@ -100,7 +100,7 @@ function parseRuntimeSandbox(flags, options = { requireRuntime: true }) {
|
|
|
100
100
|
return value;
|
|
101
101
|
}
|
|
102
102
|
function printUsage(io) {
|
|
103
|
-
io.writeStderr("Usage: codex-plugin-doctor check <path|--installed> [filter] [--policy codex-publish|mcp-strict|security] [--compat] [--json|--markdown|--badge-json|--badge-markdown] [--output <path>] [--history <path>] [--runtime] [--sandbox docker] [--require-runtime-approval --runtime-approval-digest <digest>] [--verbose-runtime] [--explain] [--no-animations] [--ascii] [--changed-since <ref>]\n codex-plugin-doctor audit --installed [filter] [--policy codex-publish|mcp-strict|security] [--security] [--compat] [--json] [--output <path>] [--cache] [--changed]\n codex-plugin-doctor audit deps <path> [--policy codex-publish|mcp-strict|security] [--recommend] [--json|--sarif] [--output <path>]\n codex-plugin-doctor mcp <path> [--json] [--output <path>]\n codex-plugin-doctor security <path> [--policy security] [--json|--scorecard]\n codex-plugin-doctor compat <path> [--all|--client <client>] [--json] [--scorecard] [--output <path>] [--install-preview|--apply --backup]\n codex-plugin-doctor suppress add <path> [--fingerprint <sha256> --reason <text> --expires-at YYYY-MM-DD] [--config <path>] [--json]\n codex-plugin-doctor suppress list <path> [--config <path>] [--json]\n codex-plugin-doctor suppress remove <path> [--fingerprint <sha256>|--index <n>] [--config <path>] [--json]\n codex-plugin-doctor fix <path> (--dry-run|--interactive --backup|--apply --backup)\n codex-plugin-doctor history <history.jsonl> [--json] [--fail-on-regression]\n codex-plugin-doctor watch <path> [--runtime] [--json] [--output <path>] [--debounce-ms <ms>] [--max-iterations <n>] [--fail-fast] [--accumulate-json <path>]\n codex-plugin-doctor doctor [npm <package>|contract|corpus [--manifest <corpus.json>] [--json] [--output <path>]|corpus metrics --manifest <corpus.json> [--json|--markdown] [--output <path>] [--min-precision <0..1>] [--min-recall <0..1>] [--max-false-positive-rate <0..1>]|runtime-plan <path> [--runtime --sandbox docker] [--json|--markdown] [--output <path>]|runtime-policy <path> [--runtime --sandbox docker] [--json] [--output <path>]|review-bundle <path> --output <dir> --sign-key-env NAME [--json] [--allow-dirty] [--allow-untagged]|review-bundle verify <bundle-dir> --target <path> --sign-key-env NAME [--json] [--output <path>] [--failures-only]|review-bundle diff --before <dir> --after <dir> [--json]|attest <path> [--sign-key-env NAME]|attest verify <attestation.json> --target <path> --sign-key-env NAME|release-evidence <path> --sign-key-env NAME [--runtime --sandbox docker] [--allow-dirty] [--allow-untagged] [--require-runtime-approval --runtime-approval-digest <digest>]|release-evidence verify <evidence.json> --target <path> --sign-key-env NAME|release-evidence asset <path> --tag <tag> --output <evidence.json> --sign-key-env NAME [--upload]|mcp <path>|inspector <path>|diff --before <path> --after <path>|recommend <path>|trust <path>|perf <path> [--max-total-ms <ms>] [--max-stage-ms stage=ms]|export --bundle <path>|snapshot|clients|--json|--update-check]\n codex-plugin-doctor init [path] [--template skill-only|mcp-stdio|mcp-http|full-runtime]\n codex-plugin-doctor init-ci [path]\n codex-plugin-doctor init-git-hooks [path] [--force] [--json]\n codex-plugin-doctor init-git-hooks [path] --remove [--json]\n codex-plugin-doctor completion bash|zsh|fish\n codex-plugin-doctor config validate <path> [--json]\n codex-plugin-doctor release check <path> [--json] [--runtime --sandbox docker]\n codex-plugin-doctor self-test\n codex-plugin-doctor list --installed\n codex-plugin-doctor explain <finding-id>\n codex-plugin-doctor --version\n\nFirst run:\n codex-plugin-doctor doctor\n codex-plugin-doctor self-test\n codex-plugin-doctor init my-plugin\n codex-plugin-doctor check . --runtime --explain");
|
|
103
|
+
io.writeStderr("Usage: codex-plugin-doctor check <path|--installed> [filter] [--policy codex-publish|mcp-strict|security] [--compat] [--json|--markdown|--badge-json|--badge-markdown] [--output <path>] [--history <path>] [--runtime] [--sandbox docker] [--require-runtime-approval --runtime-approval-digest <digest>] [--verbose-runtime] [--explain] [--no-animations] [--ascii] [--changed-since <ref>]\n codex-plugin-doctor audit --installed [filter] [--policy codex-publish|mcp-strict|security] [--security] [--compat] [--json] [--output <path>] [--cache] [--changed]\n codex-plugin-doctor audit deps <path> [--policy codex-publish|mcp-strict|security] [--recommend] [--json|--sarif] [--output <path>]\n codex-plugin-doctor mcp <path> [--runtime] [--json] [--output <path>]\n codex-plugin-doctor security <path> [--policy security] [--json|--scorecard]\n codex-plugin-doctor compat <path> [--all|--client <client>] [--json] [--scorecard] [--output <path>] [--install-preview|--apply --backup]\n codex-plugin-doctor suppress add <path> [--fingerprint <sha256> --reason <text> --expires-at YYYY-MM-DD] [--config <path>] [--json]\n codex-plugin-doctor suppress list <path> [--config <path>] [--json]\n codex-plugin-doctor suppress remove <path> [--fingerprint <sha256>|--index <n>] [--config <path>] [--json]\n codex-plugin-doctor fix <path> (--dry-run|--interactive --backup|--apply --backup)\n codex-plugin-doctor history <history.jsonl> [--json] [--fail-on-regression]\n codex-plugin-doctor watch <path> [--runtime] [--json] [--output <path>] [--debounce-ms <ms>] [--max-iterations <n>] [--fail-fast] [--accumulate-json <path>]\n codex-plugin-doctor doctor [npm <package>|contract|corpus [--manifest <corpus.json>] [--json] [--output <path>]|corpus metrics --manifest <corpus.json> [--json|--markdown] [--output <path>] [--min-precision <0..1>] [--min-recall <0..1>] [--max-false-positive-rate <0..1>]|runtime-plan <path> [--runtime --sandbox docker] [--json|--markdown] [--output <path>]|runtime-policy <path> [--runtime --sandbox docker] [--json] [--output <path>]|review-bundle <path> --output <dir> --sign-key-env NAME [--json] [--allow-dirty] [--allow-untagged]|review-bundle verify <bundle-dir> --target <path> --sign-key-env NAME [--json] [--output <path>] [--failures-only]|review-bundle diff --before <dir> --after <dir> [--json]|attest <path> [--sign-key-env NAME]|attest verify <attestation.json> --target <path> --sign-key-env NAME|release-evidence <path> --sign-key-env NAME [--runtime --sandbox docker] [--allow-dirty] [--allow-untagged] [--require-runtime-approval --runtime-approval-digest <digest>]|release-evidence verify <evidence.json> --target <path> --sign-key-env NAME|release-evidence asset <path> --tag <tag> --output <evidence.json> --sign-key-env NAME [--upload]|mcp <path>|inspector <path>|diff --before <path> --after <path>|recommend <path>|trust <path>|perf <path> [--max-total-ms <ms>] [--max-stage-ms stage=ms]|export --bundle <path>|snapshot|clients|--json|--update-check]\n codex-plugin-doctor init [path] [--template skill-only|mcp-stdio|mcp-http|full-runtime]\n codex-plugin-doctor init-ci [path]\n codex-plugin-doctor init-git-hooks [path] [--force] [--json]\n codex-plugin-doctor init-git-hooks [path] --remove [--json]\n codex-plugin-doctor completion bash|zsh|fish\n codex-plugin-doctor config validate <path> [--json]\n codex-plugin-doctor release check <path> [--json] [--runtime --sandbox docker]\n codex-plugin-doctor self-test\n codex-plugin-doctor list --installed\n codex-plugin-doctor explain <finding-id>\n codex-plugin-doctor --version\n\nFirst run:\n codex-plugin-doctor doctor\n codex-plugin-doctor self-test\n codex-plugin-doctor init my-plugin\n codex-plugin-doctor check . --runtime --explain");
|
|
104
104
|
io.writeStderr("Corpus quality regression: codex-plugin-doctor doctor corpus metrics diff --before <metrics.json> --after <metrics.json> [--fail-on-regression] [--json|--markdown] [--output <path>]");
|
|
105
105
|
io.writeStderr("Suppression governance: codex-plugin-doctor suppress list <path> [--fail-on-expired] [--fail-on-invalid] [--warn-expiring-within-days <days>]\n codex-plugin-doctor suppress prune <path> [--apply] [--json]");
|
|
106
106
|
io.writeStderr("Baseline gating: codex-plugin-doctor baseline create <path> --output <path> [--runtime]\n codex-plugin-doctor check <path> --baseline <path>");
|
|
@@ -647,17 +647,48 @@ async function executeSuppressCommand(command, io, options = {}) {
|
|
|
647
647
|
}
|
|
648
648
|
function buildGenericMcpDoctorCommandArgs(commandTarget, flags) {
|
|
649
649
|
if (!commandTarget || commandTarget.startsWith("--")) {
|
|
650
|
-
return "Missing target path. Usage: codex-plugin-doctor mcp <path> [--json] [--output <path>]";
|
|
650
|
+
return "Missing target path. Usage: codex-plugin-doctor mcp <path> [--runtime] [--json] [--output <path>]";
|
|
651
651
|
}
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
652
|
+
let jsonOutput = false;
|
|
653
|
+
let outputPath = null;
|
|
654
|
+
let runtime = false;
|
|
655
|
+
for (let index = 0; index < flags.length; index += 1) {
|
|
656
|
+
const flag = flags[index];
|
|
657
|
+
if (flag === "--runtime") {
|
|
658
|
+
if (runtime) {
|
|
659
|
+
return "Duplicate MCP flag: --runtime.";
|
|
660
|
+
}
|
|
661
|
+
runtime = true;
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
if (flag === "--json") {
|
|
665
|
+
if (jsonOutput) {
|
|
666
|
+
return "Duplicate MCP flag: --json.";
|
|
667
|
+
}
|
|
668
|
+
jsonOutput = true;
|
|
669
|
+
continue;
|
|
670
|
+
}
|
|
671
|
+
if (flag === "--output") {
|
|
672
|
+
if (outputPath !== null) {
|
|
673
|
+
return "Duplicate MCP flag: --output.";
|
|
674
|
+
}
|
|
675
|
+
const value = flags[index + 1];
|
|
676
|
+
if (!value || value.startsWith("--")) {
|
|
677
|
+
return "Missing path after --output.";
|
|
678
|
+
}
|
|
679
|
+
outputPath = value;
|
|
680
|
+
index += 1;
|
|
681
|
+
continue;
|
|
682
|
+
}
|
|
683
|
+
return flag.startsWith("--")
|
|
684
|
+
? `Unknown MCP flag: ${flag}.`
|
|
685
|
+
: `Unexpected MCP argument: ${flag}.`;
|
|
656
686
|
}
|
|
657
687
|
return {
|
|
658
688
|
targetPath: commandTarget,
|
|
659
|
-
jsonOutput
|
|
660
|
-
outputPath
|
|
689
|
+
jsonOutput,
|
|
690
|
+
outputPath,
|
|
691
|
+
runtime
|
|
661
692
|
};
|
|
662
693
|
}
|
|
663
694
|
function parsePerformanceThresholds(flags) {
|
|
@@ -944,6 +975,8 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
944
975
|
const report = await buildGenericMcpDoctor(parsedMcpArgs.targetPath, {
|
|
945
976
|
env: terminalContext.env,
|
|
946
977
|
platform: terminalContext.platform
|
|
978
|
+
}, {
|
|
979
|
+
runtime: parsedMcpArgs.runtime
|
|
947
980
|
});
|
|
948
981
|
const renderedReport = parsedMcpArgs.jsonOutput
|
|
949
982
|
? renderGenericMcpDoctorJson(report)
|
|
@@ -2223,6 +2256,8 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
2223
2256
|
const report = await buildGenericMcpDoctor(parsedMcpArgs.targetPath, {
|
|
2224
2257
|
env: terminalContext.env,
|
|
2225
2258
|
platform: terminalContext.platform
|
|
2259
|
+
}, {
|
|
2260
|
+
runtime: parsedMcpArgs.runtime
|
|
2226
2261
|
});
|
|
2227
2262
|
const renderedReport = parsedMcpArgs.jsonOutput
|
|
2228
2263
|
? renderGenericMcpDoctorJson(report)
|
package/examples/README.md
CHANGED
|
@@ -1,94 +1,94 @@
|
|
|
1
|
-
# Examples
|
|
2
|
-
|
|
3
|
-
This folder contains manual example packs for local testing. Unlike `tests/fixtures`, these examples are meant for humans to run directly against the CLI.
|
|
4
|
-
|
|
5
|
-
## Example Packs
|
|
6
|
-
|
|
7
|
-
Use the smallest example that matches what you need to prove:
|
|
8
|
-
|
|
9
|
-
- `codex-doctor-starter` for static manifest and skill validation.
|
|
10
|
-
- `codex-doctor-runtime` for full MCP runtime probing and compatibility scoring.
|
|
11
|
-
- `codex-doctor-risky` for security finding output.
|
|
12
|
-
- `codex-doctor-generic-mcp` for MCP-only packages without a Codex plugin manifest.
|
|
13
|
-
|
|
14
|
-
### `codex-doctor-starter`
|
|
15
|
-
|
|
16
|
-
Minimal valid Codex plugin package with one skill and no runtime MCP server.
|
|
17
|
-
|
|
18
|
-
Expected result:
|
|
19
|
-
|
|
20
|
-
- static validation passes
|
|
21
|
-
- no runtime probing needed
|
|
22
|
-
|
|
23
|
-
Command:
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
codex-plugin-doctor check examples/codex-doctor-starter
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
### `codex-doctor-runtime`
|
|
30
|
-
|
|
31
|
-
Valid Codex plugin package with:
|
|
32
|
-
|
|
33
|
-
- skill metadata
|
|
34
|
-
- `.mcp.json`
|
|
35
|
-
- mock MCP stdio server
|
|
36
|
-
- `tools/list`
|
|
37
|
-
- `tools/call`
|
|
38
|
-
- `resources/list`
|
|
39
|
-
- `resources/read`
|
|
40
|
-
- `resources/templates/list`
|
|
41
|
-
- `prompts/list`
|
|
42
|
-
- `prompts/get`
|
|
43
|
-
|
|
44
|
-
Expected result:
|
|
45
|
-
|
|
46
|
-
- static validation passes
|
|
47
|
-
- runtime validation passes
|
|
48
|
-
- runtime scorecard shows all supported runtime capabilities as `pass`
|
|
49
|
-
|
|
50
|
-
Command:
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
codex-plugin-doctor check examples/codex-doctor-runtime --json --runtime --verbose-runtime
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
### `codex-doctor-risky`
|
|
57
|
-
|
|
58
|
-
Intentionally flawed package for showing failure output.
|
|
59
|
-
|
|
60
|
-
Expected result:
|
|
61
|
-
|
|
62
|
-
- security finding for hard-coded secret
|
|
63
|
-
|
|
64
|
-
Command:
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
codex-plugin-doctor check examples/codex-doctor-risky --ascii
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
### `codex-doctor-generic-mcp`
|
|
71
|
-
|
|
72
|
-
Generic MCP package with a `.mcp.json` file and no Codex plugin manifest.
|
|
73
|
-
|
|
74
|
-
Expected result:
|
|
75
|
-
|
|
76
|
-
- `codex-plugin-doctor mcp` passes generic MCP static health checks
|
|
77
|
-
- `codex-plugin-doctor check` still reports the missing Codex plugin manifest
|
|
78
|
-
|
|
79
|
-
Command:
|
|
80
|
-
|
|
81
|
-
```bash
|
|
82
|
-
codex-plugin-doctor mcp examples/codex-doctor-generic-mcp
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
## Suggested Local Flow
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
npm install
|
|
89
|
-
npm run build
|
|
90
|
-
npm link
|
|
91
|
-
codex-plugin-doctor check examples/codex-doctor-starter
|
|
92
|
-
codex-plugin-doctor check examples/codex-doctor-runtime --json --runtime --verbose-runtime
|
|
93
|
-
codex-plugin-doctor check examples/codex-doctor-risky --ascii
|
|
94
|
-
```
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
This folder contains manual example packs for local testing. Unlike `tests/fixtures`, these examples are meant for humans to run directly against the CLI.
|
|
4
|
+
|
|
5
|
+
## Example Packs
|
|
6
|
+
|
|
7
|
+
Use the smallest example that matches what you need to prove:
|
|
8
|
+
|
|
9
|
+
- `codex-doctor-starter` for static manifest and skill validation.
|
|
10
|
+
- `codex-doctor-runtime` for full MCP runtime probing and compatibility scoring.
|
|
11
|
+
- `codex-doctor-risky` for security finding output.
|
|
12
|
+
- `codex-doctor-generic-mcp` for MCP-only packages without a Codex plugin manifest.
|
|
13
|
+
|
|
14
|
+
### `codex-doctor-starter`
|
|
15
|
+
|
|
16
|
+
Minimal valid Codex plugin package with one skill and no runtime MCP server.
|
|
17
|
+
|
|
18
|
+
Expected result:
|
|
19
|
+
|
|
20
|
+
- static validation passes
|
|
21
|
+
- no runtime probing needed
|
|
22
|
+
|
|
23
|
+
Command:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
codex-plugin-doctor check examples/codex-doctor-starter
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### `codex-doctor-runtime`
|
|
30
|
+
|
|
31
|
+
Valid Codex plugin package with:
|
|
32
|
+
|
|
33
|
+
- skill metadata
|
|
34
|
+
- `.mcp.json`
|
|
35
|
+
- mock MCP stdio server
|
|
36
|
+
- `tools/list`
|
|
37
|
+
- `tools/call`
|
|
38
|
+
- `resources/list`
|
|
39
|
+
- `resources/read`
|
|
40
|
+
- `resources/templates/list`
|
|
41
|
+
- `prompts/list`
|
|
42
|
+
- `prompts/get`
|
|
43
|
+
|
|
44
|
+
Expected result:
|
|
45
|
+
|
|
46
|
+
- static validation passes
|
|
47
|
+
- runtime validation passes
|
|
48
|
+
- runtime scorecard shows all supported runtime capabilities as `pass`
|
|
49
|
+
|
|
50
|
+
Command:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
codex-plugin-doctor check examples/codex-doctor-runtime --json --runtime --verbose-runtime
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### `codex-doctor-risky`
|
|
57
|
+
|
|
58
|
+
Intentionally flawed package for showing failure output.
|
|
59
|
+
|
|
60
|
+
Expected result:
|
|
61
|
+
|
|
62
|
+
- security finding for hard-coded secret
|
|
63
|
+
|
|
64
|
+
Command:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
codex-plugin-doctor check examples/codex-doctor-risky --ascii
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### `codex-doctor-generic-mcp`
|
|
71
|
+
|
|
72
|
+
Generic MCP package with a `.mcp.json` file and no Codex plugin manifest.
|
|
73
|
+
|
|
74
|
+
Expected result:
|
|
75
|
+
|
|
76
|
+
- `codex-plugin-doctor mcp` passes generic MCP static health checks
|
|
77
|
+
- `codex-plugin-doctor check` still reports the missing Codex plugin manifest
|
|
78
|
+
|
|
79
|
+
Command:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
codex-plugin-doctor mcp examples/codex-doctor-generic-mcp
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Suggested Local Flow
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npm install
|
|
89
|
+
npm run build
|
|
90
|
+
npm link
|
|
91
|
+
codex-plugin-doctor check examples/codex-doctor-starter
|
|
92
|
+
codex-plugin-doctor check examples/codex-doctor-runtime --json --runtime --verbose-runtime
|
|
93
|
+
codex-plugin-doctor check examples/codex-doctor-risky --ascii
|
|
94
|
+
```
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
{
|
|
2
|
-
"mcpServers": {
|
|
3
|
-
"genericRuntime": {
|
|
4
|
-
"command": "node",
|
|
5
|
-
"args": [
|
|
6
|
-
"server.js"
|
|
7
|
-
]
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"genericRuntime": {
|
|
4
|
+
"command": "node",
|
|
5
|
+
"args": [
|
|
6
|
+
"server.js"
|
|
7
|
+
]
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
process.stdin.resume();
|
|
1
|
+
process.stdin.resume();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "codex-doctor-risky",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"description": "Intentionally risky sample package for showing Codex Doctor failure output.",
|
|
5
|
-
"mcpServers": "./.mcp.json"
|
|
6
|
-
}
|
|
7
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "codex-doctor-risky",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Intentionally risky sample package for showing Codex Doctor failure output.",
|
|
5
|
+
"mcpServers": "./.mcp.json"
|
|
6
|
+
}
|
|
7
|
+
|