@wrongstack/mcp 0.309.1 → 0.310.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/client.d.ts +2 -22
- package/dist/contracts.d.ts +38 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +188 -198
- package/dist/manifest-cache.d.ts +1 -1
- package/dist/operations.d.ts +1 -1
- package/dist/registry-catalog.d.ts +1 -1
- package/dist/registry-connect-loop.d.ts +2 -1
- package/dist/registry-slots.d.ts +3 -2
- package/dist/registry-types.d.ts +1 -1
- package/dist/registry.d.ts +3 -2
- package/dist/test-helpers/mock-server.d.ts +1 -1
- package/dist/tool-schema.d.ts +1 -1
- package/dist/transport-base.d.ts +1 -1
- package/dist/transport-sse.d.ts +1 -1
- package/dist/transport-streamable.d.ts +1 -1
- package/dist/wrap-tool.d.ts +2 -1
- package/package.json +2 -2
package/dist/client.d.ts
CHANGED
|
@@ -24,34 +24,14 @@ export interface MCPClientOptions {
|
|
|
24
24
|
*/
|
|
25
25
|
passthroughEnv?: string[] | undefined;
|
|
26
26
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
| 'dormant';
|
|
30
|
-
export interface MCPTool {
|
|
31
|
-
name: string;
|
|
32
|
-
description?: string | undefined;
|
|
33
|
-
inputSchema: Record<string, unknown>;
|
|
34
|
-
}
|
|
35
|
-
export interface ToolCallResult {
|
|
36
|
-
content: unknown;
|
|
37
|
-
isError: boolean;
|
|
38
|
-
}
|
|
27
|
+
import type { ConnectionState, JsonRpcResponse, MCPTool, ToolCallResult } from './contracts.js';
|
|
28
|
+
export type { ConnectionState, JsonRpcResponse, MCPTool, ToolCallResult };
|
|
39
29
|
export interface MCPRequestOptions {
|
|
40
30
|
signal?: AbortSignal | undefined;
|
|
41
31
|
}
|
|
42
32
|
export interface MCPPageOptions extends MCPRequestOptions {
|
|
43
33
|
cursor?: string | undefined;
|
|
44
34
|
}
|
|
45
|
-
export interface JsonRpcResponse {
|
|
46
|
-
jsonrpc: '2.0';
|
|
47
|
-
id: number;
|
|
48
|
-
result?: unknown | undefined;
|
|
49
|
-
error?: {
|
|
50
|
-
code: number | undefined;
|
|
51
|
-
message: string;
|
|
52
|
-
data?: unknown | undefined;
|
|
53
|
-
} | undefined;
|
|
54
|
-
}
|
|
55
35
|
type ExitListener = (name: string, code: number | null, signal: string | null) => void;
|
|
56
36
|
type ToolsChangedListener = (name: string, tools: MCPTool[]) => void;
|
|
57
37
|
export type MCPListChangedListener = (name: string) => void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Leaf contract types for the @wrongstack/mcp public surface.
|
|
3
|
+
*
|
|
4
|
+
* Lives separately from `client.ts` so callers (transport implementations,
|
|
5
|
+
* schema normalisers, registry helpers) can depend on the wire-level types
|
|
6
|
+
* without pulling in the MCP client implementation. Breaks the long-standing
|
|
7
|
+
* type-level SCC ARCH-CYCLE-TYPE-17 where `client.ts` both homed the shared
|
|
8
|
+
* contracts and was imported back by every transport/schema module.
|
|
9
|
+
*
|
|
10
|
+
* This module MUST contain no runtime imports.
|
|
11
|
+
*/
|
|
12
|
+
/** Connection lifecycle states for an MCP client. */
|
|
13
|
+
export type ConnectionState = 'idle' | 'connecting' | 'connected' | 'disconnected' | 'reconnecting' | 'failed'
|
|
14
|
+
/** Lazy server: registered from a cached manifest, process not spawned. */
|
|
15
|
+
| 'dormant';
|
|
16
|
+
/** Minimal MCP tool descriptor returned by `tools/list`. */
|
|
17
|
+
export interface MCPTool {
|
|
18
|
+
name: string;
|
|
19
|
+
description?: string | undefined;
|
|
20
|
+
inputSchema: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
/** Result envelope returned by an MCP `tools/call`. */
|
|
23
|
+
export interface ToolCallResult {
|
|
24
|
+
content: unknown;
|
|
25
|
+
isError: boolean;
|
|
26
|
+
}
|
|
27
|
+
/** JSON-RPC 2.0 response shape (success or error). */
|
|
28
|
+
export interface JsonRpcResponse {
|
|
29
|
+
jsonrpc: '2.0';
|
|
30
|
+
id: number;
|
|
31
|
+
result?: unknown | undefined;
|
|
32
|
+
error?: {
|
|
33
|
+
code: number | undefined;
|
|
34
|
+
message: string;
|
|
35
|
+
data?: unknown | undefined;
|
|
36
|
+
} | undefined;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=contracts.d.ts.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export { authorizationHeaderForToken, authorizationServerMetadataUrls, canonicalMcpResource, createMcpAuthorizationRequest, discoverMcpAuthorization, exchangeMcpAuthorizationCode, type MCPAccessToken, type MCPAuthorizationChallenge, type MCPAuthorizationContext, type MCPAuthorizationDiscoveryOptions, type MCPAuthorizationDiscoveryResult, type MCPAuthorizationJsonFetcher, type MCPAuthorizationProvider, type MCPAuthorizationRequestOptions, type MCPAuthorizationServerMetadata, type MCPAuthorizationSession, type MCPProtectedResourceMetadata, type MCPTokenExchangeOptions, type MCPTokenRefreshOptions, type MCPTokenSet, parseAuthorizationServerMetadata, parseMcpAuthorizationCallback, parseMcpBearerChallenge, parseProtectedResourceMetadata, protectedResourceMetadataUrls, refreshMcpAccessToken, validateMcpAuthorizationServerMetadata, } from './authorization.js';
|
|
2
2
|
export { type MCPAuthorizationCompleteInput, MCPAuthorizationManager, type MCPAuthorizationManagerOptions, type MCPAuthorizationStartInput, type MCPAuthorizationStartResult, type MCPAuthorizationStatus, } from './authorization-manager.js';
|
|
3
|
-
export {
|
|
3
|
+
export { MCPClient, type MCPClientOptions, type MCPListChangedListener, type MCPPageOptions, type MCPRequestOptions, type Transport, } from './client.js';
|
|
4
4
|
export { MCP_CONSTANTS } from './constants.js';
|
|
5
5
|
export { DEFAULT_MCP_INSERTION_MAX_BYTES, DEFAULT_MCP_RESOURCE_SCHEMES, type MCPContentProvenance, type MCPInsertionPolicy, type MCPPromptInsertion, type MCPResourceInsertion, preparePromptInsertion, prepareResourceInsertion, } from './content-selection.js';
|
|
6
|
+
export type { ConnectionState, JsonRpcResponse, MCPTool, ToolCallResult, } from './contracts.js';
|
|
6
7
|
export { addMcp, disableMcp, discoverMcp, enableMcp, listMcp, MCP_ENV_MASK, type McpManageDeps, type McpOpResult, type McpServerInfo, type McpServerInput, removeMcp, restartMcp, updateMcp, } from './manage.js';
|
|
7
8
|
export { type MCPCapabilityManifest, manifestConfigHash, readCapabilityManifest, readManifest, writeCapabilityManifest, writeManifest, } from './manifest-cache.js';
|
|
8
9
|
export { MCP_OPERATION_LIMITS, type MCPFailureKind, type MCPHealthState, type MCPLatencySummary, type MCPOperationEvent, type MCPOperationKind, type MCPServerOperationalHealth, } from './operations.js';
|
package/dist/index.js
CHANGED
|
@@ -3471,94 +3471,6 @@ function percentile(sorted, ratio) {
|
|
|
3471
3471
|
// src/registry.ts
|
|
3472
3472
|
import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
|
|
3473
3473
|
|
|
3474
|
-
// src/registry-catalog.ts
|
|
3475
|
-
var MAX_CATALOG_PAGES = 100;
|
|
3476
|
-
var MAX_CATALOG_ITEMS = 1e4;
|
|
3477
|
-
async function collectCatalogPages(load, select) {
|
|
3478
|
-
const items = [];
|
|
3479
|
-
const seenCursors = /* @__PURE__ */ new Set();
|
|
3480
|
-
let cursor;
|
|
3481
|
-
for (let pageNumber = 0; pageNumber < MAX_CATALOG_PAGES; pageNumber++) {
|
|
3482
|
-
const page = await load(cursor);
|
|
3483
|
-
items.push(...select(page));
|
|
3484
|
-
if (items.length > MAX_CATALOG_ITEMS) {
|
|
3485
|
-
throw new Error(`MCP catalog exceeds ${MAX_CATALOG_ITEMS} items`);
|
|
3486
|
-
}
|
|
3487
|
-
const next = page.nextCursor;
|
|
3488
|
-
if (!next) return items;
|
|
3489
|
-
if (seenCursors.has(next)) throw new Error(`MCP catalog repeated cursor "${next}"`);
|
|
3490
|
-
seenCursors.add(next);
|
|
3491
|
-
cursor = next;
|
|
3492
|
-
}
|
|
3493
|
-
throw new Error(`MCP catalog exceeds ${MAX_CATALOG_PAGES} pages`);
|
|
3494
|
-
}
|
|
3495
|
-
function cloneCatalogRecords(records) {
|
|
3496
|
-
return structuredClone(records);
|
|
3497
|
-
}
|
|
3498
|
-
function registryCatalogSnapshot(slot) {
|
|
3499
|
-
return {
|
|
3500
|
-
name: slot.cfg.name,
|
|
3501
|
-
state: slot.state,
|
|
3502
|
-
serverMetadata: slot.serverMetadata ? structuredClone(slot.serverMetadata) : void 0,
|
|
3503
|
-
resources: slot.resources ? cloneCatalogRecords(slot.resources) : void 0,
|
|
3504
|
-
resourceTemplates: slot.resourceTemplates ? cloneCatalogRecords(slot.resourceTemplates) : void 0,
|
|
3505
|
-
prompts: slot.prompts ? cloneCatalogRecords(slot.prompts) : void 0
|
|
3506
|
-
};
|
|
3507
|
-
}
|
|
3508
|
-
|
|
3509
|
-
// src/registry-health.ts
|
|
3510
|
-
function buildRegistryOperationalHealth(servers, disabledServers) {
|
|
3511
|
-
const active = Array.from(servers).map((slot) => {
|
|
3512
|
-
const op = slot.operations;
|
|
3513
|
-
const baseHealth = healthStateFor(slot.state, op, slot.cfg.enabled !== false);
|
|
3514
|
-
const checks = evaluateHealthThresholds(op, slot.cfg.health?.thresholds);
|
|
3515
|
-
return {
|
|
3516
|
-
name: slot.cfg.name,
|
|
3517
|
-
connectionState: slot.state,
|
|
3518
|
-
healthState: applyHealthThresholds(baseHealth, checks),
|
|
3519
|
-
lastSuccessAt: op.lastSuccessAt,
|
|
3520
|
-
lastFailureAt: op.lastFailureAt,
|
|
3521
|
-
lastFailureKind: op.lastFailureKind,
|
|
3522
|
-
lastReason: op.lastReason,
|
|
3523
|
-
consecutiveFailures: op.consecutiveFailures,
|
|
3524
|
-
failures: { ...op.failures },
|
|
3525
|
-
reconnectCount: op.reconnectCount,
|
|
3526
|
-
wakeCount: op.wakeCount,
|
|
3527
|
-
sleepCount: op.sleepCount,
|
|
3528
|
-
restartCount: op.restartCount,
|
|
3529
|
-
connectionLatency: summarizeLatency(op.connectionSamples),
|
|
3530
|
-
discoveryLatency: summarizeLatency(op.discoverySamples),
|
|
3531
|
-
callLatency: summarizeLatency(op.callSamples),
|
|
3532
|
-
inFlightCalls: op.inFlightCalls,
|
|
3533
|
-
peakInFlightCalls: op.peakInFlightCalls,
|
|
3534
|
-
recentEvents: op.recentEvents.map((event) => ({ ...event })),
|
|
3535
|
-
healthChecks: checks
|
|
3536
|
-
};
|
|
3537
|
-
});
|
|
3538
|
-
const disabled = Array.from(disabledServers).map((cfg) => {
|
|
3539
|
-
const operations = createMCPServerOperationState();
|
|
3540
|
-
return {
|
|
3541
|
-
name: cfg.name,
|
|
3542
|
-
connectionState: "idle",
|
|
3543
|
-
healthState: "disabled",
|
|
3544
|
-
consecutiveFailures: 0,
|
|
3545
|
-
failures: { ...operations.failures },
|
|
3546
|
-
reconnectCount: 0,
|
|
3547
|
-
wakeCount: 0,
|
|
3548
|
-
sleepCount: 0,
|
|
3549
|
-
restartCount: 0,
|
|
3550
|
-
connectionLatency: summarizeLatency([]),
|
|
3551
|
-
discoveryLatency: summarizeLatency([]),
|
|
3552
|
-
callLatency: summarizeLatency([]),
|
|
3553
|
-
inFlightCalls: 0,
|
|
3554
|
-
peakInFlightCalls: 0,
|
|
3555
|
-
recentEvents: [],
|
|
3556
|
-
healthChecks: []
|
|
3557
|
-
};
|
|
3558
|
-
});
|
|
3559
|
-
return [...active, ...disabled];
|
|
3560
|
-
}
|
|
3561
|
-
|
|
3562
3474
|
// src/registry-authorization.ts
|
|
3563
3475
|
function requireAuthorizationManager(manager) {
|
|
3564
3476
|
if (!manager) {
|
|
@@ -3590,109 +3502,39 @@ async function completeRegistryAuthorization(manager, cfg, name, callbackUrl, si
|
|
|
3590
3502
|
});
|
|
3591
3503
|
}
|
|
3592
3504
|
|
|
3593
|
-
// src/registry-
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
const safeReason = safeOperationReason(reason);
|
|
3606
|
-
operations.lastFailureAt = Date.now();
|
|
3607
|
-
operations.lastFailureKind = failureKind;
|
|
3608
|
-
operations.lastReason = safeReason;
|
|
3609
|
-
operations.consecutiveFailures++;
|
|
3610
|
-
operations.failures[failureKind]++;
|
|
3611
|
-
recordRegistryOperation(slot, listeners, "failure", safeReason, failureKind, durationMs);
|
|
3612
|
-
}
|
|
3613
|
-
function recordRegistryOperation(slot, listeners, kind, reason, failureKind, durationMs, retain = true) {
|
|
3614
|
-
const operations = operationsForSlot(slot);
|
|
3615
|
-
const baseHealth = healthStateFor(slot.state, operations, slot.cfg.enabled !== false);
|
|
3616
|
-
const checks = evaluateHealthThresholds(operations, slot.cfg.health?.thresholds);
|
|
3617
|
-
const event = {
|
|
3618
|
-
serverName: slot.cfg.name,
|
|
3619
|
-
kind,
|
|
3620
|
-
at: Date.now(),
|
|
3621
|
-
connectionState: slot.state,
|
|
3622
|
-
healthState: applyHealthThresholds(baseHealth, checks)
|
|
3623
|
-
};
|
|
3624
|
-
if (reason !== void 0) event.reason = safeOperationReason(reason);
|
|
3625
|
-
if (failureKind !== void 0) event.failureKind = failureKind;
|
|
3626
|
-
if (durationMs !== void 0) event.durationMs = Math.max(0, Math.round(durationMs));
|
|
3627
|
-
if (retain) pushBounded(operations.recentEvents, event, MCP_OPERATION_LIMITS.RECENT_EVENTS);
|
|
3628
|
-
for (const listener of listeners) {
|
|
3629
|
-
try {
|
|
3630
|
-
listener({ ...event });
|
|
3631
|
-
} catch {
|
|
3505
|
+
// src/registry-catalog.ts
|
|
3506
|
+
var MAX_CATALOG_PAGES = 100;
|
|
3507
|
+
var MAX_CATALOG_ITEMS = 1e4;
|
|
3508
|
+
async function collectCatalogPages(load, select) {
|
|
3509
|
+
const items = [];
|
|
3510
|
+
const seenCursors = /* @__PURE__ */ new Set();
|
|
3511
|
+
let cursor;
|
|
3512
|
+
for (let pageNumber = 0; pageNumber < MAX_CATALOG_PAGES; pageNumber++) {
|
|
3513
|
+
const page = await load(cursor);
|
|
3514
|
+
items.push(...select(page));
|
|
3515
|
+
if (items.length > MAX_CATALOG_ITEMS) {
|
|
3516
|
+
throw new Error(`MCP catalog exceeds ${MAX_CATALOG_ITEMS} items`);
|
|
3632
3517
|
}
|
|
3518
|
+
const next = page.nextCursor;
|
|
3519
|
+
if (!next) return items;
|
|
3520
|
+
if (seenCursors.has(next)) throw new Error(`MCP catalog repeated cursor "${next}"`);
|
|
3521
|
+
seenCursors.add(next);
|
|
3522
|
+
cursor = next;
|
|
3633
3523
|
}
|
|
3524
|
+
throw new Error(`MCP catalog exceeds ${MAX_CATALOG_PAGES} pages`);
|
|
3634
3525
|
}
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
function resetDisconnectedSlotTools(slot, toolRegistry) {
|
|
3638
|
-
for (const t of slot.toolNames) {
|
|
3639
|
-
try {
|
|
3640
|
-
toolRegistry.unregister(t);
|
|
3641
|
-
} catch {
|
|
3642
|
-
}
|
|
3643
|
-
}
|
|
3644
|
-
slot.toolNames = [];
|
|
3645
|
-
slot.lazyTools = [];
|
|
3646
|
-
slot.serverMetadata = void 0;
|
|
3647
|
-
slot.resources = void 0;
|
|
3648
|
-
slot.resourceTemplates = void 0;
|
|
3649
|
-
slot.prompts = void 0;
|
|
3526
|
+
function cloneCatalogRecords(records) {
|
|
3527
|
+
return structuredClone(records);
|
|
3650
3528
|
}
|
|
3651
|
-
function
|
|
3652
|
-
|
|
3653
|
-
slot.state = "dormant";
|
|
3654
|
-
events.emit("mcp.server.disconnected", {
|
|
3529
|
+
function registryCatalogSnapshot(slot) {
|
|
3530
|
+
return {
|
|
3655
3531
|
name: slot.cfg.name,
|
|
3656
|
-
|
|
3657
|
-
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
slot,
|
|
3663
|
-
events,
|
|
3664
|
-
log,
|
|
3665
|
-
maxReconnectCycles,
|
|
3666
|
-
baseReconnectDelayMs,
|
|
3667
|
-
maxReconnectDelayMs,
|
|
3668
|
-
recordReconnectExhausted,
|
|
3669
|
-
attemptReconnect
|
|
3670
|
-
}) {
|
|
3671
|
-
if (slot.reconnectPending) return;
|
|
3672
|
-
if (slot.reconnectCycles >= maxReconnectCycles) {
|
|
3673
|
-
slot.state = "failed";
|
|
3674
|
-
recordReconnectExhausted(slot);
|
|
3675
|
-
log.error(
|
|
3676
|
-
`MCP server "${slot.cfg.name}" giving up after ${slot.reconnectCycles} reconnect cycles. Use \`/mcp restart ${slot.cfg.name}\` to retry.`
|
|
3677
|
-
);
|
|
3678
|
-
events.emit("mcp.server.disconnected", {
|
|
3679
|
-
name: slot.cfg.name,
|
|
3680
|
-
reason: `reconnect-exhausted:${slot.reconnectCycles}`
|
|
3681
|
-
});
|
|
3682
|
-
return;
|
|
3683
|
-
}
|
|
3684
|
-
slot.reconnectPending = true;
|
|
3685
|
-
if (slot.reconnectTimer) {
|
|
3686
|
-
clearTimeout(slot.reconnectTimer);
|
|
3687
|
-
slot.reconnectTimer = void 0;
|
|
3688
|
-
}
|
|
3689
|
-
const base = Math.min(baseReconnectDelayMs * 2 ** slot.reconnectCycles, maxReconnectDelayMs);
|
|
3690
|
-
const jitter = base * MCP_CONSTANTS.RECONNECT.JITTER_FACTOR * (Math.random() * 2 - 1);
|
|
3691
|
-
const delay = Math.max(100, Math.round(base + jitter));
|
|
3692
|
-
slot.reconnectTimer = setTimeout(() => {
|
|
3693
|
-
slot.reconnectTimer = void 0;
|
|
3694
|
-
void attemptReconnect(slot);
|
|
3695
|
-
}, delay);
|
|
3532
|
+
state: slot.state,
|
|
3533
|
+
serverMetadata: slot.serverMetadata ? structuredClone(slot.serverMetadata) : void 0,
|
|
3534
|
+
resources: slot.resources ? cloneCatalogRecords(slot.resources) : void 0,
|
|
3535
|
+
resourceTemplates: slot.resourceTemplates ? cloneCatalogRecords(slot.resourceTemplates) : void 0,
|
|
3536
|
+
prompts: slot.prompts ? cloneCatalogRecords(slot.prompts) : void 0
|
|
3537
|
+
};
|
|
3696
3538
|
}
|
|
3697
3539
|
|
|
3698
3540
|
// src/registry-connect-loop.ts
|
|
@@ -3782,11 +3624,7 @@ function applySlotTools(ctx, slot, tools, client) {
|
|
|
3782
3624
|
},
|
|
3783
3625
|
onFinish: ({ durationMs, ok }) => {
|
|
3784
3626
|
slot.operations.inFlightCalls = Math.max(0, slot.operations.inFlightCalls - 1);
|
|
3785
|
-
pushBounded(
|
|
3786
|
-
slot.operations.callSamples,
|
|
3787
|
-
durationMs,
|
|
3788
|
-
MCP_OPERATION_LIMITS.LATENCY_SAMPLES
|
|
3789
|
-
);
|
|
3627
|
+
pushBounded(slot.operations.callSamples, durationMs, MCP_OPERATION_LIMITS.LATENCY_SAMPLES);
|
|
3790
3628
|
if (ok) {
|
|
3791
3629
|
ctx.recordSuccess(slot);
|
|
3792
3630
|
ctx.recordOperation(slot, "call", "ok", void 0, durationMs, false);
|
|
@@ -3997,6 +3835,83 @@ async function attemptConnectSlot(ctx, slot) {
|
|
|
3997
3835
|
}
|
|
3998
3836
|
}
|
|
3999
3837
|
|
|
3838
|
+
// src/registry-disconnect.ts
|
|
3839
|
+
function resetDisconnectedSlotTools(slot, toolRegistry) {
|
|
3840
|
+
for (const t of slot.toolNames) {
|
|
3841
|
+
try {
|
|
3842
|
+
toolRegistry.unregister(t);
|
|
3843
|
+
} catch {
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
slot.toolNames = [];
|
|
3847
|
+
slot.lazyTools = [];
|
|
3848
|
+
slot.serverMetadata = void 0;
|
|
3849
|
+
slot.resources = void 0;
|
|
3850
|
+
slot.resourceTemplates = void 0;
|
|
3851
|
+
slot.prompts = void 0;
|
|
3852
|
+
}
|
|
3853
|
+
function markLazySlotDormant(slot, events, reason) {
|
|
3854
|
+
slot.client = void 0;
|
|
3855
|
+
slot.state = "dormant";
|
|
3856
|
+
events.emit("mcp.server.disconnected", {
|
|
3857
|
+
name: slot.cfg.name,
|
|
3858
|
+
reason: `${reason} (dormant)`
|
|
3859
|
+
});
|
|
3860
|
+
}
|
|
3861
|
+
|
|
3862
|
+
// src/registry-health.ts
|
|
3863
|
+
function buildRegistryOperationalHealth(servers, disabledServers) {
|
|
3864
|
+
const active = Array.from(servers).map((slot) => {
|
|
3865
|
+
const op = slot.operations;
|
|
3866
|
+
const baseHealth = healthStateFor(slot.state, op, slot.cfg.enabled !== false);
|
|
3867
|
+
const checks = evaluateHealthThresholds(op, slot.cfg.health?.thresholds);
|
|
3868
|
+
return {
|
|
3869
|
+
name: slot.cfg.name,
|
|
3870
|
+
connectionState: slot.state,
|
|
3871
|
+
healthState: applyHealthThresholds(baseHealth, checks),
|
|
3872
|
+
lastSuccessAt: op.lastSuccessAt,
|
|
3873
|
+
lastFailureAt: op.lastFailureAt,
|
|
3874
|
+
lastFailureKind: op.lastFailureKind,
|
|
3875
|
+
lastReason: op.lastReason,
|
|
3876
|
+
consecutiveFailures: op.consecutiveFailures,
|
|
3877
|
+
failures: { ...op.failures },
|
|
3878
|
+
reconnectCount: op.reconnectCount,
|
|
3879
|
+
wakeCount: op.wakeCount,
|
|
3880
|
+
sleepCount: op.sleepCount,
|
|
3881
|
+
restartCount: op.restartCount,
|
|
3882
|
+
connectionLatency: summarizeLatency(op.connectionSamples),
|
|
3883
|
+
discoveryLatency: summarizeLatency(op.discoverySamples),
|
|
3884
|
+
callLatency: summarizeLatency(op.callSamples),
|
|
3885
|
+
inFlightCalls: op.inFlightCalls,
|
|
3886
|
+
peakInFlightCalls: op.peakInFlightCalls,
|
|
3887
|
+
recentEvents: op.recentEvents.map((event) => ({ ...event })),
|
|
3888
|
+
healthChecks: checks
|
|
3889
|
+
};
|
|
3890
|
+
});
|
|
3891
|
+
const disabled = Array.from(disabledServers).map((cfg) => {
|
|
3892
|
+
const operations = createMCPServerOperationState();
|
|
3893
|
+
return {
|
|
3894
|
+
name: cfg.name,
|
|
3895
|
+
connectionState: "idle",
|
|
3896
|
+
healthState: "disabled",
|
|
3897
|
+
consecutiveFailures: 0,
|
|
3898
|
+
failures: { ...operations.failures },
|
|
3899
|
+
reconnectCount: 0,
|
|
3900
|
+
wakeCount: 0,
|
|
3901
|
+
sleepCount: 0,
|
|
3902
|
+
restartCount: 0,
|
|
3903
|
+
connectionLatency: summarizeLatency([]),
|
|
3904
|
+
discoveryLatency: summarizeLatency([]),
|
|
3905
|
+
callLatency: summarizeLatency([]),
|
|
3906
|
+
inFlightCalls: 0,
|
|
3907
|
+
peakInFlightCalls: 0,
|
|
3908
|
+
recentEvents: [],
|
|
3909
|
+
healthChecks: []
|
|
3910
|
+
};
|
|
3911
|
+
});
|
|
3912
|
+
return [...active, ...disabled];
|
|
3913
|
+
}
|
|
3914
|
+
|
|
4000
3915
|
// src/registry-idle.ts
|
|
4001
3916
|
async function sleepIdleSlot(ctx, slot) {
|
|
4002
3917
|
slot.reconnectPending = false;
|
|
@@ -4032,6 +3947,87 @@ async function sweepIdleSlots(ctx) {
|
|
|
4032
3947
|
);
|
|
4033
3948
|
}
|
|
4034
3949
|
|
|
3950
|
+
// src/registry-operations.ts
|
|
3951
|
+
function operationsForSlot(slot) {
|
|
3952
|
+
if (!slot.operations) slot.operations = createMCPServerOperationState();
|
|
3953
|
+
return slot.operations;
|
|
3954
|
+
}
|
|
3955
|
+
function recordRegistrySuccess(slot, resetFailures = true) {
|
|
3956
|
+
const operations = operationsForSlot(slot);
|
|
3957
|
+
operations.lastSuccessAt = Date.now();
|
|
3958
|
+
if (resetFailures) operations.consecutiveFailures = 0;
|
|
3959
|
+
}
|
|
3960
|
+
function recordRegistryFailure(slot, listeners, failureKind, reason, durationMs) {
|
|
3961
|
+
const operations = operationsForSlot(slot);
|
|
3962
|
+
const safeReason = safeOperationReason(reason);
|
|
3963
|
+
operations.lastFailureAt = Date.now();
|
|
3964
|
+
operations.lastFailureKind = failureKind;
|
|
3965
|
+
operations.lastReason = safeReason;
|
|
3966
|
+
operations.consecutiveFailures++;
|
|
3967
|
+
operations.failures[failureKind]++;
|
|
3968
|
+
recordRegistryOperation(slot, listeners, "failure", safeReason, failureKind, durationMs);
|
|
3969
|
+
}
|
|
3970
|
+
function recordRegistryOperation(slot, listeners, kind, reason, failureKind, durationMs, retain = true) {
|
|
3971
|
+
const operations = operationsForSlot(slot);
|
|
3972
|
+
const baseHealth = healthStateFor(slot.state, operations, slot.cfg.enabled !== false);
|
|
3973
|
+
const checks = evaluateHealthThresholds(operations, slot.cfg.health?.thresholds);
|
|
3974
|
+
const event = {
|
|
3975
|
+
serverName: slot.cfg.name,
|
|
3976
|
+
kind,
|
|
3977
|
+
at: Date.now(),
|
|
3978
|
+
connectionState: slot.state,
|
|
3979
|
+
healthState: applyHealthThresholds(baseHealth, checks)
|
|
3980
|
+
};
|
|
3981
|
+
if (reason !== void 0) event.reason = safeOperationReason(reason);
|
|
3982
|
+
if (failureKind !== void 0) event.failureKind = failureKind;
|
|
3983
|
+
if (durationMs !== void 0) event.durationMs = Math.max(0, Math.round(durationMs));
|
|
3984
|
+
if (retain) pushBounded(operations.recentEvents, event, MCP_OPERATION_LIMITS.RECENT_EVENTS);
|
|
3985
|
+
for (const listener of listeners) {
|
|
3986
|
+
try {
|
|
3987
|
+
listener({ ...event });
|
|
3988
|
+
} catch {
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3992
|
+
|
|
3993
|
+
// src/registry-reconnect.ts
|
|
3994
|
+
function scheduleRegistryReconnect({
|
|
3995
|
+
slot,
|
|
3996
|
+
events,
|
|
3997
|
+
log,
|
|
3998
|
+
maxReconnectCycles,
|
|
3999
|
+
baseReconnectDelayMs,
|
|
4000
|
+
maxReconnectDelayMs,
|
|
4001
|
+
recordReconnectExhausted,
|
|
4002
|
+
attemptReconnect
|
|
4003
|
+
}) {
|
|
4004
|
+
if (slot.reconnectPending) return;
|
|
4005
|
+
if (slot.reconnectCycles >= maxReconnectCycles) {
|
|
4006
|
+
slot.state = "failed";
|
|
4007
|
+
recordReconnectExhausted(slot);
|
|
4008
|
+
log.error(
|
|
4009
|
+
`MCP server "${slot.cfg.name}" giving up after ${slot.reconnectCycles} reconnect cycles. Use \`/mcp restart ${slot.cfg.name}\` to retry.`
|
|
4010
|
+
);
|
|
4011
|
+
events.emit("mcp.server.disconnected", {
|
|
4012
|
+
name: slot.cfg.name,
|
|
4013
|
+
reason: `reconnect-exhausted:${slot.reconnectCycles}`
|
|
4014
|
+
});
|
|
4015
|
+
return;
|
|
4016
|
+
}
|
|
4017
|
+
slot.reconnectPending = true;
|
|
4018
|
+
if (slot.reconnectTimer) {
|
|
4019
|
+
clearTimeout(slot.reconnectTimer);
|
|
4020
|
+
slot.reconnectTimer = void 0;
|
|
4021
|
+
}
|
|
4022
|
+
const base = Math.min(baseReconnectDelayMs * 2 ** slot.reconnectCycles, maxReconnectDelayMs);
|
|
4023
|
+
const jitter = base * MCP_CONSTANTS.RECONNECT.JITTER_FACTOR * (Math.random() * 2 - 1);
|
|
4024
|
+
const delay = Math.max(100, Math.round(base + jitter));
|
|
4025
|
+
slot.reconnectTimer = setTimeout(() => {
|
|
4026
|
+
slot.reconnectTimer = void 0;
|
|
4027
|
+
void attemptReconnect(slot);
|
|
4028
|
+
}, delay);
|
|
4029
|
+
}
|
|
4030
|
+
|
|
4035
4031
|
// src/registry.ts
|
|
4036
4032
|
var MCPRegistry = class _MCPRegistry {
|
|
4037
4033
|
servers = /* @__PURE__ */ new Map();
|
|
@@ -4069,13 +4065,7 @@ var MCPRegistry = class _MCPRegistry {
|
|
|
4069
4065
|
}
|
|
4070
4066
|
async completeAuthorization(name, callbackUrl, signal) {
|
|
4071
4067
|
const cfg = this.requireHttpServerConfig(name);
|
|
4072
|
-
return completeRegistryAuthorization(
|
|
4073
|
-
this.authorizationManager,
|
|
4074
|
-
cfg,
|
|
4075
|
-
name,
|
|
4076
|
-
callbackUrl,
|
|
4077
|
-
signal
|
|
4078
|
-
);
|
|
4068
|
+
return completeRegistryAuthorization(this.authorizationManager, cfg, name, callbackUrl, signal);
|
|
4079
4069
|
}
|
|
4080
4070
|
async authorizationStatus(name) {
|
|
4081
4071
|
const manager = requireAuthorizationManager(this.authorizationManager);
|
package/dist/manifest-cache.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { MCPTool } from './
|
|
1
|
+
import type { MCPTool } from './contracts.js';
|
|
2
2
|
import { type MCPPrompt, type MCPResource, type MCPResourceTemplate, type MCPServerMetadata } from './protocol.js';
|
|
3
3
|
export interface MCPCapabilityManifest {
|
|
4
4
|
tools: MCPTool[];
|
package/dist/operations.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { MCPHealthThresholds } from '@wrongstack/core/types';
|
|
2
|
-
import type { ConnectionState } from './
|
|
2
|
+
import type { ConnectionState } from './contracts.js';
|
|
3
3
|
/** Operator-facing health state. Intentionally separate from transport lifecycle state. */
|
|
4
4
|
export type MCPHealthState = 'disabled' | 'dormant' | 'connecting' | 'healthy' | 'degraded' | 'failed';
|
|
5
5
|
export type MCPFailureKind = 'transport' | 'protocol' | 'tool';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ConnectionState } from './
|
|
1
|
+
import type { ConnectionState } from './contracts.js';
|
|
2
2
|
import type { MCPPrompt, MCPResource, MCPResourceTemplate, MCPServerMetadata } from './protocol.js';
|
|
3
3
|
import type { MCPRegistryCatalog } from './registry-types.js';
|
|
4
4
|
export declare function collectCatalogPages<Page extends {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { EventBus } from '@wrongstack/core/kernel';
|
|
2
2
|
import type { ToolRegistry } from '@wrongstack/core/registry';
|
|
3
3
|
import type { Logger } from '@wrongstack/core/types';
|
|
4
|
-
import { MCPClient
|
|
4
|
+
import { MCPClient } from './client.js';
|
|
5
|
+
import type { MCPTool } from './contracts.js';
|
|
5
6
|
import { type MCPFailureKind, type MCPOperationKind, type MCPOperationListener } from './operations.js';
|
|
6
7
|
import type { ServerSlot } from './registry-slots.js';
|
|
7
8
|
import type { MCPRegistryOptions } from './registry-types.js';
|
package/dist/registry-slots.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { MCPServerConfig, Tool } from '@wrongstack/core/types';
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
2
|
+
import type { MCPClient } from './client.js';
|
|
3
|
+
import type { ConnectionState } from './contracts.js';
|
|
4
4
|
import type { MCPServerOperationState } from './operations.js';
|
|
5
|
+
import type { MCPPrompt, MCPResource, MCPResourceTemplate, MCPServerMetadata } from './protocol.js';
|
|
5
6
|
export interface ServerSlot {
|
|
6
7
|
cfg: MCPServerConfig;
|
|
7
8
|
client?: MCPClient | undefined;
|
package/dist/registry-types.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { ToolRegistry } from '@wrongstack/core/registry';
|
|
|
3
3
|
import type { Logger, MCPServerConfig } from '@wrongstack/core/types';
|
|
4
4
|
import type { MCPAuthorizationProvider } from './authorization.js';
|
|
5
5
|
import type { MCPAuthorizationManager } from './authorization-manager.js';
|
|
6
|
-
import type { ConnectionState } from './
|
|
6
|
+
import type { ConnectionState } from './contracts.js';
|
|
7
7
|
import type { MCPPrompt, MCPResource, MCPResourceTemplate, MCPServerMetadata } from './protocol.js';
|
|
8
8
|
export interface MCPRegistryOptions {
|
|
9
9
|
toolRegistry: ToolRegistry;
|
package/dist/registry.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import type { MCPServerConfig } from '@wrongstack/core/types';
|
|
2
2
|
import type { MCPAuthorizationStartResult, MCPAuthorizationStatus } from './authorization-manager.js';
|
|
3
|
-
import type {
|
|
3
|
+
import type { MCPClient } from './client.js';
|
|
4
4
|
import { type MCPInsertionPolicy, type MCPPromptInsertion, type MCPResourceInsertion } from './content-selection.js';
|
|
5
|
+
import type { ConnectionState } from './contracts.js';
|
|
5
6
|
import { type MCPOperationListener, type MCPServerOperationalHealth } from './operations.js';
|
|
6
|
-
import type { MCPRegistryCatalog, MCPRegistryOptions } from './registry-types.js';
|
|
7
7
|
import type { MCPGetPromptResult, MCPPrompt, MCPReadResourceResult, MCPResource, MCPResourceTemplate } from './protocol.js';
|
|
8
|
+
import type { MCPRegistryCatalog, MCPRegistryOptions } from './registry-types.js';
|
|
8
9
|
export type { MCPRegistryCatalog, MCPRegistryOptions } from './registry-types.js';
|
|
9
10
|
export declare class MCPRegistry {
|
|
10
11
|
private readonly servers;
|
package/dist/tool-schema.d.ts
CHANGED
package/dist/transport-base.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as https from 'node:https';
|
|
2
2
|
import { type MCPAuthorizationProvider } from './authorization.js';
|
|
3
|
-
import type { ConnectionState, MCPTool } from './
|
|
3
|
+
import type { ConnectionState, MCPTool } from './contracts.js';
|
|
4
4
|
import type { MCPServerMetadata } from './protocol.js';
|
|
5
5
|
export interface HttpTransportOptions {
|
|
6
6
|
name: string;
|
package/dist/transport-sse.d.ts
CHANGED
package/dist/wrap-tool.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Permission, Tool } from '@wrongstack/core/types';
|
|
2
|
-
import type { MCPClient
|
|
2
|
+
import type { MCPClient } from './client.js';
|
|
3
|
+
import type { MCPTool } from './contracts.js';
|
|
3
4
|
/**
|
|
4
5
|
* Resolves the live client for a tool call. A plain {@link MCPClient} for eager
|
|
5
6
|
* servers, or a thunk that connects-on-demand for lazy/dormant servers (the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrongstack/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.310.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "WrongStack Model Context Protocol client and registry: stdio, SSE, and streamable HTTP transports.",
|
|
6
6
|
"repository": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"!dist/**/*.map"
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@wrongstack/core": "0.
|
|
29
|
+
"@wrongstack/core": "0.310.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^26.2.0",
|