@dotsetlabs/bellwether 2.0.0 → 2.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 +55 -0
- package/README.md +9 -0
- package/dist/auth/credentials.js +2 -0
- package/dist/baseline/accessors.js +12 -0
- package/dist/baseline/baseline-format.d.ts +48 -0
- package/dist/baseline/comparator.js +263 -20
- package/dist/baseline/converter.js +52 -4
- package/dist/baseline/dependency-analyzer.js +46 -25
- package/dist/baseline/diff.js +51 -39
- package/dist/baseline/documentation-scorer.d.ts +1 -1
- package/dist/baseline/documentation-scorer.js +4 -4
- package/dist/baseline/error-analyzer.js +1 -1
- package/dist/baseline/external-dependency-detector.js +16 -7
- package/dist/baseline/performance-tracker.js +2 -2
- package/dist/baseline/response-fingerprint.js +1 -1
- package/dist/baseline/response-schema-tracker.js +17 -22
- package/dist/baseline/saver.js +34 -0
- package/dist/baseline/types.d.ts +21 -1
- package/dist/cache/response-cache.js +9 -2
- package/dist/cli/commands/auth.js +15 -18
- package/dist/cli/commands/baseline-accept.js +1 -1
- package/dist/cli/commands/baseline.js +71 -36
- package/dist/cli/commands/check.js +54 -14
- package/dist/cli/commands/discover.js +2 -2
- package/dist/cli/commands/explore.js +38 -5
- package/dist/cli/commands/golden.js +20 -23
- package/dist/cli/commands/init.js +10 -7
- package/dist/cli/commands/registry.js +37 -35
- package/dist/cli/commands/watch.js +5 -5
- package/dist/cli/output/terminal-reporter.js +9 -9
- package/dist/cli/output.d.ts +1 -1
- package/dist/cli/output.js +9 -11
- package/dist/config/loader.js +2 -2
- package/dist/config/validator.d.ts +33 -33
- package/dist/constants/core.d.ts +4 -8
- package/dist/constants/core.js +4 -8
- package/dist/constants/testing.d.ts +11 -11
- package/dist/constants/testing.js +11 -11
- package/dist/contract/validator.js +7 -7
- package/dist/discovery/discovery.js +88 -14
- package/dist/discovery/types.d.ts +5 -1
- package/dist/docs/agents.js +145 -57
- package/dist/docs/contract.js +136 -40
- package/dist/errors/retry.js +11 -5
- package/dist/interview/dependency-resolver.d.ts +3 -2
- package/dist/interview/dependency-resolver.js +31 -2
- package/dist/interview/interviewer.js +10 -2
- package/dist/interview/rate-limiter.js +7 -3
- package/dist/interview/stateful-test-runner.d.ts +1 -0
- package/dist/interview/stateful-test-runner.js +4 -0
- package/dist/interview/types.d.ts +3 -0
- package/dist/llm/anthropic.js +14 -4
- package/dist/llm/fallback.d.ts +1 -0
- package/dist/llm/fallback.js +7 -1
- package/dist/llm/openai.js +15 -4
- package/dist/prompts/templates.js +30 -15
- package/dist/protocol/index.d.ts +2 -0
- package/dist/protocol/index.js +2 -0
- package/dist/protocol/version-registry.d.ts +66 -0
- package/dist/protocol/version-registry.js +159 -0
- package/dist/scenarios/evaluator.js +9 -10
- package/dist/transport/http-transport.d.ts +11 -1
- package/dist/transport/http-transport.js +21 -2
- package/dist/transport/mcp-client.d.ts +29 -1
- package/dist/transport/mcp-client.js +92 -7
- package/dist/transport/sse-transport.js +5 -4
- package/dist/transport/types.d.ts +134 -1
- package/dist/utils/concurrency.d.ts +2 -0
- package/dist/utils/concurrency.js +9 -2
- package/dist/utils/markdown.js +13 -18
- package/dist/utils/timeout.js +2 -1
- package/dist/version.js +1 -1
- package/man/bellwether.1 +1 -1
- package/man/bellwether.1.md +2 -2
- package/package.json +2 -1
|
@@ -5,6 +5,7 @@ import { HTTPTransport } from './http-transport.js';
|
|
|
5
5
|
import { getLogger, startTiming } from '../logging/logger.js';
|
|
6
6
|
import { TIMEOUTS, MCP, TRANSPORT_ERRORS } from '../constants.js';
|
|
7
7
|
import { VERSION } from '../version.js';
|
|
8
|
+
import { getFeatureFlags, isKnownProtocolVersion, } from '../protocol/index.js';
|
|
8
9
|
/**
|
|
9
10
|
* Environment variables to filter out when spawning MCP server processes.
|
|
10
11
|
* These may contain sensitive credentials that should not be exposed.
|
|
@@ -75,6 +76,7 @@ export class MCPClient {
|
|
|
75
76
|
requestId = 0;
|
|
76
77
|
pendingRequests = new Map();
|
|
77
78
|
serverCapabilities = null;
|
|
79
|
+
serverInstructions;
|
|
78
80
|
timeout;
|
|
79
81
|
startupDelay;
|
|
80
82
|
serverReady = false;
|
|
@@ -92,6 +94,8 @@ export class MCPClient {
|
|
|
92
94
|
transportErrors = [];
|
|
93
95
|
/** Connection state for diagnostic error messages */
|
|
94
96
|
connectionState = { attempted: false };
|
|
97
|
+
/** Protocol version negotiated with the server during initialization */
|
|
98
|
+
negotiatedProtocolVersion = null;
|
|
95
99
|
constructor(options) {
|
|
96
100
|
this.timeout = options?.timeout ?? DEFAULT_TIMEOUT;
|
|
97
101
|
this.startupDelay = options?.startupDelay ?? DEFAULT_STARTUP_DELAY;
|
|
@@ -444,9 +448,19 @@ export class MCPClient {
|
|
|
444
448
|
},
|
|
445
449
|
});
|
|
446
450
|
this.serverCapabilities = result.capabilities;
|
|
451
|
+
this.serverInstructions = result.instructions;
|
|
452
|
+
this.negotiatedProtocolVersion = result.protocolVersion;
|
|
453
|
+
// Warn if server returned an unknown protocol version
|
|
454
|
+
if (result.protocolVersion && !isKnownProtocolVersion(result.protocolVersion)) {
|
|
455
|
+
this.logger.warn({ protocolVersion: result.protocolVersion }, 'Server returned unknown protocol version');
|
|
456
|
+
}
|
|
457
|
+
// Update HTTP transport with negotiated version for subsequent requests
|
|
458
|
+
if (this.transport instanceof HTTPTransport && result.protocolVersion) {
|
|
459
|
+
this.transport.setNegotiatedVersion(result.protocolVersion);
|
|
460
|
+
}
|
|
447
461
|
// Send initialized notification
|
|
448
462
|
this.sendNotification('notifications/initialized', {});
|
|
449
|
-
this.logger.info({ capabilities: result.capabilities }, 'MCP server initialized successfully');
|
|
463
|
+
this.logger.info({ capabilities: result.capabilities, protocolVersion: result.protocolVersion }, 'MCP server initialized successfully');
|
|
450
464
|
return result;
|
|
451
465
|
}
|
|
452
466
|
catch (error) {
|
|
@@ -477,24 +491,71 @@ export class MCPClient {
|
|
|
477
491
|
}
|
|
478
492
|
/**
|
|
479
493
|
* List all tools available on the server.
|
|
494
|
+
* Handles pagination via nextCursor per MCP 2025-11-25 spec.
|
|
480
495
|
*/
|
|
481
496
|
async listTools(options) {
|
|
482
|
-
const
|
|
483
|
-
|
|
497
|
+
const allTools = [];
|
|
498
|
+
let cursor;
|
|
499
|
+
do {
|
|
500
|
+
const params = {};
|
|
501
|
+
if (cursor)
|
|
502
|
+
params.cursor = cursor;
|
|
503
|
+
const result = await this.sendRequest('tools/list', params, options);
|
|
504
|
+
allTools.push(...result.tools);
|
|
505
|
+
cursor = result.nextCursor;
|
|
506
|
+
} while (cursor);
|
|
507
|
+
return allTools;
|
|
484
508
|
}
|
|
485
509
|
/**
|
|
486
510
|
* List all prompts available on the server.
|
|
511
|
+
* Handles pagination via nextCursor per MCP 2025-11-25 spec.
|
|
487
512
|
*/
|
|
488
513
|
async listPrompts(options) {
|
|
489
|
-
const
|
|
490
|
-
|
|
514
|
+
const allPrompts = [];
|
|
515
|
+
let cursor;
|
|
516
|
+
do {
|
|
517
|
+
const params = {};
|
|
518
|
+
if (cursor)
|
|
519
|
+
params.cursor = cursor;
|
|
520
|
+
const result = await this.sendRequest('prompts/list', params, options);
|
|
521
|
+
allPrompts.push(...result.prompts);
|
|
522
|
+
cursor = result.nextCursor;
|
|
523
|
+
} while (cursor);
|
|
524
|
+
return allPrompts;
|
|
491
525
|
}
|
|
492
526
|
/**
|
|
493
527
|
* List all resources available on the server.
|
|
528
|
+
* Handles pagination via nextCursor per MCP 2025-11-25 spec.
|
|
494
529
|
*/
|
|
495
530
|
async listResources(options) {
|
|
496
|
-
const
|
|
497
|
-
|
|
531
|
+
const allResources = [];
|
|
532
|
+
let cursor;
|
|
533
|
+
do {
|
|
534
|
+
const params = {};
|
|
535
|
+
if (cursor)
|
|
536
|
+
params.cursor = cursor;
|
|
537
|
+
const result = await this.sendRequest('resources/list', params, options);
|
|
538
|
+
allResources.push(...result.resources);
|
|
539
|
+
cursor = result.nextCursor;
|
|
540
|
+
} while (cursor);
|
|
541
|
+
return allResources;
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* List all resource templates available on the server.
|
|
545
|
+
* Handles pagination via nextCursor per MCP 2025-11-25 spec.
|
|
546
|
+
*/
|
|
547
|
+
async listResourceTemplates(options) {
|
|
548
|
+
const allTemplates = [];
|
|
549
|
+
let cursor;
|
|
550
|
+
do {
|
|
551
|
+
const params = {};
|
|
552
|
+
if (cursor)
|
|
553
|
+
params.cursor = cursor;
|
|
554
|
+
const result = await this.sendRequest('resources/templates/list', params, options);
|
|
555
|
+
allTemplates.push(...result.resourceTemplates);
|
|
556
|
+
cursor = result.nextCursor;
|
|
557
|
+
} while (cursor);
|
|
558
|
+
return allTemplates;
|
|
498
559
|
}
|
|
499
560
|
/**
|
|
500
561
|
* Read a resource from the server by URI.
|
|
@@ -555,6 +616,28 @@ export class MCPClient {
|
|
|
555
616
|
getCapabilities() {
|
|
556
617
|
return this.serverCapabilities;
|
|
557
618
|
}
|
|
619
|
+
/**
|
|
620
|
+
* Get server instructions from initialization.
|
|
621
|
+
*/
|
|
622
|
+
getInstructions() {
|
|
623
|
+
return this.serverInstructions;
|
|
624
|
+
}
|
|
625
|
+
/**
|
|
626
|
+
* Get the protocol version negotiated with the server during initialization.
|
|
627
|
+
* Returns null if initialization hasn't completed yet.
|
|
628
|
+
*/
|
|
629
|
+
getNegotiatedProtocolVersion() {
|
|
630
|
+
return this.negotiatedProtocolVersion;
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Get feature flags based on the negotiated protocol version.
|
|
634
|
+
* Returns null if initialization hasn't completed yet.
|
|
635
|
+
*/
|
|
636
|
+
getFeatureFlags() {
|
|
637
|
+
if (!this.negotiatedProtocolVersion)
|
|
638
|
+
return null;
|
|
639
|
+
return getFeatureFlags(this.negotiatedProtocolVersion);
|
|
640
|
+
}
|
|
558
641
|
/**
|
|
559
642
|
* Check if the server is ready (startup delay complete and initialized).
|
|
560
643
|
* Note: This only indicates if startup delay has passed - true readiness
|
|
@@ -764,6 +847,8 @@ export class MCPClient {
|
|
|
764
847
|
this.transport = null;
|
|
765
848
|
this.process = null;
|
|
766
849
|
this.serverCapabilities = null;
|
|
850
|
+
this.serverInstructions = undefined;
|
|
851
|
+
this.negotiatedProtocolVersion = null;
|
|
767
852
|
this.serverReady = false;
|
|
768
853
|
}
|
|
769
854
|
}
|
|
@@ -316,10 +316,11 @@ export class SSETransport extends BaseTransport {
|
|
|
316
316
|
// Use the endpoint provided by the server, or default to /message
|
|
317
317
|
const endpoint = this.messageEndpoint || `${this.baseUrl}/message`;
|
|
318
318
|
this.log('Sending message', { endpoint, message });
|
|
319
|
-
// Create a
|
|
320
|
-
|
|
319
|
+
// Create a local abort controller for this request to avoid overwriting
|
|
320
|
+
// the instance controller and leaking previous controllers
|
|
321
|
+
const requestController = new AbortController();
|
|
321
322
|
const timeoutId = setTimeout(() => {
|
|
322
|
-
|
|
323
|
+
requestController.abort();
|
|
323
324
|
}, this.timeout);
|
|
324
325
|
fetch(endpoint, {
|
|
325
326
|
method: 'POST',
|
|
@@ -328,7 +329,7 @@ export class SSETransport extends BaseTransport {
|
|
|
328
329
|
...this.headers,
|
|
329
330
|
},
|
|
330
331
|
body: JSON.stringify(message),
|
|
331
|
-
signal:
|
|
332
|
+
signal: requestController.signal,
|
|
332
333
|
})
|
|
333
334
|
.then(async (response) => {
|
|
334
335
|
clearTimeout(timeoutId);
|
|
@@ -25,41 +25,122 @@ export interface JSONRPCNotification {
|
|
|
25
25
|
}
|
|
26
26
|
export type JSONRPCMessage = JSONRPCRequest | JSONRPCResponse | JSONRPCNotification;
|
|
27
27
|
/**
|
|
28
|
-
* MCP Protocol types
|
|
28
|
+
* MCP Protocol types (aligned with MCP specification 2025-11-25)
|
|
29
29
|
*/
|
|
30
|
+
/**
|
|
31
|
+
* Icon for MCP entities (tools, resources, prompts, server).
|
|
32
|
+
*/
|
|
33
|
+
export interface MCPIcon {
|
|
34
|
+
/** URI or data URI for the icon */
|
|
35
|
+
src: string;
|
|
36
|
+
/** MIME type of the icon */
|
|
37
|
+
mimeType?: string;
|
|
38
|
+
/** Supported sizes (e.g., ['16x16', '32x32']) */
|
|
39
|
+
sizes?: string[];
|
|
40
|
+
/** Theme hint for the icon */
|
|
41
|
+
theme?: 'light' | 'dark';
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Annotations for content blocks and resources.
|
|
45
|
+
* Provides metadata about intended audience, priority, and freshness.
|
|
46
|
+
*/
|
|
47
|
+
export interface MCPAnnotations {
|
|
48
|
+
/** Intended audience for the content */
|
|
49
|
+
audience?: ('user' | 'assistant')[];
|
|
50
|
+
/** Priority hint (0.0 = lowest, 1.0 = highest) */
|
|
51
|
+
priority?: number;
|
|
52
|
+
/** ISO 8601 timestamp of when the content was last modified */
|
|
53
|
+
lastModified?: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Behavioral annotations for tools.
|
|
57
|
+
* Provides hints about tool behavior to help clients make decisions.
|
|
58
|
+
*/
|
|
59
|
+
export interface MCPToolAnnotations {
|
|
60
|
+
/** Human-readable title for the annotation group */
|
|
61
|
+
title?: string;
|
|
62
|
+
/** Whether the tool only reads data and does not modify state */
|
|
63
|
+
readOnlyHint?: boolean;
|
|
64
|
+
/** Whether the tool may perform destructive operations */
|
|
65
|
+
destructiveHint?: boolean;
|
|
66
|
+
/** Whether calling the tool multiple times with the same args has the same effect */
|
|
67
|
+
idempotentHint?: boolean;
|
|
68
|
+
/** Whether the tool interacts with entities outside the server's controlled environment */
|
|
69
|
+
openWorldHint?: boolean;
|
|
70
|
+
}
|
|
30
71
|
export interface MCPTool {
|
|
31
72
|
name: string;
|
|
32
73
|
description?: string;
|
|
33
74
|
inputSchema?: Record<string, unknown>;
|
|
75
|
+
/** Human-readable title for the tool */
|
|
76
|
+
title?: string;
|
|
77
|
+
/** Icons for the tool */
|
|
78
|
+
icons?: MCPIcon[];
|
|
79
|
+
/** JSON Schema for the tool's output (structured content) */
|
|
80
|
+
outputSchema?: Record<string, unknown>;
|
|
81
|
+
/** Behavioral annotations/hints */
|
|
82
|
+
annotations?: MCPToolAnnotations;
|
|
83
|
+
/** Task execution configuration */
|
|
84
|
+
execution?: {
|
|
85
|
+
taskSupport?: 'forbidden' | 'optional' | 'required';
|
|
86
|
+
};
|
|
87
|
+
/** Extension metadata */
|
|
88
|
+
_meta?: Record<string, unknown>;
|
|
34
89
|
}
|
|
35
90
|
export interface MCPPrompt {
|
|
36
91
|
name: string;
|
|
37
92
|
description?: string;
|
|
38
93
|
arguments?: MCPPromptArgument[];
|
|
94
|
+
/** Human-readable title for the prompt */
|
|
95
|
+
title?: string;
|
|
96
|
+
/** Icons for the prompt */
|
|
97
|
+
icons?: MCPIcon[];
|
|
98
|
+
/** Extension metadata */
|
|
99
|
+
_meta?: Record<string, unknown>;
|
|
39
100
|
}
|
|
40
101
|
export interface MCPPromptArgument {
|
|
41
102
|
name: string;
|
|
42
103
|
description?: string;
|
|
43
104
|
required?: boolean;
|
|
105
|
+
/** Human-readable title for the argument */
|
|
106
|
+
title?: string;
|
|
44
107
|
}
|
|
45
108
|
export interface MCPServerCapabilities {
|
|
46
109
|
tools?: Record<string, unknown>;
|
|
47
110
|
prompts?: Record<string, unknown>;
|
|
48
111
|
resources?: Record<string, unknown>;
|
|
49
112
|
logging?: Record<string, unknown>;
|
|
113
|
+
/** Server supports completions (autocomplete) */
|
|
114
|
+
completions?: Record<string, unknown>;
|
|
115
|
+
/** Server supports task management */
|
|
116
|
+
tasks?: Record<string, unknown>;
|
|
117
|
+
/** Experimental/vendor-specific capabilities */
|
|
118
|
+
experimental?: Record<string, unknown>;
|
|
50
119
|
}
|
|
51
120
|
export interface MCPServerInfo {
|
|
52
121
|
name: string;
|
|
53
122
|
version: string;
|
|
123
|
+
/** Human-readable title for the server */
|
|
124
|
+
title?: string;
|
|
125
|
+
/** Description of the server */
|
|
126
|
+
description?: string;
|
|
127
|
+
/** Icons for the server */
|
|
128
|
+
icons?: MCPIcon[];
|
|
129
|
+
/** Website URL for the server */
|
|
130
|
+
websiteUrl?: string;
|
|
54
131
|
}
|
|
55
132
|
export interface MCPInitializeResult {
|
|
56
133
|
protocolVersion: string;
|
|
57
134
|
capabilities: MCPServerCapabilities;
|
|
58
135
|
serverInfo: MCPServerInfo;
|
|
136
|
+
/** Server-provided instructions for the client */
|
|
137
|
+
instructions?: string;
|
|
59
138
|
}
|
|
60
139
|
export interface MCPToolCallResult {
|
|
61
140
|
content: MCPContentBlock[];
|
|
62
141
|
isError?: boolean;
|
|
142
|
+
/** Structured output content (validated against tool's outputSchema) */
|
|
143
|
+
structuredContent?: Record<string, unknown>;
|
|
63
144
|
}
|
|
64
145
|
/**
|
|
65
146
|
* MCP content block types per MCP specification (2025-11-25).
|
|
@@ -75,12 +156,26 @@ export interface MCPContentBlock {
|
|
|
75
156
|
mimeType?: string;
|
|
76
157
|
/** URI reference (for type: 'resource_link') */
|
|
77
158
|
uri?: string;
|
|
159
|
+
/** Content/resource annotations */
|
|
160
|
+
annotations?: MCPAnnotations;
|
|
161
|
+
/** Extension metadata */
|
|
162
|
+
_meta?: Record<string, unknown>;
|
|
163
|
+
/** Embedded resource content (for type: 'resource') */
|
|
164
|
+
resource?: MCPResourceContent;
|
|
165
|
+
/** Resource name (for type: 'resource_link') */
|
|
166
|
+
name?: string;
|
|
167
|
+
/** Resource description (for type: 'resource_link') */
|
|
168
|
+
description?: string;
|
|
78
169
|
}
|
|
79
170
|
export interface MCPToolsListResult {
|
|
80
171
|
tools: MCPTool[];
|
|
172
|
+
/** Cursor for pagination */
|
|
173
|
+
nextCursor?: string;
|
|
81
174
|
}
|
|
82
175
|
export interface MCPPromptsListResult {
|
|
83
176
|
prompts: MCPPrompt[];
|
|
177
|
+
/** Cursor for pagination */
|
|
178
|
+
nextCursor?: string;
|
|
84
179
|
}
|
|
85
180
|
/**
|
|
86
181
|
* MCP Resource types
|
|
@@ -94,9 +189,21 @@ export interface MCPResource {
|
|
|
94
189
|
description?: string;
|
|
95
190
|
/** MIME type of the resource content */
|
|
96
191
|
mimeType?: string;
|
|
192
|
+
/** Human-readable title for the resource */
|
|
193
|
+
title?: string;
|
|
194
|
+
/** Icons for the resource */
|
|
195
|
+
icons?: MCPIcon[];
|
|
196
|
+
/** Resource annotations */
|
|
197
|
+
annotations?: MCPAnnotations;
|
|
198
|
+
/** Size of the resource in bytes */
|
|
199
|
+
size?: number;
|
|
200
|
+
/** Extension metadata */
|
|
201
|
+
_meta?: Record<string, unknown>;
|
|
97
202
|
}
|
|
98
203
|
export interface MCPResourcesListResult {
|
|
99
204
|
resources: MCPResource[];
|
|
205
|
+
/** Cursor for pagination */
|
|
206
|
+
nextCursor?: string;
|
|
100
207
|
}
|
|
101
208
|
export interface MCPResourceReadResult {
|
|
102
209
|
contents: MCPResourceContent[];
|
|
@@ -111,6 +218,32 @@ export interface MCPResourceContent {
|
|
|
111
218
|
/** Binary content as base64 (for binary resources) */
|
|
112
219
|
blob?: string;
|
|
113
220
|
}
|
|
221
|
+
/**
|
|
222
|
+
* MCP Resource Template for URI-templated resources.
|
|
223
|
+
*/
|
|
224
|
+
export interface MCPResourceTemplate {
|
|
225
|
+
/** URI template (RFC 6570) */
|
|
226
|
+
uriTemplate: string;
|
|
227
|
+
/** Human-readable name */
|
|
228
|
+
name: string;
|
|
229
|
+
/** Human-readable title */
|
|
230
|
+
title?: string;
|
|
231
|
+
/** Description of the resource template */
|
|
232
|
+
description?: string;
|
|
233
|
+
/** Expected MIME type of resources matching this template */
|
|
234
|
+
mimeType?: string;
|
|
235
|
+
/** Icons for the resource template */
|
|
236
|
+
icons?: MCPIcon[];
|
|
237
|
+
/** Resource annotations */
|
|
238
|
+
annotations?: MCPAnnotations;
|
|
239
|
+
/** Extension metadata */
|
|
240
|
+
_meta?: Record<string, unknown>;
|
|
241
|
+
}
|
|
242
|
+
export interface MCPResourceTemplatesListResult {
|
|
243
|
+
resourceTemplates: MCPResourceTemplate[];
|
|
244
|
+
/** Cursor for pagination */
|
|
245
|
+
nextCursor?: string;
|
|
246
|
+
}
|
|
114
247
|
export interface MCPPromptMessage {
|
|
115
248
|
role: 'user' | 'assistant';
|
|
116
249
|
content: MCPPromptContent;
|
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
export interface ParallelOptions<T> {
|
|
8
8
|
/** Maximum concurrent tasks (default: 3) */
|
|
9
9
|
concurrency?: number;
|
|
10
|
+
/** Timeout per task in milliseconds (default: no timeout) */
|
|
11
|
+
taskTimeoutMs?: number;
|
|
10
12
|
/** Callback when a task completes */
|
|
11
13
|
onTaskComplete?: (result: T, index: number) => void;
|
|
12
14
|
/** Callback when a task fails */
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* @returns Results of all tasks
|
|
11
11
|
*/
|
|
12
12
|
export async function parallelLimit(tasks, options = {}) {
|
|
13
|
-
const { concurrency = 3, onTaskComplete, onTaskError } = options;
|
|
13
|
+
const { concurrency = 3, taskTimeoutMs, onTaskComplete, onTaskError } = options;
|
|
14
14
|
const results = new Array(tasks.length);
|
|
15
15
|
const errors = new Map();
|
|
16
16
|
let running = 0;
|
|
@@ -30,7 +30,14 @@ export async function parallelLimit(tasks, options = {}) {
|
|
|
30
30
|
while (running < concurrency && index < tasks.length) {
|
|
31
31
|
const currentIndex = index++;
|
|
32
32
|
running++;
|
|
33
|
-
tasks[currentIndex]()
|
|
33
|
+
const taskPromise = tasks[currentIndex]();
|
|
34
|
+
const wrappedPromise = taskTimeoutMs
|
|
35
|
+
? Promise.race([
|
|
36
|
+
taskPromise,
|
|
37
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error(`Task ${currentIndex} timed out after ${taskTimeoutMs}ms`)), taskTimeoutMs)),
|
|
38
|
+
])
|
|
39
|
+
: taskPromise;
|
|
40
|
+
wrappedPromise
|
|
34
41
|
.then((result) => {
|
|
35
42
|
results[currentIndex] = result;
|
|
36
43
|
onTaskComplete?.(result, currentIndex);
|
package/dist/utils/markdown.js
CHANGED
|
@@ -15,13 +15,13 @@ import { DISPLAY_LIMITS } from '../constants.js';
|
|
|
15
15
|
export function escapeTableCell(text) {
|
|
16
16
|
if (!text)
|
|
17
17
|
return '';
|
|
18
|
-
return text
|
|
18
|
+
return (text
|
|
19
19
|
// Escape pipe characters (break table columns)
|
|
20
20
|
.replace(/\|/g, '\\|')
|
|
21
21
|
// Escape newlines (break table rows)
|
|
22
22
|
.replace(/\r?\n/g, '<br>')
|
|
23
23
|
// Escape leading/trailing whitespace that might affect rendering
|
|
24
|
-
.trim();
|
|
24
|
+
.trim());
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
27
|
* Escape a string for use inside a code block.
|
|
@@ -47,7 +47,7 @@ export function escapeCodeBlock(text) {
|
|
|
47
47
|
export function escapeMermaid(text) {
|
|
48
48
|
if (!text)
|
|
49
49
|
return '';
|
|
50
|
-
return text
|
|
50
|
+
return (text
|
|
51
51
|
// Escape double quotes (break Mermaid string literals)
|
|
52
52
|
.replace(/"/g, '#quot;')
|
|
53
53
|
// Escape square brackets (node syntax)
|
|
@@ -64,7 +64,7 @@ export function escapeMermaid(text) {
|
|
|
64
64
|
.replace(/->/g, '#rarr;')
|
|
65
65
|
.replace(/\|/g, '#pipe;')
|
|
66
66
|
// Escape newlines
|
|
67
|
-
.replace(/\r?\n/g, ' ');
|
|
67
|
+
.replace(/\r?\n/g, ' '));
|
|
68
68
|
}
|
|
69
69
|
/**
|
|
70
70
|
* Escape a string for use as a Mermaid node label.
|
|
@@ -81,10 +81,7 @@ export function mermaidLabel(text) {
|
|
|
81
81
|
return text;
|
|
82
82
|
}
|
|
83
83
|
// Escape and wrap in quotes for complex text
|
|
84
|
-
const escaped = text
|
|
85
|
-
.replace(/"/g, "'")
|
|
86
|
-
.replace(/\r?\n/g, ' ')
|
|
87
|
-
.trim();
|
|
84
|
+
const escaped = text.replace(/"/g, "'").replace(/\r?\n/g, ' ').trim();
|
|
88
85
|
return `"${escaped}"`;
|
|
89
86
|
}
|
|
90
87
|
/**
|
|
@@ -104,9 +101,7 @@ export function validateJsonForCodeBlock(json, options = {}) {
|
|
|
104
101
|
if (typeof json === 'string') {
|
|
105
102
|
try {
|
|
106
103
|
const parsed = JSON.parse(json);
|
|
107
|
-
content = prettyPrint
|
|
108
|
-
? JSON.stringify(parsed, null, indent)
|
|
109
|
-
: JSON.stringify(parsed);
|
|
104
|
+
content = prettyPrint ? JSON.stringify(parsed, null, indent) : JSON.stringify(parsed);
|
|
110
105
|
}
|
|
111
106
|
catch (e) {
|
|
112
107
|
valid = false;
|
|
@@ -116,9 +111,7 @@ export function validateJsonForCodeBlock(json, options = {}) {
|
|
|
116
111
|
}
|
|
117
112
|
else {
|
|
118
113
|
try {
|
|
119
|
-
content = prettyPrint
|
|
120
|
-
? JSON.stringify(json, null, indent)
|
|
121
|
-
: JSON.stringify(json);
|
|
114
|
+
content = prettyPrint ? JSON.stringify(json, null, indent) : JSON.stringify(json);
|
|
122
115
|
}
|
|
123
116
|
catch (e) {
|
|
124
117
|
valid = false;
|
|
@@ -164,12 +157,12 @@ export function escapeInlineCode(text) {
|
|
|
164
157
|
export function escapeLinkTitle(text) {
|
|
165
158
|
if (!text)
|
|
166
159
|
return '';
|
|
167
|
-
return text
|
|
160
|
+
return (text
|
|
168
161
|
// Escape quotes
|
|
169
162
|
.replace(/"/g, '\\"')
|
|
170
163
|
// Escape parentheses
|
|
171
164
|
.replace(/\)/g, '\\)')
|
|
172
|
-
.replace(/\(/g, '\\(');
|
|
165
|
+
.replace(/\(/g, '\\('));
|
|
173
166
|
}
|
|
174
167
|
/**
|
|
175
168
|
* Escape text for use in a Markdown bullet list item.
|
|
@@ -236,7 +229,7 @@ export function wrapTableCell(text, maxWidth = DISPLAY_LIMITS.TABLE_CELL_MAX_WID
|
|
|
236
229
|
export function buildTable(headers, rows, alignments) {
|
|
237
230
|
const lines = [];
|
|
238
231
|
// Header row
|
|
239
|
-
const escapedHeaders = headers.map(h => escapeTableCell(h));
|
|
232
|
+
const escapedHeaders = headers.map((h) => escapeTableCell(h));
|
|
240
233
|
lines.push(`| ${escapedHeaders.join(' | ')} |`);
|
|
241
234
|
// Separator row with alignment
|
|
242
235
|
const separators = headers.map((_, i) => {
|
|
@@ -253,11 +246,13 @@ export function buildTable(headers, rows, alignments) {
|
|
|
253
246
|
lines.push(`| ${separators.join(' | ')} |`);
|
|
254
247
|
// Data rows
|
|
255
248
|
for (const row of rows) {
|
|
256
|
-
const escapedCells = row.map(cell => escapeTableCell(cell));
|
|
249
|
+
const escapedCells = row.map((cell) => escapeTableCell(cell));
|
|
257
250
|
// Pad row if needed
|
|
258
251
|
while (escapedCells.length < headers.length) {
|
|
259
252
|
escapedCells.push('');
|
|
260
253
|
}
|
|
254
|
+
// Truncate excess columns
|
|
255
|
+
escapedCells.length = headers.length;
|
|
261
256
|
lines.push(`| ${escapedCells.join(' | ')} |`);
|
|
262
257
|
}
|
|
263
258
|
return lines.join('\n');
|
package/dist/utils/timeout.js
CHANGED
package/dist/version.js
CHANGED
package/man/bellwether.1
CHANGED
package/man/bellwether.1.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotsetlabs/bellwether",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "The open-source MCP testing tool. Structural drift detection and behavioral documentation for Model Context Protocol servers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -95,6 +95,7 @@
|
|
|
95
95
|
"@types/node": "^25.0.9",
|
|
96
96
|
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
|
97
97
|
"@typescript-eslint/parser": "^6.21.0",
|
|
98
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
98
99
|
"eslint": "^8.57.1",
|
|
99
100
|
"husky": "^9.1.0",
|
|
100
101
|
"lint-staged": "^16.2.7",
|