/**
* Built-in MCP tools for user event tracking
* These tools are automatically registered when the MCP server initializes
*/
import { queryEvents } from '../shared/database';
import {
toolRegistry,
type ToolDefinition,
type ToolHandler,
} from './tool-registry';
import type { TabManager } from './tab-manager';
import {
isGetUserEventsArgs,
isGetNavigationHistoryArgs,
isGetClickEventsArgs,
} from './validation';
// Built-in tool definitions and handlers
const builtInTools: Array<{
definition: ToolDefinition;
handler: ToolHandler;
}> = [
// get_user_events tool
{
definition: {
name: 'get_user_events',
description: 'Get user activity events (navigation, clicks, etc.)',
inputSchema: {
type: 'object',
properties: {
type: {
type: 'string',
enum: ['navigation', 'click', 'input', 'custom'],
description: 'Filter by event type',
},
startTime: {
type: 'number',
description: 'Start timestamp (Unix timestamp in milliseconds)',
},
endTime: {
type: 'number',
description: 'End timestamp (Unix timestamp in milliseconds)',
},
path: {
type: 'string',
description: 'Filter by path/URL',
},
limit: {
type: 'number',
description: 'Maximum number of events to return',
default: 100,
},
},
},
},
handler: async (args: unknown) => {
if (!isGetUserEventsArgs(args)) {
throw new Error('Invalid arguments for get_user_events');
}
const { type, startTime, endTime, path, limit = 100 } = args || {};
const events = await queryEvents({
type,
startTime,
endTime,
path,
limit,
});
return {
content: [
{
type: 'text',
text: JSON.stringify({ events }, null, 2),
},
],
};
},
},
// get_navigation_history tool
{
definition: {
name: 'get_navigation_history',
description: 'Get user navigation history',
inputSchema: {
type: 'object',
properties: {
limit: {
type: 'number',
description: 'Maximum number of navigation events to return',
default: 50,
},
},
},
},
handler: async (args: unknown) => {
if (!isGetNavigationHistoryArgs(args)) {
throw new Error('Invalid arguments for get_navigation_history');
}
const { limit = 50 } = args || {};
const events = await queryEvents({
type: 'navigation',
limit,
});
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
navigationHistory: events.map((e) => ({
from: e.from,
to: e.to,
path: e.path,
timestamp: e.timestamp,
})),
},
null,
2,
),
},
],
};
},
},
// get_click_events tool
{
definition: {
name: 'get_click_events',
description: 'Get user click events',
inputSchema: {
type: 'object',
properties: {
element: {
type: 'string',
description: 'Filter by element selector or text',
},
limit: {
type: 'number',
description: 'Maximum number of click events to return',
default: 100,
},
},
},
},
handler: async (args: unknown) => {
if (!isGetClickEventsArgs(args)) {
throw new Error('Invalid arguments for get_click_events');
}
const { element, limit = 100 } = args || {};
const events = await queryEvents({
type: 'click',
limit,
});
let filteredEvents = events;
if (element) {
const elementFilter = element.toLowerCase();
filteredEvents = events.filter(
(e) =>
e.element?.toLowerCase().includes(elementFilter) ||
e.elementText?.toLowerCase().includes(elementFilter) ||
e.elementId?.toLowerCase().includes(elementFilter) ||
e.elementClass?.toLowerCase().includes(elementFilter),
);
}
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
clickEvents: filteredEvents.map((e) => ({
element: e.element,
elementId: e.elementId,
elementClass: e.elementClass,
elementText: e.elementText,
path: e.path,
timestamp: e.timestamp,
metadata: e.metadata,
})),
},
null,
2,
),
},
],
};
},
},
];
/**
* Register event tracking built-in tools with the tool registry
* Called once when MCP server is initialized
*/
export function registerBuiltInTools(): void {
// Register event tracking tools
builtInTools.forEach(({ definition, handler }) => {
toolRegistry.register(definition, handler);
});
}
/**
* Register tab management tool with the tool registry
* Called by MCPController with its TabManager instance
* @param tabManager - TabManager instance for multi-tab routing
*/
export function registerTabManagementTool(tabManager: TabManager): void {
toolRegistry.register(
{
name: 'list_browser_tabs',
description:
'List all active browser tabs running this application. Returns tab IDs, URLs, titles, and active status. Use this to discover available tabs before calling tools with specific tabId parameters.',
inputSchema: {
type: 'object',
properties: {},
},
},
async () => {
const tabs = tabManager.getAllTabs().map((tab) => ({
...tab,
isActive: tab.tabId === tabManager.getActiveTabId(),
lastSeen: new Date(tab.lastSeen).toISOString(),
}));
return {
content: [
{
type: 'text',
text: JSON.stringify(tabs, null, 2),
},
],
};
},
);
}