anythingllm-plugin.js•7.12 kB
/**
* FM8 MIDI Controller Plugin for AnythingLLM
*
* This plugin allows AnythingLLM agents to control Native Instruments FM8
* synthesizer via MIDI using a local MCP server.
*
* Setup:
* 1. Start the MCP server: npm run start:http
* 2. Place this file in AnythingLLM's custom agent plugins folder
* 3. Enable Agent mode in your workspace
*/
const MCP_SERVER_URL = "http://localhost:3333/mcp";
/**
* Helper function to call MCP server tools
*/
async function callMCPTool(toolName, args) {
const response = await fetch(MCP_SERVER_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
id: Date.now(),
method: "tools/call",
params: {
name: toolName,
arguments: args
}
})
});
if (!response.ok) {
throw new Error(`MCP server error: ${response.status}`);
}
const result = await response.json();
if (result.error) {
throw new Error(result.error.message || "MCP tool call failed");
}
return result.result;
}
const plugin = {
name: "fm8-controller",
startupConfig: {
params: {},
},
setup: async function () {
// Test connection to MCP server
try {
const healthCheck = await fetch("http://localhost:3333/health");
if (healthCheck.ok) {
return { success: true, message: "FM8 MCP server connected" };
}
} catch (error) {
return {
success: false,
message: "FM8 MCP server not running. Start with: npm run start:http"
};
}
},
plugin: function () {
return {
name: "FM8 Controller",
setup(aibitat) {
// Tool 1: Send by CC
aibitat.tool(
"send_midi_cc",
"Send a raw MIDI CC (Control Change) message to FM8. CC numbers range from 0-127, values from 0-127.",
{
cc: {
type: "number",
description: "MIDI CC number (0-127)",
required: true,
},
value: {
type: "number",
description: "MIDI CC value (0-127)",
required: true,
},
},
async ({ cc, value }) => {
try {
const result = await callMCPTool("send_by_cc", { cc, value });
return JSON.stringify({
success: true,
cc,
value,
mappedLabel: result.content?.[0]?.text || "Sent successfully"
});
} catch (error) {
return JSON.stringify({ success: false, error: error.message });
}
}
);
// Tool 2: Send by Route
aibitat.tool(
"send_fm8_route",
"Send a FM8 matrix modulation route. Routes connect a source (like LFO1, ENV1) to a destination (like PITCH, CUTOFF). Common sources: LFO1, LFO2, ENV1, ENV2, A, B, C, D. Common destinations: PITCH, CUTOFF, RESONANCE, A, B, C, D, E, F, X, Y, Z.",
{
source: {
type: "string",
description: "Modulation source (e.g., 'LFO1', 'ENV1', 'A')",
required: true,
},
dest: {
type: "string",
description: "Modulation destination (e.g., 'PITCH', 'CUTOFF', 'B')",
required: true,
},
value: {
type: "number",
description: "Modulation amount (0-127)",
required: true,
},
},
async ({ source, dest, value }) => {
try {
const result = await callMCPTool("send_by_route", { source, dest, value });
return JSON.stringify({
success: true,
route: `${source}->${dest}`,
value,
message: result.content?.[0]?.text || "Route set successfully"
});
} catch (error) {
return JSON.stringify({
success: false,
error: error.message,
tip: "Make sure the route exists. Use list_fm8_mappings to see available routes."
});
}
}
);
// Tool 3: List Mappings
aibitat.tool(
"list_fm8_mappings",
"List all available FM8 matrix mappings, showing routes, labels, CC numbers, and types. Use this to discover what modulation routes are available.",
{},
async () => {
try {
const result = await callMCPTool("list_mappings", {});
const mappings = result.structuredContent?.mappings || [];
return JSON.stringify({
success: true,
totalMappings: mappings.length,
mappings: mappings.slice(0, 20), // Limit to first 20 for readability
note: mappings.length > 20 ? `Showing first 20 of ${mappings.length} mappings` : ""
}, null, 2);
} catch (error) {
return JSON.stringify({ success: false, error: error.message });
}
}
);
// Tool 4: Panic
aibitat.tool(
"fm8_panic",
"Emergency stop! Sends Reset All Controllers, All Sound Off, and All Notes Off to FM8. Use this to immediately silence all sounds.",
{
channel: {
type: "number",
description: "MIDI channel (1-16). Optional, defaults to configured channel.",
required: false,
},
},
async ({ channel }) => {
try {
const args = channel ? { channel } : {};
const result = await callMCPTool("panic", args);
return JSON.stringify({
success: true,
message: "Panic sent - all sounds stopped",
channel: channel || "default"
});
} catch (error) {
return JSON.stringify({ success: false, error: error.message });
}
}
);
// Tool 5: Status
aibitat.tool(
"fm8_server_status",
"Get the current status of the FM8 MCP server, including transport mode, MIDI port, channel, instrument info, and number of available routes.",
{},
async () => {
try {
const result = await callMCPTool("status", {});
const status = result.structuredContent || {};
return JSON.stringify({
success: true,
status: {
midiPort: status.midiPort,
midiChannel: status.channel,
instrument: status.instrument,
section: status.section,
availableRoutes: status.routes,
transport: status.transport
}
}, null, 2);
} catch (error) {
return JSON.stringify({ success: false, error: error.message });
}
}
);
},
};
},
};
module.exports = plugin;