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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ Call `get_connection_context` before deciding whether to create or select a proj

### manage\_\* tools

- `manage_browsers` - Create, update, list, get, and delete browser sessions, and read archived telemetry for active or deleted sessions. Supports headless/stealth modes, profiles, proxies, viewports, extensions, and SSH tunneling.
- `manage_browsers` - Create, update, list, get, and delete browser sessions, and read archived telemetry for active or deleted sessions. Supports headless/stealth modes, profiles, proxies, viewports, extensions, names and tags, and SSH tunneling. The browser tools (`manage_browsers`, `computer_action`, `execute_playwright_code`, `execute_shell_command`, `browser_curl`, `manage_replays`) accept a live session's name in place of its `session_id`; deleted sessions, and `manage_browser_pools` release, take the ID only.
- `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. Create returns the plaintext key once.
Expand Down Expand Up @@ -332,7 +332,7 @@ Call `get_connection_context` before deciding whether to create or select a proj

Project resources use the prefix `kernel://orgs/{organization_id}/projects/{project_id}`.

- `/browsers` and `/browsers/{session_id}` - List or access browser sessions
- `/browsers` and `/browsers/{session_id}` - List or access browser sessions (`{session_id}` may be a live session's ID or name)
- `/browser-pools` and `/browser-pools/{id_or_name}` - List or access browser pools
- `/profiles` and `/profiles/{profile_name}` - List or access browser profiles
- `/apps` and `/apps/{app_name}` - List or access deployed apps
Expand Down
6 changes: 3 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"@clerk/themes": "^2.4.19",
"@modelcontextprotocol/sdk": "1.26.0",
"@onkernel/managed-auth-react": "0.5.1",
"@onkernel/sdk": "^0.97.0",
"@onkernel/sdk": "^0.98.0",
"@posthog/mcp": "0.10.1",
"@types/jsonwebtoken": "^9.0.10",
"@types/redis": "^4.0.11",
Expand Down
4 changes: 2 additions & 2 deletions src/lib/mcp/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ Production-ready platform for deploying and hosting browser automation code. Han
session_id: z
.string()
.describe(
"The browser session ID to debug (e.g., 'abc123example456xyz')",
"The browser session ID or name to debug (e.g., 'abc123example456xyz' or 'checkout-flow'). A name resolves only a live session; if the session was deleted, pass its ID so telemetry can still be read.",
),
issue_description: z
.string()
Expand Down Expand Up @@ -135,7 +135,7 @@ kernel browsers playwright --help

## Telemetry Events (structured signal — works even after the session is deleted)

When telemetry was captured, it's usually the fastest way to pinpoint a failure — read it before reaching for screenshots or logs. If the session has been deleted, it's the only signal still available: every CLI command in this guide needs a live session.
When telemetry was captured, it's usually the fastest way to pinpoint a failure — read it before reaching for screenshots or logs. If the session has been deleted, it's the only signal still available: every CLI command in this guide needs a live session. A deleted session must be addressed by its ID; its name no longer resolves.

Start broad: call \`manage_browsers\` with action "get_telemetry", session_id "${session_id}", and no filters. That starts at session creation and returns the first page (up to 100 events); page with \`next_offset\` as \`offset\` while \`has_more\` is true, preserving \`categories\`, \`until\`, and \`order\`. An empty unfiltered read is definitive: nothing was archived. Narrow when the output is too large to scan or you already know where to look: \`categories\` to isolate a signal you've spotted, \`order\` "desc" when the end of the session matters most, \`since\`/\`until\` to bracket a known failing step. Correlate event timestamps with the failing automation step.

Expand Down
2 changes: 1 addition & 1 deletion src/lib/mcp/tools/browser-curl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function registerBrowserCurlTool(server: McpServer) {
"Send an HTTP request through an existing Kernel browser session's Chrome network stack. Use when the request needs that browser session's cookies, proxy, network context, or origin behavior; do not use for general documentation lookup or web search.",
{
...projectSelectionInputSchema(),
session_id: z.string().describe("Browser session ID."),
session_id: z.string().describe("Browser session ID or name."),
url: z.string().url().describe("Target http or https URL."),
method: z
.enum(["GET", "HEAD", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"])
Expand Down
4 changes: 3 additions & 1 deletion src/lib/mcp/tools/browser-pools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,9 @@ export function registerBrowserPoolCapabilities(server: McpServer) {
.optional(),
session_id: z
.string()
.describe("(release) Session ID of browser to release.")
.describe(
"(release) Session ID of the browser to release. Must be the ID, not the session name.",
)
.optional(),
reuse: z
.boolean()
Expand Down
130 changes: 130 additions & 0 deletions src/lib/mcp/tools/browsers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,6 +393,136 @@ describe("manage_browsers region", () => {
});
});

describe("manage_browsers id or name", () => {
test("passes name and tags through create and update", async () => {
const createCalls: unknown[] = [];
const updateCalls: unknown[] = [];
const kernelClient = {
browsers: {
create: async (params: unknown) => {
createCalls.push(params);
return { session_id: "brr_123", name: "checkout-flow" };
},
update: async (idOrName: string, params: unknown) => {
updateCalls.push([idOrName, params]);
return { session_id: "brr_123" };
},
},
};
const { client, close } = await connectTestMcp(
registerBrowserCapabilities,
kernelClient,
);

try {
await client.callTool({
name: "manage_browsers",
arguments: {
action: "create",
name: "checkout-flow",
tags: { team: "payments" },
},
});
expect(createCalls).toEqual([
{ name: "checkout-flow", tags: { team: "payments" } },
]);

await client.callTool({
name: "manage_browsers",
arguments: {
action: "update",
session_id: "checkout-flow",
name: "checkout-flow-2",
tags: {},
},
});
expect(updateCalls).toEqual([
["checkout-flow", { name: "checkout-flow-2", tags: {} }],
]);

await client.callTool({
name: "manage_browsers",
arguments: { action: "update", session_id: "brr_123", name: "" },
});
expect(updateCalls[1]).toEqual(["brr_123", { name: "" }]);
} finally {
await close();
}
});

test("passes query and tags filters to list", async () => {
const listCalls: unknown[] = [];
const kernelClient = {
browsers: {
list: async (params: unknown) => {
listCalls.push(params);
return {
getPaginatedItems: () => [],
has_more: false,
next_offset: null,
};
},
},
};
const { client, close } = await connectTestMcp(
registerBrowserCapabilities,
kernelClient,
);

try {
await client.callTool({
name: "manage_browsers",
arguments: {
action: "list",
query: "checkout",
tags: { team: "payments" },
},
});
expect(listCalls).toEqual([
{ query: "checkout", tags: { team: "payments" } },
]);
} finally {
await close();
}
});

test("forwards a session name unchanged to get and delete", async () => {
const seen: string[] = [];
const kernelClient = {
browsers: {
retrieve: async (idOrName: string) => {
seen.push(`get:${idOrName}`);
return { session_id: "brr_123", name: idOrName };
},
deleteByID: async (idOrName: string) => {
seen.push(`delete:${idOrName}`);
},
},
};
const { client, close } = await connectTestMcp(
registerBrowserCapabilities,
kernelClient,
);

try {
const got = toolResultJSON(
await client.callTool({
name: "manage_browsers",
arguments: { action: "get", session_id: "checkout-flow" },
}),
);
expect(got.session_id).toBe("brr_123");
await client.callTool({
name: "manage_browsers",
arguments: { action: "delete", session_id: "checkout-flow" },
});
expect(seen).toEqual(["get:checkout-flow", "delete:checkout-flow"]);
} finally {
await close();
}
});
});

describe("browser resources", () => {
test("uses injected dependencies", async () => {
const kernelClient = {
Expand Down
28 changes: 26 additions & 2 deletions src/lib/mcp/tools/browsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ export function registerBrowserCapabilities(
// manage_browsers -- Manage browser sessions and read archived telemetry
server.tool(
"manage_browsers",
'Manage browser sessions and their archived telemetry. Use "list" to choose an existing session, "create" before browser control, "update" to change supported session settings, "get" for full details, "get_telemetry" to diagnose active or deleted sessions, and "delete" when finished. get_telemetry compacts events by default; set compact=false with explicit categories and a limit of at most 5 when raw headers, request data, response bodies, or other omitted fields are needed.',
'Manage browser sessions and their archived telemetry. Use "list" to choose an existing session, "create" before browser control, "update" to change supported session settings, "get" for full details, "get_telemetry" to diagnose active or deleted sessions, and "delete" when finished. Live sessions can be addressed by ID or by the name given at creation or set on update; deleted sessions only by ID. get_telemetry compacts events by default; set compact=false with explicit categories and a limit of at most 5 when raw headers, request data, response bodies, or other omitted fields are needed.',
{
...projectSelectionInputSchema(),
action: z
Expand All @@ -442,7 +442,25 @@ export function registerBrowserCapabilities(
session_id: z
.string()
.describe(
"Browser session ID. Required for update, get, get_telemetry, and delete actions.",
"Browser session ID or name. Required for update, get, get_telemetry, and delete actions. A name resolves only a live session; for a deleted session (get_telemetry) pass its ID.",
)
.optional(),
name: z
.string()
.describe(
"(create, update) Human-readable session name, unique among active sessions in the project. 1-255 chars of letters, digits, '.', '_' or '-', and not a cuid-like ID. While the session is live it can be passed as session_id to the browser tools (manage_browsers, computer_action, execute_playwright_code, execute_shell_command, browser_curl, manage_replays). On update, an empty string clears the name.",
)
.optional(),
tags: z
.record(z.string())
.describe(
"(create, update) Key-value tags for grouping sessions. Up to 50 pairs. On update, an empty object clears all tags. (list) Return only sessions carrying all of these tags.",
)
.optional(),
query: z
.string()
.describe(
"(list) Text filter matched against session name, session ID, profile name or ID, proxy ID, or pool name.",
)
.optional(),
start_url: z
Expand Down Expand Up @@ -679,6 +697,8 @@ export function registerBrowserCapabilities(
createParams.chrome_policy = params.chrome_policy;
}
if (params.proxy_id) createParams.proxy_id = params.proxy_id;
if (params.name !== undefined) createParams.name = params.name;
if (params.tags !== undefined) createParams.tags = params.tags;
const browserConfig = buildBrowserCreateConfig(params);
if (!browserConfig.ok) return errorResponse(browserConfig.error);
Object.assign(createParams, browserConfig.value);
Expand Down Expand Up @@ -723,6 +743,8 @@ export function registerBrowserCapabilities(
} else if (params.proxy_id !== undefined) {
updateParams.proxy_id = params.proxy_id;
}
if (params.name !== undefined) updateParams.name = params.name;
if (params.tags !== undefined) updateParams.tags = params.tags;
const browserConfig = buildBrowserUpdateConfig(params);
if (!browserConfig.ok) return errorResponse(browserConfig.error);
Object.assign(updateParams, browserConfig.value);
Expand Down Expand Up @@ -752,6 +774,8 @@ export function registerBrowserCapabilities(
const page = await client.browsers.list({
...(params.status && { status: params.status }),
...(params.region && { region: params.region }),
...(params.query && { query: params.query }),
...(params.tags !== undefined && { tags: params.tags }),
...(params.limit !== undefined && { limit: params.limit }),
...(params.offset !== undefined && { offset: params.offset }),
});
Expand Down
2 changes: 1 addition & 1 deletion src/lib/mcp/tools/computer-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ export function registerComputerActionTool(server: McpServer) {
"Execute computer actions on a browser session. Pass a single action for simple operations (e.g. one click or one screenshot), or pass multiple actions to batch them into a single request for lower latency (e.g. click, type, press_key in one call). Use sleep actions between steps when the page needs time to react (e.g. after a click that triggers navigation or animation). IMPORTANT: Always include a screenshot as the last action so you can see the result of your actions. Action types: click_mouse, move_mouse, type_text, press_key, scroll, drag_mouse, set_cursor, sleep, write_clipboard, read_clipboard, screenshot, get_mouse_position. screenshot, read_clipboard, and get_mouse_position return data, so they must be the last action if included.",
{
...projectSelectionInputSchema(),
session_id: z.string().describe("Browser session ID."),
session_id: z.string().describe("Browser session ID or name."),
actions: z
.array(computerActionSchema)
.min(1)
Expand Down
2 changes: 1 addition & 1 deletion src/lib/mcp/tools/playwright.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function registerPlaywrightTool(
session_id: z
.string()
.min(1, "session_id is required")
.describe("Browser session ID to execute the code against."),
.describe("Browser session ID or name to execute the code against."),
},
{
title: "Execute Playwright code",
Expand Down
4 changes: 2 additions & 2 deletions src/lib/mcp/tools/replays.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export function registerReplayTools(server: McpServer) {
action: z
.enum(["start", "stop", "list"])
.describe("Operation to perform."),
session_id: z.string().describe("Browser session ID."),
session_id: z.string().describe("Browser session ID or name."),
replay_id: z.string().describe("(stop) Replay ID to stop.").optional(),
framerate: z
.number()
Expand Down Expand Up @@ -84,7 +84,7 @@ export function registerReplayTools(server: McpServer) {
if (!params.replay_id)
return errorResponse("Error: replay_id is required for stop.");
await client.browsers.replays.stop(params.replay_id, {
id: params.session_id,
id_or_name: params.session_id,
});
return textResponse("Replay stopped successfully");
}
Expand Down
2 changes: 1 addition & 1 deletion src/lib/mcp/tools/shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ export function registerShellTool(
'Execute a command synchronously inside a browser VM. Returns stdout, stderr, and exit code. The command field is the executable; use args for its arguments. Common uses: read files (command: "cat", args: ["/var/log/supervisord.log"]), list dirs (command: "ls", args: ["/var/log"]), check DNS (command: "cat", args: ["/etc/resolv.conf"]), test connectivity (command: "curl", args: ["-I", "https://example.com"]).',
{
...projectSelectionInputSchema(),
session_id: z.string().describe("Browser session ID."),
session_id: z.string().describe("Browser session ID or name."),
command: z
.string()
.describe("Executable to run (e.g., 'cat', 'ls', 'curl')."),
Expand Down
Loading