@beeeeen/mcp-probe 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/dist/cli.js ADDED
@@ -0,0 +1,292 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, writeFileSync } from 'node:fs';
3
+ import { run, exitCodeFor } from './run.js';
4
+ import { renderTerminal } from './report/terminal.js';
5
+ import { renderJUnit } from './report/junit.js';
6
+ const VERSION = '0.1.0';
7
+ const HELP = `
8
+ mcp-probe ${VERSION}
9
+ Conformance and robustness tests for MCP servers. Built for CI.
10
+
11
+ USAGE
12
+ mcp-probe <command> [args...] run a stdio server and probe it
13
+ mcp-probe --url <url> probe a streamable-HTTP server
14
+ mcp-probe --config <file> --server <name>
15
+
16
+ EXAMPLES
17
+ mcp-probe node build/index.js
18
+ mcp-probe -- npx -y @modelcontextprotocol/server-filesystem /tmp
19
+ mcp-probe --url http://localhost:3000/mcp
20
+ mcp-probe --config ~/.claude.json --server github
21
+ mcp-probe --junit results.xml --strict -- node server.js
22
+
23
+ OUTPUT
24
+ --json machine-readable report on stdout
25
+ --json-out <file> write the JSON report to a file, keeping the
26
+ human-readable report on stdout
27
+ --junit <file> write JUnit XML (GitHub, GitLab, Jenkins, ...)
28
+ --quiet suppress the terminal report
29
+ --verbose expand the explanation for warnings too
30
+
31
+ SELECTION
32
+ --only <id> run only these checks; repeatable, prefix match
33
+ --skip <id> skip these checks; repeatable, prefix match
34
+ groups: protocol, schema, robustness, hygiene
35
+
36
+ BEHAVIOUR
37
+ --timeout <ms> per-request timeout (default 10000)
38
+ --strict treat warnings as failures for the exit code
39
+ --call-tools invoke every tool with empty arguments.
40
+ OFF by default: mcp-probe never triggers a side
41
+ effect you did not ask for.
42
+ --safe-tool <name> invoke just this tool; repeatable
43
+ --header <k:v> extra HTTP header; repeatable (http only)
44
+ --env <k=v> extra environment variable; repeatable (stdio only)
45
+
46
+ EXIT CODES
47
+ 0 no failures
48
+ 1 at least one failure (or a warning under --strict)
49
+ 2 could not run at all
50
+
51
+ mcp-probe's own flags come first. Everything from the first non-flag argument
52
+ (or from a bare "--") onward is the server's command line, passed through
53
+ untouched, so a server flag can never collide with one of ours. Use "--"
54
+ whenever the command itself starts with a dash.
55
+ `;
56
+ function parseArgs(argv) {
57
+ const out = {
58
+ target: null,
59
+ options: {},
60
+ json: false,
61
+ jsonOut: null,
62
+ junit: null,
63
+ quiet: false,
64
+ verbose: false,
65
+ help: false,
66
+ version: false,
67
+ };
68
+ const only = [];
69
+ const skip = [];
70
+ const safeTools = [];
71
+ const headers = {};
72
+ const env = {};
73
+ let url = null;
74
+ let configPath = null;
75
+ let serverName = null;
76
+ let command = null;
77
+ let commandArgs = [];
78
+ for (let i = 0; i < argv.length; i++) {
79
+ const arg = argv[i];
80
+ // Once a command is known, the rest belongs to the server, untouched.
81
+ if (command !== null) {
82
+ commandArgs.push(arg);
83
+ continue;
84
+ }
85
+ // `--` ends our own flags: the next token is the command, whatever it
86
+ // looks like. Needed when the command itself begins with a dash.
87
+ if (arg === '--') {
88
+ const rest = argv.slice(i + 1);
89
+ if (rest.length === 0)
90
+ return { ...out, error: '`--` must be followed by the server command' };
91
+ command = rest[0];
92
+ commandArgs = rest.slice(1);
93
+ break;
94
+ }
95
+ const next = () => argv[++i];
96
+ switch (arg) {
97
+ case '-h':
98
+ case '--help':
99
+ out.help = true;
100
+ break;
101
+ case '-v':
102
+ case '--version':
103
+ out.version = true;
104
+ break;
105
+ case '--json':
106
+ out.json = true;
107
+ break;
108
+ case '--quiet':
109
+ out.quiet = true;
110
+ break;
111
+ case '--verbose':
112
+ out.verbose = true;
113
+ break;
114
+ case '--strict':
115
+ out.options.strict = true;
116
+ break;
117
+ case '--call-tools':
118
+ out.options.callTools = true;
119
+ break;
120
+ case '--junit':
121
+ out.junit = next() ?? null;
122
+ break;
123
+ case '--json-out':
124
+ out.jsonOut = next() ?? null;
125
+ break;
126
+ case '--url':
127
+ url = next() ?? null;
128
+ break;
129
+ case '--config':
130
+ configPath = next() ?? null;
131
+ break;
132
+ case '--server':
133
+ serverName = next() ?? null;
134
+ break;
135
+ case '--timeout': {
136
+ const v = Number(next());
137
+ if (!Number.isFinite(v) || v <= 0)
138
+ return { ...out, error: '--timeout needs a positive number of milliseconds' };
139
+ out.options.timeoutMs = v;
140
+ break;
141
+ }
142
+ case '--only':
143
+ only.push(...(next() ?? '').split(',').filter(Boolean));
144
+ break;
145
+ case '--skip':
146
+ skip.push(...(next() ?? '').split(',').filter(Boolean));
147
+ break;
148
+ case '--safe-tool':
149
+ safeTools.push(...(next() ?? '').split(',').filter(Boolean));
150
+ break;
151
+ case '--header': {
152
+ const raw = next() ?? '';
153
+ const idx = raw.indexOf(':');
154
+ if (idx < 1)
155
+ return { ...out, error: `--header expects "Name: value", got "${raw}"` };
156
+ headers[raw.slice(0, idx).trim()] = raw.slice(idx + 1).trim();
157
+ break;
158
+ }
159
+ case '--env': {
160
+ const raw = next() ?? '';
161
+ const idx = raw.indexOf('=');
162
+ if (idx < 1)
163
+ return { ...out, error: `--env expects "KEY=value", got "${raw}"` };
164
+ env[raw.slice(0, idx)] = raw.slice(idx + 1);
165
+ break;
166
+ }
167
+ default:
168
+ if (arg.startsWith('-'))
169
+ return { ...out, error: `Unknown option "${arg}". Try --help.` };
170
+ command = arg;
171
+ }
172
+ }
173
+ if (only.length)
174
+ out.options.only = only;
175
+ if (skip.length)
176
+ out.options.skip = skip;
177
+ if (safeTools.length)
178
+ out.options.safeTools = safeTools;
179
+ if (configPath) {
180
+ const resolved = resolveFromConfig(configPath, serverName);
181
+ if ('error' in resolved)
182
+ return { ...out, error: resolved.error };
183
+ out.target = resolved.target;
184
+ if (Object.keys(env).length)
185
+ out.target.env = { ...out.target.env, ...env };
186
+ return out;
187
+ }
188
+ if (url) {
189
+ out.target = { kind: 'http', url, headers };
190
+ return out;
191
+ }
192
+ if (command) {
193
+ out.target = { kind: 'stdio', command, args: commandArgs, env };
194
+ return out;
195
+ }
196
+ return out;
197
+ }
198
+ /**
199
+ * Read a server definition out of an existing MCP config file, so a user can
200
+ * point mcp-probe at a server they already run without retyping its command.
201
+ * Handles both the `mcpServers` shape (Claude Desktop, Cursor) and the
202
+ * `servers` shape (VS Code).
203
+ */
204
+ function resolveFromConfig(path, name) {
205
+ let parsed;
206
+ try {
207
+ parsed = JSON.parse(readFileSync(path, 'utf8'));
208
+ }
209
+ catch (e) {
210
+ return { error: `Could not read ${path}: ${e.message}` };
211
+ }
212
+ const block = (parsed['mcpServers'] ?? parsed['servers']);
213
+ if (!block || typeof block !== 'object') {
214
+ return { error: `${path} has no "mcpServers" or "servers" object.` };
215
+ }
216
+ const names = Object.keys(block);
217
+ if (names.length === 0)
218
+ return { error: `${path} defines no servers.` };
219
+ const chosen = name ?? (names.length === 1 ? names[0] : null);
220
+ if (!chosen) {
221
+ return { error: `${path} defines ${names.length} servers. Pick one with --server <name>: ${names.join(', ')}` };
222
+ }
223
+ const entry = block[chosen];
224
+ if (!entry)
225
+ return { error: `No server named "${chosen}" in ${path}. Available: ${names.join(', ')}` };
226
+ if (typeof entry['url'] === 'string') {
227
+ return { target: { kind: 'http', url: entry['url'], headers: entry['headers'] ?? {} } };
228
+ }
229
+ if (typeof entry['command'] === 'string') {
230
+ return {
231
+ target: {
232
+ kind: 'stdio',
233
+ command: entry['command'],
234
+ args: Array.isArray(entry['args']) ? entry['args'] : [],
235
+ env: entry['env'] ?? {},
236
+ },
237
+ };
238
+ }
239
+ return { error: `Server "${chosen}" in ${path} has neither "command" nor "url".` };
240
+ }
241
+ async function main() {
242
+ const parsed = parseArgs(process.argv.slice(2));
243
+ if (parsed.help) {
244
+ process.stdout.write(HELP);
245
+ process.exit(0);
246
+ }
247
+ if (parsed.version) {
248
+ process.stdout.write(VERSION + '\n');
249
+ process.exit(0);
250
+ }
251
+ if (parsed.error) {
252
+ process.stderr.write(`mcp-probe: ${parsed.error}\n`);
253
+ process.exit(2);
254
+ }
255
+ if (!parsed.target) {
256
+ process.stderr.write(HELP);
257
+ process.exit(2);
258
+ }
259
+ const report = await run(parsed.target, parsed.options);
260
+ if (parsed.junit) {
261
+ try {
262
+ writeFileSync(parsed.junit, renderJUnit(report), 'utf8');
263
+ }
264
+ catch (e) {
265
+ process.stderr.write(`mcp-probe: could not write ${parsed.junit}: ${e.message}\n`);
266
+ }
267
+ }
268
+ if (parsed.jsonOut) {
269
+ try {
270
+ writeFileSync(parsed.jsonOut, JSON.stringify(report, null, 2), 'utf8');
271
+ }
272
+ catch (e) {
273
+ process.stderr.write(`mcp-probe: could not write ${parsed.jsonOut}: ${e.message}
274
+ `);
275
+ }
276
+ }
277
+ if (parsed.json) {
278
+ process.stdout.write(JSON.stringify(report, null, 2) + '\n');
279
+ }
280
+ else if (!parsed.quiet) {
281
+ process.stdout.write(renderTerminal(report, { verbose: parsed.verbose }));
282
+ }
283
+ // Nothing ran at all: distinguish "server is broken" from "could not start".
284
+ if (report.results.length === 1 && report.results[0]?.id === 'connect' && report.results[0].status === 'fail') {
285
+ process.exit(2);
286
+ }
287
+ process.exit(exitCodeFor(report, parsed.options.strict ?? false));
288
+ }
289
+ main().catch((e) => {
290
+ process.stderr.write(`mcp-probe: internal error: ${e.stack ?? String(e)}\n`);
291
+ process.exit(2);
292
+ });
@@ -0,0 +1,37 @@
1
+ import type { Transport } from './transport.js';
2
+ import { type JsonRpcResponse } from './jsonrpc.js';
3
+ export interface HttpOptions {
4
+ url: string;
5
+ headers?: Record<string, string>;
6
+ }
7
+ /**
8
+ * Streamable HTTP transport. Each request is a POST; the server may answer
9
+ * with `application/json` or an SSE stream, and may hand us a session id on
10
+ * the initialize response that must be echoed on every later call.
11
+ */
12
+ export declare class HttpTransport implements Transport {
13
+ private opts;
14
+ readonly kind = "http";
15
+ readonly target: string;
16
+ readonly stdoutNoise: string[];
17
+ readonly stderr: string[];
18
+ readonly serverNotifications: JsonRpcResponse[];
19
+ private nextId;
20
+ private sessionId;
21
+ private closed;
22
+ /** Populated when a response arrives with a shape we could not read. */
23
+ readonly protocolNotes: string[];
24
+ constructor(opts: HttpOptions);
25
+ start(): Promise<void>;
26
+ private headers;
27
+ request(method: string, params?: unknown, timeoutMs?: number): Promise<JsonRpcResponse>;
28
+ requestRaw(payload: Record<string, unknown>, _id: number | string, method: string, timeoutMs?: number): Promise<JsonRpcResponse>;
29
+ private post;
30
+ /** Pull the first `data:` frame that carries a JSON-RPC reply. */
31
+ private parseSse;
32
+ notify(method: string, params?: unknown): void;
33
+ writeRaw(text: string): void;
34
+ isAlive(): boolean;
35
+ exitInfo(): null;
36
+ close(): Promise<void>;
37
+ }
@@ -0,0 +1,133 @@
1
+ import { TimeoutError, TransportClosedError } from './jsonrpc.js';
2
+ /**
3
+ * Streamable HTTP transport. Each request is a POST; the server may answer
4
+ * with `application/json` or an SSE stream, and may hand us a session id on
5
+ * the initialize response that must be echoed on every later call.
6
+ */
7
+ export class HttpTransport {
8
+ opts;
9
+ kind = 'http';
10
+ target;
11
+ stdoutNoise = [];
12
+ stderr = [];
13
+ serverNotifications = [];
14
+ nextId = 1;
15
+ sessionId = null;
16
+ closed = false;
17
+ /** Populated when a response arrives with a shape we could not read. */
18
+ protocolNotes = [];
19
+ constructor(opts) {
20
+ this.opts = opts;
21
+ this.target = opts.url;
22
+ }
23
+ async start() {
24
+ /* Nothing to spawn; the first POST is the real connection test. */
25
+ }
26
+ headers() {
27
+ const h = {
28
+ 'content-type': 'application/json',
29
+ accept: 'application/json, text/event-stream',
30
+ ...this.opts.headers,
31
+ };
32
+ if (this.sessionId)
33
+ h['mcp-session-id'] = this.sessionId;
34
+ return h;
35
+ }
36
+ request(method, params, timeoutMs = 10_000) {
37
+ const id = this.nextId++;
38
+ return this.post({ jsonrpc: '2.0', id, method, ...(params !== undefined ? { params } : {}) }, method, timeoutMs);
39
+ }
40
+ requestRaw(payload, _id, method, timeoutMs = 10_000) {
41
+ return this.post(payload, method, timeoutMs);
42
+ }
43
+ async post(payload, method, timeoutMs) {
44
+ if (this.closed)
45
+ throw new TransportClosedError('Transport already closed');
46
+ const ac = new AbortController();
47
+ const timer = setTimeout(() => ac.abort(), timeoutMs);
48
+ let res;
49
+ try {
50
+ res = await fetch(this.opts.url, {
51
+ method: 'POST',
52
+ headers: this.headers(),
53
+ body: JSON.stringify(payload),
54
+ signal: ac.signal,
55
+ });
56
+ }
57
+ catch (e) {
58
+ clearTimeout(timer);
59
+ if (ac.signal.aborted)
60
+ throw new TimeoutError(method, timeoutMs);
61
+ throw new TransportClosedError(`POST ${this.opts.url} failed: ${e.message}`);
62
+ }
63
+ clearTimeout(timer);
64
+ const sid = res.headers.get('mcp-session-id');
65
+ if (sid)
66
+ this.sessionId = sid;
67
+ // 202 with no body is the legal answer to a notification.
68
+ if (res.status === 202)
69
+ return {};
70
+ const ctype = res.headers.get('content-type') ?? '';
71
+ const body = await res.text();
72
+ if (!res.ok) {
73
+ throw new TransportClosedError(`HTTP ${res.status} ${res.statusText} from ${this.opts.url}: ${body.slice(0, 400)}`);
74
+ }
75
+ if (ctype.includes('text/event-stream'))
76
+ return this.parseSse(body, method);
77
+ if (!body.trim())
78
+ return {};
79
+ try {
80
+ return JSON.parse(body);
81
+ }
82
+ catch {
83
+ this.protocolNotes.push(`Non-JSON body for \`${method}\` (content-type: ${ctype || 'none'}): ${body.slice(0, 200)}`);
84
+ throw new TransportClosedError(`Server returned unparseable body for \`${method}\``);
85
+ }
86
+ }
87
+ /** Pull the first `data:` frame that carries a JSON-RPC reply. */
88
+ parseSse(body, method) {
89
+ const frames = body.split(/\n\n/);
90
+ let last = null;
91
+ for (const frame of frames) {
92
+ const data = frame
93
+ .split('\n')
94
+ .filter((l) => l.startsWith('data:'))
95
+ .map((l) => l.slice(5).trim())
96
+ .join('');
97
+ if (!data)
98
+ continue;
99
+ let msg;
100
+ try {
101
+ msg = JSON.parse(data);
102
+ }
103
+ catch {
104
+ this.protocolNotes.push(`Unparseable SSE frame during \`${method}\`: ${data.slice(0, 200)}`);
105
+ continue;
106
+ }
107
+ if (msg.id !== undefined && msg.id !== null)
108
+ last = msg;
109
+ else
110
+ this.serverNotifications.push(msg);
111
+ }
112
+ if (!last)
113
+ throw new TransportClosedError(`SSE stream for \`${method}\` carried no JSON-RPC response`);
114
+ return last;
115
+ }
116
+ notify(method, params) {
117
+ void this.post({ jsonrpc: '2.0', method, ...(params !== undefined ? { params } : {}) }, method, 5000).catch(() => {
118
+ /* Notifications are fire-and-forget; a failure here is not a check result. */
119
+ });
120
+ }
121
+ writeRaw(text) {
122
+ void fetch(this.opts.url, { method: 'POST', headers: this.headers(), body: text }).catch(() => { });
123
+ }
124
+ isAlive() {
125
+ return !this.closed;
126
+ }
127
+ exitInfo() {
128
+ return null;
129
+ }
130
+ async close() {
131
+ this.closed = true;
132
+ }
133
+ }
@@ -0,0 +1,47 @@
1
+ import type { Transport } from './transport.js';
2
+ import type { JsonRpcResponse } from './jsonrpc.js';
3
+ import type { ToolDef } from '../types.js';
4
+ export { StdioTransport } from './stdio.js';
5
+ export { HttpTransport } from './http.js';
6
+ export type { Transport } from './transport.js';
7
+ /** Versions we will negotiate, newest first. */
8
+ export declare const SUPPORTED_PROTOCOL_VERSIONS: string[];
9
+ export interface HandshakeResult {
10
+ raw: JsonRpcResponse;
11
+ protocolVersion: string | null;
12
+ serverInfo: {
13
+ name?: string;
14
+ version?: string;
15
+ } | null;
16
+ capabilities: Record<string, unknown>;
17
+ ms: number;
18
+ }
19
+ /**
20
+ * A thin, unopinionated MCP client. It performs the handshake and exposes the
21
+ * few calls the checks need -- but never normalises or repairs a response,
22
+ * because the checks have to see exactly what the server sent.
23
+ */
24
+ export declare class McpClient {
25
+ readonly transport: Transport;
26
+ private timeoutMs;
27
+ constructor(transport: Transport, timeoutMs?: number);
28
+ get target(): string;
29
+ start(): Promise<void>;
30
+ initialize(protocolVersion?: string): Promise<HandshakeResult>;
31
+ /** The spec requires this notification before any other request. */
32
+ notifyInitialized(): void;
33
+ call(method: string, params?: unknown, timeoutMs?: number): Promise<JsonRpcResponse>;
34
+ callRaw(payload: Record<string, unknown>, id: number | string, method: string, timeoutMs?: number): Promise<JsonRpcResponse>;
35
+ /** Walk `nextCursor` so a paginated server does not under-report. */
36
+ listAll(method: 'tools/list' | 'resources/list' | 'prompts/list', key: string): Promise<{
37
+ items: unknown[];
38
+ pages: number;
39
+ error?: JsonRpcResponse;
40
+ }>;
41
+ listTools(): Promise<{
42
+ tools: ToolDef[];
43
+ pages: number;
44
+ error?: JsonRpcResponse;
45
+ }>;
46
+ close(): Promise<void>;
47
+ }
@@ -0,0 +1,79 @@
1
+ export { StdioTransport } from './stdio.js';
2
+ export { HttpTransport } from './http.js';
3
+ /** Versions we will negotiate, newest first. */
4
+ export const SUPPORTED_PROTOCOL_VERSIONS = ['2025-06-18', '2025-03-26', '2024-11-05'];
5
+ /**
6
+ * A thin, unopinionated MCP client. It performs the handshake and exposes the
7
+ * few calls the checks need -- but never normalises or repairs a response,
8
+ * because the checks have to see exactly what the server sent.
9
+ */
10
+ export class McpClient {
11
+ transport;
12
+ timeoutMs;
13
+ constructor(transport, timeoutMs = 10_000) {
14
+ this.transport = transport;
15
+ this.timeoutMs = timeoutMs;
16
+ }
17
+ get target() {
18
+ return this.transport.target;
19
+ }
20
+ async start() {
21
+ await this.transport.start();
22
+ }
23
+ async initialize(protocolVersion = SUPPORTED_PROTOCOL_VERSIONS[0]) {
24
+ const t0 = Date.now();
25
+ const raw = await this.transport.request('initialize', {
26
+ protocolVersion,
27
+ capabilities: { roots: { listChanged: true }, sampling: {}, elicitation: {} },
28
+ clientInfo: { name: 'mcp-probe', version: '0.1.0' },
29
+ }, this.timeoutMs);
30
+ const ms = Date.now() - t0;
31
+ const result = (raw.result ?? {});
32
+ return {
33
+ raw,
34
+ ms,
35
+ protocolVersion: typeof result['protocolVersion'] === 'string' ? result['protocolVersion'] : null,
36
+ serverInfo: result['serverInfo'] ?? null,
37
+ capabilities: result['capabilities'] ?? {},
38
+ };
39
+ }
40
+ /** The spec requires this notification before any other request. */
41
+ notifyInitialized() {
42
+ this.transport.notify('notifications/initialized');
43
+ }
44
+ call(method, params, timeoutMs) {
45
+ return this.transport.request(method, params, timeoutMs ?? this.timeoutMs);
46
+ }
47
+ callRaw(payload, id, method, timeoutMs) {
48
+ return this.transport.requestRaw(payload, id, method, timeoutMs ?? this.timeoutMs);
49
+ }
50
+ /** Walk `nextCursor` so a paginated server does not under-report. */
51
+ async listAll(method, key) {
52
+ const items = [];
53
+ let cursor;
54
+ let pages = 0;
55
+ for (;;) {
56
+ const res = await this.call(method, cursor ? { cursor } : {});
57
+ if (res.error)
58
+ return { items, pages, error: res };
59
+ pages++;
60
+ const result = (res.result ?? {});
61
+ const batch = result[key];
62
+ if (Array.isArray(batch))
63
+ items.push(...batch);
64
+ const next = result['nextCursor'];
65
+ if (typeof next === 'string' && next && pages < 50)
66
+ cursor = next;
67
+ else
68
+ break;
69
+ }
70
+ return { items, pages };
71
+ }
72
+ async listTools() {
73
+ const { items, pages, error } = await this.listAll('tools/list', 'tools');
74
+ return { tools: items, pages, error };
75
+ }
76
+ close() {
77
+ return this.transport.close();
78
+ }
79
+ }
@@ -0,0 +1,40 @@
1
+ export interface JsonRpcRequest {
2
+ jsonrpc: '2.0';
3
+ id: number | string;
4
+ method: string;
5
+ params?: unknown;
6
+ }
7
+ export interface JsonRpcNotification {
8
+ jsonrpc: '2.0';
9
+ method: string;
10
+ params?: unknown;
11
+ }
12
+ export interface JsonRpcError {
13
+ code: number;
14
+ message: string;
15
+ data?: unknown;
16
+ }
17
+ export interface JsonRpcResponse {
18
+ jsonrpc?: string;
19
+ id?: number | string | null;
20
+ result?: unknown;
21
+ error?: JsonRpcError;
22
+ }
23
+ /** Error codes the spec pins down. Servers get these wrong constantly. */
24
+ export declare const RPC: {
25
+ readonly PARSE_ERROR: -32700;
26
+ readonly INVALID_REQUEST: -32600;
27
+ readonly METHOD_NOT_FOUND: -32601;
28
+ readonly INVALID_PARAMS: -32602;
29
+ readonly INTERNAL_ERROR: -32603;
30
+ };
31
+ export declare class TimeoutError extends Error {
32
+ method: string;
33
+ ms: number;
34
+ constructor(method: string, ms: number);
35
+ }
36
+ export declare class TransportClosedError extends Error {
37
+ code?: number | null | undefined;
38
+ stderr?: string | undefined;
39
+ constructor(message: string, code?: number | null | undefined, stderr?: string | undefined);
40
+ }
@@ -0,0 +1,28 @@
1
+ /** Error codes the spec pins down. Servers get these wrong constantly. */
2
+ export const RPC = {
3
+ PARSE_ERROR: -32700,
4
+ INVALID_REQUEST: -32600,
5
+ METHOD_NOT_FOUND: -32601,
6
+ INVALID_PARAMS: -32602,
7
+ INTERNAL_ERROR: -32603,
8
+ };
9
+ export class TimeoutError extends Error {
10
+ method;
11
+ ms;
12
+ constructor(method, ms) {
13
+ super(`No response to \`${method}\` within ${ms}ms`);
14
+ this.method = method;
15
+ this.ms = ms;
16
+ this.name = 'TimeoutError';
17
+ }
18
+ }
19
+ export class TransportClosedError extends Error {
20
+ code;
21
+ stderr;
22
+ constructor(message, code, stderr) {
23
+ super(message);
24
+ this.code = code;
25
+ this.stderr = stderr;
26
+ this.name = 'TransportClosedError';
27
+ }
28
+ }