docs: Ledger Live integration, contract deploy learnings, NEXT_STEPS updates
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled

- ADD_CHAIN138_TO_LEDGER_LIVE: Ledger form done; public code review repo bis-innovations/LedgerLive; init/push commands
- CONTRACT_DEPLOYMENT_RUNBOOK: Chain 138 gas price 1 gwei, 36-addr check, TransactionMirror workaround
- CONTRACT_*: AddressMapper, MirrorManager deployed 2026-02-12; 36-address on-chain check
- NEXT_STEPS_FOR_YOU: Ledger done; steps completable now (no LAN); run-completable-tasks-from-anywhere
- MASTER_INDEX, OPERATOR_OPTIONAL, SMART_CONTRACTS_INVENTORY_SIMPLE: updates
- LEDGER_BLOCKCHAIN_INTEGRATION_COMPLETE: bis-innovations/LedgerLive reference

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
defiQUG
2026-02-12 15:46:57 -08:00
parent cc8dcaf356
commit fbda1b4beb
5114 changed files with 498901 additions and 4567 deletions

156
mcp-unifi/README.md Normal file
View File

@@ -0,0 +1,156 @@
# UniFi MCP Server
Model Context Protocol (MCP) server for managing Ubiquiti UniFi/UDM Pro devices through Claude Desktop and other MCP clients.
## Features
- List and query UniFi devices (APs, switches, gateways)
- View client/station information
- Get network and VLAN configurations
- View WLAN/WiFi configurations
- Monitor events and alarms
- Get system information and health status
## Installation
```bash
pnpm install
pnpm build
```
## Configuration
### Environment Variables
Create or update `~/.env` with the following:
#### Private API Mode (Default)
```bash
# UniFi Controller Configuration
UNIFI_UDM_URL=https://192.168.1.1
UNIFI_USERNAME=admin
UNIFI_PASSWORD=your-password
UNIFI_SITE_ID=default # Optional, will use default site if not set
UNIFI_API_MODE=private # Optional, defaults to private
UNIFI_VERIFY_SSL=false # Set to true for production (requires valid SSL cert)
```
#### Official API Mode
```bash
# UniFi Controller Configuration
UNIFI_UDM_URL=https://192.168.1.1
UNIFI_API_KEY=your-api-key
UNIFI_SITE_ID=default # Optional, will use default site if not set
UNIFI_API_MODE=official
UNIFI_VERIFY_SSL=false # Set to true for production (requires valid SSL cert)
```
### Getting API Credentials
#### Official API (API Key)
1. Access your UniFi Network app
2. Navigate to **Settings → Control Plane → Integrations**
3. Generate an API key
4. Use the API key in `UNIFI_API_KEY` environment variable
#### Private API (Username/Password)
- Use your UniFi Controller admin username and password
- Private API uses cookie-based session authentication
## Claude Desktop Integration
Add to your Claude Desktop config file:
```json
{
"mcpServers": {
"unifi": {
"command": "node",
"args": ["/path/to/proxmox/mcp-unifi/dist/index.js"]
}
}
}
```
### Config File Locations
- **macOS**: `~/Library/Application Support/Claude/claude_desktop_config.json`
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
- **Linux**: `~/.config/Claude/claude_desktop_config.json`
## Available Tools
### Site Management
- `unifi_list_sites` - List all sites
- `unifi_get_site_stats` - Get site statistics
### Device Management
- `unifi_list_devices` - List all devices
- `unifi_get_device` - Get device by MAC address
- `unifi_get_device_stats` - Get device statistics
### Client Management
- `unifi_list_clients` - List all active clients
- `unifi_get_client` - Get client by ID or MAC address
### Network Management
- `unifi_list_networks` - List all networks/VLANs
- `unifi_get_network` - Get network by ID
### WLAN Management
- `unifi_list_wlans` - List all WLAN configurations
- `unifi_get_wlan` - Get WLAN by ID
### Events & Monitoring
- `unifi_list_events` - List events
- `unifi_list_alarms` - List alarms
### System Operations
- `unifi_get_system_info` - Get system information
- `unifi_get_health` - Get site health status
## Usage Examples
Once configured, you can ask Claude Desktop:
- "List all devices in my UniFi network"
- "Show me the active clients"
- "What are the network configurations?"
- "Get system information"
- "Show me recent events"
- "What's the health status of the site?"
## Troubleshooting
### Connection Errors
- Verify `UNIFI_UDM_URL` is correct (IP address or hostname)
- Check that the UniFi Controller is running and accessible
- If using self-signed certificates, ensure `UNIFI_VERIFY_SSL=false`
### Authentication Errors
- For Private API: Verify `UNIFI_USERNAME` and `UNIFI_PASSWORD` are correct
- For Official API: Verify `UNIFI_API_KEY` is correct and valid
- Check that the API mode matches your credentials
### Device/Client Not Found
- Verify IDs/MAC addresses are correct
- Check that `siteId` matches the device's site (if provided)
- Ensure the device/client is adopted and online
## License
MIT

