mcp-medic 1.2.1 → 1.2.2
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/protocol/connect.js +77 -11
- package/dist/report.js +14 -0
- package/dist/types.d.ts +21 -0
- package/package.json +1 -1
package/dist/protocol/connect.js
CHANGED
|
@@ -82,6 +82,37 @@ function normalizeTools(result) {
|
|
|
82
82
|
};
|
|
83
83
|
});
|
|
84
84
|
}
|
|
85
|
+
function normalizeResources(result) {
|
|
86
|
+
if (!Array.isArray(result.resources))
|
|
87
|
+
throw new Error('resources/list response has no resources array');
|
|
88
|
+
return result.resources.map((resource, index) => {
|
|
89
|
+
if (!resource || typeof resource !== 'object' || typeof resource.uri !== 'string') {
|
|
90
|
+
throw new Error(`resources/list returned an invalid resource at index ${index}`);
|
|
91
|
+
}
|
|
92
|
+
const value = resource;
|
|
93
|
+
return {
|
|
94
|
+
uri: value.uri,
|
|
95
|
+
...(typeof value.name === 'string' ? { name: value.name } : {}),
|
|
96
|
+
...(typeof value.description === 'string' ? { description: value.description } : {}),
|
|
97
|
+
...(typeof value.mimeType === 'string' ? { mimeType: value.mimeType } : {}),
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function normalizePrompts(result) {
|
|
102
|
+
if (!Array.isArray(result.prompts))
|
|
103
|
+
throw new Error('prompts/list response has no prompts array');
|
|
104
|
+
return result.prompts.map((prompt, index) => {
|
|
105
|
+
if (!prompt || typeof prompt !== 'object' || typeof prompt.name !== 'string') {
|
|
106
|
+
throw new Error(`prompts/list returned an invalid prompt at index ${index}`);
|
|
107
|
+
}
|
|
108
|
+
const value = prompt;
|
|
109
|
+
return {
|
|
110
|
+
name: value.name,
|
|
111
|
+
...(typeof value.description === 'string' ? { description: value.description } : {}),
|
|
112
|
+
...(Array.isArray(value.arguments) ? { arguments: value.arguments } : {}),
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
}
|
|
85
116
|
async function refreshTokenIfNeeded(config, options) {
|
|
86
117
|
const headers = { ...(config.headers ?? {}) };
|
|
87
118
|
if (!config.tokenRefreshUrl) {
|
|
@@ -409,6 +440,11 @@ export async function connect(config, timeoutMs, options) {
|
|
|
409
440
|
else {
|
|
410
441
|
return failed(config, 'failed', 'spawn', `unsupported transport: ${String(config.transport)}`);
|
|
411
442
|
}
|
|
443
|
+
// Requests are numbered sequentially in the order they're actually sent
|
|
444
|
+
// (notifications don't consume an id) — tracked locally rather than
|
|
445
|
+
// hardcoded, since which optional capability calls happen below depends
|
|
446
|
+
// on what the server declares.
|
|
447
|
+
let nextExpectedId = 1;
|
|
412
448
|
let initialize;
|
|
413
449
|
try {
|
|
414
450
|
const response = await withTimeout(transport.request('initialize', {
|
|
@@ -416,7 +452,7 @@ export async function connect(config, timeoutMs, options) {
|
|
|
416
452
|
capabilities: {},
|
|
417
453
|
clientInfo: CLIENT_INFO,
|
|
418
454
|
}), timeoutMs, 'initialize handshake');
|
|
419
|
-
initialize = validateResponse(response,
|
|
455
|
+
initialize = validateResponse(response, nextExpectedId++);
|
|
420
456
|
if (!initialize.capabilities || typeof initialize.capabilities !== 'object') {
|
|
421
457
|
throw new Error('initialize response has no capabilities object');
|
|
422
458
|
}
|
|
@@ -450,21 +486,51 @@ export async function connect(config, timeoutMs, options) {
|
|
|
450
486
|
catch (error) {
|
|
451
487
|
return failed(config, messageOf(error).includes('timed out') ? 'timeout' : 'failed', 'capability-negotiation', messageOf(error), error, { protocolVersion, serverInfo });
|
|
452
488
|
}
|
|
489
|
+
let tools;
|
|
453
490
|
try {
|
|
454
|
-
|
|
455
|
-
return {
|
|
456
|
-
server: config,
|
|
457
|
-
status: 'connected',
|
|
458
|
-
capabilities,
|
|
459
|
-
tools,
|
|
460
|
-
protocolVersion,
|
|
461
|
-
serverInfo,
|
|
462
|
-
latencyMs: Date.now() - started,
|
|
463
|
-
};
|
|
491
|
+
tools = normalizeTools(validateResponse(await withTimeout(transport.request('tools/list'), timeoutMs, 'tools/list'), nextExpectedId++));
|
|
464
492
|
}
|
|
465
493
|
catch (error) {
|
|
466
494
|
return failed(config, messageOf(error).includes('timed out') ? 'timeout' : 'failed', 'list-tools', messageOf(error), error, { protocolVersion, serverInfo });
|
|
467
495
|
}
|
|
496
|
+
// Resources and prompts are optional MCP capabilities: only inspect them
|
|
497
|
+
// if the server actually declared support in its initialize response.
|
|
498
|
+
// Passive enumeration only (resources/list, prompts/list) — never
|
|
499
|
+
// resources/read or prompts/get, which would be real invocation.
|
|
500
|
+
// A failure here is never fatal to the connection: tools is the one
|
|
501
|
+
// capability mcp-medic requires, so a broken resources/prompts listing
|
|
502
|
+
// is reported alongside a still-successful connection.
|
|
503
|
+
let resources;
|
|
504
|
+
let prompts;
|
|
505
|
+
const capabilityErrors = {};
|
|
506
|
+
if (capabilities.resources && typeof capabilities.resources === 'object') {
|
|
507
|
+
try {
|
|
508
|
+
resources = normalizeResources(validateResponse(await withTimeout(transport.request('resources/list'), timeoutMs, 'resources/list'), nextExpectedId++));
|
|
509
|
+
}
|
|
510
|
+
catch (error) {
|
|
511
|
+
capabilityErrors.resources = messageOf(error);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
if (capabilities.prompts && typeof capabilities.prompts === 'object') {
|
|
515
|
+
try {
|
|
516
|
+
prompts = normalizePrompts(validateResponse(await withTimeout(transport.request('prompts/list'), timeoutMs, 'prompts/list'), nextExpectedId++));
|
|
517
|
+
}
|
|
518
|
+
catch (error) {
|
|
519
|
+
capabilityErrors.prompts = messageOf(error);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
return {
|
|
523
|
+
server: config,
|
|
524
|
+
status: 'connected',
|
|
525
|
+
capabilities,
|
|
526
|
+
tools,
|
|
527
|
+
...(resources !== undefined ? { resources } : {}),
|
|
528
|
+
...(prompts !== undefined ? { prompts } : {}),
|
|
529
|
+
...(Object.keys(capabilityErrors).length > 0 ? { capabilityErrors } : {}),
|
|
530
|
+
protocolVersion,
|
|
531
|
+
serverInfo,
|
|
532
|
+
latencyMs: Date.now() - started,
|
|
533
|
+
};
|
|
468
534
|
}
|
|
469
535
|
catch (error) {
|
|
470
536
|
return failed(config, 'failed', 'handshake', messageOf(error), error);
|
package/dist/report.js
CHANGED
|
@@ -13,6 +13,20 @@ export function formatReportHuman(report, options = {}) {
|
|
|
13
13
|
const statusText = compatible ? '✓ compatible' : '✗ incompatible';
|
|
14
14
|
lines.push(` Protocol: requested ${requested}, server negotiated ${negotiated ?? '(none)'} — ${statusText}`);
|
|
15
15
|
}
|
|
16
|
+
if (conn.tools) {
|
|
17
|
+
const parts = [`${conn.tools.length} tool(s)`];
|
|
18
|
+
if (conn.resources)
|
|
19
|
+
parts.push(`${conn.resources.length} resource(s)`);
|
|
20
|
+
if (conn.prompts)
|
|
21
|
+
parts.push(`${conn.prompts.length} prompt(s)`);
|
|
22
|
+
lines.push(` Capabilities: ${parts.join(', ')}`);
|
|
23
|
+
}
|
|
24
|
+
if (conn.capabilityErrors?.resources) {
|
|
25
|
+
lines.push(` resources/list: ${conn.capabilityErrors.resources}`);
|
|
26
|
+
}
|
|
27
|
+
if (conn.capabilityErrors?.prompts) {
|
|
28
|
+
lines.push(` prompts/list: ${conn.capabilityErrors.prompts}`);
|
|
29
|
+
}
|
|
16
30
|
if (conn.error) {
|
|
17
31
|
lines.push(` ${conn.error.stage}: ${conn.error.message}`);
|
|
18
32
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -19,6 +19,17 @@ export interface MCPToolDefinition {
|
|
|
19
19
|
description?: string;
|
|
20
20
|
inputSchema: unknown;
|
|
21
21
|
}
|
|
22
|
+
export interface MCPResourceDefinition {
|
|
23
|
+
uri: string;
|
|
24
|
+
name?: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
mimeType?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface MCPPromptDefinition {
|
|
29
|
+
name: string;
|
|
30
|
+
description?: string;
|
|
31
|
+
arguments?: unknown[];
|
|
32
|
+
}
|
|
22
33
|
export interface ProtocolVersionInfo {
|
|
23
34
|
/** The protocolVersion this client sent in `initialize`. */
|
|
24
35
|
requested: string;
|
|
@@ -36,6 +47,16 @@ export interface MCPConnection {
|
|
|
36
47
|
status: 'connected' | 'failed' | 'timeout';
|
|
37
48
|
capabilities?: Record<string, unknown>;
|
|
38
49
|
tools?: MCPToolDefinition[];
|
|
50
|
+
/** Populated only if the server's `initialize` response declared a `resources` capability. */
|
|
51
|
+
resources?: MCPResourceDefinition[];
|
|
52
|
+
/** Populated only if the server's `initialize` response declared a `prompts` capability. */
|
|
53
|
+
prompts?: MCPPromptDefinition[];
|
|
54
|
+
/** Best-effort failures from optional capability inspection (resources/prompts) — these
|
|
55
|
+
* never fail the overall connection, since `tools` is the one capability mcp-medic requires. */
|
|
56
|
+
capabilityErrors?: {
|
|
57
|
+
resources?: string;
|
|
58
|
+
prompts?: string;
|
|
59
|
+
};
|
|
39
60
|
/** Set once an `initialize` response was received, even if negotiation was incompatible or a later stage failed. */
|
|
40
61
|
protocolVersion?: ProtocolVersionInfo;
|
|
41
62
|
serverInfo?: MCPServerInfo;
|