mcp-medic 1.2.2 → 1.2.4

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 (38) hide show
  1. package/README.md +82 -8
  2. package/dist/checks/index.d.ts +8 -0
  3. package/dist/checks/index.js +24 -0
  4. package/dist/checks/malformed-schema.js +17 -0
  5. package/dist/checks/protocol-connection-health.d.ts +17 -0
  6. package/dist/checks/protocol-connection-health.js +78 -0
  7. package/dist/checks/quality-prompts.d.ts +8 -0
  8. package/dist/checks/quality-prompts.js +121 -0
  9. package/dist/checks/quality-resources.d.ts +8 -0
  10. package/dist/checks/quality-resources.js +92 -0
  11. package/dist/checks/quality-tool-annotations.d.ts +10 -0
  12. package/dist/checks/quality-tool-annotations.js +63 -0
  13. package/dist/checks/quality-tool-descriptions.d.ts +2 -0
  14. package/dist/checks/quality-tool-descriptions.js +106 -0
  15. package/dist/checks/quality-tool-names.d.ts +35 -0
  16. package/dist/checks/quality-tool-names.js +160 -0
  17. package/dist/checks/quality-tool-output-schema.d.ts +10 -0
  18. package/dist/checks/quality-tool-output-schema.js +76 -0
  19. package/dist/checks/quality-tool-surface.d.ts +13 -0
  20. package/dist/checks/quality-tool-surface.js +99 -0
  21. package/dist/cli.d.ts +3 -1
  22. package/dist/cli.js +35 -3
  23. package/dist/diagnostics.d.ts +11 -0
  24. package/dist/diagnostics.js +28 -0
  25. package/dist/index.d.ts +7 -0
  26. package/dist/index.js +5 -0
  27. package/dist/orchestrator.js +7 -1
  28. package/dist/policy.d.ts +9 -0
  29. package/dist/policy.js +72 -0
  30. package/dist/protocol/connect.js +42 -1
  31. package/dist/protocol/quality-rules.d.ts +46 -0
  32. package/dist/protocol/quality-rules.js +37 -0
  33. package/dist/quality-score.d.ts +76 -0
  34. package/dist/quality-score.js +242 -0
  35. package/dist/report.d.ts +3 -0
  36. package/dist/report.js +54 -0
  37. package/dist/types.d.ts +81 -1
  38. package/package.json +1 -1