33
mcp-unifi/package.json Normal file
View File

@@ -0,0 +1,33 @@
{
"name": "mcp-unifi-server",
"version": "1.0.0",
"description": "MCP server for Ubiquiti UniFi/UDM Pro Controller management",
"main": "dist/index.js",
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "tsc --watch & node --watch dist/index.js",
"clean": "rm -rf dist"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^0.4.0",
"unifi-api": "workspace:*"
},
"devDependencies": {
"@types/node": "^20.0.0",
"typescript": "^5.9.0"
},
"keywords": [
"mcp",
"unifi",
"ubiquiti",
"udm-pro",
"network"
],
"author": "",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
}
}

110
mcp-unifi/src/index.ts Normal file
View File

@@ -0,0 +1,110 @@
#!/usr/bin/env node
/**
* MCP Server for UniFi Controller
*
* Entry point for the Model Context Protocol server that provides
* tools for managing Ubiquiti UniFi/UDM Pro devices
*/
import { readFileSync } from 'fs';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { homedir } from 'os';
import { UnifiServer } from './server/UnifiServer.js';
import { ApiMode } from 'unifi-api';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
// Load environment variables from ~/.env file (standardized location)
const envPath = join(homedir(), '.env');
const envPathFallback = join(__dirname, '../.env');
function loadEnvFile(filePath: string): boolean {
try {
const envFile = readFileSync(filePath, 'utf8');
const envVars = envFile.split('\n').filter(
(line) => line.includes('=') && !line.trim().startsWith('#')
);
for (const line of envVars) {
const [key, ...values] = line.split('=');
// Validate key is a valid environment variable name
if (key && values.length > 0 && /^[A-Z_][A-Z0-9_]*$/.test(key.trim())) {
// Remove surrounding quotes if present and trim
let value = values.join('=').trim();
if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1);
}
process.env[key.trim()] = value;
}
}
return true;
} catch (error) {
return false;
}
}
// Try ~/.env first, then fallback to relative path
if (!loadEnvFile(envPath)) {
if (!loadEnvFile(envPathFallback)) {
console.error('Warning: Could not load .env file from ~/.env or ../.env');
}
}
// Get configuration from environment variables
const baseUrl = process.env.UNIFI_UDM_URL || 'https://192.168.1.1';
const apiMode = (process.env.UNIFI_API_MODE || 'private') as ApiMode;
const siteId = process.env.UNIFI_SITE_ID;
const verifySSL = process.env.UNIFI_VERIFY_SSL !== 'false';
let config: {
baseUrl: string;
apiMode: ApiMode;
siteId?: string;
verifySSL: boolean;
apiKey?: string;
username?: string;
password?: string;
};
if (apiMode === ApiMode.OFFICIAL) {
const apiKey = process.env.UNIFI_API_KEY;
if (!apiKey) {
console.error('Error: UNIFI_API_KEY must be set in environment variables for Official API mode');
process.exit(1);
}
config = {
baseUrl,
apiMode,
apiKey,
siteId,
verifySSL,
};
} else {
const username = process.env.UNIFI_USERNAME;
const password = process.env.UNIFI_PASSWORD;
if (!username || !password) {
console.error('Error: UNIFI_USERNAME and UNIFI_PASSWORD must be set in environment variables for Private API mode');
process.exit(1);
}
config = {
baseUrl,
apiMode,
username,
password,
siteId,
verifySSL,
};
}
// Create and run the server
const server = new UnifiServer(config);
server.run().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});

