import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import type { KeyInput } from 'puppeteer';
import { keyboardSchema, mouseSchema, scrollSchema } from '../schemas.js';
import { getPageForOperation } from '../tabs.js';
import {
handleResult,
ok,
err,
selectorNotFound,
normalizeError,
} from '../errors.js';
import type { KeyModifier, MouseButton, MouseAction, ScrollDirection } from '../types.js';
/**
* Register input tools
*/
export function registerInputTools(server: McpServer): void {
// Keyboard input
server.tool(
'keyboard',
'Press a key or key combination',
keyboardSchema.shape,
async ({ key, modifiers, tabId }) => {
const pageResult = await getPageForOperation(tabId);
if (!pageResult.success) {
return handleResult(pageResult);
}
const page = pageResult.data;
const mods = (modifiers ?? []) as KeyModifier[];
try {
// Press modifier keys
for (const mod of mods) {
await page.keyboard.down(mod);
}
// Press the main key
await page.keyboard.press(key as KeyInput);
// Release modifier keys
for (const mod of mods.reverse()) {
await page.keyboard.up(mod);
}
return handleResult(ok({
pressed: true,
key,
modifiers: mods,
}));
} catch (error) {
return handleResult(err(normalizeError(error)));
}
}
);
// Mouse input
server.tool(
'mouse',
'Perform mouse actions at specific coordinates',
mouseSchema.shape,
async ({ x, y, button, action, tabId }) => {
const pageResult = await getPageForOperation(tabId);
if (!pageResult.success) {
return handleResult(pageResult);
}
const page = pageResult.data;
const mouseButton = (button ?? 'left') as MouseButton;
const mouseAction = (action ?? 'click') as MouseAction;
try {
switch (mouseAction) {
case 'move':
await page.mouse.move(x, y);
break;
case 'click':
await page.mouse.click(x, y, { button: mouseButton });
break;
case 'down':
await page.mouse.move(x, y);
await page.mouse.down({ button: mouseButton });
break;
case 'up':
await page.mouse.move(x, y);
await page.mouse.up({ button: mouseButton });
break;
}
return handleResult(ok({
action: mouseAction,
x,
y,
button: mouseButton,
}));
} catch (error) {
return handleResult(err(normalizeError(error)));
}
}
);
// Scroll
server.tool(
'scroll',
'Scroll the page or a specific element',
scrollSchema.shape,
async ({ direction, amount, selector, smooth, tabId }) => {
const pageResult = await getPageForOperation(tabId);
if (!pageResult.success) {
return handleResult(pageResult);
}
const page = pageResult.data;
const scrollDirection = (direction ?? 'down') as ScrollDirection;
const scrollAmount = amount ?? 100;
const useSmooth = smooth ?? true;
try {
// Calculate scroll deltas
let deltaX = 0;
let deltaY = 0;
switch (scrollDirection) {
case 'up':
deltaY = -scrollAmount;
break;
case 'down':
deltaY = scrollAmount;
break;
case 'left':
deltaX = -scrollAmount;
break;
case 'right':
deltaX = scrollAmount;
break;
}
if (selector) {
// Scroll specific element
const element = await page.$(selector);
if (!element) {
return handleResult(err(selectorNotFound(selector)));
}
await element.evaluate(
(el, dx, dy, smoothScroll) => {
el.scrollBy({
left: dx,
top: dy,
behavior: smoothScroll ? 'smooth' : 'auto',
});
},
deltaX,
deltaY,
useSmooth
);
} else {
// Scroll page
await page.evaluate(
(dx, dy, smoothScroll) => {
window.scrollBy({
left: dx,
top: dy,
behavior: smoothScroll ? 'smooth' : 'auto',
});
},
deltaX,
deltaY,
useSmooth
);
}
return handleResult(ok({
scrolled: true,
direction: scrollDirection,
amount: scrollAmount,
selector,
}));
} catch (error) {
return handleResult(err(normalizeError(error)));
}
}
);
}