@@ -0,0 +1,242 @@
1
+ import { categoryOf, inferDiagnosticCategory } from './diagnostics.js';
2
+ import { allChecks } from './checks/index.js';
3
+ export const QUALITY_DIMENSIONS = [
4
+ 'protocol',
5
+ 'schema',
6
+ 'usability',
7
+ 'security',
8
+ 'reliability',
9
+ ];
10
+ /** Suggested weights from the product spec; documented here since every
11
+ * point deduction must be explainable in terms of these. Do not change
12
+ * without a documented reason — see .agent-room/DECISIONS.md. */
13
+ export const QUALITY_DIMENSION_WEIGHTS = {
14
+ protocol: 0.25,
15
+ schema: 0.2,
16
+ usability: 0.2,
17
+ security: 0.2,
18
+ reliability: 0.15,
19
+ };
20
+ /**
21
+ * The score is a measurement, not a certification: it reports what
22
+ * mcp-medic's passive checks actually found (or, per `coverage`, actually
23
+ * looked for) — never a formal audit of the server's real security or
24
+ * behavior. Surfaced in both JSON (`ReportQualityScore.disclaimer`) and the
25
+ * human report.
26
+ */
27
+ export const QUALITY_SCORE_DISCLAIMER = "This score reflects issues detectable by mcp-medic's passive inspection (see \"coverage\") — " +
28
+ 'it is not a certification of the server\'s actual security, correctness, or behavior.';
29
+ // Deduction weights: chosen so a handful of real problems meaningfully move
30
+ // the score, while a single noisy checkId can never wipe out a whole
31
+ // dimension on its own (the per-checkId cap) — this directly implements
32
+ // "avoid double-counting" / "sensible caps" from the product spec: e.g. 20
33
+ // tools sharing one description problem cost at most 15 points total, not 60.
34
+ const SEVERITY_POINTS = { error: 8, warning: 3, info: 1 };
35
+ const SEVERITY_CAP_PER_CHECK = { error: 25, warning: 15, info: 5 };
36
+ function categoryToDimension(category) {
37
+ switch (category) {
38
+ case 'protocol':
39
+ return 'protocol';
40
+ case 'schema':
41
+ return 'schema';
42
+ case 'security':
43
+ return 'security';
44
+ case 'reliability':
45
+ return 'reliability';
46
+ // 'usability', 'quality' (the broad bucket most tool/resource/prompt
47
+ // quality checks use), and 'configuration' (policy.* checks) all land
48
+ // under the "Agent usability" dimension from the product spec.
49
+ case 'usability':
50
+ case 'quality':
51
+ case 'configuration':
52
+ default:
53
+ return 'usability';
54
+ }
55
+ }
56
+ function describeDeduction(checkId, severity, count) {
57
+ const plural = count === 1 ? '' : 's';
58
+ return `${count} ${severity}${plural} from "${checkId}"`;
59
+ }
60
+ function deductionsFromDiagnostics(dimension, diagnostics) {
61
+ const grouped = new Map();
62
+ for (const d of diagnostics) {
63
+ if (categoryToDimension(categoryOf(d)) !== dimension)
64
+ continue;
65
+ const key = `${d.checkId}|${d.severity}`;
66
+ const entry = grouped.get(key) ?? { checkId: d.checkId, severity: d.severity, count: 0 };
67
+ entry.count += 1;
68
+ grouped.set(key, entry);
69
+ }
70
+ const deductions = [];
71
+ for (const { checkId, severity, count } of grouped.values()) {
72
+ const points = Math.min(count * SEVERITY_POINTS[severity], SEVERITY_CAP_PER_CHECK[severity]);
73
+ deductions.push({ dimension, checkId, severity, count, points, description: describeDeduction(checkId, severity, count) });
74
+ }
75
+ return deductions;
76
+ }
77
+ function dimensionScore(deductions) {
78
+ const totalPoints = deductions.reduce((sum, d) => sum + d.points, 0);
79
+ return Math.max(0, Math.min(100, Math.round(100 - totalPoints)));
80
+ }
81
+ /**
82
+ * Computes a quality score for one connection, purely from `diagnostics`
83
+ * (filtered to that server) — no connection metadata is consulted
84
+ * directly. Returns `undefined` for a connection that never connected —
85
+ * there's nothing meaningful to score (no tools/schema/capabilities were
86
+ * ever observed); see `computeReportQualityScore` for how the report-level
87
+ * score surfaces that instead of silently dropping it.
88
+ */
89
+ export function computeConnectionQualityScore(connection, diagnostics) {
90
+ if (connection.status !== 'connected')
91
+ return undefined;
92
+ const relevant = diagnostics.filter((d) => d.serverName === connection.server.name);
93
+ const allDeductions = QUALITY_DIMENSIONS.flatMap((dim) => deductionsFromDiagnostics(dim, relevant));
94
+ const dimensions = Object.fromEntries(QUALITY_DIMENSIONS.map((dim) => [dim, dimensionScore(allDeductions.filter((d) => d.dimension === dim))]));
95
+ const overall = Math.round(QUALITY_DIMENSIONS.reduce((sum, dim) => sum + dimensions[dim] * QUALITY_DIMENSION_WEIGHTS[dim], 0));
96
+ return {
97
+ overall,
98
+ dimensions,
99
+ deductions: allDeductions.filter((d) => d.points > 0).sort((a, b) => b.points - a.points),
100
+ };
101
+ }
102
+ const COVERAGE_WEIGHT = { covered: 1, partial: 0.5, 'not-covered': 0 };
103
+ /**
104
+ * Coverage answers "was this dimension actually evaluated," independent of
105
+ * what score it got — a 100 from zero checks run means "nothing was
106
+ * looked for," not "nothing is wrong." Derived from the *ids* of the
107
+ * checks that actually ran (`executedChecks`), compared against the
108
+ * built-in `allChecks` reference set grouped by dimension. Reliability is
109
+ * special-cased to always 'covered': its signal (did the connection
110
+ * succeed) isn't produced by any optional `Check` — it's inherent to
111
+ * attempting a connection at all, so it's never "not covered."
112
+ */
113
+ export function computeQualityCoverage(executedChecks) {
114
+ const referenceByDimension = new Map();
115
+ for (const check of allChecks) {
116
+ const dim = categoryToDimension(inferDiagnosticCategory(check.id));
117
+ if (dim === 'reliability')
118
+ continue; // no built-in check drives reliability; handled unconditionally below
119
+ if (!referenceByDimension.has(dim))
120
+ referenceByDimension.set(dim, new Set());
121
+ referenceByDimension.get(dim).add(check.id);
122
+ }
123
+ const coverage = {};
124
+ for (const dim of QUALITY_DIMENSIONS) {
125
+ if (dim === 'reliability') {
126
+ coverage[dim] = 'covered';
127
+ continue;
128
+ }
129
+ const reference = referenceByDimension.get(dim) ?? new Set();
130
+ // Every executed check that maps to this dimension — including custom/
131
+ // community checks not in the built-in reference set, so an unmapped
132
+ // check still counts as (at least partial) coverage for the dimension
133
+ // it was inferred into, rather than being invisible to this count.
134
+ const executedForDim = executedChecks.filter((c) => categoryToDimension(inferDiagnosticCategory(c.id)) === dim);
135
+ if (executedForDim.length === 0) {
136
+ coverage[dim] = 'not-covered';
137
+ continue;
138
+ }
139
+ if (reference.size === 0) {
140
+ // No built-in check exists for this dimension at all, but something
141
+ // (necessarily a custom check) ran for it — can't call that "full"
142
+ // coverage without a reference to compare against.
143
+ coverage[dim] = 'partial';
144
+ continue;
145
+ }
146
+ const executedKnownIds = new Set(executedForDim.map((c) => c.id).filter((id) => reference.has(id)));
147
+ coverage[dim] = executedKnownIds.size === reference.size ? 'covered' : 'partial';
148
+ }
149
+ return coverage;
150
+ }
151
+ function coveragePercent(coverage) {
152
+ const total = QUALITY_DIMENSIONS.reduce((sum, dim) => sum + COVERAGE_WEIGHT[coverage[dim]], 0);
153
+ return Math.round((total / QUALITY_DIMENSIONS.length) * 100);
154
+ }
155
+ /**
156
+ * Aggregate score across every connected server in a report (simple mean —
157
+ * deliberately not weighted by tool count, so one huge server can't drown
158
+ * out the rest of a fleet). `executedChecks` should be the exact list
159
+ * passed to `runChecks({ checks })` for this report, so `coverage` reflects
160
+ * what actually ran (defaults to the full built-in set if omitted, for
161
+ * callers that haven't been updated to pass it through).
162
+ * Returns `undefined` if no server connected — nothing to score at all.
163
+ */
164
+ export function computeReportQualityScore(report, executedChecks = allChecks) {
165
+ const perServer = {};
166
+ for (const connection of report.connections) {
167
+ const breakdown = computeConnectionQualityScore(connection, report.diagnostics);
168
+ if (breakdown)
169
+ perServer[connection.server.name] = breakdown;
170
+ }
171
+ const scoredServers = Object.keys(perServer);
172
+ const unscoredServers = report.connections
173
+ .map((c) => c.server.name)
174
+ .filter((name) => !perServer[name]);
175
+ if (scoredServers.length === 0)
176
+ return undefined;
177
+ const dimensions = Object.fromEntries(QUALITY_DIMENSIONS.map((dim) => [
178
+ dim,
179
+ Math.round(scoredServers.reduce((sum, n) => sum + perServer[n].dimensions[dim], 0) / scoredServers.length),
180
+ ]));
181
+ const overall = Math.round(scoredServers.reduce((sum, n) => sum + perServer[n].overall, 0) / scoredServers.length);
182
+ const deductions = scoredServers
183
+ .flatMap((n) => perServer[n].deductions)
184
+ .sort((a, b) => b.points - a.points);
185
+ const coverage = computeQualityCoverage(executedChecks);
186
+ return {
187
+ overall,
188
+ dimensions,
189
+ deductions,
190
+ perServer,
191
+ coverage,
192
+ coveragePercent: coveragePercent(coverage),
193
+ scoredServers,
194
+ unscoredServers,
195
+ disclaimer: QUALITY_SCORE_DISCLAIMER,
196
+ };
197
+ }
198
+ /**
199
+ * Enforces `.mcp-medic-policy.json`'s `quality.minimumScore` as a CI gate.
200
+ * Pure function so it's directly unit-testable without needing a real CLI
201
+ * invocation or a restricted check set — see PHASE 12 ("policy/CI") in
202
+ * .agent-room/DECISIONS.md.
203
+ *
204
+ * Returns diagnostics to append to the report (0, 1, or 2):
205
+ * - an 'error' if the score is below `minimumScore` (fails the run).
206
+ * - a 'warning' if the score was computed from a partial assessment
207
+ * (incomplete coverage, or a server that couldn't be scored) — a
208
+ * minimumScore gate must never silently "pass" as if a full check had
209
+ * run when it didn't.
210
+ */
211
+ export function checkMinimumScorePolicy(quality, minimumScore, serverNamesLabel) {
212
+ const results = [];
213
+ if (quality.overall < minimumScore) {
214
+ results.push({
215
+ checkId: 'policy.minimum-quality-score',
216
+ severity: 'error',
217
+ message: `MCP quality score ${quality.overall} is below the policy minimum of ${minimumScore}.`,
218
+ serverName: serverNamesLabel,
219
+ category: 'configuration',
220
+ });
221
+ }
222
+ if (quality.coveragePercent < 100 || quality.unscoredServers.length > 0) {
223
+ const gaps = Object.entries(quality.coverage)
224
+ .filter(([, status]) => status !== 'covered')
225
+ .map(([dim, status]) => `${dim}: ${status}`)
226
+ .join(', ');
227
+ const parts = [
228
+ gaps ? `coverage is ${quality.coveragePercent}% (${gaps})` : undefined,
229
+ quality.unscoredServers.length > 0
230
+ ? `${quality.unscoredServers.length} server(s) could not be scored: ${quality.unscoredServers.join(', ')}`
231
+ : undefined,
232
+ ].filter(Boolean);
233
+ results.push({
234
+ checkId: 'policy.partial-coverage-with-minimum-score',
235
+ severity: 'warning',
236
+ message: `quality.minimumScore was evaluated against a partial assessment (${parts.join('; ')}) — it may not reflect a full check of this server.`,
237
+ serverName: serverNamesLabel,
238
+ category: 'configuration',
239
+ });
240
+ }
241
+ return results;
242
+ }
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,44 @@
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
+ }
24
+ /** Printed once per report (not per-server) — coverage and the disclaimer
25
+ * are report-level facts, and repeating them per server would overwhelm
26
+ * the output for a multi-server fleet. */
27
+ function formatCoverageAndDisclaimer(quality) {
28
+ const lines = [];
29
+ const gaps = Object.entries(quality.coverage).filter(([, status]) => status !== 'covered');
30
+ const coverageLine = gaps.length === 0
31
+ ? `Coverage: ${quality.coveragePercent}% (all dimensions fully evaluated)`
32
+ : `Coverage: ${quality.coveragePercent}% (${gaps
33
+ .map(([dim, status]) => `${DIMENSION_LABELS[dim] ?? dim}: ${status}`)
34
+ .join(', ')})`;
35
+ lines.push(coverageLine);
36
+ if (quality.unscoredServers.length > 0) {
37
+ lines.push(`Note: ${quality.unscoredServers.length} server(s) could not be scored (connection failed): ${quality.unscoredServers.join(', ')}`);
38
+ }
39
+ lines.push(quality.disclaimer);
40
+ return lines;
41
+ }
1
42
  /** Human-readable plain text report formatter. */
