MCP test client: fix prompts/resources support and schema validation (#21521)
- Add $schema to allowed root properties in JSON schema validation - Add dynamic method injection for prompts (like tools already had) - Add dynamic method injection for resources - Refresh prompts/resources UI when data arrives from server - Make prompts/resources list timeouts non-fatal during handshake (previously a timeout would disconnect the entire client)
Costa Tsaousis committed
Jan 8, 2026 at 13:56 UTC
3c7b50bd763999d1312f25ffe3520a701b608cbb
2 files changed
+78
-23
src/web/mcp/mcp-test-client/index.html
+77
-22
@@ -2078,7 +2078,54 @@
2078
});
2079
}
2080
}
2081
-
2081
+
2082
+ // Add dynamic methods for prompts
2083
+ if (flowName === 'prompts' && Object.keys(availablePrompts).length > 0) {
2084
+ for (const [promptName, prompt] of Object.entries(availablePrompts)) {
2085
+ allMethods.push({
2086
+ name: promptName,
2087
+ title: prompt.title || '',
2088
+ type: 'request',
2089
+ description: prompt.description || 'No description available',
2090
+ dynamic: true,
2091
+ promptName: promptName,
2092
+ schema: prompt,
2093
+ template: {
2094
+ jsonrpc: "2.0",
2095
+ id: 1,
2096
+ method: "prompts/get",
2097
+ params: {
2098
+ name: promptName,
2099
+ arguments: {}
2100
+ }
2101
+ }
2102
+ });
2103
+ }
2104
+ }
2105
+
2106
+ // Add dynamic methods for resources
2107
+ if (flowName === 'resources' && Object.keys(availableResources).length > 0) {
2108
+ for (const [resourceUri, resource] of Object.entries(availableResources)) {
2109
+ allMethods.push({
2110
+ name: resource.name || resourceUri,
2111
+ title: resourceUri,
2112
+ type: 'request',
2113
+ description: resource.description || 'No description available',
2114
+ dynamic: true,
2115
+ resourceUri: resourceUri,
2116
+ schema: resource,
2117
+ template: {
2118
+ jsonrpc: "2.0",
2119
+ id: 1,
2120
+ method: "resources/read",
2121
+ params: {
2122
+ uri: resourceUri
2123
+ }
2124
+ }
2125
+ });
2126
+ }
2127
+ }
2128
+
2129
// Add history for custom flow
2130
if (flowName === 'custom') {
2131
const savedHistory = loadRequestHistory();
@@ -2552,18 +2599,17 @@
2599
10000,
2600
'Prompts list request timed out'
2601
);
2602
+ if (promptsResponse && promptsResponse.error) {
2603
+ log('⚠ Prompts list failed: ' + JSON.stringify(promptsResponse.error));
2604
+ } else {
2605
+ const promptCount = promptsResponse && promptsResponse.result && promptsResponse.result.prompts
2606
+ ? promptsResponse.result.prompts.length
2607
+ : 0;
2608
+ log('✓ Prompts list received (' + promptCount + ' prompts)');
2609
+ }
2610
} catch (err) {
2611
pendingRequests.delete(promptsRequest.id);
2557
- throw err;
2558
- }
2559
-
2560
- if (promptsResponse && promptsResponse.error) {
2561
- log('⚠ Prompts list failed: ' + JSON.stringify(promptsResponse.error));
2562
- } else {
2563
- const promptCount = promptsResponse && promptsResponse.result && promptsResponse.result.prompts
2564
- ? promptsResponse.result.prompts.length
2565
- : 0;
2566
- log('✓ Prompts list received (' + promptCount + ' prompts)');
2612
+ log('⚠ Prompts list skipped: ' + err.message);
2613
}
2614
2615
// Step 6: Request resources list
@@ -2581,18 +2627,17 @@
2627
10000,
2628
'Resources list request timed out'
2629
);
2630
+ if (resourcesResponse && resourcesResponse.error) {
2631
+ log('⚠ Resources list failed: ' + JSON.stringify(resourcesResponse.error));
2632
+ } else {
2633
+ const resourceCount = resourcesResponse && resourcesResponse.result && resourcesResponse.result.resources
2634
+ ? resourcesResponse.result.resources.length
2635
+ : 0;
2636
+ log('✓ Resources list received (' + resourceCount + ' resources)');
2637
+ }
2638
} catch (err) {
2639
pendingRequests.delete(resourcesRequest.id);
2586
- throw err;
2587
- }
2588
-
2589
- if (resourcesResponse && resourcesResponse.error) {
2590
- log('⚠ Resources list failed: ' + JSON.stringify(resourcesResponse.error));
2591
- } else {
2592
- const resourceCount = resourcesResponse && resourcesResponse.result && resourcesResponse.result.resources
2593
- ? resourcesResponse.result.resources.length
2594
- : 0;
2595
- log('✓ Resources list received (' + resourceCount + ' resources)');
2640
+ log('⚠ Resources list skipped: ' + err.message);
2641
}
2642
2643
// Step 7: Switch UI to tools flow
@@ -3227,8 +3272,13 @@
3272
availablePrompts[prompt.name] = prompt;
3273
});
3274
log(`Loaded ${Object.keys(availablePrompts).length} prompts`);
3275
+ // Refresh prompts methods if currently viewing
3276
+ const activeFlow = document.querySelector('.flow-item.active');
3277
+ if (activeFlow && activeFlow.dataset.flow === 'prompts') {
3278
+ displayMethods('prompts');
3279
+ }
3280
}
3231
-
3281
+
3282
// Handle resources/list response
3283
if (response.result && response.result.resources) {
3284
availableResources = {};
@@ -3236,6 +3286,11 @@
3286
availableResources[resource.uri] = resource;
3287
});
3288
log(`Loaded ${Object.keys(availableResources).length} resources`);
3289
+ // Refresh resources methods if currently viewing
3290
+ const activeFlow = document.querySelector('.flow-item.active');
3291
+ if (activeFlow && activeFlow.dataset.flow === 'resources') {
3292
+ displayMethods('resources');
3293
+ }
3294
}
3295
}
3296
src/web/mcp/mcp-test-client/mcp-schema-ui-generator.js
+1
-1
@@ -1631,7 +1631,7 @@ class MCPSchemaUIGenerator {
1631
}
1632
1633
// Check for allowed root properties
1634
- const allowedRootProps = ['type', 'properties', 'required', 'title', 'description', 'additionalProperties'];
1634
+ const allowedRootProps = ['type', 'properties', 'required', 'title', 'description', 'additionalProperties', '$schema'];
1635
for (const prop of Object.keys(schema)) {
1636
if (!allowedRootProps.includes(prop)) {
1637
errors.push(`Unknown root property: "${prop}"`);