Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/firefox/snapshot/formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
import type { SnapshotNode } from './types.js';

/**
* Max attribute value length
* Max attribute value length (aggressive truncation for token efficiency)
*/
const MAX_ATTR_LENGTH = 50;
const MAX_ATTR_LENGTH = 30;

/**
* Formatting options
Expand Down
7 changes: 2 additions & 5 deletions src/tools/console.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ export async function handleListConsoleMessages(args: unknown): Promise<McpToolR
}

if (truncated) {
output += `\n... ${filteredCount - messages.length} more messages (increase limit to see more)`;
output += `\n[+${filteredCount - messages.length} more]`;
}

return successResponse(output);
Expand All @@ -235,10 +235,7 @@ export async function handleClearConsoleMessages(_args: unknown): Promise<McpToo
const count = (await firefox.getConsoleMessages()).length;
firefox.clearConsoleMessages();

return successResponse(
`Cleared ${count} console message(s) from buffer.\n\n` +
'You can now capture fresh console output from new page activity.'
);
return successResponse(`✅ cleared ${count} messages`);
} catch (error) {
return errorResponse(error as Error);
}
Expand Down
54 changes: 12 additions & 42 deletions src/tools/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { successResponse, errorResponse } from '../utils/response-helpers.js';
import type { McpToolResponse } from '../types/common.js';

/**
* Transform UID resolution errors into friendly messages
* Transform UID resolution errors into concise messages
*/
function handleUidError(error: Error, uid: string): Error {
const errorMsg = error.message;
Expand All @@ -18,11 +18,7 @@ function handleUidError(error: Error, uid: string): Error {
errorMsg.includes('UID') ||
errorMsg.includes('not found')
) {
return new Error(
`UID "${uid}" is stale or invalid.\n\n` +
'The page may have changed since the snapshot was taken.\n' +
'Please call take_snapshot to get fresh UIDs and try again.'
);
return new Error(`${uid} stale/invalid. Call take_snapshot first.`);
}

return error;
Expand Down Expand Up @@ -163,9 +159,7 @@ export async function handleClickByUid(args: unknown): Promise<McpToolResponse>

try {
await firefox.clickByUid(uid, dblClick);
return successResponse(
`✅ ${dblClick ? 'Double-clicked' : 'Clicked'} element with UID "${uid}"`
);
return successResponse(`✅ ${dblClick ? 'dblclick' : 'click'} ${uid}`);
} catch (error) {
throw handleUidError(error as Error, uid);
}
Expand All @@ -187,7 +181,7 @@ export async function handleHoverByUid(args: unknown): Promise<McpToolResponse>

try {
await firefox.hoverByUid(uid);
return successResponse(`✅ Hovered over element with UID "${uid}"`);
return successResponse(`✅ hover ${uid}`);
} catch (error) {
throw handleUidError(error as Error, uid);
}
Expand All @@ -213,9 +207,7 @@ export async function handleFillByUid(args: unknown): Promise<McpToolResponse> {

try {
await firefox.fillByUid(uid, value);
return successResponse(
`✅ Filled element with UID "${uid}"\nValue: ${value.substring(0, 50)}${value.length > 50 ? '...' : ''}`
);
return successResponse(`✅ fill ${uid}`);
} catch (error) {
throw handleUidError(error as Error, uid);
}
Expand All @@ -241,16 +233,12 @@ export async function handleDragByUidToUid(args: unknown): Promise<McpToolRespon

try {
await firefox.dragByUidToUid(fromUid, toUid);
return successResponse(`✅ Dragged element "${fromUid}" to "${toUid}"`);
return successResponse(`✅ drag ${fromUid}${toUid}`);
} catch (error) {
// Check both UIDs for staleness
const errorMsg = (error as Error).message;
if (errorMsg.includes('stale') || errorMsg.includes('Snapshot') || errorMsg.includes('UID')) {
throw new Error(
`One or both UIDs (from: "${fromUid}", to: "${toUid}") are stale or invalid.\n\n` +
'The page may have changed since the snapshot was taken.\n' +
'Please call take_snapshot to get fresh UIDs and try again.'
);
throw new Error(`UIDs stale/invalid. Call take_snapshot first.`);
}
throw error;
}
Expand Down Expand Up @@ -282,23 +270,11 @@ export async function handleFillFormByUid(args: unknown): Promise<McpToolRespons

try {
await firefox.fillFormByUid(elements);
return successResponse(
`✅ Filled ${elements.length} form field(s):\n` +
elements
.map(
(el) =>
` - ${el.uid}: ${el.value.substring(0, 30)}${el.value.length > 30 ? '...' : ''}`
)
.join('\n')
);
return successResponse(`✅ filled ${elements.length} fields`);
} catch (error) {
const errorMsg = (error as Error).message;
if (errorMsg.includes('stale') || errorMsg.includes('Snapshot') || errorMsg.includes('UID')) {
throw new Error(
`One or more UIDs are stale or invalid.\n\n` +
'The page may have changed since the snapshot was taken.\n' +
'Please call take_snapshot to get fresh UIDs and try again.'
);
throw new Error(`UIDs stale/invalid. Call take_snapshot first.`);
}
throw error;
}
Expand All @@ -324,7 +300,7 @@ export async function handleUploadFileByUid(args: unknown): Promise<McpToolRespo

try {
await firefox.uploadFileByUid(uid, filePath);
return successResponse(`✅ Uploaded file to element with UID "${uid}"\nFile: ${filePath}`);
return successResponse(`✅ upload ${uid}`);
} catch (error) {
const errorMsg = (error as Error).message;

Expand All @@ -335,17 +311,11 @@ export async function handleUploadFileByUid(args: unknown): Promise<McpToolRespo

// Check for file input specific errors
if (errorMsg.includes('not a file input') || errorMsg.includes('type="file"')) {
throw new Error(
`Element with UID "${uid}" is not an <input type="file"> element.\n\n` +
'Please ensure the UID points to a file input element.'
);
throw new Error(`${uid} is not a file input`);
}

if (errorMsg.includes('hidden') || errorMsg.includes('not visible')) {
throw new Error(
`File input element with UID "${uid}" is hidden or not interactable.\n\n` +
'Some file inputs are hidden and cannot be directly interacted with.'
);
throw new Error(`${uid} is hidden/not interactable`);
}

throw error;
Expand Down
51 changes: 12 additions & 39 deletions src/tools/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -232,16 +232,8 @@ export async function handleListNetworkRequests(args: unknown): Promise<McpToolR
return `${req.id} | ${req.method} ${req.url} ${statusInfo}${req.isXHR ? ' (XHR)' : ''}`;
});

const summary = [
`Found ${requests.length} network request(s)${hasMore ? ` (showing first ${limit})` : ''}`,
'',
'Network Requests:',
...formattedRequests,
'',
'TIP: Use the request ID (first column) with get_network_request for full details.',
].join('\n');

return successResponse(summary);
const header = `📡 ${requests.length} requests${hasMore ? ` (limit ${limit})` : ''}\n`;
return successResponse(header + formattedRequests.join('\n'));
} else if (detail === 'min') {
// Compact JSON
const minData = limitedRequests.map((req) => ({
Expand All @@ -256,7 +248,7 @@ export async function handleListNetworkRequests(args: unknown): Promise<McpToolR
}));

return successResponse(
`Found ${requests.length} requests${hasMore ? ` (showing first ${limit})` : ''}\n\n` +
`📡 ${requests.length} requests${hasMore ? ` (limit ${limit})` : ''}\n` +
JSON.stringify(minData, null, 2)
);
} else {
Expand All @@ -275,14 +267,12 @@ export async function handleListNetworkRequests(args: unknown): Promise<McpToolR
}));

