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
183 lines (155 loc) • 5.49 kB
JavaScript
module.exports = function (RED)
{
"use strict";
function MCPToolRegistryNode(config)
{
RED.nodes.createNode(this, config);
const node = this;
// Configuration
node.toolName = config.toolName || "";
node.toolDescription = config.toolDescription || "";
node.toolSchema = config.toolSchema || "{}";
node.autoRegister = config.autoRegister !== false;
// Runtime state
node.isRegistered = false;
// Set initial status
node.status({ fill: "grey", shape: "ring", text: "unregistered" });
// Parse tool schema
let parsedSchema = {};
try
{
parsedSchema = JSON.parse(node.toolSchema);
} catch (error)
{
node.warn(`Invalid tool schema JSON: ${error.message}`);
parsedSchema = {
type: "object",
properties: {},
required: []
};
}
// Register tool function
node.registerTool = function ()
{
if (!node.toolName)
{
node.warn("Tool name is required for registration");
return;
}
if (node.isRegistered)
{
node.warn("Tool is already registered");
return;
}
const toolDefinition = {
name: node.toolName,
description: node.toolDescription || `Tool: ${node.toolName}`,
inputSchema: parsedSchema,
registeredBy: node.id,
registrationTime: new Date()
};
// Emit registration event
RED.events.emit('mcp-tool-register', toolDefinition);
node.isRegistered = true;
node.status({ fill: "green", shape: "dot", text: "registered" });
node.log(`Tool "${node.toolName}" registered successfully`);
// Send registration message
node.send({
topic: 'tool-registered',
payload: {
toolName: node.toolName,
description: node.toolDescription,
schema: parsedSchema
}
});
};
// Unregister tool function
node.unregisterTool = function ()
{
if (!node.isRegistered)
{
node.warn("Tool is not currently registered");
return;
}
// Emit unregistration event
RED.events.emit('mcp-tool-unregister', node.toolName);
node.isRegistered = false;
node.status({ fill: "grey", shape: "ring", text: "unregistered" });
node.log(`Tool "${node.toolName}" unregistered`);
// Send unregistration message
node.send({
topic: 'tool-unregistered',
payload: {
toolName: node.toolName
}
});
};
// Update tool registration
node.updateRegistration = function ()
{
if (node.isRegistered)
{
node.unregisterTool();
setTimeout(() => node.registerTool(), 100);
}
};
// Handle input messages
node.on('input', function (msg)
{
const command = msg.topic || msg.payload.command;
switch (command)
{
case 'register':
node.registerTool();
break;
case 'unregister':
node.unregisterTool();
break;
case 'update':
// Update tool definition from message
if (msg.payload.toolName) node.toolName = msg.payload.toolName;
if (msg.payload.toolDescription) node.toolDescription = msg.payload.toolDescription;
if (msg.payload.toolSchema)
{
try
{
parsedSchema = JSON.parse(msg.payload.toolSchema);
node.toolSchema = msg.payload.toolSchema;
} catch (error)
{
node.warn(`Invalid schema in update: ${error.message}`);
}
}
node.updateRegistration();
break;
case 'status':
msg.payload = {
toolName: node.toolName,
isRegistered: node.isRegistered,
description: node.toolDescription,
schema: parsedSchema
};
node.send(msg);
break;
default:
node.warn(`Unknown command: ${command}`);
}
});
// Auto-register if configured
if (node.autoRegister && node.toolName)
{
setTimeout(() => node.registerTool(), 500);
}
// Cleanup on node close
node.on('close', function (done)
{
if (node.isRegistered)
{
node.unregisterTool();
}
done();
});
}
// Register the node
RED.nodes.registerType("mcp-tool-registry", MCPToolRegistryNode);
};