2
43
  export function formatReportHuman(report, options = {}) {
3
44
  const lines = [];
@@ -30,6 +71,19 @@ export function formatReportHuman(report, options = {}) {
30
71
  if (conn.error) {
31
72
  lines.push(` ${conn.error.stage}: ${conn.error.message}`);
32
73
  }
74
+ const perServerScore = options.showScore ? report.quality?.perServer[conn.server.name] : undefined;
75
+ if (perServerScore) {
76
+ lines.push('');
77
+ lines.push(...formatScoreBlock('QUALITY', perServerScore));
78
+ }
79
+ }
80
+ if (options.showScore && report.quality) {
81
+ if (Object.keys(report.quality.perServer).length > 1) {
82
+ lines.push('');
83
+ lines.push(...formatScoreBlock('OVERALL QUALITY (all servers)', report.quality));
84
+ }
85
+ lines.push('');
86
+ lines.push(...formatCoverageAndDisclaimer(report.quality));
33
87
  }
34
88
  if (report.diagnostics.length > 0) {
35
89
  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?: unknown[];
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,46 @@ 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
+ /** Whether the checks that feed a dimension actually ran this time.
153
+ * 'covered': every built-in check for this dimension ran.
154
+ * 'partial': at least one check contributing to this dimension ran, but not all of them.
155
+ * 'not-covered': no check contributing to this dimension ran — a 100 in
156
+ * that dimension means "nothing flagged it," not "nothing wrong exists." */
157
+ export type CoverageStatus = 'covered' | 'partial' | 'not-covered';
158
+ export type QualityCoverage = Record<QualityDimension, CoverageStatus>;
159
+ export interface ReportQualityScore extends QualityScoreBreakdown {
160
+ /** Per-connected-server breakdown; a server that never connected has no entry (nothing to score). */
161
+ perServer: Record<string, QualityScoreBreakdown>;
162
+ /** Per-dimension coverage, derived from which checks actually ran (`RunOptions.checks`) —
163
+ * never assume a dimension was fully evaluated just because it scored 100. */
164
+ coverage: QualityCoverage;
165
+ /** 0-100: 'covered' dimensions count as 1, 'partial' as 0.5, 'not-covered' as 0, averaged across all 5. */
166
+ coveragePercent: number;
167
+ /** Names of servers actually included in this score (connected + scored). */
168
+ scoredServers: string[];
169
+ /** Names of servers that could NOT be scored (failed to connect) — never silently dropped from view. */
170
+ unscoredServers: string[];
171
+ /** A short, load-bearing reminder of what this number does and doesn't mean — see src/quality-score.ts. */
172
+ disclaimer: string;
173
+ }
97
174
  export interface RunReport {
98
175
  configSource?: string;
99
176
  connections: MCPConnection[];
@@ -105,4 +182,7 @@ export interface RunReport {
105
182
  errors: number;
106
183
  warnings: number;
107
184
  };
185
+ /** Deterministic quality score derived from `diagnostics`/`connections` — see src/quality-score.ts.
186
+ * `undefined` when no server connected (nothing to score). */
187
+ quality?: ReportQualityScore;
108
188
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-medic",
3
- "version": "1.2.2",
3
+ "version": "1.2.4",
4
4
  "description": "Diagnose broken MCP (Model Context Protocol) server configs before they break your agent silently.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",