Table of Contents
- Connection Establishment
- Authentication Flow
- MCP Event Handlers
- Error Handling
- Complete Working Example
- Debugging Steps
- Common Issues
Connection Establishment
Basic WebSocket Setup
import io from 'socket.io-client';
import secureStorage from 'src/utils/secureStorage';
import routes from 'src/config/routes';
const ChatContext = () => {
const socketRef = useRef(null);
const { user } = useAuth();
const [isConnected, setIsConnected] = useState(false);
const [reconnectAttempts, setReconnectAttempts] = useState(0);
const maxReconnectAttempts = 20;
const getAuthToken = () => {
// Priority order: user context first, then secure storage
const accessToken = user?.accessToken;
const tokenFromStorage = !accessToken ? secureStorage.getItem('accessToken') : null;
return accessToken || tokenFromStorage;
};
const connectSocket = () => {
const token = getAuthToken();
if (!token) {
console.error('β No access token available for WebSocket connection');
openSnackbar('Authentication error: Please log in again', 'error');
return;
}
console.log('π Establishing WebSocket connection...');
// CRITICAL: Token must be in headers
socketRef.current = io(routes.streamChat, {
extraHeaders: {
Authorization: `Bearer ${token}`
},
transports: ['websocket', 'polling'],
reconnectionAttempts: maxReconnectAttempts,
reconnectionDelay: 1000,
});
setupEventHandlers();
};
};Authentication Flow
Token Management
// Token retrieval with fallback
const getAuthToken = () => {
const accessToken = user?.accessToken;
const tokenFromStorage = !accessToken ? secureStorage.getItem('accessToken') : null;
const finalToken = accessToken || tokenFromStorage;
console.log('π Token source:', accessToken ? 'user object' : 'secure storage');
console.log('π Token available:', !!finalToken);
return finalToken;
};
// Validate token before connection
const validateToken = (token) => {
if (!token) {
console.error('β No authentication token available');
return false;
}
// Basic JWT format check
const parts = token.split('.');
if (parts.length !== 3) {
console.error('β Invalid token format');
return false;
}
try {
// Check if token is expired (basic check)
const payload = JSON.parse(atob(parts[1]));
const now = Math.floor(Date.now() / 1000);
if (payload.exp && payload.exp < now) {
console.error('β Token expired');
return false;
}
console.log('β
Token validation passed');
return true;
} catch (error) {
console.error('β Token parsing error:', error);
return false;
}
};Connection Setup with Authentication
const setupEventHandlers = () => {
// Connection success
socketRef.current.on('connect', () => {
console.log('β
WebSocket connected successfully');
setIsConnected(true);
setReconnectAttempts(0);
removeLoadingComments();
});
// Disconnection
socketRef.current.on('disconnect', (reason) => {
console.log('π WebSocket disconnected:', reason);
setIsConnected(false);
setLoading(false);
});
// Reconnection attempts
socketRef.current.on('reconnect_attempt', () => {
console.log('π Attempting to reconnect...');
openSnackbar('Attempting to reconnect...', 'info');
setReconnectAttempts(prev => prev + 1);
});
socketRef.current.on('reconnect_failed', () => {
console.error('β Failed to reconnect after maximum attempts');
openSnackbar('Failed to reconnect to the server. Please refresh the page.', 'error');
});
setupErrorHandlers();
setupMCPHandlers();
};MCP Event Handlers
Chat Message Response Handler
const setupMCPHandlers = () => {
// Handle streaming AI responses
socketRef.current.on('chat_message_response', (data) => {
console.log('π¬ Received response chunk:', data.content?.substring(0, 50) + '...');
if (data.finish_reason === 'tool_calls') {
console.log('π§ MCP tool execution starting');
toolExecutionInProgressRef.current = true;
}
else if (!data.finish_reason || (data.finish_reason && data.finish_reason.toLowerCase() !== 'stop')) {
// Accumulate content in buffer
if (data.content) {
bufferedResponseRef.current += data.content;
}
// Add only the current chunk to chat history
addAIResponseToChatHistory(data);
} else {
// Final chunk
if (data.content) {
bufferedResponseRef.current += data.content;
addAIResponseToChatHistory({ content: data.content });
}
// Handle citations if present
if (data.citations && data.citations.length > 0) {
addCitationsToLastMessage(data.citations);
}
// Process SQL if needed
if (bufferedResponseRef.current) {
processFullResponseForSQL(bufferedResponseRef.current);
}
bufferedResponseRef.current = '';
toolExecutionInProgressRef.current = false;
}
});
// Handle MCP tool completion
socketRef.current.on('mcp_tool_complete_in_content', (data) => {
console.log('β
MCP tool execution completed:', data);
if (data.result_block) {
try {
// Parse the tool result
const resultMatch = data.result_block;
if (resultMatch) {
const toolResult = JSON.parse(resultMatch[1]);
const toolResultMessage = {
id: uuid(),
role: 'tool',
type: 'mcp_search_result',
content: resultMatch
};
setChatHistory(existingChatHistory => [...existingChatHistory, toolResultMessage]);
console.log('π Tool result added to chat history');
}
} catch (error) {
console.error('β Failed to parse MCP tool result:', error);
// Fallback: add raw result
const fallbackMessage = {
id: uuid(),
role: 'assistant',
type: 'success',
content: `Tool Result: ${JSON.stringify(data.result_block, null, 2)}`
};
setChatHistory(existingChatHistory => [...existingChatHistory, fallbackMessage]);
}
}
});
// Handle data tables from MCP tools
socketRef.current.on('data_table', (dataTableResponse) => {
console.log('π Received data table from MCP tool');
addDataTableToChatHistory(dataTableResponse.content, 'user');
setTimeout(() => {
addAIResponseToChatHistory({
content: 'Data successfully parsed. Feel free to explore or ask questions.'
});
}, 0);
});
// Handle PII scrubbing messages
socketRef.current.on('scrubbed_message', (data) => {
console.log('π‘οΈ PII scrubbing applied');
insertScrubbedMessage(data.content);
});
};Error Handling
Comprehensive Error Handler
const setupErrorHandlers = () => {
// WebSocket-level errors
socketRef.current.on('error', (data = {}) => {
console.error(`β WebSocket error: ${JSON.stringify(data) || 'unknown'} (code: ${data.code})`);
// 401 β token invalid/expired β logout to prompt re-login
if (data.code === 401) {
console.error('π Authentication failed - token invalid/expired');
logout();
return;
}
// If it's the "not publicly shared" error (400), this is likely an auth issue
if (data.code === 400 && data.error && data.error.includes('not publically shared')) {
console.error('π Authentication error: Token might not be in expected format or location');
// Try to reconnect with fresh token
if (socketRef.current) {
socketRef.current.disconnect();
setTimeout(() => {
console.log('π Retrying connection with fresh token');
connectSocket();
}, 1000);
return;
}
}
// Otherwise just surface the error to the UI and keep the session alive
openSnackbar(data.error || 'Realtime connection error', 'error');
});
// Handle connection-level errors
socketRef.current.io.on('error', () => {}); // no-op to silence default uncaught
socketRef.current.io.on('connect_error', (err) => {
console.warn('β οΈ Socket connect_error:', err.message);
openSnackbar('Unable to connect to realtime service', 'warning');
});
};Complete Working Example
Custom Hook Implementation
const useMCPWebSocket = () => {
const socketRef = useRef(null);
const [isConnected, setIsConnected] = useState(false);
const [error, setError] = useState(null);
const [reconnectAttempts, setReconnectAttempts] = useState(0);
const { user, logout } = useAuth();
const { openSnackbar } = useContext(SnackbarContext);
const connect = useCallback(() => {
// Step 1: Get and validate token
const token = user?.accessToken || secureStorage.getItem('accessToken');
if (!token) {
const errorMsg = 'No authentication token available';
console.error('β', errorMsg);
setError(errorMsg);
return false;
}
if (!validateToken(token)) {
const errorMsg = 'Invalid authentication token';
console.error('β', errorMsg);
setError(errorMsg);
return false;
}
console.log('π Starting WebSocket connection...');
// Step 2: Create socket connection
socketRef.current = io('https://api.agent700.ai/api/stream-chat', {
extraHeaders: {
Authorization: `Bearer ${token}`
},
transports: ['websocket', 'polling'],
reconnectionAttempts: 20,
reconnectionDelay: 1000,
});
// Step 3: Setup event handlers
socketRef.current.on('connect', () => {
console.log('β
Connected to MCP WebSocket');
setIsConnected(true);
setError(null);
setReconnectAttempts(0);
});
socketRef.current.on('disconnect', (reason) => {
console.log('π Disconnected from WebSocket:', reason);
setIsConnected(false);
});
socketRef.current.on('error', (data) => {
console.error('β WebSocket error:', data);
if (data.code === 401) {
console.error('π Token expired - logging out');
logout();
} else if (data.code === 400 && data.error?.includes('not publically shared')) {
console.error('π Token format issue - retrying...');
setTimeout(() => connect(), 1000);
} else {
setError(data.error || 'Connection error');
}
});
// MCP-specific handlers
socketRef.current.on('mcp_tool_complete_in_content', (data) => {
console.log('π§ MCP tool completed:', data);
// Handle tool results here
});
socketRef.current.on('chat_message_response', (data) => {
console.log('π¬ Chat response:', data.content?.substring(0, 50));
if (data.finish_reason === 'tool_calls') {
console.log('π§ Tool execution started');
}
});
return true;
}, [user, logout]);
const sendMessage = useCallback((agentId, messages) => {
const token = user?.accessToken || secureStorage.getItem('accessToken');
if (!socketRef.current || !isConnected) {
console.error('β Socket not connected');
return false;
}
if (!token) {
console.error('β No token available for message');
return false;
}
const payload = {
agentId,
messages,
Authorization: `Bearer ${token}` // Critical: token in payload too!
};
console.log('π€ Sending message to agent:', agentId);
socketRef.current.emit('send_chat_message', payload);
return true;
}, [user, isConnected]);
const disconnect = useCallback(() => {
if (socketRef.current) {
console.log('π Disconnecting WebSocket');
socketRef.current.disconnect();
socketRef.current = null;
setIsConnected(false);
}
}, []);
return {
connect,
sendMessage,
disconnect,
isConnected,
error,
reconnectAttempts
};
};Debugging Steps
Step 1: Token Verification
const debugToken = () => {
const token = user?.accessToken || secureStorage.getItem('accessToken');
console.log('π Token Debug:');
console.log('- Token exists:', !!token);
console.log('- Token source:', user?.accessToken ? 'user context' : 'secure storage');
console.log('- Token length:', token?.length || 0);
if (token) {
try {
const parts = token.split('.');
console.log('- JWT parts:', parts.length);
if (parts.length === 3) {
const payload = JSON.parse(atob(parts[1]));
const now = Math.floor(Date.now() / 1000);
console.log('- Token expires:', new Date(payload.exp * 1000));
console.log('- Token valid:', payload.exp > now);
}
} catch (e) {
console.error('- Token parse error:', e);
}
}
};Step 2: Connection Testing
const testConnection = () => {
console.log('π§ͺ Testing WebSocket connection...');
const token = user?.accessToken || secureStorage.getItem('accessToken');
if (!token) {
console.error('β Test failed: No token');
return;
}
const testSocket = io('https://api.agent700.ai/api/stream-chat', {
extraHeaders: {
Authorization: `Bearer ${token}`
},
timeout: 5000,
});
testSocket.on('connect', () => {
console.log('β
Test connection successful');
testSocket.disconnect();
});
testSocket.on('connect_error', (error) => {
console.error('β Test connection failed:', error.message);
});
testSocket.on('error', (data) => {
console.error('β Test error:', data);
});
// Auto-cleanup
setTimeout(() => {
if (testSocket.connected) {
console.log('β±οΈ Test timeout - disconnecting');
testSocket.disconnect();
}
}, 10000);
};Step 3: Network Analysis
const debugNetwork = () => {
console.log('π Network Debug:');
console.log('- API Host:', process.env.REACT_APP_API_HOST);
console.log('- Stream Chat URL:', routes.streamChat);
console.log('- Current location:', window.location.origin);
console.log('- CORS mode:', 'include');
// Test basic connectivity
fetch(routes.streamChat.replace('/stream-chat', '/health'), {
method: 'GET',
headers: {
'Authorization': `Bearer ${user?.accessToken || secureStorage.getItem('accessToken')}`
}
})
.then(response => {
console.log('β
Basic API connectivity:', response.status);
})
.catch(error => {
console.error('β Basic API connectivity failed:', error);
});
};Common Issues
Issue 1: "Connection Error" with No Details
Symptoms:
- Generic "Connection error" message
- No specific error code
- Connection drops immediately
Debug Steps:
// Add detailed logging to connection
socketRef.current = io(routes.streamChat, {
extraHeaders: { Authorization: `Bearer ${token}` },
transports: ['websocket', 'polling'],
forceNew: true, // Force new connection
debug: true // Enable debug mode
});
// Log all events
['connect', 'disconnect', 'error', 'connect_error', 'reconnect_error'].forEach(event => {
socketRef.current.on(event, (data) => {
console.log(`π Socket event '${event}':`, data);
});
});Common Causes:
- Token not in correct format
- Backend not accepting WebSocket connections
- CORS issues
- Network firewall blocking WebSocket
Issue 2: Authentication Failures
Symptoms:
- 401 errors
- "not publically shared" errors
- Immediate disconnections
Solutions:
// Ensure token in both places
const requestData = {
agentId: selectedAgent?.id,
messages: chatHistory,
Authorization: `Bearer ${token}`, // Must be here
// other data...
};
// AND in headers
socketRef.current = io(routes.streamChat, {
extraHeaders: {
Authorization: `Bearer ${token}` // AND here
}
});Issue 3: MCP Function Name Length (68 vs 64 characters)
Problem: MCP protocol limits function names to 64 characters
Solutions:
- Rename the function (if you control the MCP server):
// Instead of: "get_detailed_user_information_with_preferences_and_settings"
// Use: "get_user_info_detailed"- Create alias functions (if using third-party server):
// Map long names to short ones in your server config
const functionAliases = {
'get_user_info': 'get_detailed_user_information_with_preferences_and_settings'
};- Update MCP server configuration to use shorter, descriptive names
Issue 4: Tool Results Not Displaying
Debug Steps:
// Add logging to MCP tool handler
socketRef.current.on('mcp_tool_complete_in_content', (data) => {
console.log('π§ Raw MCP tool data:', data);
console.log('π§ Result block exists:', !!data.result_block);
if (data.result_block) {
console.log('π§ Result block content:', data.result_block);
try {
const parsed = JSON.parse(data.result_block[1]);
console.log('π§ Parsed result:', parsed);
} catch (e) {
console.error('π§ Parse error:', e);
}
}
});Testing Checklist
- Token exists and is valid
- WebSocket URL is correct
- Authentication headers are set
- Token is in both headers AND payload
- Network connectivity to backend
- MCP server function names are β€ 64 characters
- Event handlers are properly attached
- Error handling is comprehensive
- Tool results are being parsed correctly
Environment Variables Check
// Add to your debugging routine
const checkEnvironment = () => {
console.log('π§ Environment Check:');
console.log('- REACT_APP_API_HOST:', process.env.REACT_APP_API_HOST);
console.log('- NODE_ENV:', process.env.NODE_ENV);
console.log('- Stream Chat URL:', routes.streamChat);
console.log('- Current origin:', window.location.origin);
};Use this guide to systematically debug WebSocket connection issues in your Agent700 MCP integration.
Updated about 1 month ago
