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
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,15 +320,17 @@ Self-hosted deployments can select tool families with `KERNEL_MCP_ENABLED_TOOLSE

Call `get_connection_context` before deciding whether to create or select a project. Its canonical `connection_scope` reports whether the connection is organization-wide or fixed to a project. Project-scoped tools advertise an optional `project` (name or ID) and a deprecated `project_id`: organization-wide connections may omit them to preserve organization-wide reads and API default-project behavior, while fixed-project connections may omit them or pass the matching project. Project resources use project-qualified `kernel://orgs/{organizationId}/projects/{projectId}/...` URIs. Authorization remains enforced by the Kernel API; selecting a project never grants access to it.

Inputs that mirror the API's nested `proxy`, `network`, `browser`, and proxy `config` objects replace older flat inputs such as `proxy_id`, `proxy_routes`, `browser_region`, and `custom_host`. The flat inputs remain as deprecated aliases, and combining one with its nested replacement is rejected. Tool-call analytics records the names, never the values, of deprecated inputs each call used in `$mcp_deprecated_params`; the tracked list lives in `src/lib/mcp/deprecated-params.ts`.

### manage\_\* tools

- `manage_browsers` - Create, update, list, get, and delete browser sessions, and read archived telemetry for active or deleted sessions. Supports headless mode, site-compatibility settings, profiles, proxies, create-only per-host proxy routes (`proxy_routes`), viewports, extensions, names and tags, and SSH tunneling. The browser tools (`manage_browsers`, `computer_action`, `execute_playwright_code`, `browser_repl`, `exec_command`, `browser_curl`, `manage_replays`, `webmcp`) accept a live session's name in place of its `session_id`; deleted sessions, and `manage_browser_pools` release, take the ID only.
- `manage_browsers` - Create, update, list, get, and delete browser sessions, and read archived telemetry for active or deleted sessions. Supports headless mode, site-compatibility settings, profiles, proxies (`proxy` by id, name, or mode), create-only per-host proxy routes (`network.proxy_routes`) and private-host routing (`network.private_hosts`), viewports, extensions, names and tags, and SSH tunneling. The browser tools (`manage_browsers`, `computer_action`, `execute_playwright_code`, `browser_repl`, `exec_command`, `browser_curl`, `manage_replays`, `webmcp`) 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.
- `manage_browser_pools` - Create, list, get, delete, and flush pools of pre-warmed browsers. Acquire and release browsers from pools.
- `manage_config_registry` - Look up current browser and proxy recommendations, start and inspect analyses, request cancellation, and list project configurations or analysis history.
- `manage_proxies` - Create, list, get, check, and delete proxy configurations (datacenter, ISP, residential, mobile, custom).
- `manage_proxies` - Create, list, get, check, and delete proxy configurations (datacenter, ISP, residential, mobile, custom). List filters by exact `name` or a `query` substring.
- `manage_replays` - Start, stop, and list MP4 video replay recordings for a browser session. Session-scoped: start once, run your automation, then stop. Requires a paid Kernel plan.
- `web_search` - Search the web, retrieve retained results, and inspect provider capabilities. Tool visibility uses a per-credential, per-connection Search entitlement snapshot cached for up to 30 minutes; the Search API remains authoritative for execution access. Search creation is billable and is not automatically retried.
- `manage_extensions` - List and delete uploaded browser extensions.
Expand Down Expand Up @@ -443,7 +445,7 @@ Returns: the REPL ID, ordered text output, and screenshot. Later browser_repl ca

Example: “Log me into my Hacker News account and update my profile to add a random emoji at the bottom.” The agent should discover `news.ycombinator.com`, open the App when needed, wait for authentication, then continue the profile edit without asking for credentials or a profile name in chat.

The secure App defaults `record_session` and `browser_telemetry.enabled` to `true`, recording replay video plus the operational telemetry categories (`control`, `connection`, `system`, and `captcha`) for managed-auth browser sessions. Callers can explicitly disable either setting. Set `region` in `open_auth_login`, or `browser_region` in `manage_auth_connections`, to choose where a managed-auth browser runs. Create and update set the connection default; login and reauth overrides apply only to that flow. Omit the field on create to use `us-east`, or omit it on update and login to preserve or inherit the connection default. The programmatic `manage_auth_connections` create, update, and login actions pass browser telemetry through the API’s current nested `browser.telemetry` configuration while preserving defaults and inheritance when the MCP parameter is omitted.
The secure App defaults `record_session` and `browser.telemetry.enabled` to `true`, recording replay video plus the operational telemetry categories (`control`, `connection`, `system`, and `captcha`) for managed-auth browser sessions. Callers can explicitly disable either setting. Set `browser.region` in `open_auth_login` or `manage_auth_connections` to choose where a managed-auth browser runs, and `browser.proxy` to choose its proxy by id, name, or mode. Create and update set the connection default; login and reauth overrides apply only to that flow. Omit the field on create to use `us-east`, or omit it on update and login to preserve or inherit the connection default. The programmatic `manage_auth_connections` create, update, and login actions pass the `browser` object through to the API unchanged, preserving defaults and inheritance when it is omitted.

