mcp-vitals 0.1.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/CHANGELOG.md +33 -0
- package/LICENSE +21 -0
- package/README.md +222 -0
- package/bin/mcp-vitals.js +12 -0
- package/dist/args.d.ts +9 -0
- package/dist/args.js +50 -0
- package/dist/assertions/loader.d.ts +9 -0
- package/dist/assertions/loader.js +75 -0
- package/dist/assertions/run.d.ts +19 -0
- package/dist/assertions/run.js +154 -0
- package/dist/assertions/schema.d.ts +147 -0
- package/dist/assertions/schema.js +72 -0
- package/dist/bench/engine.d.ts +7 -0
- package/dist/bench/engine.js +121 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +137 -0
- package/dist/commands/bench.d.ts +13 -0
- package/dist/commands/bench.js +129 -0
- package/dist/commands/call.d.ts +8 -0
- package/dist/commands/call.js +84 -0
- package/dist/commands/check.d.ts +13 -0
- package/dist/commands/check.js +140 -0
- package/dist/commands/inspect.d.ts +10 -0
- package/dist/commands/inspect.js +129 -0
- package/dist/commands/ping.d.ts +6 -0
- package/dist/commands/ping.js +55 -0
- package/dist/context.d.ts +30 -0
- package/dist/context.js +114 -0
- package/dist/errors.d.ts +33 -0
- package/dist/errors.js +52 -0
- package/dist/glob.d.ts +3 -0
- package/dist/glob.js +40 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +11 -0
- package/dist/mcpClient.d.ts +38 -0
- package/dist/mcpClient.js +192 -0
- package/dist/output.d.ts +2 -0
- package/dist/output.js +8 -0
- package/dist/renderers/colors.d.ts +9 -0
- package/dist/renderers/colors.js +16 -0
- package/dist/renderers/json.d.ts +2 -0
- package/dist/renderers/json.js +5 -0
- package/dist/renderers/junit.d.ts +3 -0
- package/dist/renderers/junit.js +32 -0
- package/dist/renderers/progress.d.ts +16 -0
- package/dist/renderers/progress.js +29 -0
- package/dist/renderers/table.d.ts +14 -0
- package/dist/renderers/table.js +43 -0
- package/dist/schema.d.ts +12 -0
- package/dist/schema.js +47 -0
- package/dist/stats.d.ts +12 -0
- package/dist/stats.js +54 -0
- package/dist/thresholds.d.ts +21 -0
- package/dist/thresholds.js +109 -0
- package/dist/transport.d.ts +8 -0
- package/dist/transport.js +68 -0
- package/dist/types.d.ts +180 -0
- package/dist/types.js +2 -0
- package/package.json +83 -0
- package/schema/assertions.schema.json +177 -0
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { UsageError } from './errors.js';
|
|
2
|
+
export const METRICS = [
|
|
3
|
+
'p50',
|
|
4
|
+
'p90',
|
|
5
|
+
'p95',
|
|
6
|
+
'p99',
|
|
7
|
+
'max',
|
|
8
|
+
'min',
|
|
9
|
+
'mean',
|
|
10
|
+
'stddev',
|
|
11
|
+
'errorRate',
|
|
12
|
+
];
|
|
13
|
+
const OPS = ['<=', '>=', '!=', '==', '<', '>'];
|
|
14
|
+
/**
|
|
15
|
+
* Parse a duration to milliseconds.
|
|
16
|
+
* Bare number => ms. Suffix `ms` => ms. Suffix `s` => seconds.
|
|
17
|
+
*/
|
|
18
|
+
export function parseDuration(input) {
|
|
19
|
+
if (typeof input === 'number')
|
|
20
|
+
return input;
|
|
21
|
+
const s = input.trim();
|
|
22
|
+
const m = /^(-?\d+(?:\.\d+)?)\s*(ms|s)?$/i.exec(s);
|
|
23
|
+
if (!m)
|
|
24
|
+
throw new UsageError(`invalid duration: "${input}"`);
|
|
25
|
+
const value = Number(m[1]);
|
|
26
|
+
const unit = (m[2] ?? 'ms').toLowerCase();
|
|
27
|
+
return unit === 's' ? value * 1000 : value;
|
|
28
|
+
}
|
|
29
|
+
/** errorRate is a fraction 0..1; everything else is a duration. */
|
|
30
|
+
export function parseBound(metric, raw) {
|
|
31
|
+
if (metric === 'errorRate') {
|
|
32
|
+
const n = typeof raw === 'number' ? raw : Number(String(raw).trim());
|
|
33
|
+
if (!Number.isFinite(n))
|
|
34
|
+
throw new UsageError(`invalid errorRate bound: "${raw}"`);
|
|
35
|
+
return n;
|
|
36
|
+
}
|
|
37
|
+
return parseDuration(raw);
|
|
38
|
+
}
|
|
39
|
+
/** Parse an inline `--fail-on` expression like `p95<200ms` or `errorRate<=0`. */
|
|
40
|
+
export function parseExpr(expr) {
|
|
41
|
+
const compact = expr.replace(/\s+/g, '');
|
|
42
|
+
for (const op of OPS) {
|
|
43
|
+
const i = compact.indexOf(op);
|
|
44
|
+
if (i > 0) {
|
|
45
|
+
const metric = compact.slice(0, i);
|
|
46
|
+
const rest = compact.slice(i + op.length);
|
|
47
|
+
if (!METRICS.includes(metric)) {
|
|
48
|
+
throw new UsageError(`unknown metric "${metric}" in "${expr}" (allowed: ${METRICS.join(', ')})`);
|
|
49
|
+
}
|
|
50
|
+
if (rest === '')
|
|
51
|
+
throw new UsageError(`missing bound in "${expr}"`);
|
|
52
|
+
return { metric, op, bound: parseBound(metric, rest) };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
throw new UsageError(`invalid assertion "${expr}" (expected e.g. p95<200ms)`);
|
|
56
|
+
}
|
|
57
|
+
function metricValue(metric, stats, throughput) {
|
|
58
|
+
if (metric === 'errorRate')
|
|
59
|
+
return throughput.errorRate;
|
|
60
|
+
if (!stats)
|
|
61
|
+
return null;
|
|
62
|
+
switch (metric) {
|
|
63
|
+
case 'p50':
|
|
64
|
+
return stats.p50;
|
|
65
|
+
case 'p90':
|
|
66
|
+
return stats.p90;
|
|
67
|
+
case 'p95':
|
|
68
|
+
return stats.p95;
|
|
69
|
+
case 'p99':
|
|
70
|
+
return stats.p99;
|
|
71
|
+
case 'max':
|
|
72
|
+
return stats.max;
|
|
73
|
+
case 'min':
|
|
74
|
+
return stats.min;
|
|
75
|
+
case 'mean':
|
|
76
|
+
return stats.mean;
|
|
77
|
+
case 'stddev':
|
|
78
|
+
return stats.stddev;
|
|
79
|
+
default:
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function compare(actual, op, bound) {
|
|
84
|
+
switch (op) {
|
|
85
|
+
case '<':
|
|
86
|
+
return actual < bound;
|
|
87
|
+
case '<=':
|
|
88
|
+
return actual <= bound;
|
|
89
|
+
case '>':
|
|
90
|
+
return actual > bound;
|
|
91
|
+
case '>=':
|
|
92
|
+
return actual >= bound;
|
|
93
|
+
case '==':
|
|
94
|
+
return actual === bound;
|
|
95
|
+
case '!=':
|
|
96
|
+
return actual !== bound;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Evaluate one parsed expression against a stats + throughput pair. */
|
|
100
|
+
export function evaluate(parsed, stats, throughput) {
|
|
101
|
+
const actual = metricValue(parsed.metric, stats, throughput);
|
|
102
|
+
const exprStr = `${parsed.metric}${parsed.op}${parsed.bound}`;
|
|
103
|
+
const pass = actual === null ? false : compare(actual, parsed.op, parsed.bound);
|
|
104
|
+
return { expr: exprStr, metric: parsed.metric, op: parsed.op, bound: parsed.bound, actual, pass };
|
|
105
|
+
}
|
|
106
|
+
/** Convenience for inline string expressions. */
|
|
107
|
+
export function evaluateExpr(expr, stats, throughput) {
|
|
108
|
+
return evaluate(parseExpr(expr), stats, throughput);
|
|
109
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
2
|
+
import type { TransportKind, TransportSpec } from './types.js';
|
|
3
|
+
/** Infer the transport kind from the spec when not explicitly forced. */
|
|
4
|
+
export declare function inferKind(spec: TransportSpec): TransportKind;
|
|
5
|
+
/** Build a fresh Transport instance for the requested kind. */
|
|
6
|
+
export declare function createTransport(spec: TransportSpec, kind: TransportKind): Transport;
|
|
7
|
+
/** Human label for the resolved target, for headers and JSON output. */
|
|
8
|
+
export declare function describeTarget(spec: TransportSpec, kind: TransportKind): string;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { StdioClientTransport, getDefaultEnvironment } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
2
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
3
|
+
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
4
|
+
import { UsageError } from './errors.js';
|
|
5
|
+
/** Infer the transport kind from the spec when not explicitly forced. */
|
|
6
|
+
export function inferKind(spec) {
|
|
7
|
+
if (spec.forced)
|
|
8
|
+
return spec.forced;
|
|
9
|
+
if (spec.url)
|
|
10
|
+
return 'http';
|
|
11
|
+
if (spec.command)
|
|
12
|
+
return 'stdio';
|
|
13
|
+
throw new UsageError('no server specified: pass a stdio command or --url <url>');
|
|
14
|
+
}
|
|
15
|
+
function processEnvAsStrings() {
|
|
16
|
+
const out = {};
|
|
17
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
18
|
+
if (typeof v === 'string')
|
|
19
|
+
out[k] = v;
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
function stdioEnv(spec) {
|
|
24
|
+
return {
|
|
25
|
+
...getDefaultEnvironment(),
|
|
26
|
+
...(spec.inheritEnv ? processEnvAsStrings() : {}),
|
|
27
|
+
...spec.env,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function toUrl(spec) {
|
|
31
|
+
if (!spec.url)
|
|
32
|
+
throw new UsageError('a URL is required for http/sse transports (use --url)');
|
|
33
|
+
try {
|
|
34
|
+
return new URL(spec.url);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
throw new UsageError(`invalid --url: "${spec.url}"`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Build a fresh Transport instance for the requested kind. */
|
|
41
|
+
export function createTransport(spec, kind) {
|
|
42
|
+
switch (kind) {
|
|
43
|
+
case 'stdio': {
|
|
44
|
+
if (!spec.command)
|
|
45
|
+
throw new UsageError('no stdio command specified');
|
|
46
|
+
return new StdioClientTransport({
|
|
47
|
+
command: spec.command,
|
|
48
|
+
args: spec.args,
|
|
49
|
+
env: stdioEnv(spec),
|
|
50
|
+
stderr: 'pipe',
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
case 'http':
|
|
54
|
+
return new StreamableHTTPClientTransport(toUrl(spec), {
|
|
55
|
+
requestInit: { headers: spec.headers },
|
|
56
|
+
});
|
|
57
|
+
case 'sse':
|
|
58
|
+
return new SSEClientTransport(toUrl(spec), {
|
|
59
|
+
requestInit: { headers: spec.headers },
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/** Human label for the resolved target, for headers and JSON output. */
|
|
64
|
+
export function describeTarget(spec, kind) {
|
|
65
|
+
if (kind === 'stdio')
|
|
66
|
+
return [spec.command, ...spec.args].filter(Boolean).join(' ');
|
|
67
|
+
return spec.url ?? '(unknown)';
|
|
68
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
export type TransportKind = 'stdio' | 'http' | 'sse';
|
|
2
|
+
/** How to reach the MCP server under test. */
|
|
3
|
+
export interface TransportSpec {
|
|
4
|
+
/** Forced transport, or undefined to infer from command/url. */
|
|
5
|
+
forced?: TransportKind;
|
|
6
|
+
/** stdio: executable + argv. */
|
|
7
|
+
command?: string;
|
|
8
|
+
args: string[];
|
|
9
|
+
/** http/sse: target URL. */
|
|
10
|
+
url?: string;
|
|
11
|
+
/** http/sse: extra request headers (auth, etc). */
|
|
12
|
+
headers: Record<string, string>;
|
|
13
|
+
/** stdio: extra env vars merged over getDefaultEnvironment(). */
|
|
14
|
+
env: Record<string, string>;
|
|
15
|
+
/** stdio: spread process.env into the child before --env vars. */
|
|
16
|
+
inheritEnv: boolean;
|
|
17
|
+
/** Handshake timeout (ms). */
|
|
18
|
+
connectTimeoutMs: number;
|
|
19
|
+
/** Per-request timeout (ms). */
|
|
20
|
+
requestTimeoutMs: number;
|
|
21
|
+
}
|
|
22
|
+
/** Global options resolved once and shared by every command. */
|
|
23
|
+
export interface RunContext {
|
|
24
|
+
json: boolean;
|
|
25
|
+
color: boolean;
|
|
26
|
+
quiet: boolean;
|
|
27
|
+
verbose: boolean;
|
|
28
|
+
}
|
|
29
|
+
export interface SchemaError {
|
|
30
|
+
path: string;
|
|
31
|
+
message: string;
|
|
32
|
+
}
|
|
33
|
+
export interface ToolInfo {
|
|
34
|
+
name: string;
|
|
35
|
+
title?: string;
|
|
36
|
+
description?: string;
|
|
37
|
+
inputSchema?: unknown;
|
|
38
|
+
outputSchema?: unknown;
|
|
39
|
+
requiredArgs: number;
|
|
40
|
+
totalArgs: number;
|
|
41
|
+
schemaValid: boolean;
|
|
42
|
+
schemaErrors: SchemaError[];
|
|
43
|
+
}
|
|
44
|
+
export interface ResourceInfo {
|
|
45
|
+
uri: string;
|
|
46
|
+
name?: string;
|
|
47
|
+
mimeType?: string;
|
|
48
|
+
}
|
|
49
|
+
export interface PromptInfo {
|
|
50
|
+
name: string;
|
|
51
|
+
description?: string;
|
|
52
|
+
arguments: {
|
|
53
|
+
name: string;
|
|
54
|
+
required?: boolean;
|
|
55
|
+
}[];
|
|
56
|
+
}
|
|
57
|
+
export interface ServerIdentity {
|
|
58
|
+
name: string;
|
|
59
|
+
version: string;
|
|
60
|
+
protocolVersion?: string;
|
|
61
|
+
instructions?: string;
|
|
62
|
+
}
|
|
63
|
+
export interface CapabilitySummary {
|
|
64
|
+
tools: boolean;
|
|
65
|
+
resources: boolean;
|
|
66
|
+
prompts: boolean;
|
|
67
|
+
logging: boolean;
|
|
68
|
+
completions: boolean;
|
|
69
|
+
raw: Record<string, unknown>;
|
|
70
|
+
}
|
|
71
|
+
export interface Stats {
|
|
72
|
+
count: number;
|
|
73
|
+
min: number;
|
|
74
|
+
mean: number;
|
|
75
|
+
p50: number;
|
|
76
|
+
p90: number;
|
|
77
|
+
p95: number;
|
|
78
|
+
p99: number;
|
|
79
|
+
max: number;
|
|
80
|
+
stddev: number;
|
|
81
|
+
unit: 'ms';
|
|
82
|
+
}
|
|
83
|
+
export interface Throughput {
|
|
84
|
+
rps: number;
|
|
85
|
+
completed: number;
|
|
86
|
+
errors: number;
|
|
87
|
+
errorRate: number;
|
|
88
|
+
}
|
|
89
|
+
export type ProbeOp = 'listTools' | 'listResources' | 'listPrompts';
|
|
90
|
+
export interface BenchConfig {
|
|
91
|
+
targetKind: 'tool' | 'probe';
|
|
92
|
+
targetName: string;
|
|
93
|
+
args: unknown;
|
|
94
|
+
iterations?: number;
|
|
95
|
+
durationMs?: number;
|
|
96
|
+
warmup: number;
|
|
97
|
+
concurrency: number;
|
|
98
|
+
rps?: number;
|
|
99
|
+
keepAlive: boolean;
|
|
100
|
+
}
|
|
101
|
+
export interface BenchSample {
|
|
102
|
+
ms: number;
|
|
103
|
+
error: boolean;
|
|
104
|
+
}
|
|
105
|
+
export interface BenchResult {
|
|
106
|
+
coldStartMs: number | null;
|
|
107
|
+
warm: Stats | null;
|
|
108
|
+
throughput: Throughput;
|
|
109
|
+
samples: number[];
|
|
110
|
+
raw: BenchSample[];
|
|
111
|
+
}
|
|
112
|
+
export type CompareOp = '<' | '<=' | '>' | '>=' | '==' | '!=';
|
|
113
|
+
export interface AssertionOutcome {
|
|
114
|
+
expr: string;
|
|
115
|
+
metric: string;
|
|
116
|
+
op: CompareOp;
|
|
117
|
+
bound: number;
|
|
118
|
+
actual: number | null;
|
|
119
|
+
pass: boolean;
|
|
120
|
+
}
|
|
121
|
+
export type SuiteKind = 'presence' | 'schema' | 'latency';
|
|
122
|
+
export type CheckStatus = 'pass' | 'fail' | 'skip';
|
|
123
|
+
export interface CheckRow {
|
|
124
|
+
id: string;
|
|
125
|
+
kind: SuiteKind;
|
|
126
|
+
target: string;
|
|
127
|
+
expected: string;
|
|
128
|
+
actual: string;
|
|
129
|
+
status: CheckStatus;
|
|
130
|
+
}
|
|
131
|
+
export interface CheckSummary {
|
|
132
|
+
passed: number;
|
|
133
|
+
failed: number;
|
|
134
|
+
skipped: number;
|
|
135
|
+
durationMs: number;
|
|
136
|
+
}
|
|
137
|
+
export interface ServerConfig {
|
|
138
|
+
command?: string;
|
|
139
|
+
args?: string[];
|
|
140
|
+
env?: Record<string, string>;
|
|
141
|
+
url?: string;
|
|
142
|
+
headers?: Record<string, string>;
|
|
143
|
+
transport?: TransportKind;
|
|
144
|
+
connectTimeoutMs?: number;
|
|
145
|
+
timeoutMs?: number;
|
|
146
|
+
}
|
|
147
|
+
export interface BenchDefaults {
|
|
148
|
+
iterations?: number;
|
|
149
|
+
warmup?: number;
|
|
150
|
+
concurrency?: number;
|
|
151
|
+
timeoutMs?: number;
|
|
152
|
+
}
|
|
153
|
+
export interface ExpectConfig {
|
|
154
|
+
tools?: string[];
|
|
155
|
+
resources?: string[];
|
|
156
|
+
prompts?: string[];
|
|
157
|
+
schemasValid?: boolean;
|
|
158
|
+
}
|
|
159
|
+
export interface LatencyAssertion {
|
|
160
|
+
id: string;
|
|
161
|
+
tool?: string;
|
|
162
|
+
probe?: ProbeOp;
|
|
163
|
+
args?: unknown;
|
|
164
|
+
iterations?: number;
|
|
165
|
+
warmup?: number;
|
|
166
|
+
concurrency?: number;
|
|
167
|
+
p50?: string | number;
|
|
168
|
+
p90?: string | number;
|
|
169
|
+
p95?: string | number;
|
|
170
|
+
p99?: string | number;
|
|
171
|
+
max?: string | number;
|
|
172
|
+
mean?: string | number;
|
|
173
|
+
errorRate?: number;
|
|
174
|
+
}
|
|
175
|
+
export interface AssertionsConfig {
|
|
176
|
+
server: ServerConfig;
|
|
177
|
+
defaults?: BenchDefaults;
|
|
178
|
+
expect?: ExpectConfig;
|
|
179
|
+
latency?: LatencyAssertion[];
|
|
180
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mcp-vitals",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Vital signs for your MCP server: inspect capabilities, benchmark tool-call latency (p50/p95/p99), and assert health in CI. The ab/k6/pytest for MCP servers — non-interactive, scriptable, LLM-free.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mcp",
|
|
7
|
+
"model-context-protocol",
|
|
8
|
+
"benchmark",
|
|
9
|
+
"latency",
|
|
10
|
+
"load-test",
|
|
11
|
+
"ci",
|
|
12
|
+
"cli",
|
|
13
|
+
"inspector",
|
|
14
|
+
"anthropic",
|
|
15
|
+
"claude",
|
|
16
|
+
"devtools",
|
|
17
|
+
"p95",
|
|
18
|
+
"observability"
|
|
19
|
+
],
|
|
20
|
+
"type": "module",
|
|
21
|
+
"bin": {
|
|
22
|
+
"mcp-vitals": "bin/mcp-vitals.js"
|
|
23
|
+
},
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./dist/index.d.ts",
|
|
27
|
+
"import": "./dist/index.js"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
"dist",
|
|
32
|
+
"bin",
|
|
33
|
+
"schema",
|
|
34
|
+
"README.md",
|
|
35
|
+
"LICENSE",
|
|
36
|
+
"CHANGELOG.md"
|
|
37
|
+
],
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=20"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
43
|
+
"build": "npm run clean && tsc -p tsconfig.build.json",
|
|
44
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
45
|
+
"lint": "eslint .",
|
|
46
|
+
"pretest": "npm run build",
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"test:watch": "vitest",
|
|
49
|
+
"coverage": "vitest run --coverage",
|
|
50
|
+
"prepublishOnly": "npm run build",
|
|
51
|
+
"dev": "tsx src/cli.ts"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
55
|
+
"ajv": "^8.17.1",
|
|
56
|
+
"ajv-formats": "^3.0.1",
|
|
57
|
+
"commander": "^12.1.0",
|
|
58
|
+
"picocolors": "^1.1.1",
|
|
59
|
+
"yaml": "^2.6.1"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@eslint/js": "^9.17.0",
|
|
63
|
+
"@types/node": "^20.14.0",
|
|
64
|
+
"@vitest/coverage-v8": "^2.1.8",
|
|
65
|
+
"eslint": "^9.17.0",
|
|
66
|
+
"execa": "^9.5.2",
|
|
67
|
+
"globals": "^15.15.0",
|
|
68
|
+
"tsx": "^4.19.2",
|
|
69
|
+
"typescript": "^5.6.3",
|
|
70
|
+
"typescript-eslint": "^8.18.1",
|
|
71
|
+
"vitest": "^2.1.8"
|
|
72
|
+
},
|
|
73
|
+
"author": "Shaxzodbek Sobirov <shaxzodbek@blaze.uz>",
|
|
74
|
+
"license": "MIT",
|
|
75
|
+
"homepage": "https://github.com/shaxzodbek-uzb/mcp-vitals#readme",
|
|
76
|
+
"repository": {
|
|
77
|
+
"type": "git",
|
|
78
|
+
"url": "git+https://github.com/shaxzodbek-uzb/mcp-vitals.git"
|
|
79
|
+
},
|
|
80
|
+
"bugs": {
|
|
81
|
+
"url": "https://github.com/shaxzodbek-uzb/mcp-vitals/issues"
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://unpkg.com/mcp-vitals/schema/assertions.schema.json",
|
|
4
|
+
"title": "mcp-vitals assertions",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"additionalProperties": false,
|
|
7
|
+
"required": [
|
|
8
|
+
"server"
|
|
9
|
+
],
|
|
10
|
+
"properties": {
|
|
11
|
+
"$schema": {
|
|
12
|
+
"type": "string"
|
|
13
|
+
},
|
|
14
|
+
"server": {
|
|
15
|
+
"type": "object",
|
|
16
|
+
"additionalProperties": false,
|
|
17
|
+
"properties": {
|
|
18
|
+
"command": {
|
|
19
|
+
"type": "string"
|
|
20
|
+
},
|
|
21
|
+
"args": {
|
|
22
|
+
"type": "array",
|
|
23
|
+
"items": {
|
|
24
|
+
"type": "string"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"env": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"additionalProperties": {
|
|
30
|
+
"type": "string"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"url": {
|
|
34
|
+
"type": "string"
|
|
35
|
+
},
|
|
36
|
+
"headers": {
|
|
37
|
+
"type": "object",
|
|
38
|
+
"additionalProperties": {
|
|
39
|
+
"type": "string"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"transport": {
|
|
43
|
+
"enum": [
|
|
44
|
+
"stdio",
|
|
45
|
+
"http",
|
|
46
|
+
"sse"
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
"connectTimeoutMs": {
|
|
50
|
+
"type": "number"
|
|
51
|
+
},
|
|
52
|
+
"timeoutMs": {
|
|
53
|
+
"type": "number"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
"defaults": {
|
|
58
|
+
"type": "object",
|
|
59
|
+
"additionalProperties": false,
|
|
60
|
+
"properties": {
|
|
61
|
+
"iterations": {
|
|
62
|
+
"type": "number"
|
|
63
|
+
},
|
|
64
|
+
"warmup": {
|
|
65
|
+
"type": "number"
|
|
66
|
+
},
|
|
67
|
+
"concurrency": {
|
|
68
|
+
"type": "number"
|
|
69
|
+
},
|
|
70
|
+
"timeoutMs": {
|
|
71
|
+
"type": "number"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
"expect": {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"additionalProperties": false,
|
|
78
|
+
"properties": {
|
|
79
|
+
"tools": {
|
|
80
|
+
"type": "array",
|
|
81
|
+
"items": {
|
|
82
|
+
"type": "string"
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"resources": {
|
|
86
|
+
"type": "array",
|
|
87
|
+
"items": {
|
|
88
|
+
"type": "string"
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"prompts": {
|
|
92
|
+
"type": "array",
|
|
93
|
+
"items": {
|
|
94
|
+
"type": "string"
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"schemasValid": {
|
|
98
|
+
"type": "boolean"
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"latency": {
|
|
103
|
+
"type": "array",
|
|
104
|
+
"items": {
|
|
105
|
+
"type": "object",
|
|
106
|
+
"additionalProperties": false,
|
|
107
|
+
"required": [
|
|
108
|
+
"id"
|
|
109
|
+
],
|
|
110
|
+
"properties": {
|
|
111
|
+
"id": {
|
|
112
|
+
"type": "string"
|
|
113
|
+
},
|
|
114
|
+
"tool": {
|
|
115
|
+
"type": "string"
|
|
116
|
+
},
|
|
117
|
+
"probe": {
|
|
118
|
+
"enum": [
|
|
119
|
+
"listTools",
|
|
120
|
+
"listResources",
|
|
121
|
+
"listPrompts"
|
|
122
|
+
]
|
|
123
|
+
},
|
|
124
|
+
"args": {},
|
|
125
|
+
"iterations": {
|
|
126
|
+
"type": "number"
|
|
127
|
+
},
|
|
128
|
+
"warmup": {
|
|
129
|
+
"type": "number"
|
|
130
|
+
},
|
|
131
|
+
"concurrency": {
|
|
132
|
+
"type": "number"
|
|
133
|
+
},
|
|
134
|
+
"p50": {
|
|
135
|
+
"type": [
|
|
136
|
+
"string",
|
|
137
|
+
"number"
|
|
138
|
+
]
|
|
139
|
+
},
|
|
140
|
+
"p90": {
|
|
141
|
+
"type": [
|
|
142
|
+
"string",
|
|
143
|
+
"number"
|
|
144
|
+
]
|
|
145
|
+
},
|
|
146
|
+
"p95": {
|
|
147
|
+
"type": [
|
|
148
|
+
"string",
|
|
149
|
+
"number"
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
"p99": {
|
|
153
|
+
"type": [
|
|
154
|
+
"string",
|
|
155
|
+
"number"
|
|
156
|
+
]
|
|
157
|
+
},
|
|
158
|
+
"max": {
|
|
159
|
+
"type": [
|
|
160
|
+
"string",
|
|
161
|
+
"number"
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
"mean": {
|
|
165
|
+
"type": [
|
|
166
|
+
"string",
|
|
167
|
+
"number"
|
|
168
|
+
]
|
|
169
|
+
},
|
|
170
|
+
"errorRate": {
|
|
171
|
+
"type": "number"
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|