mcp-medic 1.2.2 → 1.2.3
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/README.md +64 -8
- package/dist/checks/index.d.ts +7 -0
- package/dist/checks/index.js +21 -0
- package/dist/checks/malformed-schema.js +17 -0
- package/dist/checks/quality-prompts.d.ts +8 -0
- package/dist/checks/quality-prompts.js +121 -0
- package/dist/checks/quality-resources.d.ts +8 -0
- package/dist/checks/quality-resources.js +92 -0
- package/dist/checks/quality-tool-annotations.d.ts +10 -0
- package/dist/checks/quality-tool-annotations.js +63 -0
- package/dist/checks/quality-tool-descriptions.d.ts +2 -0
- package/dist/checks/quality-tool-descriptions.js +106 -0
- package/dist/checks/quality-tool-names.d.ts +2 -0
- package/dist/checks/quality-tool-names.js +121 -0
- package/dist/checks/quality-tool-output-schema.d.ts +10 -0
- package/dist/checks/quality-tool-output-schema.js +76 -0
- package/dist/checks/quality-tool-surface.d.ts +13 -0
- package/dist/checks/quality-tool-surface.js +99 -0
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +40 -3
- package/dist/diagnostics.d.ts +11 -0
- package/dist/diagnostics.js +28 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/orchestrator.js +7 -1
- package/dist/policy.d.ts +9 -0
- package/dist/policy.js +72 -0
- package/dist/protocol/connect.js +42 -1
- package/dist/quality-score.d.ts +33 -0
- package/dist/quality-score.js +163 -0
- package/dist/report.d.ts +3 -0
- package/dist/report.js +32 -0
- package/dist/types.d.ts +63 -1
- package/package.json +1 -1
package/dist/protocol/connect.js
CHANGED
|
@@ -67,6 +67,23 @@ function validateResponse(response, expectedId) {
|
|
|
67
67
|
}
|
|
68
68
|
return response.result;
|
|
69
69
|
}
|
|
70
|
+
function normalizeToolAnnotations(value) {
|
|
71
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
72
|
+
return undefined;
|
|
73
|
+
const record = value;
|
|
74
|
+
const annotations = {};
|
|
75
|
+
if (typeof record.title === 'string')
|
|
76
|
+
annotations.title = record.title;
|
|
77
|
+
if (typeof record.readOnlyHint === 'boolean')
|
|
78
|
+
annotations.readOnlyHint = record.readOnlyHint;
|
|
79
|
+
if (typeof record.destructiveHint === 'boolean')
|
|
80
|
+
annotations.destructiveHint = record.destructiveHint;
|
|
81
|
+
if (typeof record.idempotentHint === 'boolean')
|
|
82
|
+
annotations.idempotentHint = record.idempotentHint;
|
|
83
|
+
if (typeof record.openWorldHint === 'boolean')
|
|
84
|
+
annotations.openWorldHint = record.openWorldHint;
|
|
85
|
+
return annotations;
|
|
86
|
+
}
|
|
70
87
|
function normalizeTools(result) {
|
|
71
88
|
if (!Array.isArray(result.tools))
|
|
72
89
|
throw new Error('tools/list response has no tools array');
|
|
@@ -77,8 +94,11 @@ function normalizeTools(result) {
|
|
|
77
94
|
const value = tool;
|
|
78
95
|
return {
|
|
79
96
|
name: value.name,
|
|
97
|
+
...(typeof value.title === 'string' ? { title: value.title } : {}),
|
|
80
98
|
...(typeof value.description === 'string' ? { description: value.description } : {}),
|
|
81
99
|
inputSchema: value.inputSchema,
|
|
100
|
+
...(value.outputSchema !== undefined ? { outputSchema: value.outputSchema } : {}),
|
|
101
|
+
...(normalizeToolAnnotations(value.annotations) ? { annotations: normalizeToolAnnotations(value.annotations) } : {}),
|
|
82
102
|
};
|
|
83
103
|
});
|
|
84
104
|
}
|
|
@@ -93,11 +113,29 @@ function normalizeResources(result) {
|
|
|
93
113
|
return {
|
|
94
114
|
uri: value.uri,
|
|
95
115
|
...(typeof value.name === 'string' ? { name: value.name } : {}),
|
|
116
|
+
...(typeof value.title === 'string' ? { title: value.title } : {}),
|
|
96
117
|
...(typeof value.description === 'string' ? { description: value.description } : {}),
|
|
97
118
|
...(typeof value.mimeType === 'string' ? { mimeType: value.mimeType } : {}),
|
|
119
|
+
...(typeof value.size === 'number' ? { size: value.size } : {}),
|
|
98
120
|
};
|
|
99
121
|
});
|
|
100
122
|
}
|
|
123
|
+
function normalizePromptArgument(value) {
|
|
124
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
125
|
+
return {};
|
|
126
|
+
}
|
|
127
|
+
const record = value;
|
|
128
|
+
const arg = {};
|
|
129
|
+
if (typeof record.name === 'string')
|
|
130
|
+
arg.name = record.name;
|
|
131
|
+
if (typeof record.title === 'string')
|
|
132
|
+
arg.title = record.title;
|
|
133
|
+
if (typeof record.description === 'string')
|
|
134
|
+
arg.description = record.description;
|
|
135
|
+
if (typeof record.required === 'boolean')
|
|
136
|
+
arg.required = record.required;
|
|
137
|
+
return arg;
|
|
138
|
+
}
|
|
101
139
|
function normalizePrompts(result) {
|
|
102
140
|
if (!Array.isArray(result.prompts))
|
|
103
141
|
throw new Error('prompts/list response has no prompts array');
|
|
@@ -108,8 +146,11 @@ function normalizePrompts(result) {
|
|
|
108
146
|
const value = prompt;
|
|
109
147
|
return {
|
|
110
148
|
name: value.name,
|
|
149
|
+
...(typeof value.title === 'string' ? { title: value.title } : {}),
|
|
111
150
|
...(typeof value.description === 'string' ? { description: value.description } : {}),
|
|
112
|
-
...(Array.isArray(value.arguments)
|
|
151
|
+
...(Array.isArray(value.arguments)
|
|
152
|
+
? { arguments: value.arguments.map(normalizePromptArgument) }
|
|
153
|
+
: {}),
|
|
113
154
|
};
|
|
114
155
|
});
|
|
115
156
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { MCPConnection, DiagnosticResult, RunReport, QualityDimension, QualityDeduction, QualityScoreBreakdown, ReportQualityScore } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Deterministic MCP quality score. No LLM, no randomness, no network
|
|
4
|
+
* calls beyond the MCP inspection `connect()` already performed — the same
|
|
5
|
+
* report always produces the same score.
|
|
6
|
+
*
|
|
7
|
+
* Design: diagnostics -> normalized per-checkId deductions -> capped
|
|
8
|
+
* per-dimension score -> weighted overall score. This keeps the score
|
|
9
|
+
* fully derived from (and consistent with) the diagnostics that are also
|
|
10
|
+
* shown in the report, rather than an independent "second opinion" — see
|
|
11
|
+
* .agent-room/DECISIONS.md for the full rationale.
|
|
12
|
+
*
|
|
13
|
+
* (Result types — QualityDimension, QualityDeduction, QualityScoreBreakdown,
|
|
14
|
+
* ReportQualityScore — live in src/types.ts alongside RunReport, since
|
|
15
|
+
* RunReport.quality is one of them; this module just implements the math.)
|
|
16
|
+
*/
|
|
17
|
+
export type { QualityDimension, QualityDeduction, QualityScoreBreakdown, ReportQualityScore };
|
|
18
|
+
export declare const QUALITY_DIMENSIONS: readonly QualityDimension[];
|
|
19
|
+
/** Suggested weights from the product spec; documented here since every
|
|
20
|
+
* point deduction must be explainable in terms of these. */
|
|
21
|
+
export declare const QUALITY_DIMENSION_WEIGHTS: Record<QualityDimension, number>;
|
|
22
|
+
/**
|
|
23
|
+
* Computes a quality score for one connection. Returns `undefined` for a
|
|
24
|
+
* connection that never connected — there's nothing meaningful to score
|
|
25
|
+
* (no tools/schema/capabilities were ever observed).
|
|
26
|
+
*/
|
|
27
|
+
export declare function computeConnectionQualityScore(connection: MCPConnection, diagnostics: DiagnosticResult[]): QualityScoreBreakdown | undefined;
|
|
28
|
+
/**
|
|
29
|
+
* Aggregate score across every connected server in a report (simple mean —
|
|
30
|
+
* deliberately not weighted by tool count, so one huge server can't drown
|
|
31
|
+
* out the rest of a fleet). Returns `undefined` if no server connected.
|
|
32
|
+
*/
|
|
33
|
+
export declare function computeReportQualityScore(report: RunReport): ReportQualityScore | undefined;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { categoryOf } from './diagnostics.js';
|
|
2
|
+
export const QUALITY_DIMENSIONS = [
|
|
3
|
+
'protocol',
|
|
4
|
+
'schema',
|
|
5
|
+
'usability',
|
|
6
|
+
'security',
|
|
7
|
+
'reliability',
|
|
8
|
+
];
|
|
9
|
+
/** Suggested weights from the product spec; documented here since every
|
|
10
|
+
* point deduction must be explainable in terms of these. */
|
|
11
|
+
export const QUALITY_DIMENSION_WEIGHTS = {
|
|
12
|
+
protocol: 0.25,
|
|
13
|
+
schema: 0.2,
|
|
14
|
+
usability: 0.2,
|
|
15
|
+
security: 0.2,
|
|
16
|
+
reliability: 0.15,
|
|
17
|
+
};
|
|
18
|
+
// Deduction weights: chosen so a handful of real problems meaningfully move
|
|
19
|
+
// the score, while a single noisy checkId can never wipe out a whole
|
|
20
|
+
// dimension on its own (the per-checkId cap) — this directly implements
|
|
21
|
+
// "avoid double-counting" / "sensible caps" from the product spec: e.g. 20
|
|
22
|
+
// tools sharing one description problem cost at most 15 points total, not 60.
|
|
23
|
+
const SEVERITY_POINTS = { error: 8, warning: 3, info: 1 };
|
|
24
|
+
const SEVERITY_CAP_PER_CHECK = { error: 25, warning: 15, info: 5 };
|
|
25
|
+
function categoryToDimension(category) {
|
|
26
|
+
switch (category) {
|
|
27
|
+
case 'protocol':
|
|
28
|
+
return 'protocol';
|
|
29
|
+
case 'schema':
|
|
30
|
+
return 'schema';
|
|
31
|
+
case 'security':
|
|
32
|
+
return 'security';
|
|
33
|
+
case 'reliability':
|
|
34
|
+
return 'reliability';
|
|
35
|
+
// 'usability', 'quality' (the broad bucket most tool/resource/prompt
|
|
36
|
+
// quality checks use), and 'configuration' (policy.* checks) all land
|
|
37
|
+
// under the "Agent usability" dimension from the product spec.
|
|
38
|
+
case 'usability':
|
|
39
|
+
case 'quality':
|
|
40
|
+
case 'configuration':
|
|
41
|
+
default:
|
|
42
|
+
return 'usability';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function describeDeduction(checkId, severity, count) {
|
|
46
|
+
const plural = count === 1 ? '' : 's';
|
|
47
|
+
return `${count} ${severity}${plural} from "${checkId}"`;
|
|
48
|
+
}
|
|
49
|
+
function deductionsFromDiagnostics(dimension, diagnostics) {
|
|
50
|
+
const grouped = new Map();
|
|
51
|
+
for (const d of diagnostics) {
|
|
52
|
+
if (categoryToDimension(categoryOf(d)) !== dimension)
|
|
53
|
+
continue;
|
|
54
|
+
const key = `${d.checkId}|${d.severity}`;
|
|
55
|
+
const entry = grouped.get(key) ?? { checkId: d.checkId, severity: d.severity, count: 0 };
|
|
56
|
+
entry.count += 1;
|
|
57
|
+
grouped.set(key, entry);
|
|
58
|
+
}
|
|
59
|
+
const deductions = [];
|
|
60
|
+
for (const { checkId, severity, count } of grouped.values()) {
|
|
61
|
+
const points = Math.min(count * SEVERITY_POINTS[severity], SEVERITY_CAP_PER_CHECK[severity]);
|
|
62
|
+
deductions.push({ dimension, checkId, severity, count, points, description: describeDeduction(checkId, severity, count) });
|
|
63
|
+
}
|
|
64
|
+
return deductions;
|
|
65
|
+
}
|
|
66
|
+
/** Protocol-dimension facts that live on `MCPConnection` rather than as
|
|
67
|
+
* diagnostics (capability negotiation isn't produced by a `Check`). */
|
|
68
|
+
function protocolConnectionDeductions(connection) {
|
|
69
|
+
const deductions = [];
|
|
70
|
+
if (connection.capabilityErrors?.resources) {
|
|
71
|
+
deductions.push({
|
|
72
|
+
dimension: 'protocol',
|
|
73
|
+
checkId: 'protocol.capability-error',
|
|
74
|
+
severity: 'warning',
|
|
75
|
+
count: 1,
|
|
76
|
+
points: 15,
|
|
77
|
+
description: 'resources/list failed despite being declared as a capability',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
if (connection.capabilityErrors?.prompts) {
|
|
81
|
+
deductions.push({
|
|
82
|
+
dimension: 'protocol',
|
|
83
|
+
checkId: 'protocol.capability-error',
|
|
84
|
+
severity: 'warning',
|
|
85
|
+
count: 1,
|
|
86
|
+
points: 15,
|
|
87
|
+
description: 'prompts/list failed despite being declared as a capability',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (connection.protocolVersion &&
|
|
91
|
+
connection.protocolVersion.negotiated &&
|
|
92
|
+
connection.protocolVersion.negotiated !== connection.protocolVersion.requested) {
|
|
93
|
+
deductions.push({
|
|
94
|
+
dimension: 'protocol',
|
|
95
|
+
checkId: 'protocol.version-downgrade',
|
|
96
|
+
severity: 'info',
|
|
97
|
+
count: 1,
|
|
98
|
+
points: 5,
|
|
99
|
+
description: `server negotiated ${connection.protocolVersion.negotiated} instead of the requested ${connection.protocolVersion.requested}`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
if (!connection.serverInfo?.name) {
|
|
103
|
+
deductions.push({
|
|
104
|
+
dimension: 'protocol',
|
|
105
|
+
checkId: 'protocol.missing-server-info',
|
|
106
|
+
severity: 'info',
|
|
107
|
+
count: 1,
|
|
108
|
+
points: 5,
|
|
109
|
+
description: 'server did not report its name/version in serverInfo',
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return deductions;
|
|
113
|
+
}
|
|
114
|
+
function dimensionScore(deductions) {
|
|
115
|
+
const totalPoints = deductions.reduce((sum, d) => sum + d.points, 0);
|
|
116
|
+
return Math.max(0, Math.min(100, Math.round(100 - totalPoints)));
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Computes a quality score for one connection. Returns `undefined` for a
|
|
120
|
+
* connection that never connected — there's nothing meaningful to score
|
|
121
|
+
* (no tools/schema/capabilities were ever observed).
|
|
122
|
+
*/
|
|
123
|
+
export function computeConnectionQualityScore(connection, diagnostics) {
|
|
124
|
+
if (connection.status !== 'connected')
|
|
125
|
+
return undefined;
|
|
126
|
+
const relevant = diagnostics.filter((d) => d.serverName === connection.server.name);
|
|
127
|
+
const allDeductions = [
|
|
128
|
+
...protocolConnectionDeductions(connection),
|
|
129
|
+
...QUALITY_DIMENSIONS.flatMap((dim) => deductionsFromDiagnostics(dim, relevant)),
|
|
130
|
+
];
|
|
131
|
+
const dimensions = Object.fromEntries(QUALITY_DIMENSIONS.map((dim) => [dim, dimensionScore(allDeductions.filter((d) => d.dimension === dim))]));
|
|
132
|
+
const overall = Math.round(QUALITY_DIMENSIONS.reduce((sum, dim) => sum + dimensions[dim] * QUALITY_DIMENSION_WEIGHTS[dim], 0));
|
|
133
|
+
return {
|
|
134
|
+
overall,
|
|
135
|
+
dimensions,
|
|
136
|
+
deductions: allDeductions.filter((d) => d.points > 0).sort((a, b) => b.points - a.points),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Aggregate score across every connected server in a report (simple mean —
|
|
141
|
+
* deliberately not weighted by tool count, so one huge server can't drown
|
|
142
|
+
* out the rest of a fleet). Returns `undefined` if no server connected.
|
|
143
|
+
*/
|
|
144
|
+
export function computeReportQualityScore(report) {
|
|
145
|
+
const perServer = {};
|
|
146
|
+
for (const connection of report.connections) {
|
|
147
|
+
const breakdown = computeConnectionQualityScore(connection, report.diagnostics);
|
|
148
|
+
if (breakdown)
|
|
149
|
+
perServer[connection.server.name] = breakdown;
|
|
150
|
+
}
|
|
151
|
+
const names = Object.keys(perServer);
|
|
152
|
+
if (names.length === 0)
|
|
153
|
+
return undefined;
|
|
154
|
+
const dimensions = Object.fromEntries(QUALITY_DIMENSIONS.map((dim) => [
|
|
155
|
+
dim,
|
|
156
|
+
Math.round(names.reduce((sum, n) => sum + perServer[n].dimensions[dim], 0) / names.length),
|
|
157
|
+
]));
|
|
158
|
+
const overall = Math.round(names.reduce((sum, n) => sum + perServer[n].overall, 0) / names.length);
|
|
159
|
+
const deductions = names
|
|
160
|
+
.flatMap((n) => perServer[n].deductions)
|
|
161
|
+
.sort((a, b) => b.points - a.points);
|
|
162
|
+
return { overall, dimensions, deductions, perServer };
|
|
163
|
+
}
|
package/dist/report.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import type { RunReport } from './types.js';
|
|
2
2
|
export interface FormatReportOptions {
|
|
3
3
|
showFixes?: boolean;
|
|
4
|
+
/** Include the MCP quality score section (Phase 7). Off by default so a
|
|
5
|
+
* plain `check` report stays short; `--score`/`score` turn it on. */
|
|
6
|
+
showScore?: boolean;
|
|
4
7
|
}
|
|
5
8
|
/** Human-readable plain text report formatter. */
|
|
6
9
|
export declare function formatReportHuman(report: RunReport, options?: FormatReportOptions): string;
|
package/dist/report.js
CHANGED
|
@@ -1,3 +1,26 @@
|
|
|
1
|
+
const DIMENSION_LABELS = {
|
|
2
|
+
protocol: 'Protocol',
|
|
3
|
+
schema: 'Schema',
|
|
4
|
+
usability: 'Agent usability',
|
|
5
|
+
security: 'Security',
|
|
6
|
+
reliability: 'Reliability',
|
|
7
|
+
};
|
|
8
|
+
function formatScoreBlock(label, score) {
|
|
9
|
+
const lines = [];
|
|
10
|
+
lines.push(label);
|
|
11
|
+
for (const [dim, value] of Object.entries(score.dimensions)) {
|
|
12
|
+
const name = (DIMENSION_LABELS[dim] ?? dim).padEnd(17, ' ');
|
|
13
|
+
lines.push(` ${name} ${String(value).padStart(3, ' ')}/100`);
|
|
14
|
+
}
|
|
15
|
+
lines.push(` ${'MCP QUALITY SCORE'.padEnd(17, ' ')} ${String(score.overall).padStart(3, ' ')}/100`);
|
|
16
|
+
if (score.deductions.length > 0) {
|
|
17
|
+
lines.push(' Deductions:');
|
|
18
|
+
for (const d of score.deductions) {
|
|
19
|
+
lines.push(` -${d.points} ${d.description}`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return lines;
|
|
23
|
+
}
|
|
1
24
|
/** Human-readable plain text report formatter. */
|
|
2
25
|
export function formatReportHuman(report, options = {}) {
|
|
3
26
|
const lines = [];
|
|
@@ -30,6 +53,15 @@ export function formatReportHuman(report, options = {}) {
|
|
|
30
53
|
if (conn.error) {
|
|
31
54
|
lines.push(` ${conn.error.stage}: ${conn.error.message}`);
|
|
32
55
|
}
|
|
56
|
+
const perServerScore = options.showScore ? report.quality?.perServer[conn.server.name] : undefined;
|
|
57
|
+
if (perServerScore) {
|
|
58
|
+
lines.push('');
|
|
59
|
+
lines.push(...formatScoreBlock('QUALITY', perServerScore));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (options.showScore && report.quality && Object.keys(report.quality.perServer).length > 1) {
|
|
63
|
+
lines.push('');
|
|
64
|
+
lines.push(...formatScoreBlock('OVERALL QUALITY (all servers)', report.quality));
|
|
33
65
|
}
|
|
34
66
|
if (report.diagnostics.length > 0) {
|
|
35
67
|
lines.push('');
|
package/dist/types.d.ts
CHANGED
|
@@ -14,21 +14,48 @@ export interface MCPConfig {
|
|
|
14
14
|
servers: MCPServerConfig[];
|
|
15
15
|
sourcePath?: string;
|
|
16
16
|
}
|
|
17
|
+
/** Per the MCP spec's `ToolAnnotations`: optional client *hints*, not
|
|
18
|
+
* authoritative security guarantees — a server can lie about any of these. */
|
|
19
|
+
export interface MCPToolAnnotations {
|
|
20
|
+
title?: string;
|
|
21
|
+
readOnlyHint?: boolean;
|
|
22
|
+
destructiveHint?: boolean;
|
|
23
|
+
idempotentHint?: boolean;
|
|
24
|
+
openWorldHint?: boolean;
|
|
25
|
+
}
|
|
17
26
|
export interface MCPToolDefinition {
|
|
18
27
|
name: string;
|
|
28
|
+
title?: string;
|
|
19
29
|
description?: string;
|
|
20
30
|
inputSchema: unknown;
|
|
31
|
+
/** Optional per spec; same shape as `inputSchema` when present. */
|
|
32
|
+
outputSchema?: unknown;
|
|
33
|
+
annotations?: MCPToolAnnotations;
|
|
21
34
|
}
|
|
22
35
|
export interface MCPResourceDefinition {
|
|
23
36
|
uri: string;
|
|
37
|
+
/** Required by the MCP spec's `Resource` (extends `BaseMetadata`) — kept
|
|
38
|
+
* optional here because mcp-medic reports a missing name as a diagnostic
|
|
39
|
+
* rather than failing the whole `resources/list` response over it. */
|
|
24
40
|
name?: string;
|
|
41
|
+
title?: string;
|
|
25
42
|
description?: string;
|
|
26
43
|
mimeType?: string;
|
|
44
|
+
size?: number;
|
|
45
|
+
}
|
|
46
|
+
export interface MCPPromptArgument {
|
|
47
|
+
/** Required by the MCP spec's `PromptArgument` (extends `BaseMetadata`) —
|
|
48
|
+
* kept optional here for the same reason as `MCPResourceDefinition.name`. */
|
|
49
|
+
name?: string;
|
|
50
|
+
title?: string;
|
|
51
|
+
description?: string;
|
|
52
|
+
required?: boolean;
|
|
27
53
|
}
|
|
28
54
|
export interface MCPPromptDefinition {
|
|
29
55
|
name: string;
|
|
56
|
+
title?: string;
|
|
30
57
|
description?: string;
|
|
31
|
-
arguments?:
|
|
58
|
+
arguments?: MCPPromptArgument[];
|
|
32
59
|
}
|
|
33
60
|
export interface ProtocolVersionInfo {
|
|
34
61
|
/** The protocolVersion this client sent in `initialize`. */
|
|
@@ -68,6 +95,10 @@ export interface MCPConnection {
|
|
|
68
95
|
latencyMs?: number;
|
|
69
96
|
}
|
|
70
97
|
export type Severity = 'error' | 'warning' | 'info';
|
|
98
|
+
/** Stable diagnostic taxonomy. Optional and additive — existing checks that
|
|
99
|
+
* don't set this are categorized by `inferDiagnosticCategory()` (src/diagnostics.ts)
|
|
100
|
+
* from their `checkId` prefix, so nothing that already works needs to change. */
|
|
101
|
+
export type DiagnosticCategory = 'protocol' | 'schema' | 'quality' | 'security' | 'reliability' | 'usability' | 'configuration';
|
|
71
102
|
export interface SuggestedFix {
|
|
72
103
|
description: string;
|
|
73
104
|
patch?: unknown;
|
|
@@ -80,6 +111,12 @@ export interface DiagnosticResult {
|
|
|
80
111
|
toolName?: string;
|
|
81
112
|
details?: unknown;
|
|
82
113
|
suggestedFix?: SuggestedFix;
|
|
114
|
+
/** Optional; see `DiagnosticCategory`. */
|
|
115
|
+
category?: DiagnosticCategory;
|
|
116
|
+
/** Optional: how confident the check is that this is a real problem (vs. a heuristic guess). */
|
|
117
|
+
confidence?: 'low' | 'medium' | 'high';
|
|
118
|
+
/** Optional: link to a doc explaining the rule this diagnostic enforces. */
|
|
119
|
+
documentationUrl?: string;
|
|
83
120
|
}
|
|
84
121
|
export interface Check {
|
|
85
122
|
id: string;
|
|
@@ -94,6 +131,28 @@ export interface RunOptions {
|
|
|
94
131
|
/** "auto" (default) requests the newest protocol version this client supports; an explicit version string requests that version instead. */
|
|
95
132
|
protocolVersion?: string;
|
|
96
133
|
}
|
|
134
|
+
/** The five scored dimensions of MCP quality (see src/quality-score.ts for the scoring model). */
|
|
135
|
+
export type QualityDimension = 'protocol' | 'schema' | 'usability' | 'security' | 'reliability';
|
|
136
|
+
export interface QualityDeduction {
|
|
137
|
+
dimension: QualityDimension;
|
|
138
|
+
checkId: string;
|
|
139
|
+
severity: Severity;
|
|
140
|
+
/** How many diagnostics from this checkId (at this severity) contributed. */
|
|
141
|
+
count: number;
|
|
142
|
+
/** Points actually deducted, after the per-checkId cap. */
|
|
143
|
+
points: number;
|
|
144
|
+
description: string;
|
|
145
|
+
}
|
|
146
|
+
export interface QualityScoreBreakdown {
|
|
147
|
+
overall: number;
|
|
148
|
+
dimensions: Record<QualityDimension, number>;
|
|
149
|
+
/** Only deductions with points > 0, most-costly first — every deduction here explains itself. */
|
|
150
|
+
deductions: QualityDeduction[];
|
|
151
|
+
}
|
|
152
|
+
export interface ReportQualityScore extends QualityScoreBreakdown {
|
|
153
|
+
/** Per-connected-server breakdown; a server that never connected has no entry (nothing to score). */
|
|
154
|
+
perServer: Record<string, QualityScoreBreakdown>;
|
|
155
|
+
}
|
|
97
156
|
export interface RunReport {
|
|
98
157
|
configSource?: string;
|
|
99
158
|
connections: MCPConnection[];
|
|
@@ -105,4 +164,7 @@ export interface RunReport {
|
|
|
105
164
|
errors: number;
|
|
106
165
|
warnings: number;
|
|
107
166
|
};
|
|
167
|
+
/** Deterministic quality score derived from `diagnostics`/`connections` — see src/quality-score.ts.
|
|
168
|
+
* `undefined` when no server connected (nothing to score). */
|
|
169
|
+
quality?: ReportQualityScore;
|
|
108
170
|
}
|