codex-plugin-doctor 1.63.0 → 1.65.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 +19 -2
- package/dist/core/mcp-tool-diff.d.ts +38 -0
- package/dist/core/mcp-tool-diff.js +168 -0
- package/dist/core/mcp-tool-file.d.ts +5 -0
- package/dist/core/mcp-tool-file.js +20 -14
- package/dist/core/mcp-tool-impact.d.ts +13 -0
- package/dist/core/mcp-tool-impact.js +230 -0
- package/dist/core/output-contract.js +55 -0
- package/dist/reporting/render-mcp-tool-diff-report.d.ts +2 -0
- package/dist/reporting/render-mcp-tool-diff-report.js +45 -0
- package/dist/run-cli.d.ts +2 -0
- package/dist/run-cli.js +58 -0
- 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,5 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { packageVersion } from "../version.js";
|
|
3
|
+
import { TOOL_CHANGE_REASONS } from "./mcp-tool-impact.js";
|
|
3
4
|
import { ruleCatalog } from "../rules/rule-catalog.js";
|
|
4
5
|
const runtimeCapabilityStatusSchema = {
|
|
5
6
|
type: "string",
|
|
@@ -767,6 +768,60 @@ const publicSchemaDefinitions = [
|
|
|
767
768
|
}
|
|
768
769
|
}
|
|
769
770
|
];
|
|
771
|
+
const toolFileReportDefinition = publicSchemaDefinitions.find((definition) => definition.id === "doctor.tools.file.json");
|
|
772
|
+
const embeddedToolFileReportSchema = {
|
|
773
|
+
type: "object",
|
|
774
|
+
required: toolFileReportDefinition.required,
|
|
775
|
+
properties: toolFileReportDefinition.properties,
|
|
776
|
+
additionalProperties: true
|
|
777
|
+
};
|
|
778
|
+
publicSchemaDefinitions.push({
|
|
779
|
+
id: "doctor.tools.diff.json",
|
|
780
|
+
command: "codex-plugin-doctor doctor tools-diff --before <path> --after <path> --json",
|
|
781
|
+
required: ["schemaVersion", "scope", "status", "before", "after", "comparison", "coverage", "changes"],
|
|
782
|
+
properties: {
|
|
783
|
+
schemaVersion: { const: 1 },
|
|
784
|
+
scope: { const: "tool-definitions-diff" },
|
|
785
|
+
status: { enum: ["pass", "warn", "incomplete", "blocked"] },
|
|
786
|
+
before: embeddedToolFileReportSchema,
|
|
787
|
+
after: embeddedToolFileReportSchema,
|
|
788
|
+
comparison: {
|
|
789
|
+
type: "object", required: ["complete", "reason", "added", "removed", "changed", "unchanged", "breaking", "unclassified"],
|
|
790
|
+
properties: {
|
|
791
|
+
complete: { type: "boolean" },
|
|
792
|
+
reason: { enum: [null, "input-blocked", "input-incomplete", "invalid-definitions", "ambiguous-names"] },
|
|
793
|
+
added: { type: ["integer", "null"], minimum: 0, maximum: 500 },
|
|
794
|
+
removed: { type: ["integer", "null"], minimum: 0, maximum: 500 },
|
|
795
|
+
changed: { type: ["integer", "null"], minimum: 0, maximum: 500 },
|
|
796
|
+
unchanged: { type: ["integer", "null"], minimum: 0, maximum: 500 },
|
|
797
|
+
breaking: { type: ["integer", "null"], minimum: 0, maximum: 1000 },
|
|
798
|
+
unclassified: { type: ["integer", "null"], minimum: 0, maximum: 1000 }
|
|
799
|
+
}, additionalProperties: false
|
|
800
|
+
},
|
|
801
|
+
coverage: {
|
|
802
|
+
type: "object", required: ["comparison", "schema", "compatibility", "serverCatalog", "toolExecution", "impactClassification"],
|
|
803
|
+
properties: {
|
|
804
|
+
comparison: { const: "structural-only" }, schema: { const: "root-shape-only" },
|
|
805
|
+
compatibility: { const: "not-tested" }, serverCatalog: { const: "not-tested" },
|
|
806
|
+
toolExecution: { const: "not-tested" }, impactClassification: { const: "heuristic" }
|
|
807
|
+
}, additionalProperties: false
|
|
808
|
+
},
|
|
809
|
+
changes: {
|
|
810
|
+
type: "array", maxItems: 1000,
|
|
811
|
+
items: {
|
|
812
|
+
type: "object", required: ["kind", "beforeToolIndex", "afterToolIndex", "fields", "impact", "reasons"],
|
|
813
|
+
properties: {
|
|
814
|
+
kind: { enum: ["added", "removed", "changed"] },
|
|
815
|
+
beforeToolIndex: { type: ["integer", "null"], minimum: 1, maximum: 500 },
|
|
816
|
+
afterToolIndex: { type: ["integer", "null"], minimum: 1, maximum: 500 },
|
|
817
|
+
fields: { type: "array", maxItems: 6, uniqueItems: true, items: { enum: ["inputSchema", "outputSchema", "description", "title", "annotations", "other"] } },
|
|
818
|
+
impact: { enum: ["breaking", "compatible", "unclassified"] },
|
|
819
|
+
reasons: { type: "array", maxItems: TOOL_CHANGE_REASONS.length, uniqueItems: true, items: { enum: [...TOOL_CHANGE_REASONS] } }
|
|
820
|
+
}, additionalProperties: false
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
});
|
|
770
825
|
function sha256(value) {
|
|
771
826
|
return `sha256:${createHash("sha256").update(value).digest("hex")}`;
|
|
772
827
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export function renderMcpToolDiffReport(report) {
|
|
2
|
+
const comparison = report.comparison;
|
|
3
|
+
return [
|
|
4
|
+
"Offline MCP Tool Diff",
|
|
5
|
+
"=====================",
|
|
6
|
+
`Status: ${report.status}`,
|
|
7
|
+
`Scope: ${report.scope}`,
|
|
8
|
+
`Comparison complete: ${comparison.complete}`,
|
|
9
|
+
...(comparison.reason === null ? [] : [`Reason: ${comparison.reason}`]),
|
|
10
|
+
`Added: ${comparison.added ?? "not compared"}`,
|
|
11
|
+
`Removed: ${comparison.removed ?? "not compared"}`,
|
|
12
|
+
`Changed: ${comparison.changed ?? "not compared"}`,
|
|
13
|
+
`Unchanged: ${comparison.unchanged ?? "not compared"}`,
|
|
14
|
+
`Breaking: ${comparison.breaking ?? "not compared"}`,
|
|
15
|
+
`Unclassified: ${comparison.unclassified ?? "not compared"}`,
|
|
16
|
+
`Comparison coverage: ${report.coverage.comparison}`,
|
|
17
|
+
`Schema coverage: ${report.coverage.schema}`,
|
|
18
|
+
`Compatibility: ${report.coverage.compatibility}`,
|
|
19
|
+
`Server catalog: ${report.coverage.serverCatalog}`,
|
|
20
|
+
`Tool execution: ${report.coverage.toolExecution}`,
|
|
21
|
+
`Impact classification: ${report.coverage.impactClassification}`,
|
|
22
|
+
"Only the supplied responses are compared. Impact labels are root-level schema heuristics: a compatible label or an unchanged result does not establish compatibility.",
|
|
23
|
+
...["before", "after"].flatMap((side) => {
|
|
24
|
+
const source = report[side];
|
|
25
|
+
const label = side === "before" ? "Before" : "After";
|
|
26
|
+
return [
|
|
27
|
+
`${label} input: ${source.status}; tools checked: ${source.inspection.toolsChecked}; bytes read: ${source.source.bytesRead}`,
|
|
28
|
+
...(source.inspection.reason === null ? [] : [`${label} reason: ${source.inspection.reason}`]),
|
|
29
|
+
...source.findings.map((finding) => {
|
|
30
|
+
const location = finding.location;
|
|
31
|
+
const locator = location ? ` [${side} tool ${location.toolIndex}${location.schema ? `, ${location.schema}` : ""}${location.relatedToolIndex ? `, related tool ${location.relatedToolIndex}` : ""}]` : "";
|
|
32
|
+
return `${label} ${finding.severity.toUpperCase()} ${finding.id}${locator}: ${finding.message}\n ${finding.suggestedFix}`;
|
|
33
|
+
})
|
|
34
|
+
];
|
|
35
|
+
}),
|
|
36
|
+
...report.changes.map((change) => {
|
|
37
|
+
const locations = [
|
|
38
|
+
...(change.beforeToolIndex === null ? [] : [`before tool ${change.beforeToolIndex}`]),
|
|
39
|
+
...(change.afterToolIndex === null ? [] : [`after tool ${change.afterToolIndex}`])
|
|
40
|
+
];
|
|
41
|
+
const impact = `${change.impact}${change.reasons.length ? ` (${change.reasons.join(", ")})` : ""}`;
|
|
42
|
+
return `${change.kind.toUpperCase()} [${locations.join(" -> ")}]${change.fields.length ? `: ${change.fields.join(", ")}` : ""}\n Impact: ${impact}`;
|
|
43
|
+
})
|
|
44
|
+
].join("\n");
|
|
45
|
+
}
|
package/dist/run-cli.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { discoverMcpServer } from "./core/mcp-discovery.js";
|
|
2
2
|
import { inspectMcpToolCatalog } from "./core/mcp-tool-catalog.js";
|
|
3
3
|
import { inspectMcpToolFile } from "./core/mcp-tool-file.js";
|
|
4
|
+
import { compareMcpToolFiles } from "./core/mcp-tool-diff.js";
|
|
4
5
|
import { buildCorpusQualityMetricsDiffReport, buildCorpusQualityMetricsReport } from "./core/corpus-quality-metrics.js";
|
|
5
6
|
import { runCheck } from "./index.js";
|
|
6
7
|
import { writeRawDoctorConfig } from "./index.js";
|
|
@@ -16,6 +17,7 @@ export interface CliTerminalContext {
|
|
|
16
17
|
platform?: NodeJS.Platform;
|
|
17
18
|
}
|
|
18
19
|
export interface RunCliOptions {
|
|
20
|
+
compareMcpToolFilesImpl?: typeof compareMcpToolFiles;
|
|
19
21
|
inspectMcpToolFileImpl?: typeof inspectMcpToolFile;
|
|
20
22
|
inspectMcpToolCatalogImpl?: typeof inspectMcpToolCatalog;
|
|
21
23
|
discoverMcpServerImpl?: typeof discoverMcpServer;
|
package/dist/run-cli.js
CHANGED
|
@@ -24,6 +24,8 @@ import { buildSubmissionPreflight } from "./core/submission-preflight.js";
|
|
|
24
24
|
import { discoverMcpServer } from "./core/mcp-discovery.js";
|
|
25
25
|
import { inspectMcpToolCatalog } from "./core/mcp-tool-catalog.js";
|
|
26
26
|
import { inspectMcpToolFile } from "./core/mcp-tool-file.js";
|
|
27
|
+
import { compareMcpToolFiles } from "./core/mcp-tool-diff.js";
|
|
28
|
+
import { renderMcpToolDiffReport } from "./reporting/render-mcp-tool-diff-report.js";
|
|
27
29
|
import { renderMcpToolFileReport } from "./reporting/render-mcp-tool-file-report.js";
|
|
28
30
|
import { renderMcpToolCatalogReport } from "./reporting/render-mcp-tool-catalog-report.js";
|
|
29
31
|
import { renderMcpDiscoveryReport } from "./reporting/render-mcp-discovery-report.js";
|
|
@@ -105,6 +107,7 @@ function writeExactStdout(io, message) {
|
|
|
105
107
|
}
|
|
106
108
|
class CliUsageError extends Error {
|
|
107
109
|
}
|
|
110
|
+
const toolsDiffUsage = "Usage: codex-plugin-doctor doctor tools-diff --before <path> --after <path> [--json] [--fail-on any|breaking]";
|
|
108
111
|
const toolsFileUsage = "Usage: codex-plugin-doctor doctor tools-file <path> [--json]";
|
|
109
112
|
const toolsUsage = "Usage: codex-plugin-doctor doctor tools <url> --allow-network [--allow-local-network] [--json]";
|
|
110
113
|
const discoveryUsage = "Usage: codex-plugin-doctor doctor discover <url> --allow-network [--allow-local-network] [--json]";
|
|
@@ -193,6 +196,7 @@ function printUsage(io) {
|
|
|
193
196
|
io.writeStderr(discoveryUsage);
|
|
194
197
|
io.writeStderr(toolsUsage);
|
|
195
198
|
io.writeStderr(toolsFileUsage);
|
|
199
|
+
io.writeStderr(toolsDiffUsage);
|
|
196
200
|
io.writeStderr("Registry readiness: codex-plugin-doctor registry check <server.json|directory> [--json] [--output <path>] [--require-registry-readiness]\n"
|
|
197
201
|
+ " codex-plugin-doctor registry inspect <server-name> --allow-network [--json] [--output <path>] [--require-registry-readiness]\n"
|
|
198
202
|
+ "Registry publication preflight: codex-plugin-doctor registry preflight <server.json|directory> [--allow-network] [--json] [--output <path>] [--require-publish-ready]");
|
|
@@ -1203,6 +1207,60 @@ export async function runCli(args, io = defaultIo, options = {}) {
|
|
|
1203
1207
|
return 0;
|
|
1204
1208
|
}
|
|
1205
1209
|
if (command === "doctor") {
|
|
1210
|
+
if (maybePath === "tools-diff") {
|
|
1211
|
+
if (remainingArgs.length === 1 && remainingArgs[0] === "--help") {
|
|
1212
|
+
io.writeStdout(`${toolsDiffUsage}\nCompares two saved MCP tools/list responses offline. Changes are classified heuristically as breaking, compatible, or unclassified; compatibility is not tested.\n--fail-on breaking exits 1 only for breaking or unclassified changes (default: any change or input warning).`);
|
|
1213
|
+
return 0;
|
|
1214
|
+
}
|
|
1215
|
+
let beforePath;
|
|
1216
|
+
let afterPath;
|
|
1217
|
+
let json = false;
|
|
1218
|
+
let failOn;
|
|
1219
|
+
let invalid = false;
|
|
1220
|
+
for (let index = 0; index < remainingArgs.length; index += 1) {
|
|
1221
|
+
const flag = remainingArgs[index];
|
|
1222
|
+
if (flag === "--json" && !json) {
|
|
1223
|
+
json = true;
|
|
1224
|
+
}
|
|
1225
|
+
else if (flag === "--fail-on" && failOn === undefined) {
|
|
1226
|
+
const value = remainingArgs[index + 1];
|
|
1227
|
+
if (value !== "any" && value !== "breaking") {
|
|
1228
|
+
invalid = true;
|
|
1229
|
+
break;
|
|
1230
|
+
}
|
|
1231
|
+
failOn = value;
|
|
1232
|
+
index += 1;
|
|
1233
|
+
}
|
|
1234
|
+
else if ((flag === "--before" && beforePath === undefined) || (flag === "--after" && afterPath === undefined)) {
|
|
1235
|
+
const value = remainingArgs[index + 1];
|
|
1236
|
+
if (!value || value.startsWith("-")) {
|
|
1237
|
+
invalid = true;
|
|
1238
|
+
break;
|
|
1239
|
+
}
|
|
1240
|
+
if (flag === "--before")
|
|
1241
|
+
beforePath = value;
|
|
1242
|
+
else
|
|
1243
|
+
afterPath = value;
|
|
1244
|
+
index += 1;
|
|
1245
|
+
}
|
|
1246
|
+
else {
|
|
1247
|
+
invalid = true;
|
|
1248
|
+
break;
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
if (invalid || !beforePath || !afterPath) {
|
|
1252
|
+
io.writeStderr(`Invalid comparison arguments. ${toolsDiffUsage}`);
|
|
1253
|
+
return 2;
|
|
1254
|
+
}
|
|
1255
|
+
const report = await (options.compareMcpToolFilesImpl ?? compareMcpToolFiles)(beforePath, afterPath);
|
|
1256
|
+
io.writeStdout(json ? JSON.stringify(report, null, 2) : renderMcpToolDiffReport(report));
|
|
1257
|
+
if (report.status !== "pass" && report.status !== "warn")
|
|
1258
|
+
return 2;
|
|
1259
|
+
if (failOn === "breaking") {
|
|
1260
|
+
return (report.comparison.breaking ?? 0) + (report.comparison.unclassified ?? 0) > 0 ? 1 : 0;
|
|
1261
|
+
}
|
|
1262
|
+
return report.status === "pass" ? 0 : 1;
|
|
1263
|
+
}
|
|
1206
1264
|
if (maybePath === "tools-file") {
|
|
1207
1265
|
if (remainingArgs.length === 1 && remainingArgs[0] === "--help") {
|
|
1208
1266
|
io.writeStdout(`${toolsFileUsage}\nInspects one saved UTF-8 MCP tools/list response. No server connection or tool execution.`);
|
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
|
+
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
{
|
|
2
|
-
"mcpServers": {
|
|
3
|
-
"dangerServer": {
|
|
4
|
-
"command": "node",
|
|
5
|
-
"args": ["./mock-server.js"],
|
|
6
|
-
"env": {
|
|
7
|
-
"OPENAI_API_KEY": "sk-live-example-secret-value"
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"dangerServer": {
|
|
4
|
+
"command": "node",
|
|
5
|
+
"args": ["./mock-server.js"],
|
|
6
|
+
"env": {
|
|
7
|
+
"OPENAI_API_KEY": "sk-live-example-secret-value"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
@@ -1 +1 @@
|
|
|
1
|
-
process.stdin.resume();
|
|
1
|
+
process.stdin.resume();
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "codex-doctor-runtime",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"description": "Runtime-complete Codex Doctor sample plugin with MCP validation coverage.",
|
|
5
|
-
"skills": "./skills/",
|
|
6
|
-
"mcpServers": "./.mcp.json"
|
|
7
|
-
}
|
|
8
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "codex-doctor-runtime",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Runtime-complete Codex Doctor sample plugin with MCP validation coverage.",
|
|
5
|
+
"skills": "./skills/",
|
|
6
|
+
"mcpServers": "./.mcp.json"
|
|
7
|
+
}
|
|
8
|
+
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
{
|
|
2
|
-
"mcpServers": {
|
|
3
|
-
"doctorRuntime": {
|
|
4
|
-
"command": "node",
|
|
5
|
-
"args": ["./mock-server.js"]
|
|
6
|
-
}
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"doctorRuntime": {
|
|
4
|
+
"command": "node",
|
|
5
|
+
"args": ["./mock-server.js"]
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|