Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions src/lib/mcp/register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { registerDocsTools } from "@/lib/mcp/tools/docs";
import { registerExtensionTools } from "@/lib/mcp/tools/extensions";
import { registerPlaywrightTool } from "@/lib/mcp/tools/playwright";
import { registerProfileCapabilities } from "@/lib/mcp/tools/profiles";
import { registerProjectCapabilities } from "@/lib/mcp/tools/projects";
import { registerProxyTools } from "@/lib/mcp/tools/proxies";
import { registerShellTool } from "@/lib/mcp/tools/shell";

Expand All @@ -16,6 +17,7 @@ export function registerMcpCapabilities(server: McpServer) {
registerKernelPrompts(server);
registerDocsTools(server);
registerBrowserCapabilities(server);
registerProjectCapabilities(server);
registerBrowserPoolCapabilities(server);
registerProxyTools(server);
registerExtensionTools(server);
Expand Down
159 changes: 159 additions & 0 deletions src/lib/mcp/tools/projects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { createKernelClient } from "@/lib/mcp/kernel-client";

export function registerProjectCapabilities(server: McpServer) {
// manage_projects -- Create, list, get, update, and delete organization projects
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.',
{
action: z
.enum(["create", "list", "get", "update", "delete"])
.describe("Operation to perform."),
project_id: z
.string()
.describe("Project ID. Required for get, update, and delete.")
.optional(),
name: z.string().describe("(create, update) Project name.").optional(),
status: z
.enum(["active", "archived"])
.describe('(update) Project status. Use "archived" to archive.')
.optional(),
query: z
.string()
.describe(
"(list) Case-insensitive substring match against project name.",
)
.optional(),
limit: z.number().describe("(list) Max results per page.").optional(),
offset: z.number().describe("(list) Pagination offset.").optional(),
},
async (params, extra) => {
if (!extra.authInfo) throw new Error("Authentication required");
const client = createKernelClient(extra.authInfo.token);

try {
switch (params.action) {
case "create": {
if (!params.name) {
return {
content: [
{ type: "text", text: "Error: name is required for create." },
],
};
}
const project = await client.projects.create({ name: params.name });
return {
content: [
{ type: "text", text: JSON.stringify(project, null, 2) },
],
};
}
case "list": {
const page = await client.projects.list({
...(params.query && { query: params.query }),
...(params.limit !== undefined && { limit: params.limit }),
...(params.offset !== undefined && { offset: params.offset }),
});
const items = page.getPaginatedItems();
return {
content: [
{
type: "text",
text: JSON.stringify(
{
items,
has_more: page.has_more,
next_offset: page.next_offset,
},
null,
2,
),
},
],
};
}
case "get": {
if (!params.project_id) {
return {
content: [
{
type: "text",
text: "Error: project_id is required for get.",
},
],
};
}
const project = await client.projects.retrieve(params.project_id);
return {
content: [
{ type: "text", text: JSON.stringify(project, null, 2) },
],
};
}
case "update": {
if (!params.project_id) {
return {
content: [
{
type: "text",
text: "Error: project_id is required for update.",
},
],
};
}
if (!params.name && !params.status) {
return {
content: [
{
type: "text",
text: "Error: name or status is required for update.",
},
],
};
}
const updateParams: Parameters<typeof client.projects.update>[1] =
{};
if (params.name) updateParams.name = params.name;
if (params.status) updateParams.status = params.status;
const project = await client.projects.update(
params.project_id,
updateParams,
);
return {
content: [
{ type: "text", text: JSON.stringify(project, null, 2) },
],
};
}
case "delete": {
if (!params.project_id) {
return {
content: [
{
type: "text",
text: "Error: project_id is required for delete.",
},
],
};
}
await client.projects.delete(params.project_id);
return {
content: [{ type: "text", text: "Project deleted successfully" }],
};
}
}
} catch (error) {
return {
content: [
{
type: "text",
text: `Error in manage_projects (${params.action}): ${error instanceof Error ? error.message : String(error)}`,
},
],
};
}
},
);
}