UNPKG

node-red-contrib-mcp-server

Version:

A comprehensive Node-RED wrapper for Model Context Protocol (MCP) servers providing standardized AI agent tool interfaces, server lifecycle management, real-time communication capabilities, and visual MCP server creation

412 lines (356 loc) 17.8 kB
<!-- MCP Client Node --> <script type="text/javascript"> RED.nodes.registerType('mcp-client', { category: 'mcp', color: '#68C4E8', defaults: { name: {value: ""}, serverUrl: {value: "http://localhost:8000", required: true}, connectionType: {value: "http", required: true}, autoConnect: {value: false}, reconnect: {value: true}, reconnectInterval: {value: 5000, validate: function(v) { return v >= 1000; }}, timeout: {value: 30000, validate: function(v) { return v >= 1000; }} }, inputs: 1, outputs: 1, icon: "font-awesome/fa-link", label: function() { return this.name || `MCP Client (${this.connectionType})`; }, labelStyle: function() { return this.name ? "node_label_italic" : ""; }, oneditprepare: function() { var node = this; // Update connection type info function updateConnectionTypeInfo() { var connectionType = $("#node-input-connectionType").val(); switch (connectionType) { case "http": $("#connection-type-info").html(` <i class="fa fa-info-circle"></i> <strong>HTTP:</strong> Simple request-response communication. Best for basic operations and polling. `); break; case "sse": $("#connection-type-info").html(` <i class="fa fa-info-circle"></i> <strong>Server-Sent Events:</strong> Real-time server-to-client streaming. Good for notifications and live updates. `); break; case "websocket": $("#connection-type-info").html(` <i class="fa fa-info-circle"></i> <strong>WebSocket:</strong> Full-duplex real-time communication. Best for interactive applications. `); break; } } // Initialize connection type change handler $("#node-input-connectionType").change(updateConnectionTypeInfo); // Initialize form updateConnectionTypeInfo(); // Test connection button with type-specific testing $("#test-connection-btn").click(function() { var button = $(this); var originalText = button.text(); button.text("Testing...").prop('disabled', true); var serverUrl = $("#node-input-serverUrl").val(); var connectionType = $("#node-input-connectionType").val(); if (!serverUrl) { button.text(originalText).prop('disabled', false); RED.notify("Server URL is required", "error"); return; } function resetButton() { button.text(originalText).prop('disabled', false); } // Test based on connection type switch (connectionType) { case "http": // Test HTTP with health check $.ajax({ url: serverUrl + "/health", method: "GET", timeout: 5000 }) .done(function(data) { resetButton(); RED.notify("HTTP connection successful! Server is reachable.", "success"); }) .fail(function(xhr, status, error) { resetButton(); RED.notify(`HTTP connection failed: ${error || status}`, "error"); }); break; case "sse": // Test SSE connection var sseUrl = serverUrl + "/sse"; var eventSource = new EventSource(sseUrl); var sseTimeout = setTimeout(function() { eventSource.close(); resetButton(); RED.notify("SSE connection timeout - server may not support SSE", "warning"); }, 5000); eventSource.onopen = function(event) { clearTimeout(sseTimeout); eventSource.close(); resetButton(); RED.notify("SSE connection successful! Real-time streaming available.", "success"); }; eventSource.onerror = function(event) { clearTimeout(sseTimeout); eventSource.close(); resetButton(); RED.notify("SSE connection failed - check server SSE support", "error"); }; break; case "websocket": // Test WebSocket connection var wsUrl = serverUrl.replace(/^http/, 'ws') + '/ws'; try { var ws = new WebSocket(wsUrl); var wsTimeout = setTimeout(function() { ws.close(); resetButton(); RED.notify("WebSocket connection timeout - server may not support WebSockets", "warning"); }, 5000); ws.onopen = function(event) { clearTimeout(wsTimeout); ws.close(); resetButton(); RED.notify("WebSocket connection successful! Full-duplex communication available.", "success"); }; ws.onerror = function(event) { clearTimeout(wsTimeout); resetButton(); RED.notify("WebSocket connection failed - check server WebSocket support", "error"); }; } catch (error) { resetButton(); RED.notify("WebSocket connection failed: " + error.message, "error"); } break; default: resetButton(); RED.notify("Unknown connection type", "error"); } }); // Load Omnispindle preset $("#load-omnispindle-preset-btn").click(function() { $("#node-input-serverUrl").val("http://localhost:8000"); $("#node-input-connectionType").val("sse"); $("#node-input-autoConnect").prop('checked', true); $("#node-input-reconnect").prop('checked', true); updateConnectionTypeInfo(); RED.notify("Omnispindle preset loaded", "success"); }); // URL validation $("#node-input-serverUrl").on('input', function() { var url = $(this).val(); var isValid = /^https?:\/\/.+/.test(url); if (url && !isValid) { $(this).addClass("input-error"); $("#url-validation").show().text("URL must start with http:// or https://"); } else { $(this).removeClass("input-error"); $("#url-validation").hide(); } }); } }); </script> <script type="text/html" data-template-name="mcp-client"> <div class="form-row"> <label for="node-input-name"><i class="fa fa-tag"></i> Name</label> <input type="text" id="node-input-name" placeholder="Optional display name"> </div> <div class="form-row"> <label for="node-input-serverUrl"><i class="fa fa-globe"></i> Server URL</label> <input type="text" id="node-input-serverUrl" placeholder="http://localhost:8000"> <div id="url-validation" style="display: none; margin-top: 5px; font-size: 12px; color: #e74c3c;"></div> <div style="margin-top: 5px; font-size: 12px; color: #666;"> Base URL of the MCP server (include http:// or https://) </div> <button type="button" id="test-connection-btn" class="btn btn-secondary" style="margin-top: 5px;"> <i class="fa fa-check"></i> Test Connection </button> </div> <hr> <h4>Connection Settings</h4> <div class="form-row"> <label for="node-input-connectionType"><i class="fa fa-exchange"></i> Connection Type</label> <select id="node-input-connectionType"> <option value="http">HTTP (Request/Response)</option> <option value="sse">Server-Sent Events</option> <option value="websocket">WebSocket</option> </select> <div id="connection-type-info" style="margin-top: 10px; padding: 10px; background-color: #f8f9fa; border-left: 4px solid #007bff; font-size: 12px;"> <!-- Connection type info will be populated by JavaScript --> </div> </div> <div class="form-row"> <label for="node-input-autoConnect"><i class="fa fa-play"></i> Auto Connect</label> <input type="checkbox" id="node-input-autoConnect" style="width: auto;"> <span style="margin-left: 10px; font-size: 12px; color: #666;">Automatically connect when Node-RED starts</span> </div> <div class="form-row"> <label for="node-input-reconnect"><i class="fa fa-refresh"></i> Auto Reconnect</label> <input type="checkbox" id="node-input-reconnect" style="width: auto;"> <span style="margin-left: 10px; font-size: 12px; color: #666;">Automatically reconnect on connection loss</span> </div> <div class="form-row"> <label for="node-input-reconnectInterval"><i class="fa fa-clock-o"></i> Reconnect Interval</label> <input type="number" id="node-input-reconnectInterval" min="1000" step="1000" placeholder="5000"> <span style="margin-left: 10px; font-size: 12px; color: #666;">milliseconds</span> </div> <div class="form-row"> <label for="node-input-timeout"><i class="fa fa-hourglass"></i> Request Timeout</label> <input type="number" id="node-input-timeout" min="1000" step="1000" placeholder="30000"> <span style="margin-left: 10px; font-size: 12px; color: #666;">milliseconds</span> </div> <!-- Preset Buttons --> <hr> <div class="form-row"> <label></label> <button type="button" id="load-omnispindle-preset-btn" class="btn btn-primary"> <i class="fa fa-download"></i> Load Omnispindle Preset </button> </div> </script> <script type="text/html" data-help-name="mcp-client"> <p>A Node-RED client for connecting to and communicating with Model Context Protocol (MCP) servers. Supports HTTP, Server-Sent Events, and WebSocket connections.</p> <h3>Configuration</h3> <dl class="message-properties"> <dt>Server URL <span class="property-type">string</span></dt> <dd>Base URL of the MCP server (e.g., http://localhost:8000)</dd> <dt>Connection Type <span class="property-type">string</span></dt> <dd>Type of connection: HTTP, Server-Sent Events, or WebSocket</dd> <dt>Auto Connect <span class="property-type">boolean</span></dt> <dd>Whether to automatically connect when Node-RED starts</dd> <dt>Auto Reconnect <span class="property-type">boolean</span></dt> <dd>Whether to automatically reconnect on connection loss</dd> <dt>Reconnect Interval <span class="property-type">number</span></dt> <dd>Delay between reconnection attempts in milliseconds</dd> <dt>Request Timeout <span class="property-type">number</span></dt> <dd>Timeout for requests in milliseconds</dd> </dl> <h3>Connection Types</h3> <dl class="message-properties"> <dt>HTTP <span class="property-type">request/response</span></dt> <dd>Simple HTTP requests. Good for basic operations and polling scenarios.</dd> <dt>Server-Sent Events <span class="property-type">streaming</span></dt> <dd>Real-time server-to-client streaming. Ideal for notifications and live updates.</dd> <dt>WebSocket <span class="property-type">full-duplex</span></dt> <dd>Bidirectional real-time communication. Best for interactive applications.</dd> </dl> <h3>Test Connection</h3> <p>The Test Connection button now performs connection type-specific testing:</p> <ul> <li><strong>HTTP:</strong> Tests /health endpoint with GET request</li> <li><strong>SSE:</strong> Attempts to establish Server-Sent Events connection to /sse</li> <li><strong>WebSocket:</strong> Attempts to establish WebSocket connection to /ws</li> </ul> <h3>Input Commands</h3> <p>Send messages with the following topics to control the client:</p> <dl class="message-properties"> <dt>connect <span class="property-type">string</span></dt> <dd>Establish connection to the MCP server</dd> <dt>disconnect <span class="property-type">string</span></dt> <dd>Disconnect from the MCP server</dd> <dt>request <span class="property-type">object</span></dt> <dd>Send MCP request: <code>{method: "tool_name", params: {...}}</code></dd> <dt>status <span class="property-type">string</span></dt> <dd>Get current connection status</dd> </dl> <h3>Output Messages</h3> <p>The node outputs messages with different topics based on events:</p> <dl class="message-properties"> <dt>connected <span class="property-type">object</span></dt> <dd>Connection established successfully</dd> <dt>handshake <span class="property-type">object</span></dt> <dd>MCP server capabilities and available tools (SSE and WebSocket)</dd> <dt>response <span class="property-type">object</span></dt> <dd>Response from MCP server to a request</dd> <dt>message <span class="property-type">object</span></dt> <dd>General message from MCP server (SSE/WebSocket)</dd> <dt>error <span class="property-type">object</span></dt> <dd>Error occurred during communication</dd> <dt>raw <span class="property-type">string</span></dt> <dd>Raw message that couldn't be parsed as JSON</dd> </dl> <h3>Handshake Information (SSE & WebSocket)</h3> <p>When using Server-Sent Events or WebSocket, the client automatically performs an MCP handshake and outputs discovery information:</p> <pre><code>{ "topic": "handshake", "payload": { "type": "tools_discovered", "serverUrl": "http://localhost:8000", "connectionType": "sse", "serverInfo": { "name": "omnispindle-mcp", "version": "1.0.0", "capabilities": {...} }, "availableTools": [ { "name": "add_todo_tool", "description": "Create a new todo item", "inputSchema": {...} } ], "toolCount": 12, "toolNames": ["add_todo_tool", "list_todos_tool", ...], "mcpSyntax": { "requestFormat": { "topic": "request", "payload": { "method": "tool_name_here", "params": {...} } }, "exampleCall": { "topic": "request", "payload": { "method": "add_todo_tool", "params": { "description": "example_string", "project": "Omnispindle" } } } } } }</code></pre> <h3>MCP Request Format</h3> <p>To send MCP requests, use the following message format:</p> <pre><code>{ "topic": "request", "payload": { "method": "add_todo_tool", "params": { "description": "Task description", "project": "ProjectName" } } }</code></pre> <h3>Examples</h3> <p><strong>Connect to Omnispindle:</strong></p> <ul> <li>Server URL: <code>http://localhost:8000</code></li> <li>Connection Type: Server-Sent Events</li> <li>Auto Connect: Enabled</li> </ul> <p><strong>Call MCP Tool:</strong></p> <pre><code>msg.topic = "request"; msg.payload = { method: "list_todos_by_status_tool", params: { status: "pending" } }; return msg;</code></pre> <h3>Omnispindle Integration</h3> <p>This client is designed to work seamlessly with the Omnispindle MCP server included in this project. Use the "Load Omnispindle Preset" button to automatically configure for local Omnispindle connections.</p> <h3>Requirements</h3> <p>The target MCP server must be running and accessible at the specified URL. For Omnispindle, ensure the server is started with the MCP server node or manually.</p> </script>