### Set up browser profiles for authentication

Expand Down
56 changes: 56 additions & 0 deletions src/lib/mcp/analytics.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
MCP_FEEDBACK_SUBMITTED_EVENT,
MCP_USED_PROJECT_ID_PROPERTY,
MCP_USED_PROJECT_PROPERTY,
MCP_DEPRECATED_PARAMS_PROPERTY,
OAUTH_TOKEN_EXCHANGE_EVENT,
sanitizeMcpAnalyticsEvent,
} from "@/lib/mcp/analytics";
Expand Down Expand Up @@ -513,6 +514,61 @@ describe("sanitizeMcpAnalyticsEvent", () => {
expect(result?.properties[MCP_USED_PROJECT_PROPERTY]).toBe(true);
});

test("lists deprecated parameter names a tool call used, without values", async () => {
const event = toolCallEvent({
[PostHogMCPAnalyticsProperty.ToolName]: "manage_browsers",
[PostHogMCPAnalyticsProperty.Parameters]: {
request: {
params: {
arguments: {
action: "update",
session_id: "brr_123",
proxy_id: "prx_secret",
clear_proxy: false,
},
},
},
},
});

const result = await sanitizeMcpAnalyticsEvent(event);

expect(result?.properties[MCP_DEPRECATED_PARAMS_PROPERTY]).toEqual([
"proxy_id",
"clear_proxy",
]);
expect(JSON.stringify(result)).not.toContain("prx_secret");
});

test("records an empty deprecated parameter list for current inputs only", async () => {
const event = toolCallEvent({
[PostHogMCPAnalyticsProperty.ToolName]: "manage_proxies",
[PostHogMCPAnalyticsProperty.Parameters]: {
request: {
params: {
arguments: { action: "create", type: "isp", config: {} },
},
},
},
});

const result = await sanitizeMcpAnalyticsEvent(event);

expect(result?.properties[MCP_DEPRECATED_PARAMS_PROPERTY]).toEqual([]);
});

test("omits the deprecated parameter list for tools without deprecated inputs", async () => {
const event = toolCallEvent({
[PostHogMCPAnalyticsProperty.ToolName]: "computer_action",
});

const result = await sanitizeMcpAnalyticsEvent(event);

expect(result?.properties).not.toHaveProperty(
MCP_DEPRECATED_PARAMS_PROPERTY,
);
});

