import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { navigateSchema, reloadSchema, goBackSchema, goForwardSchema } from '../schemas.js';
import { getPageForOperation } from '../tabs.js';
import { getDefaultTimeout } from '../browser.js';
import {
handleResult,
ok,
err,
navigationTimeout,
navigationFailed,
normalizeError,
} from '../errors.js';
import type { WaitUntilOption } from '../types.js';
/**
* Register navigation tools
*/
export function registerNavigationTools(server: McpServer): void {
// Navigate to URL
server.tool(
'navigate',
'Navigate to a URL in the browser',
navigateSchema.shape,
async ({ url, waitUntil, timeout, tabId }) => {
const pageResult = await getPageForOperation(tabId);
if (!pageResult.success) {
return handleResult(pageResult);
}
const page = pageResult.data;
const timeoutMs = timeout ?? getDefaultTimeout();
try {
const response = await page.goto(url, {
waitUntil: (waitUntil ?? 'load') as WaitUntilOption,
timeout: timeoutMs,
});
if (!response) {
return handleResult(err(navigationFailed(url, 'No response received')));
}
const status = response.status();
if (status >= 400) {
return handleResult(err(navigationFailed(url, `HTTP ${status}`)));
}
return handleResult(ok({
url: page.url(),
title: await page.title(),
status,
}));
} catch (error) {
if (error instanceof Error && error.message.includes('timeout')) {
return handleResult(err(navigationTimeout(url, timeoutMs)));
}
return handleResult(err(normalizeError(error)));
}
}
);
// Reload page
server.tool(
'reload',
'Reload the current page',
reloadSchema.shape,
async ({ waitUntil, timeout, tabId }) => {
const pageResult = await getPageForOperation(tabId);
if (!pageResult.success) {
return handleResult(pageResult);
}
const page = pageResult.data;
try {
await page.reload({
waitUntil: (waitUntil ?? 'load') as WaitUntilOption,
timeout: timeout ?? getDefaultTimeout(),
});
return handleResult(ok({
url: page.url(),
title: await page.title(),
}));
} catch (error) {
return handleResult(err(normalizeError(error)));
}
}
);
// Go back
server.tool(
'go_back',
'Navigate back in browser history',
goBackSchema.shape,
async ({ waitUntil, timeout, tabId }) => {
const pageResult = await getPageForOperation(tabId);
if (!pageResult.success) {
return handleResult(pageResult);
}
const page = pageResult.data;
try {
const response = await page.goBack({
waitUntil: (waitUntil ?? 'load') as WaitUntilOption,
timeout: timeout ?? getDefaultTimeout(),
});
return handleResult(ok({
url: page.url(),
title: await page.title(),
navigated: response !== null,
}));
} catch (error) {
return handleResult(err(normalizeError(error)));
}
}
);
// Go forward
server.tool(
'go_forward',
'Navigate forward in browser history',
goForwardSchema.shape,
async ({ waitUntil, timeout, tabId }) => {
const pageResult = await getPageForOperation(tabId);
if (!pageResult.success) {
return handleResult(pageResult);
}
const page = pageResult.data;
try {
const response = await page.goForward({
waitUntil: (waitUntil ?? 'load') as WaitUntilOption,
timeout: timeout ?? getDefaultTimeout(),
});
return handleResult(ok({
url: page.url(),
title: await page.title(),
navigated: response !== null,
}));
} catch (error) {
return handleResult(err(normalizeError(error)));
}
}
);
}