@cryptotaxi247 / netdata-1 / commits / 3c7b50bd7

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}"`);