return successResponse(
`Found ${requests.length} requests${hasMore ? ` (showing first ${limit})` : ''}\n\n` +
`📡 ${requests.length} requests${hasMore ? ` (limit ${limit})` : ''}\n` +
JSON.stringify(fullData, null, 2)
);
}
} catch (error) {
return errorResponse(
`Failed to list network requests: ${error instanceof Error ? error.message : String(error)}`
);
return errorResponse(error instanceof Error ? error : new Error(String(error)));
}
}

Expand All @@ -295,10 +285,7 @@ export async function handleGetNetworkRequest(args: unknown): Promise<McpToolRes
} = args as { id?: string; url?: string; format?: 'text' | 'json' };

if (!id && !url) {
return errorResponse(
'Either "id" or "url" parameter is required.\n\n' +
'TIP: Call list_network_requests first and use the returned ID for reliable lookup.'
);
return errorResponse('id or url required');
}

const { getFirefox } = await import('../index.js');
Expand All @@ -311,31 +298,19 @@ export async function handleGetNetworkRequest(args: unknown): Promise<McpToolRes
if (id) {
request = requests.find((req) => req.id === id);
if (!request) {
return errorResponse(
`No network request found with ID: ${id}\n\n` +
'TIP: The request may have been cleared. Call list_network_requests to see available requests.'
);
return errorResponse(`ID ${id} not found`);
}
} else if (url) {
// Fallback: lookup by URL (with collision detection)
const matches = requests.filter((req) => req.url === url);

if (matches.length === 0) {
return errorResponse(
`No network request found with URL: ${url}\n\n` +
'TIP: Use list_network_requests to see available requests.'
);
return errorResponse(`URL not found: ${url}`);
}

if (matches.length > 1) {
const matchInfo = matches
.map((req) => ` - ID: ${req.id} | ${req.method} [${req.status || 'pending'}]`)
.join('\n');
return errorResponse(
`Multiple requests (${matches.length}) found with URL: ${url}\n\n` +
'Please use one of these IDs with the "id" parameter:\n' +
matchInfo
);
const ids = matches.map((req) => req.id).join(', ');
return errorResponse(`Multiple matches, use id: ${ids}`);
}

request = matches[0];
Expand Down Expand Up @@ -364,10 +339,8 @@ export async function handleGetNetworkRequest(args: unknown): Promise<McpToolRes
return jsonResponse(details);
}

return successResponse('Network Request Details:\n\n' + JSON.stringify(details, null, 2));
return successResponse(JSON.stringify(details, null, 2));
} catch (error) {
return errorResponse(
`Failed to get network request: ${error instanceof Error ? error.message : String(error)}`
);
return errorResponse(error instanceof Error ? error : new Error(String(error)));
}
}
Loading