Skip to content
Open
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
27 changes: 18 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,32 +255,41 @@ Many other MCP-capable tools accept:

Configure these values wherever the tool expects MCP server settings.

## Tools (10 total)
## Tools (15 total)

Each Kernel feature has a single `manage_*` tool with an `action` parameter, keeping the tool set small and consistent. Four standalone tools handle high-frequency workflows.
Each Kernel feature has a single `manage_*` tool with an `action` parameter, keeping the tool set small and consistent. Seven standalone tools handle high-frequency workflows.

### manage\_\* tools

- `manage_browsers` - Create, list, get, and delete browser sessions. Supports headless/stealth modes, profiles, proxies, viewports, extensions, and SSH tunneling.
- `manage_profiles` - Setup (with guided live browser session), list, and delete browser profiles for persisting cookies and logins.
- `manage_profiles` - Setup (with guided live browser session), search/list with pagination, get, and delete browser profiles for persisting cookies and logins.
- `manage_projects` - Create, list, get, update, and delete organization projects. Inspect and update per-project resource limits.
- `manage_api_keys` - Create, list, get, update, and delete org-wide or project-scoped API keys.
- `manage_browser_pools` - Create, list, get, delete, and flush pools of pre-warmed browsers. Acquire and release browsers from pools.
- `manage_proxies` - Create, list, and delete proxy configurations (datacenter, ISP, residential, mobile, custom).
- `manage_proxies` - Create, list, get, check, and delete proxy configurations (datacenter, ISP, residential, mobile, custom).
- `manage_extensions` - List and delete uploaded browser extensions.
- `manage_apps` - List apps, invoke actions, get/list deployments, and get invocation results.
- `manage_apps` - List/search apps, invoke actions, get/list/delete deployments, and get invocation results.

### Standalone tools

- `browser_curl` - Run an HTTP request from inside a browser session's network context.
- `read_browser_clipboard` - Read clipboard text from a browser session.
- `write_browser_clipboard` - Write clipboard text to a browser session.
- `computer_action` - Mouse, keyboard, and screenshot controls for browser sessions (click, type, press_key, scroll, move, get_position, screenshot).
- `execute_playwright_code` - Execute Playwright/TypeScript code against a browser with automatic video replay and cleanup.
- `exec_command` - Run shell commands inside a browser VM. Returns decoded stdout/stderr.
- `search_docs` - Search Kernel platform documentation and guides.

## Resources

- `browsers://` - Access browser sessions (list all or get specific session)
- `browser_pools://` - Access browser pools (list all or get specific pool)
- `profiles://` - Access browser profiles (list all or get specific profile)
- `apps://` - Access deployed apps (list all or get specific app)
- `browsers://` - List browser sessions
- `browser-pools://` - List browser pools
- `profiles://` - List browser profiles
- `apps://` - List deployed apps
- `browsers://{session_id}` - Access one browser session
- `browser-pools://{id_or_name}` - Access one browser pool
- `profiles://{profile_name}` - Access one browser profile
- `apps://{app_name}` - Access one deployed app

## Prompts

Expand Down
114 changes: 114 additions & 0 deletions src/lib/mcp/browser-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
export type BrowserProfileParams = {
profile_name?: string;
profile_id?: string;
save_profile_changes?: boolean;
};

export type BrowserExtensionParams = {
extension_id?: string;
extension_name?: string;
};

export type BrowserViewportParams = {
viewport_width?: number;
viewport_height?: number;
viewport_refresh_rate?: number;
};

export type BrowserViewportUpdateParams = BrowserViewportParams & {
viewport_force?: boolean;
};

export function buildBrowserStartUrl(startUrl: string | undefined) {
if (startUrl === undefined) return undefined;

try {
new URL(startUrl);
} catch {
throw new Error("start_url must be a valid URL.");
}

return startUrl;
}

export function buildBrowserProfile(params: BrowserProfileParams) {
if (params.profile_name && params.profile_id) {
throw new Error("Cannot specify both profile_name and profile_id.");
}
if (
params.save_profile_changes !== undefined &&
!params.profile_name &&
!params.profile_id
) {
throw new Error(
"profile_name or profile_id is required when save_profile_changes is set.",
);
}
if (!params.profile_name && !params.profile_id) return undefined;
return {
...(params.profile_name && { name: params.profile_name }),
...(params.profile_id && { id: params.profile_id }),
...(params.save_profile_changes !== undefined && {
save_changes: params.save_profile_changes,
}),
};
}

export function buildBrowserExtensions(params: BrowserExtensionParams) {
if (params.extension_id && params.extension_name) {
throw new Error("Cannot specify both extension_id and extension_name.");
}
if (!params.extension_id && !params.extension_name) return undefined;
return [
{
...(params.extension_id && { id: params.extension_id }),
...(params.extension_name && { name: params.extension_name }),
},
];
}

