Skip to content

Commit 99bedc0

Browse files
committed
Add admin cleanup MCP passthroughs
Expose the remaining small SDK passthroughs for project limits, deployment deletion/version filters, proxy checks, and paginated profile lookup while keeping the handlers on shared response helpers for clearer agent output.
1 parent c1074ec commit 99bedc0

6 files changed

Lines changed: 292 additions & 298 deletions

File tree

README.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -255,21 +255,26 @@ Many other MCP-capable tools accept:
255255

256256
Configure these values wherever the tool expects MCP server settings.
257257

258-
## Tools (10 total)
258+
## Tools (15 total)
259259

260-
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.
260+
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.
261261

262262
### manage\_\* tools
263263

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

271273
### Standalone tools
272274

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

src/lib/mcp/tools/apps.ts

Lines changed: 58 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22
import { z } from "zod";
33
import { createKernelClient } from "@/lib/mcp/kernel-client";
44
import { registerJsonResourceTemplate } from "@/lib/mcp/resource-templates";
5+
import { errorMessage, jsonResponse, textResponse } from "@/lib/mcp/responses";
56

67
export function registerAppCapabilities(server: McpServer) {
78
server.resource("apps", "apps://", async (uri, extra) => {
@@ -38,14 +39,15 @@ export function registerAppCapabilities(server: McpServer) {
3839
// manage_apps -- List apps, invoke actions, manage deployments, check invocations
3940
server.tool(
4041
"manage_apps",
41-
'Manage Kernel apps, deployments, and invocations. Use "list_apps" to discover apps, "invoke" to execute an app action, "get_deployment"/"list_deployments" to check deployment status, or "get_invocation" to check action results.',
42+
'Manage Kernel apps, deployments, and invocations. Use "list_apps" to discover apps, "invoke" to execute an app action, "get_deployment"/"list_deployments" to check deployment status, "delete_deployment" to remove a deployment, or "get_invocation" to check action results.',
4243
{
4344
action: z
4445
.enum([
4546
"list_apps",
4647
"invoke",
4748
"get_deployment",
4849
"list_deployments",
50+
"delete_deployment",
4951
"get_invocation",
5052
])
5153
.describe("Operation to perform."),
@@ -58,9 +60,10 @@ export function registerAppCapabilities(server: McpServer) {
5860
version: z
5961
.string()
6062
.describe(
61-
"(list_apps, invoke) App version filter. Defaults to 'latest' for invoke.",
63+
"(list_apps, invoke, list_deployments) App version filter. Defaults to 'latest' for invoke. Deployment version filtering requires app_name.",
6264
)
6365
.optional(),
66+
query: z.string().describe("(list_apps) Search apps by name.").optional(),
6467
action_name: z
6568
.string()
6669
.describe("(invoke) Action to execute within the app.")
@@ -71,7 +74,7 @@ export function registerAppCapabilities(server: McpServer) {
7174
.optional(),
7275
deployment_id: z
7376
.string()
74-
.describe("(get_deployment) Deployment ID to retrieve.")
77+
.describe("(get_deployment, delete_deployment) Deployment ID.")
7578
.optional(),
7679
invocation_id: z
7780
.string()
@@ -96,40 +99,23 @@ export function registerAppCapabilities(server: McpServer) {
9699
const page = await client.apps.list({
97100
...(params.app_name && { app_name: params.app_name }),
98101
...(params.version && { version: params.version }),
102+
...(params.query && { query: params.query }),
99103
...(params.limit !== undefined && { limit: params.limit }),
100104
...(params.offset !== undefined && { offset: params.offset }),
101105
});
102106
const items = page.getPaginatedItems();
103-
return {
104-
content: [
105-
{
106-
type: "text",
107-
text:
108-
items.length > 0
109-
? JSON.stringify(
110-
{
111-
items,
112-
has_more: page.has_more,
113-
next_offset: page.next_offset,
114-
},
115-
null,
116-
2,
117-
)
118-
: "No apps found",
119-
},
120-
],
121-
};
107+
if (items.length === 0) return textResponse("No apps found");
108+
return jsonResponse({
109+
items,
110+
has_more: page.has_more,
111+
next_offset: page.next_offset,
112+
});
122113
}
123114
case "invoke": {
124115
if (!params.app_name || !params.action_name) {
125-
return {
126-
content: [
127-
{
128-
type: "text",
129-
text: "Error: app_name and action_name are required for invoke.",
130-
},
131-
],
132-
};
116+
return textResponse(
117+
"Error: app_name and action_name are required for invoke.",
118+
);
133119
}
134120
const invocation = await client.invocations.create({
135121
app_name: params.app_name,
@@ -144,22 +130,11 @@ export function registerAppCapabilities(server: McpServer) {
144130
let finalInvocation = invocation;
145131
for await (const evt of stream) {
146132
if (evt.event === "error") {
147-
return {
148-
content: [
149-
{
150-
type: "text",
151-
text: JSON.stringify(
152-
{
153-
status: "error",
154-
invocation_id: invocation.id,
155-
error: evt,
156-
},
157-
null,
158-
2,
159-
),
160-
},
161-
],
162-
};
133+
return jsonResponse({
134+
status: "error",
135+
invocation_id: invocation.id,
136+
error: evt,
137+
});
163138
}
164139
if (evt.event === "invocation_state") {
165140
finalInvocation = evt.invocation || finalInvocation;
@@ -170,102 +145,68 @@ export function registerAppCapabilities(server: McpServer) {
170145
break;
171146
}
172147
}
173-
return {
174-
content: [
175-
{
176-
type: "text",
177-
text: JSON.stringify(finalInvocation, null, 2),
178-
},
179-
],
180-
};
148+
return jsonResponse(finalInvocation);
181149
}
182150
case "get_deployment": {
183151
if (!params.deployment_id)
184-
return {
185-
content: [
186-
{ type: "text", text: "Error: deployment_id is required." },
187-
],
188-
};
152+
return textResponse("Error: deployment_id is required.");
189153
const deployment = await client.deployments.retrieve(
190154
params.deployment_id,
191155
);
192156
if (!deployment)
193-
return {
194-
content: [
195-
{
196-
type: "text",
197-
text: `Deployment "${params.deployment_id}" not found`,
198-
},
199-
],
200-
};
201-
return {
202-
content: [
203-
{ type: "text", text: JSON.stringify(deployment, null, 2) },
204-
],
205-
};
157+
return textResponse(
158+
`Deployment "${params.deployment_id}" not found`,
159+
);
160+
return jsonResponse(deployment);
206161
}
207162
case "list_deployments": {
163+
if (params.version && !params.app_name) {
164+
return textResponse(
165+
"Error: app_name is required when filtering deployments by version.",
166+
);
167+
}
208168
const page = await client.deployments.list({
209169
...(params.app_name && { app_name: params.app_name }),
170+
...(params.version && { app_version: params.version }),
210171
...(params.limit !== undefined && { limit: params.limit }),
211172
...(params.offset !== undefined && { offset: params.offset }),
212173
});
213174
const items = page.getPaginatedItems();
214-
return {
215-
content: [
216-
{
217-
type: "text",
218-
text:
219-
items.length > 0
220-
? JSON.stringify(
221-
{
222-
items,
223-
has_more: page.has_more,
224-
next_offset: page.next_offset,
225-
},
226-
null,
227-
2,
228-
)
229-
: "No deployments found",
230-
},
231-
],
232-
};
175+
if (items.length === 0) return textResponse("No deployments found");
176+
return jsonResponse({
177+
items,
178+
has_more: page.has_more,
179+
next_offset: page.next_offset,
180+
});
181+
}
182+
case "delete_deployment": {
183+
if (!params.deployment_id) {
184+
return textResponse(
185+
"Error: deployment_id is required for delete_deployment.",
186+
);
187+
}
188+
await client.deployments.delete(params.deployment_id);
189+
return textResponse(
190+
`Deployment "${params.deployment_id}" deleted successfully.`,
191+
);
233192
}
234193
case "get_invocation": {
235194
if (!params.invocation_id)
236-
return {
237-
content: [
238-
{ type: "text", text: "Error: invocation_id is required." },
239-
],
240-
};
195+
return textResponse("Error: invocation_id is required.");
241196
const invocation = await client.invocations.retrieve(
242197
params.invocation_id,
243198
);
244199
if (!invocation)
245-
return {
246-
content: [
247-
{
248-
type: "text",
249-
text: `Invocation "${params.invocation_id}" not found`,
250-
},
251-
],
252-
};
253-
return {
254-
content: [
255-
{ type: "text", text: JSON.stringify(invocation, null, 2) },
256-
],
257-
};
200+
return textResponse(
201+
`Invocation "${params.invocation_id}" not found`,
202+
);
203+
return jsonResponse(invocation);
258204
}
259205
}
260206
} catch (error) {
261-
return {
262-
content: [
263-
{
264-
type: "text",
265-
text: `Error in manage_apps (${params.action}): ${error}`,
266-
},
267-
],
268-
};
207+
return textResponse(
208+
`Error in manage_apps (${params.action}): ${errorMessage(error)}`,
209+
);
269210
}
270211
},
271212
);

src/lib/mcp/tools/extensions.ts

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22
import { z } from "zod";
33
import { createKernelClient } from "@/lib/mcp/kernel-client";
4+
import { errorMessage, textResponse } from "@/lib/mcp/responses";
45

56
export function registerExtensionTools(server: McpServer) {
67
// manage_extensions -- List and delete browser extensions
78
server.tool(
89
"manage_extensions",
9-
'Manage browser extensions uploaded to your organization. Use "list" to see all extensions or "delete" to remove one.',
10+
'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.',
1011
{
1112
action: z.enum(["list", "delete"]).describe("Operation to perform."),
1213
id_or_name: z
@@ -22,17 +23,11 @@ export function registerExtensionTools(server: McpServer) {
2223
switch (params.action) {
2324
case "list": {
2425
const extensions = await client.extensions.list();
25-
return {
26-
content: [
27-
{
28-
type: "text",
29-
text:
30-
extensions?.length > 0
31-
? JSON.stringify(extensions, null, 2)
32-
: "No extensions found",
33-
},
34-
],
35-
};
26+
return textResponse(
27+
extensions?.length > 0
28+
? JSON.stringify(extensions, null, 2)
29+
: "No extensions found",
30+
);
3631
}
3732
case "delete": {
3833
if (!params.id_or_name)
@@ -45,22 +40,13 @@ export function registerExtensionTools(server: McpServer) {
4540
],
4641
};
4742
await client.extensions.delete(params.id_or_name);
48-
return {
49-
content: [
50-
{ type: "text", text: "Extension deleted successfully" },
51-
],
52-
};
43+
return textResponse("Extension deleted successfully");
5344
}
5445
}
5546
} catch (error) {
56-
return {
57-
content: [
58-
{
59-
type: "text",
60-
text: `Error in manage_extensions (${params.action}): ${error}`,
61-
},
62-
],
63-
};
47+
return textResponse(
48+
`Error in manage_extensions (${params.action}): ${errorMessage(error)}`,
49+
);
6450
}
6551
},
6652
);

0 commit comments

Comments
 (0)