#!/usr/bin/env node
import * as fs from 'fs';
import * as path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// Read the tools data from parent directory
const toolsData = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'tools-generated.json'), 'utf8'));
// Generate the single-file server with modular tools
const serverCode = `#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ErrorCode,
McpError,
} from '@modelcontextprotocol/sdk/types.js';
import { OPNsenseClient } from '@richard-stovall/opnsense-typescript-client';
// Embedded modular tool definitions
const TOOLS = ${JSON.stringify(toolsData.tools, null, 2)};
// Method documentation for help
const METHOD_DOCS = ${JSON.stringify(toolsData.methodDocs, null, 2)};
class OPNsenseMCPServer {
constructor(config) {
this.config = config;
this.client = null;
this.server = new Server(
{
name: 'opnsense-mcp-server',
version: '0.6.0',
},
{
capabilities: {
tools: {},
},
}
);
this.setupHandlers();
}
ensureClient() {
if (!this.client) {
this.client = new OPNsenseClient({
baseUrl: this.config.url,
apiKey: this.config.apiKey,
apiSecret: this.config.apiSecret,
verifySsl: this.config.verifySsl ?? true,
});
}
return this.client;
}
setupHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: this.getAvailableTools(),
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
const tool = TOOLS.find(t => t.name === name);
if (!tool) {
throw new McpError(ErrorCode.MethodNotFound, \`Tool \${name} not found\`);
}
// Skip plugin tools if not enabled
if (tool.module === 'plugins' && !this.config.includePlugins) {
throw new McpError(ErrorCode.MethodNotFound, \`Plugin tools not enabled. Use --plugins flag to enable.\`);
}
try {
const result = await this.callModularTool(tool, args);
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
} catch (error) {
console.error('Tool call error:', {
tool: tool.name,
module: tool.module,
args,
error: error instanceof Error ? error.message : 'Unknown error',
stack: error instanceof Error ? error.stack : undefined
});
// Extract more details from axios errors
let errorMessage = 'Unknown error';
if (error instanceof Error) {
errorMessage = error.message;
if (error.response) {
const response = error.response;
errorMessage = \`HTTP \${response.status}: \${response.statusText}\\n\`;
if (response.data) {
errorMessage += \`Response: \${JSON.stringify(response.data, null, 2)}\`;
}
}
}
return {
content: [{
type: 'text',
text: \`Error calling \${tool.name}.\${args.method || 'unknown'}: \${errorMessage}\`
}],
};
}
});
}
getAvailableTools() {
return TOOLS.filter(tool => {
// Include all non-plugin tools
if (tool.module !== 'plugins') return true;
// Include plugin tools only if enabled
return this.config.includePlugins;
}).map(tool => ({
name: tool.name,
description: tool.description,
inputSchema: tool.inputSchema
}));
}
async callModularTool(tool, args) {
const client = this.ensureClient();
// Validate method parameter
if (!args.method) {
throw new Error(\`Missing required parameter 'method'. Available methods: \${tool.methods.join(', ')}\`);
}
if (!tool.methods.includes(args.method)) {
throw new Error(\`Invalid method '\${args.method}'. Available methods: \${tool.methods.join(', ')}\`);
}
// Get the module
let moduleObj;
if (tool.module === 'plugins' && tool.submodule) {
moduleObj = client.plugins[tool.submodule];
} else {
moduleObj = client[tool.module];
}
if (!moduleObj) {
throw new Error(\`Module \${tool.module} not found\`);
}
// Get the method
const method = moduleObj[args.method];
if (!method || typeof method !== 'function') {
throw new Error(\`Method \${args.method} not found in module \${tool.module}\`);
}
// Call the method with params (if provided)
console.error(\`Calling \${tool.module}.\${args.method} with params:\`, args.params);
// Extract params, excluding the method field
const { method: _, params = {}, ...otherArgs } = args;
const callParams = { ...params, ...otherArgs };
// Only pass parameters if there are any
if (Object.keys(callParams).length > 0) {
return await method.call(moduleObj, callParams);
} else {
return await method.call(moduleObj);
}
}
async start() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('OPNsense MCP server v0.6.0 (modular) started');
console.error(\`Core tools: ${toolsData.coreTools} modules\`);
console.error(\`Plugin tools: ${toolsData.pluginTools} modules (\${this.config.includePlugins ? 'enabled' : 'disabled'})\`);
console.error(\`Total available: \${this.config.includePlugins ? '${toolsData.totalTools}' : '${toolsData.coreTools}'} modules\`);
}
}
// Parse command line arguments
function parseArgs() {
const args = process.argv.slice(2);
const config = {
url: '',
apiKey: '',
apiSecret: '',
verifySsl: true,
includePlugins: false,
};
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case '--url':
case '-u':
config.url = args[++i];
break;
case '--api-key':
case '-k':
config.apiKey = args[++i];
break;
case '--api-secret':
case '-s':
config.apiSecret = args[++i];
break;
case '--no-verify-ssl':
config.verifySsl = false;
break;
case '--plugins':
config.includePlugins = true;
break;
case '--help':
case '-h':
showHelp();
process.exit(0);
break;
}
}
return config;
}
function showHelp() {
console.log(\`
OPNsense MCP Server v0.6.0 (Modular Edition)
Usage: opnsense-mcp-server --url <url> --api-key <key> --api-secret <secret> [options]
Required:
-u, --url <url> OPNsense API URL (e.g., https://192.168.1.1)
-k, --api-key <key> API Key for authentication
-s, --api-secret <secret> API Secret for authentication
Options:
--no-verify-ssl Disable SSL certificate verification
--plugins Include plugin tools (adds ${toolsData.pluginTools} plugin modules)
-h, --help Show this help message
Environment Variables:
OPNSENSE_URL OPNsense API URL
OPNSENSE_API_KEY API Key
OPNSENSE_API_SECRET API Secret
OPNSENSE_VERIFY_SSL Set to 'false' to disable SSL verification
INCLUDE_PLUGINS Set to 'true' to include plugin tools
Examples:
# Basic usage (${toolsData.coreTools} core modules)
opnsense-mcp-server --url https://192.168.1.1 --api-key mykey --api-secret mysecret
# With plugins enabled (${toolsData.totalTools} total modules)
opnsense-mcp-server --url https://192.168.1.1 --api-key mykey --api-secret mysecret --plugins
Tool Usage:
Each tool represents a module and accepts a 'method' parameter to specify the operation.
Example: firewall_manage
- method: "aliasSearchItem" - Search firewall aliases
- method: "aliasAddItem" - Add a new alias
- method: "aliasSetItem" - Update an existing alias (requires uuid in params)
Parameters are passed in the 'params' object:
{
"method": "aliasSearchItem",
"params": {
"searchPhrase": "web",
"current": 1,
"rowCount": 20
}
}
Based on @richard-stovall/opnsense-typescript-client v0.5.3
\`);
}
// Main entry point
async function main() {
const config = parseArgs();
// Use environment variables as fallback
config.url = config.url || process.env.OPNSENSE_URL || '';
config.apiKey = config.apiKey || process.env.OPNSENSE_API_KEY || '';
config.apiSecret = config.apiSecret || process.env.OPNSENSE_API_SECRET || '';
if (!config.verifySsl || process.env.OPNSENSE_VERIFY_SSL === 'false') {
config.verifySsl = false;
}
if (config.includePlugins || process.env.INCLUDE_PLUGINS === 'true') {
config.includePlugins = true;
}
// Validate required arguments
if (!config.url || !config.apiKey || !config.apiSecret) {
console.error('Error: Missing required arguments\\n');
showHelp();
process.exit(1);
}
// Create and start server
const server = new OPNsenseMCPServer(config);
await server.start();
}
// Run the server
main().catch((error) => {
console.error('Failed to start server:', error);
process.exit(1);
});
`;
// Write the single-file server to parent directory
fs.writeFileSync(path.join(__dirname, '..', 'index.js'), serverCode);
console.log('Built index.js successfully');
console.log(`Total tools: ${toolsData.totalTools} modules`);
console.log(`Core tools: ${toolsData.coreTools} modules`);
console.log(`Plugin tools: ${toolsData.pluginTools} modules`);