View File

@@ -0,0 +1,467 @@
/**
* MCP Server for UniFi Controller
*/
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import {
UnifiClient,
ApiMode,
SitesService,
DevicesService,
ClientsService,
NetworksService,
WlansService,
EventsService,
SystemService,
} from 'unifi-api';
export interface UnifiServerConfig {
baseUrl: string;
apiMode?: ApiMode;
apiKey?: string;
username?: string;
password?: string;
siteId?: string;
verifySSL?: boolean;
}
export class UnifiServer {
private server: Server;
private client: UnifiClient;
private sitesService: SitesService;
private devicesService: DevicesService;
private clientsService: ClientsService;
private networksService: NetworksService;
private wlansService: WlansService;
private eventsService: EventsService;
private systemService: SystemService;
constructor(config: UnifiServerConfig) {
this.server = new Server(
{
name: 'unifi-server',
version: '1.0.0',
}
);
this.client = new UnifiClient(config);
this.sitesService = new SitesService(this.client);
this.devicesService = new DevicesService(this.client);
this.clientsService = new ClientsService(this.client);
this.networksService = new NetworksService(this.client);
this.wlansService = new WlansService(this.client);
this.eventsService = new EventsService(this.client);
this.systemService = new SystemService(this.client);
this.setupToolHandlers();
}
private setupToolHandlers() {
// List tools
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'unifi_list_sites',
description: 'List all sites',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'unifi_get_site_stats',
description: 'Get site statistics',
inputSchema: {
type: 'object',
properties: {
siteId: {
type: 'string',
description: 'Site ID (optional, uses default if not provided)',
},
},
},
},
{
name: 'unifi_list_devices',
description: 'List all devices',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'unifi_get_device',
description: 'Get device by MAC address',
inputSchema: {
type: 'object',
properties: {
mac: {
type: 'string',
description: 'Device MAC address',
},
},
required: ['mac'],
},
},
{
name: 'unifi_get_device_stats',
description: 'Get device statistics',
inputSchema: {
type: 'object',
properties: {
mac: {
type: 'string',
description: 'Device MAC address (optional, returns all if not provided)',
},
},
},
},
{
name: 'unifi_list_clients',
description: 'List all active clients',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'unifi_get_client',
description: 'Get client by ID or MAC address',
inputSchema: {
type: 'object',
properties: {
clientId: {
type: 'string',
description: 'Client ID or MAC address',
},
},
required: ['clientId'],
},
},
{
name: 'unifi_list_networks',
description: 'List all networks/VLANs',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'unifi_get_network',
description: 'Get network by ID',
inputSchema: {
type: 'object',
properties: {
networkId: {
type: 'string',
description: 'Network ID',
},
},
required: ['networkId'],
},
},
{
name: 'unifi_list_wlans',
description: 'List all WLAN configurations',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'unifi_get_wlan',
description: 'Get WLAN by ID',
inputSchema: {
type: 'object',
properties: {
wlanId: {
type: 'string',
description: 'WLAN ID',
},
},
required: ['wlanId'],
},
},
{
name: 'unifi_list_events',
description: 'List events',
inputSchema: {
type: 'object',
properties: {
limit: {
type: 'number',
description: 'Maximum number of events to return',
},
},
},
},
{
name: 'unifi_list_alarms',
description: 'List alarms',
inputSchema: {
type: 'object',
properties: {
archived: {
type: 'boolean',
description: 'Include archived alarms',
default: false,
},
},
},
},
{
name: 'unifi_get_system_info',
description: 'Get system information',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'unifi_get_health',
description: 'Get site health status',
inputSchema: {
type: 'object',
properties: {},
},
},
],
}));
// Handle tool calls
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'unifi_list_sites': {
const sites = await this.sitesService.listSites();
return {
content: [
{
type: 'text',
text: JSON.stringify(sites, null, 2),
},
],
};
}
case 'unifi_get_site_stats': {
const siteId = args?.siteId as string | undefined;
if (siteId) {
this.client.setSiteId(siteId);
}
const stats = await this.sitesService.getSiteStats(siteId);
return {
content: [
{
type: 'text',
text: JSON.stringify(stats, null, 2),
},
],
};
}
case 'unifi_list_devices': {
const devices = await this.devicesService.listDevices();
return {
content: [
{
type: 'text',
text: JSON.stringify(devices, null, 2),
},
],
};
}
case 'unifi_get_device': {
const mac = args?.mac as string;
if (!mac) {
throw new Error('MAC address is required');
}
const device = await this.devicesService.getDevice(mac);
return {
content: [
{
type: 'text',
text: JSON.stringify(device, null, 2),
},
],
};
}
case 'unifi_get_device_stats': {
const mac = args?.mac as string | undefined;
const stats = await this.devicesService.getDeviceStats(mac);
return {
content: [
{
type: 'text',
text: JSON.stringify(stats, null, 2),
},
],
};
}
case 'unifi_list_clients': {
const clients = await this.clientsService.listClients();
return {
content: [
{
type: 'text',
text: JSON.stringify(clients, null, 2),
},
],
};
}
case 'unifi_get_client': {
const clientId = args?.clientId as string;
if (!clientId) {
throw new Error('Client ID is required');
}
const client = await this.clientsService.getClient(clientId);
return {
content: [
{
type: 'text',
text: JSON.stringify(client, null, 2),
},
],
};
}
case 'unifi_list_networks': {
const networks = await this.networksService.listNetworks();
return {
content: [
{
type: 'text',
text: JSON.stringify(networks, null, 2),
},
],
};
}
case 'unifi_get_network': {
const networkId = args?.networkId as string;
if (!networkId) {
throw new Error('Network ID is required');
}
const network = await this.networksService.getNetwork(networkId);
return {
content: [
{
type: 'text',
text: JSON.stringify(network, null, 2),
},
],
};
}
case 'unifi_list_wlans': {
const wlans = await this.wlansService.listWlans();
return {
content: [
{
type: 'text',
text: JSON.stringify(wlans, null, 2),
},
],
};
}
case 'unifi_get_wlan': {
const wlanId = args?.wlanId as string;
if (!wlanId) {
throw new Error('WLAN ID is required');
}
const wlan = await this.wlansService.getWlan(wlanId);
return {
content: [
{
type: 'text',
text: JSON.stringify(wlan, null, 2),
},
],
};
}
case 'unifi_list_events': {
const limit = args?.limit as number | undefined;
const events = await this.eventsService.listEvents(limit);
return {
content: [
{
type: 'text',
text: JSON.stringify(events, null, 2),
},
],
};
}
case 'unifi_list_alarms': {
const archived = (args?.archived as boolean) ?? false;
const alarms = await this.eventsService.listAlarms(archived);
return {
content: [
{
type: 'text',
text: JSON.stringify(alarms, null, 2),
},
],
};
}
case 'unifi_get_system_info': {
const info = await this.systemService.getSystemInfo();
return {
content: [
{
type: 'text',
text: JSON.stringify(info, null, 2),
},
],
};
}
case 'unifi_get_health': {
const health = await this.systemService.getHealth();
return {
content: [
{
type: 'text',
text: JSON.stringify(health, null, 2),
},
],
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
return {
content: [
{
type: 'text',
text: `Error: ${errorMessage}`,
},
],
isError: true,
};
}
});
}
async run(): Promise<void> {
const transport = new StdioServerTransport();
await this.server.connect(transport);
}
}

23
mcp-unifi/tsconfig.json Normal file
View File

@@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"lib": ["ES2022"],
"moduleResolution": "node",
"rootDir": "./src",
"outDir": "./dist",
"declaration": true,
"sourceMap": true,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}