test("records false/false when a tool call omits both project selectors", async () => {
const result = await sanitizeMcpAnalyticsEvent(toolCallEvent());

Expand Down
24 changes: 23 additions & 1 deletion src/lib/mcp/analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ import type {
McpConnectionContext,
} from "@/lib/mcp/auth-context";
import { MCP_INTENT_ARGUMENT_DESCRIPTION } from "@/lib/mcp/analytics-context";
import {
DEPRECATED_TOOL_PARAMS,
deprecatedParamsUsed,
} from "@/lib/mcp/deprecated-params";
import {
type KernelFeedback,
KERNEL_FEEDBACK_TOOL_NAME,
Expand Down Expand Up @@ -98,6 +102,7 @@ const posthog = projectToken

export const MCP_USED_PROJECT_ID_PROPERTY = "$mcp_used_project_id";
export const MCP_USED_PROJECT_PROPERTY = "$mcp_used_project";
export const MCP_DEPRECATED_PARAMS_PROPERTY = "$mcp_deprecated_params";
export const MCP_CLIENT_SUPPORTS_SAMPLING_PROPERTY =
"$mcp_client_supports_sampling";
export const MCP_CLIENT_SUPPORTS_SAMPLING_TOOLS_PROPERTY =
Expand Down Expand Up @@ -138,7 +143,8 @@ const CLIENT_EXTENSION_PROPERTIES = {
// property the pinned SDK doesn't emit today — a renamed payload field, a new one —
// can't start flowing on an upgrade. Deliberately absent: $mcp_parameters and
// $mcp_response (call payloads), and $mcp_error_message (the text a failed tool
// returned). $mcp_used_project_id / $mcp_used_project are presence flags only.
// returned). $mcp_used_project_id / $mcp_used_project are presence flags only, and
// $mcp_deprecated_params lists parameter names, never values.
const SENT_PROPERTIES = new Set<string>([
"$groups",
"$insert_id",
Expand All @@ -149,6 +155,7 @@ const SENT_PROPERTIES = new Set<string>([
"$mcp_scope_source",
MCP_USED_PROJECT_ID_PROPERTY,
MCP_USED_PROJECT_PROPERTY,
MCP_DEPRECATED_PARAMS_PROPERTY,
MCP_CLIENT_SUPPORTS_SAMPLING_PROPERTY,
MCP_CLIENT_SUPPORTS_SAMPLING_TOOLS_PROPERTY,
MCP_CLIENT_ELICITATION_MODE_PROPERTY,
Expand Down Expand Up @@ -332,6 +339,20 @@ function annotateProjectParamUsage(properties: Record<string, unknown>) {
properties[MCP_USED_PROJECT_PROPERTY] = hasNonEmptyParam(args, "project");
}

function annotateDeprecatedParamUsage(properties: Record<string, unknown>) {
const toolName = properties[PostHogMCPAnalyticsProperty.ToolName];
if (
typeof toolName !== "string" ||
!Object.prototype.hasOwnProperty.call(DEPRECATED_TOOL_PARAMS, toolName)
) {
return;
}
properties[MCP_DEPRECATED_PARAMS_PROPERTY] = deprecatedParamsUsed(
toolCallArguments(properties) ?? {},
DEPRECATED_TOOL_PARAMS[toolName as keyof typeof DEPRECATED_TOOL_PARAMS],
);
}

const IPV6_CANDIDATE_PATTERN =
/(?<![A-Za-z0-9:])(?:[A-Fa-f0-9]{0,4}:){2,}(?:[A-Fa-f0-9]{0,4}|(?:\d{1,3}\.){3}\d{1,3})(?:%[A-Za-z0-9_.-]+)?(?![A-Za-z0-9:.])/g;

Expand Down Expand Up @@ -373,6 +394,7 @@ export const sanitizeMcpAnalyticsEvent: BeforeSendFn = (event) => {
enrichMcpAnalyticsEvent(event);
if (event.event === PostHogMCPAnalyticsEvent.ToolCall) {
annotateProjectParamUsage(properties);
annotateDeprecatedParamUsage(properties);
const errorMessage = properties[PostHogMCPAnalyticsProperty.ErrorMessage];
if (
properties[PostHogMCPAnalyticsProperty.ToolName] ===
Expand Down
2 changes: 1 addition & 1 deletion src/lib/mcp/apps/generated/managed-auth-app.ts

Large diffs are not rendered by default.

23 changes: 1 addition & 22 deletions src/lib/mcp/apps/managed-auth-entry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ import { useManagedAuthAutofocus } from "./managed-auth-focus";
import {
initialManagedAuthFlowState,
managedAuthFlowReducer,
sanitizeBeginArguments,
waitArgumentsFromBegin,
} from "./managed-auth-flow";
import { ManagedAuthHostBridge } from "./managed-auth-host";
import type {
BeginResult,
JsonObject,
LauncherResult,
WaitToolResult,
} from "./managed-auth-types";
Expand All @@ -30,27 +30,6 @@ const FAILURE_CONTEXT =
"Managed authentication stopped. Verify its terminal state and report the recovery option; do not continue the protected action.";
const host = new ManagedAuthHostBridge();

function sanitizeBeginArguments(input: JsonObject): JsonObject {
const allowed = [
"mode",
"connection_id",
"domain",
"profile_name",
"project_id",
"save_credentials",
"record_session",
"browser_telemetry",
"region",
"proxy_id",
"proxy_name",
];
return Object.fromEntries(
allowed
.filter((key) => input[key] !== undefined)
.map((key) => [key, input[key]]),
);
}

function ManagedAuthApp() {
const launcher = useSyncExternalStore(
host.subscribe,
Expand Down
16 changes: 16 additions & 0 deletions src/lib/mcp/apps/managed-auth-flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
import {
initialManagedAuthFlowState,
managedAuthFlowReducer,
sanitizeBeginArguments,
waitArgumentsFromBegin,
} from "./managed-auth-flow";
import type { BeginResult, SafeConnection } from "./managed-auth-types";
Expand Down Expand Up @@ -126,3 +127,18 @@ describe("managed-auth App flow reducer", () => {
expect(complete.outcome).toBe("success");
});
});

describe("sanitizeBeginArguments", () => {
test("forwards launcher login arguments and drops everything else", () => {
const args = {
mode: "new_login",
domain: "example.com",
profile_name: "work",
project: "billing",
browser: { proxy: { name: "residential" }, stealth: false },
};
expect(
sanitizeBeginArguments({ ...args, intent: "log in", password: "x" }),
).toEqual(args);
});
});
23 changes: 23 additions & 0 deletions src/lib/mcp/apps/managed-auth-flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,3 +135,26 @@ export function waitArgumentsFromBegin(
}
return action.arguments;
}

export function sanitizeBeginArguments(input: JsonObject): JsonObject {
const allowed = [
"mode",
"connection_id",
"domain",
"profile_name",
"project",
"project_id",
"save_credentials",
"record_session",
"browser",
"browser_telemetry",
"region",
"proxy_id",
"proxy_name",
];
return Object.fromEntries(
allowed
.filter((key) => input[key] !== undefined)
.map((key) => [key, input[key]]),
);
}
37 changes: 37 additions & 0 deletions src/lib/mcp/deprecated-params.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { describe, expect, test } from "bun:test";
import { DEPRECATED_TOOL_PARAMS } from "@/lib/mcp/deprecated-params";
import { connectTestMcp } from "@/lib/mcp/mcp-test-fixtures";
import { registerMcpCapabilities } from "@/lib/mcp/register";

describe("deprecated tool params", () => {
test("tracked tools advertise each deprecated param without schema references", async () => {
const mcp = await connectTestMcp((server) => {
registerMcpCapabilities(server, {
mcpApps: true,
vaults: true,
search: true,
});
}, {});
try {
const { tools } = await mcp.client.listTools();
for (const [toolName, params] of Object.entries(DEPRECATED_TOOL_PARAMS)) {
const properties = tools.find((tool) => tool.name === toolName)
?.inputSchema.properties as
| Record<string, { description?: string }>
| undefined;
expect(
JSON.stringify(properties),
`${toolName} input schema`,
).not.toContain('"$ref"');
for (const param of params) {
expect(
properties?.[param]?.description,
`${toolName}.${param}`,
).toStartWith("deprecated: ");
}
}
} finally {
await mcp.close();
}
});
});
55 changes: 55 additions & 0 deletions src/lib/mcp/deprecated-params.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Flat inputs kept as aliases for the nested fields that mirror the KERNEL api.
// Tool-call analytics records which of these each call used, so they can be
// removed once usage drops off.
const AUTH_LOGIN_DEPRECATED_PARAMS = [
"proxy_id",
"proxy_name",
"region",
"browser_telemetry",
] as const;

export const DEPRECATED_TOOL_PARAMS = {
manage_browsers: [
"proxy_id",
"clear_proxy",
"disable_default_proxy",
"proxy_routes",
],
manage_auth_connections: [
"proxy_id",
"proxy_name",
"proxy_mode",
"browser_region",
"browser_stealth",
"browser_telemetry",
],
open_auth_login: AUTH_LOGIN_DEPRECATED_PARAMS,
begin_auth_login: AUTH_LOGIN_DEPRECATED_PARAMS,
manage_proxies: [
"country",
"city",
"state",
"custom_host",
"custom_port",
"custom_username",
"custom_password",
],
} as const satisfies Record<string, readonly string[]>;

export function deprecatedParamsUsed(
params: Record<string, unknown>,
deprecated: readonly string[],
): string[] {
return deprecated.filter((key) => params[key] !== undefined);
}

export function deprecatedParamConflict(
field: string,
params: Record<string, unknown>,
deprecated: readonly string[],
): string | undefined {
const used = deprecatedParamsUsed(params, deprecated);
return used.length > 0
? `${field} cannot be combined with ${used.join(", ")}.`
: undefined;
}
4 changes: 4 additions & 0 deletions src/lib/mcp/prompts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ const API_FIELD_NAMES = new Set([
"manage_browsers:captcha",
"manage_browser_pools:stealth",
"manage_auth_connections:browser_stealth",
"manage_auth_connections:stealth",
"open_auth_login:stealth",
"begin_auth_login:stealth",
"manage_proxies:bypass_hosts",
"manage_auth_connections:captcha",
"open_auth_login:captcha",
"begin_auth_login:captcha",
Expand Down
Loading
Loading