export function buildBrowserViewport(params: BrowserViewportParams) {
const width = params.viewport_width;
const height = params.viewport_height;
const hasViewportOptions =
width !== undefined ||
height !== undefined ||
params.viewport_refresh_rate !== undefined;

if (!hasViewportOptions) return undefined;
if (width === undefined || height === undefined) {
throw new Error(
"viewport_width and viewport_height must be provided together.",
);
}

return {
width,
height,
...(params.viewport_refresh_rate !== undefined && {
refresh_rate: params.viewport_refresh_rate,
}),
};
}

export function buildBrowserViewportUpdate(
params: BrowserViewportUpdateParams,
) {
const viewport = buildBrowserViewport(params);

if (!viewport) {
if (params.viewport_force !== undefined) {
throw new Error(
"viewport_width and viewport_height must be provided when viewport_force is set.",
);
}
return undefined;
}

return {
...viewport,
...(params.viewport_force !== undefined && {
force: params.viewport_force,
}),
};
}
2 changes: 2 additions & 0 deletions src/lib/mcp/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { registerKernelPrompts } from "@/lib/mcp/prompts";
import { registerAPIKeyCapabilities } from "@/lib/mcp/tools/api-keys";
import { registerAppCapabilities } from "@/lib/mcp/tools/apps";
import { registerBrowserPoolCapabilities } from "@/lib/mcp/tools/browser-pools";
import { registerBrowserUtilityTools } from "@/lib/mcp/tools/browser-utilities";
import { registerBrowserCapabilities } from "@/lib/mcp/tools/browsers";
import { registerComputerActionTool } from "@/lib/mcp/tools/computer-action";
import { registerDocsTools } from "@/lib/mcp/tools/docs";
Expand All @@ -21,6 +22,7 @@ export function registerMcpCapabilities(server: McpServer) {
registerProjectCapabilities(server);
registerAPIKeyCapabilities(server);
registerBrowserPoolCapabilities(server);
registerBrowserUtilityTools(server);
registerProxyTools(server);
registerExtensionTools(server);
registerAppCapabilities(server);
Expand Down
61 changes: 61 additions & 0 deletions src/lib/mcp/resource-templates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import {
ResourceTemplate,
type McpServer,
} from "@modelcontextprotocol/sdk/server/mcp.js";
import { createKernelClient, type KernelClient } from "@/lib/mcp/kernel-client";

type JsonResourceTemplateOptions = {
name: string;
uriTemplate: string;
variableName: string;
resourceLabel: string;
read: (
client: KernelClient,
identifier: string,
) => Promise<unknown | null | undefined>;
};

function templateVariableValue(
variables: Record<string, string | string[]>,
name: string,
) {
const value = variables[name];
return Array.isArray(value) ? value[0] : value;
}

export function registerJsonResourceTemplate(
server: McpServer,
options: JsonResourceTemplateOptions,
) {
server.resource(
options.name,
new ResourceTemplate(options.uriTemplate, { list: undefined }),
async (uri, variables, extra) => {
if (!extra.authInfo) {
throw new Error("Authentication required");
}

const identifier = templateVariableValue(variables, options.variableName);
if (!identifier) {
throw new Error(`Invalid ${options.resourceLabel} URI: ${uri}`);
}

const client = createKernelClient(extra.authInfo.token);
const resource = await options.read(client, identifier);

if (!resource) {
throw new Error(`${options.resourceLabel} "${identifier}" not found`);
}

return {
contents: [
{
uri: uri.toString(),
mimeType: "application/json",
text: JSON.stringify(resource, null, 2),
},
],
};
},
);
}
37 changes: 37 additions & 0 deletions src/lib/mcp/responses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export function textResponse(text: string) {
return { content: [{ type: "text" as const, text }] };
}

export function jsonResponse(value: unknown) {
return textResponse(JSON.stringify(value, null, 2));
}

export function jsonListResponse<T>(
items: readonly T[] | null | undefined,
emptyText: string,
) {
return items && items.length > 0
? jsonResponse(items)
: textResponse(emptyText);
}

export function paginatedJsonResponse<T>(
page: {
getPaginatedItems(): T[];
has_more?: boolean | null;
next_offset?: number | null;
},
emptyText?: string,
) {
const items = page.getPaginatedItems();
if (items.length === 0 && emptyText) return textResponse(emptyText);
return jsonResponse({
items,
has_more: page.has_more,
next_offset: page.next_offset,
});
}

export function errorMessage(error: unknown) {
return error instanceof Error ? error.message : String(error);
}
Loading