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
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,13 +264,13 @@ Self-hosted deployments can hide sensitive tool families by setting `KERNEL_MCP_
### 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. 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_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_projects` - Create, list, get, update, and delete organization projects.
- `manage_api_keys` - Create, list, get, update, and delete Kernel API keys. Create returns the plaintext key once.
- `manage_apps` - List/search apps, invoke actions, get/list/delete deployments, and get invocation results.
- `manage_auth_connections` - Create, list, get, delete managed auth connections; start login flows (returns a hosted URL and live view); submit MFA codes or SSO selections.
- `manage_credentials` - Create, list, get, update, and delete stored credentials; fetch a current TOTP code for credentials with a configured totp_secret.
- `manage_credential_providers` - Create, list, get, update, and delete external credential providers (e.g. 1Password); list available items and test the provider connection.
Expand Down
26 changes: 23 additions & 3 deletions src/lib/mcp/tools/apps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,15 @@ export function registerAppCapabilities(server: McpServer) {
// manage_apps -- List apps, invoke actions, manage deployments, check invocations
server.tool(
"manage_apps",
'Manage Kernel apps when an agent needs to discover deployed app actions, invoke an app, or inspect deployment/invocation state. Use "list_apps" before invoking an unknown app, "invoke" to run an action, and get/list actions to inspect results.',
'Manage Kernel apps when an agent needs to discover deployed app actions, invoke an app, or inspect deployment/invocation state. Use "list_apps" before invoking an unknown app, "invoke" to run an action, get/list actions to inspect results, and "delete_deployment" to remove a deployment.',
{
action: z
.enum([
"list_apps",
"invoke",
"get_deployment",
"list_deployments",
"delete_deployment",
"get_invocation",
])
.describe("Operation to perform."),
Expand All @@ -66,9 +67,10 @@ export function registerAppCapabilities(server: McpServer) {
version: z
.string()
.describe(
"(list_apps, invoke) App version filter. Defaults to 'latest' for invoke.",
"(list_apps, invoke, list_deployments) App version filter. Defaults to 'latest' for invoke. Deployment version filtering requires app_name.",
)
.optional(),
query: z.string().describe("(list_apps) Search apps by name.").optional(),
action_name: z
.string()
.describe("(invoke) Action to execute within the app.")
Expand All @@ -79,7 +81,7 @@ export function registerAppCapabilities(server: McpServer) {
.optional(),
deployment_id: z
.string()
.describe("(get_deployment) Deployment ID to retrieve.")
.describe("(get_deployment, delete_deployment) Deployment ID.")
.optional(),
invocation_id: z
.string()
Expand All @@ -97,6 +99,7 @@ export function registerAppCapabilities(server: McpServer) {
const page = await client.apps.list({
...(params.app_name && { app_name: params.app_name }),
...(params.version && { version: params.version }),
...(params.query && { query: params.query }),
...(params.limit !== undefined && { limit: params.limit }),
...(params.offset !== undefined && { offset: params.offset }),
});
Expand Down Expand Up @@ -158,13 +161,30 @@ export function registerAppCapabilities(server: McpServer) {
return jsonResponse(deployment);
}
case "list_deployments": {
if (params.version && !params.app_name) {
return errorResponse(
"Error: app_name is required when filtering deployments by version.",
);
}
const page = await client.deployments.list({
...(params.app_name && { app_name: params.app_name }),
...(params.version && { app_version: params.version }),
...(params.limit !== undefined && { limit: params.limit }),
...(params.offset !== undefined && { offset: params.offset }),
});
return paginatedJsonResponse(page);
}
case "delete_deployment": {
if (!params.deployment_id) {
return errorResponse(
"Error: deployment_id is required for delete_deployment.",
);
}
await client.deployments.delete(params.deployment_id);
return textResponse(
`Deployment "${params.deployment_id}" deleted successfully.`,
);
}
case "get_invocation": {
if (!params.invocation_id)
return errorResponse("Error: invocation_id is required.");
Expand Down
51 changes: 17 additions & 34 deletions src/lib/mcp/tools/extensions.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { createKernelClient } from "@/lib/mcp/kernel-client";
import {
errorResponse,
itemsJsonResponse,
textResponse,
toolErrorResponse,
} from "@/lib/mcp/responses";

export function registerExtensionTools(server: McpServer) {
// manage_extensions -- List and delete browser extensions
server.tool(
"manage_extensions",
'Manage browser extensions uploaded to your organization. Use "list" to see all extensions or "delete" to remove one.',
'Manage browser extensions uploaded to Kernel. Use "list" to see all extensions available to the current project or "delete" to remove one by ID or name.',
{
action: z.enum(["list", "delete"]).describe("Operation to perform."),
id_or_name: z
Expand All @@ -22,45 +28,22 @@ export function registerExtensionTools(server: McpServer) {
switch (params.action) {
case "list": {
const extensions = await client.extensions.list();
return {
content: [
{
type: "text",
text:
extensions?.length > 0
? JSON.stringify(extensions, null, 2)
: "No extensions found",
},
],
};
return itemsJsonResponse(extensions ?? [], {
has_more: false,
next_offset: null,
emptyText: "No extensions found",
});
}
case "delete": {
if (!params.id_or_name)
return {
content: [
{
type: "text",
text: "Error: id_or_name is required for delete.",
},
],
};
if (!params.id_or_name) {
return errorResponse("Error: id_or_name is required for delete.");
}
await client.extensions.delete(params.id_or_name);
return {
content: [
{ type: "text", text: "Extension deleted successfully" },
],
};
return textResponse("Extension deleted successfully");
}
}
} catch (error) {
return {
content: [
{
type: "text",
text: `Error in manage_extensions (${params.action}): ${error}`,
},
],
};
return toolErrorResponse("manage_extensions", params.action, error);
}
},
);
Expand Down
30 changes: 23 additions & 7 deletions src/lib/mcp/tools/profiles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { registerJsonResourceTemplate } from "@/lib/mcp/resource-templates";
import {
errorResponse,
itemsJsonResponse,
jsonResponse,
paginatedJsonResponse,
textResponse,
toolErrorResponse,
Expand Down Expand Up @@ -65,20 +66,18 @@ export function registerProfileCapabilities(server: McpServer) {

server.tool(
"manage_profiles",
'Manage browser profiles when an agent needs persistent cookies, login state, or reusable browser state. Use "setup" for a guided login session, "list" to find a profile, and "delete" only when a profile should be removed.',
'Manage browser profiles when an agent needs persistent cookies, login state, or reusable browser state. Use "setup" for a guided login session, "list" to find a profile, "get" to retrieve one, and "delete" only when a profile should be removed.',
{
action: z
.enum(["setup", "list", "delete"])
.enum(["setup", "list", "get", "delete"])
.describe("Operation to perform."),
profile_name: z
.string()
.describe(
"(setup, delete) Profile name. For setup: 1-255 chars. For delete: name of profile to remove.",
)
.describe("(setup, get, delete) Profile name. For setup: 1-255 chars.")
.optional(),
profile_id: z
.string()
.describe("(delete) Profile ID to delete. Alternative to profile_name.")
.describe("(get, delete) Profile ID. Alternative to profile_name.")
.optional(),
update_existing: z
.boolean()
Expand All @@ -101,7 +100,9 @@ export function registerProfileCapabilities(server: McpServer) {
return errorResponse(
"Error: profile_name is required for setup.",
);
const existingProfiles = await listProfiles(client);
const existingProfiles = await listProfiles(client, {
query: params.profile_name,
});
const existingProfile = existingProfiles?.find(
(p) => p.name === params.profile_name,
);
Expand Down Expand Up @@ -156,6 +157,21 @@ export function registerProfileCapabilities(server: McpServer) {
} satisfies ProfileListParams);
return paginatedJsonResponse(page);
}
case "get": {
if (params.profile_name && params.profile_id) {
return errorResponse(
"Error: Cannot specify both profile_name and profile_id.",
);
}
const identifier = params.profile_name || params.profile_id;
if (!identifier) {
return errorResponse(
"Error: profile_name or profile_id is required for get.",
);
}
const profile = await client.profiles.retrieve(identifier);
return jsonResponse(profile);
}
case "delete": {
if (params.profile_name && params.profile_id) {
return errorResponse(
Expand Down
87 changes: 83 additions & 4 deletions src/lib/mcp/tools/projects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,27 @@ import {
import { paginationParams } from "@/lib/mcp/schemas";

export function registerProjectCapabilities(server: McpServer) {
// manage_projects -- Create, list, get, update, and delete organization projects
// manage_projects -- Create, list, get, update, delete, and manage organization project limits
server.tool(
"manage_projects",
'Manage Kernel projects for resource isolation within an organization. Use "create" to create a project, "list" to discover projects, "get" to retrieve one, "update" to rename or archive one, or "delete" to remove an empty project.',
'Manage Kernel projects for resource isolation within an organization. Use "create" to create a project, "list" to discover projects, "get" to retrieve one, "update" to rename or archive one, "delete" to remove an empty project, "get_limits" to inspect project caps, or "update_limits" to change project caps.',
{
action: z
.enum(["create", "list", "get", "update", "delete"])
.enum([
"create",
"list",
"get",
"update",
"delete",
"get_limits",
"update_limits",
])
.describe("Operation to perform."),
project_id: z
.string()
.describe("Project ID. Required for get, update, and delete.")
.describe(
"Project ID. Required for get, update, delete, get_limits, and update_limits.",
)
.optional(),
name: z.string().describe("(create, update) Project name.").optional(),
status: z
Expand All @@ -35,6 +45,33 @@ export function registerProjectCapabilities(server: McpServer) {
)
.optional(),
...paginationParams,
max_concurrent_invocations: z
.number()
.int()
.min(0)
.nullable()
.describe(
"(update_limits) Maximum concurrent app invocations for this project. Set 0 to remove the cap.",
)
.optional(),
max_concurrent_sessions: z
.number()
.int()
.min(0)
.nullable()
.describe(
"(update_limits) Maximum concurrent browser sessions for this project. Set 0 to remove the cap.",
)
.optional(),
max_pooled_sessions: z
.number()
.int()
.min(0)
.nullable()
.describe(
"(update_limits) Maximum pooled sessions capacity for this project. Set 0 to remove the cap.",
)
.optional(),
},
async (params, extra) => {
if (!extra.authInfo) throw new Error("Authentication required");
Expand Down Expand Up @@ -90,6 +127,48 @@ export function registerProjectCapabilities(server: McpServer) {
await client.projects.delete(params.project_id);
return textResponse("Project deleted successfully");
}
case "get_limits": {
if (!params.project_id) {
return errorResponse(
"Error: project_id is required for get_limits.",
);
}
const limits = await client.projects.limits.retrieve(
params.project_id,
);
return jsonResponse(limits);
}
case "update_limits": {
if (!params.project_id) {
return errorResponse(
"Error: project_id is required for update_limits.",
);
}
const updateParams: Parameters<
typeof client.projects.limits.update
>[1] = {};
if (params.max_concurrent_invocations !== undefined) {
updateParams.max_concurrent_invocations =
params.max_concurrent_invocations;
}
if (params.max_concurrent_sessions !== undefined) {
updateParams.max_concurrent_sessions =
params.max_concurrent_sessions;
}
if (params.max_pooled_sessions !== undefined) {
updateParams.max_pooled_sessions = params.max_pooled_sessions;
}
if (Object.keys(updateParams).length === 0) {
return errorResponse(
"Error: at least one limit field is required for update_limits.",
);
}
const limits = await client.projects.limits.update(
params.project_id,
updateParams,
);
return jsonResponse(limits);
}
}
} catch (error) {
return toolErrorResponse("manage_projects", params.action, error);
Expand Down
Loading