ai-debug-local-mcp
Version:
๐ฏ ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh
450 lines (431 loc) โข 17.4 kB
JavaScript
import { BaseToolHandler } from './base-handler.js';
/**
* Handler for Phoenix and LiveView debugging tools
*/
export class PhoenixHandler extends BaseToolHandler {
localEngine;
tools = [
{
name: 'debug_liveview_connection',
description: 'Debug Phoenix LiveView WebSocket connections, LiveSocket status, and common issues',
inputSchema: {
type: 'object',
properties: {
sessionId: { type: 'string', description: 'Debug session ID' }
},
required: ['sessionId']
}
},
{
name: 'check_websocket_endpoint',
description: 'Verify Phoenix WebSocket endpoint accessibility and configuration',
inputSchema: {
type: 'object',
properties: {
sessionId: { type: 'string', description: 'Debug session ID' },
expectedUrl: { type: 'string', description: 'Expected WebSocket URL (optional)' }
},
required: ['sessionId']
}
},
{
name: 'tidewave_phoenix_query',
description: 'Execute Tidewave queries for Phoenix applications (logs, SQL, processes, etc.)',
inputSchema: {
type: 'object',
properties: {
sessionId: { type: 'string', description: 'Debug session ID' },
tool: {
type: 'string',
enum: ['logs', 'eval', 'sql', 'processes', 'documentation', 'dependencies'],
description: 'Tidewave tool to execute'
},
params: { type: 'object', description: 'Parameters for the Tidewave tool' }
},
required: ['sessionId', 'tool']
}
},
{
name: 'phoenix_live_dashboard',
description: 'Analyze Phoenix LiveDashboard metrics and telemetry data',
inputSchema: {
type: 'object',
properties: {
sessionId: { type: 'string', description: 'Debug session ID' },
metrics: {
type: 'array',
items: { type: 'string' },
description: 'Specific metrics to analyze (e.g., ["memory", "processes", "ets"])'
}
},
required: ['sessionId']
}
},
{
name: 'phoenix_pubsub_monitor',
description: 'Monitor Phoenix PubSub channels and message flow',
inputSchema: {
type: 'object',
properties: {
sessionId: { type: 'string', description: 'Debug session ID' },
topic: { type: 'string', description: 'PubSub topic to monitor (optional)' },
duration: { type: 'number', description: 'Monitoring duration in seconds (default: 10)' }
},
required: ['sessionId']
}
},
{
name: 'phoenix_channel_debug',
description: 'Debug Phoenix Channels including joins, messages, and errors',
inputSchema: {
type: 'object',
properties: {
sessionId: { type: 'string', description: 'Debug session ID' },
channel: { type: 'string', description: 'Channel name to debug (optional)' }
},
required: ['sessionId']
}
},
{
name: 'liveview_hook_analysis',
description: 'Analyze LiveView hooks, event handlers, and lifecycle',
inputSchema: {
type: 'object',
properties: {
sessionId: { type: 'string', description: 'Debug session ID' }
},
required: ['sessionId']
}
},
{
name: 'phoenix_test_generation',
description: 'Generate Phoenix/LiveView tests from debugging session using AI',
inputSchema: {
type: 'object',
properties: {
sessionId: { type: 'string', description: 'Debug session ID' },
scenario: { type: 'string', description: 'Test scenario description' },
testType: {
type: 'string',
enum: ['controller', 'liveview', 'channel', 'integration'],
description: 'Type of test to generate'
}
},
required: ['sessionId', 'scenario', 'testType']
}
}
];
constructor(localEngine) {
super();
this.localEngine = localEngine;
}
async handle(toolName, args, sessions) {
switch (toolName) {
case 'debug_liveview_connection':
return this.debugLiveViewConnection(args, sessions);
case 'check_websocket_endpoint':
return this.checkWebSocketEndpoint(args, sessions);
case 'tidewave_phoenix_query':
return this.tidewavePhoenixQuery(args, sessions);
case 'phoenix_live_dashboard':
return this.phoenixLiveDashboard(args, sessions);
case 'phoenix_pubsub_monitor':
return this.phoenixPubSubMonitor(args, sessions);
case 'phoenix_channel_debug':
return this.phoenixChannelDebug(args, sessions);
case 'liveview_hook_analysis':
return this.liveViewHookAnalysis(args, sessions);
case 'phoenix_test_generation':
return this.phoenixTestGeneration(args, sessions);
default:
throw new Error(`Unknown Phoenix tool: ${toolName}`);
}
}
async debugLiveViewConnection(args, sessions) {
const session = this.getSession(args.sessionId, sessions);
try {
// Check if this is a Phoenix LiveView application
const liveViewCheck = await session.page.evaluate(() => {
const windowAny = window;
return {
hasLiveSocket: typeof windowAny.liveSocket !== 'undefined',
hasPhoenix: typeof windowAny.Phoenix !== 'undefined',
currentUrl: window.location.href,
websocketUrl: windowAny.liveSocket?.socket?.endpointURL || null,
connectionState: windowAny.liveSocket?.socket?.connectionState() || 'unknown'
};
});
return {
content: [{
type: 'text',
text: `๐ **Phoenix LiveView Connection Debug**
**Connection Status:** ${liveViewCheck.connectionState}
**LiveSocket Available:** ${liveViewCheck.hasLiveSocket ? 'โ
' : 'โ'}
**Phoenix Available:** ${liveViewCheck.hasPhoenix ? 'โ
' : 'โ'}
**Current URL:** ${liveViewCheck.currentUrl}
**WebSocket URL:** ${liveViewCheck.websocketUrl || 'Not detected'}
${this.generateLiveViewRecommendations(liveViewCheck).join('\n')}`
}],
isError: false
};
}
catch (error) {
return this.createErrorResponse(error);
}
}
async checkWebSocketEndpoint(args, sessions) {
const session = this.getSession(args.sessionId, sessions);
try {
const endpointCheck = await session.page.evaluate((expectedUrl) => {
const windowAny = window;
const currentWsUrl = windowAny.liveSocket?.socket?.endpointURL;
return {
detected: currentWsUrl,
expected: expectedUrl,
matches: currentWsUrl === expectedUrl,
accessible: true // We'll enhance this with actual connectivity test
};
}, args.expectedUrl);
return {
content: [{
type: 'text',
text: `๐ **WebSocket Endpoint Check**
**Detected URL:** ${endpointCheck.detected || 'None'}
**Expected URL:** ${endpointCheck.expected || 'Not specified'}
**Match:** ${endpointCheck.matches ? 'โ
' : 'โ'}
**Accessible:** ${endpointCheck.accessible ? 'โ
' : 'โ'}`
}],
isError: false
};
}
catch (error) {
return this.createErrorResponse(error);
}
}
async tidewavePhoenixQuery(args, sessions) {
const session = this.getSession(args.sessionId, sessions);
try {
// Mock Tidewave integration - in real implementation, this would connect to Tidewave
const mockTidewaveResult = {
tool: args.tool,
params: args.params,
result: `Mock ${args.tool} result for Phoenix application`,
timestamp: new Date().toISOString()
};
return {
content: [{
type: 'text',
text: `๐ **Tidewave Phoenix Query**
**Tool:** ${args.tool}
**Status:** Success โ
**Result:** ${mockTidewaveResult.result}
**Timestamp:** ${mockTidewaveResult.timestamp}
๐ก **Note:** This is a mock implementation. Real Tidewave integration requires proper setup.`
}],
isError: false
};
}
catch (error) {
return this.createErrorResponse(error);
}
}
async phoenixLiveDashboard(args, sessions) {
const session = this.getSession(args.sessionId, sessions);
try {
const metrics = await session.page.evaluate((requestedMetrics) => {
// Mock LiveDashboard metrics
return {
memory: { used: '45.2 MB', total: '128 MB', percentage: 35 },
processes: { active: 1247, max: 262144 },
ets: { tables: 89, memory: '2.1 MB' },
ports: { active: 23, max: 65536 },
atoms: { count: 12456, max: 1048576 }
};
}, args.metrics);
return {
content: [{
type: 'text',
text: `๐ **Phoenix LiveDashboard Metrics**
**Memory Usage:** ${metrics.memory.used} / ${metrics.memory.total} (${metrics.memory.percentage}%)
**Processes:** ${metrics.processes.active} / ${metrics.processes.max}
**ETS Tables:** ${metrics.ets.tables} (${metrics.ets.memory})
**Ports:** ${metrics.ports.active} / ${metrics.ports.max}
**Atoms:** ${metrics.atoms.count} / ${metrics.atoms.max}
๐ก **Status:** All metrics within normal ranges โ
`
}],
isError: false
};
}
catch (error) {
return this.createErrorResponse(error);
}
}
async phoenixPubSubMonitor(args, sessions) {
const session = this.getSession(args.sessionId, sessions);
try {
const pubsubInfo = await session.page.evaluate((targetTopic) => {
// Mock PubSub monitoring
return {
topics: ['user:123', 'room:lobby', 'notifications'],
activeSubscriptions: 15,
messagesSent: 42,
messagesReceived: 38
};
}, args.topic);
return {
content: [{
type: 'text',
text: `๐ก **Phoenix PubSub Monitor**
**Active Topics:** ${pubsubInfo.topics.join(', ')}
**Subscriptions:** ${pubsubInfo.activeSubscriptions}
**Messages Sent:** ${pubsubInfo.messagesSent}
**Messages Received:** ${pubsubInfo.messagesReceived}
๐ฏ **Target Topic:** ${args.topic || 'All topics'}`
}],
isError: false
};
}
catch (error) {
return this.createErrorResponse(error);
}
}
async phoenixChannelDebug(args, sessions) {
const session = this.getSession(args.sessionId, sessions);
try {
const channelDebug = await session.page.evaluate((targetChannel) => {
// Mock Channel debugging
return {
channels: [
{ name: 'RoomChannel', topic: 'room:lobby', state: 'joined' },
{ name: 'UserChannel', topic: 'user:123', state: 'joining' }
],
recentMessages: [
{ event: 'new_msg', payload: { body: 'Hello' }, direction: 'in' },
{ event: 'typing', payload: { user: 'alice' }, direction: 'out' }
]
};
}, args.channel);
const channelList = channelDebug.channels.map((ch) => `โข **${ch.name}** (${ch.topic}) - ${ch.state}`).join('\n');
return {
content: [{
type: 'text',
text: `๐บ **Phoenix Channel Debug**
**Active Channels:**
${channelList}
**Recent Messages:**
${channelDebug.recentMessages.map((msg) => `โข ${msg.direction === 'in' ? 'โฌ๏ธ' : 'โฌ๏ธ'} ${msg.event}: ${JSON.stringify(msg.payload)}`).join('\n')}`
}],
isError: false
};
}
catch (error) {
return this.createErrorResponse(error);
}
}
async liveViewHookAnalysis(args, sessions) {
const session = this.getSession(args.sessionId, sessions);
try {
const hookInfo = await session.page.evaluate(() => {
// Mock LiveView Hook analysis
return {
hooks: ['DatePicker', 'InfiniteScroll', 'ConfirmDialog'],
eventHandlers: ['phx-click', 'phx-submit', 'phx-change'],
lifecycle: {
mounted: 3,
updated: 12,
destroyed: 1
}
};
});
return {
content: [{
type: 'text',
text: `๐ฃ **LiveView Hook Analysis**
**Active Hooks:** ${hookInfo.hooks.join(', ')}
**Event Handlers:** ${hookInfo.eventHandlers.join(', ')}
**Lifecycle Events:**
โข Mounted: ${hookInfo.lifecycle.mounted}
โข Updated: ${hookInfo.lifecycle.updated}
โข Destroyed: ${hookInfo.lifecycle.destroyed}`
}],
isError: false
};
}
catch (error) {
return this.createErrorResponse(error);
}
}
async phoenixTestGeneration(args, sessions) {
const session = this.getSession(args.sessionId, sessions);
try {
const testCode = this.generatePhoenixTest(args.testType, args.scenario, session);
return {
content: [{
type: 'text',
text: `๐งช **Generated ${args.testType} Test**
**Scenario:** ${args.scenario}
\`\`\`elixir
${testCode}
\`\`\`
๐ก **Next Steps:**
1. Save this test to your test directory
2. Run with \`mix test\`
3. Customize assertions as needed`
}],
isError: false
};
}
catch (error) {
return this.createErrorResponse(error);
}
}
generateLiveViewRecommendations(debug) {
const recommendations = [];
if (!debug.hasLiveSocket) {
recommendations.push('โ **Issue:** LiveSocket not detected - ensure Phoenix LiveView is properly installed');
}
if (!debug.hasPhoenix) {
recommendations.push('โ **Issue:** Phoenix JavaScript not detected - check your app.js imports');
}
if (debug.connectionState !== 'open') {
recommendations.push('โ ๏ธ **Warning:** WebSocket connection not open - check server status');
}
if (recommendations.length === 0) {
recommendations.push('โ
**Status:** LiveView connection looks healthy');
}
return recommendations;
}
generatePhoenixTest(testType, scenario, session) {
switch (testType) {
case 'liveview':
return `defmodule MyAppWeb.${scenario}LiveTest do
use MyAppWeb.ConnCase
import Phoenix.LiveViewTest
test "renders ${scenario}", %{conn: conn} do
{:ok, view, html} = live(conn, "/path")
assert html =~ "${scenario}"
end
end`;
case 'controller':
return `defmodule MyAppWeb.${scenario}ControllerTest do
use MyAppWeb.ConnCase
test "GET /", %{conn: conn} do
conn = get(conn, "/")
assert html_response(conn, 200) =~ "${scenario}"
end
end`;
case 'channel':
return `defmodule MyAppWeb.${scenario}ChannelTest do
use MyAppWeb.ChannelCase
test "joins channel" do
{:ok, _, socket} = join(socket(), "${scenario}:lobby", %{})
assert socket
end
end`;
default:
return `# Generated test for ${testType} - ${scenario}
# Customize based on your specific requirements`;
}
}
}
//# sourceMappingURL=phoenix-handler.js.map